@0xbigboss/gh-pulse-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/cache.cjs +312 -0
  2. package/dist/cache.d.cts +58 -0
  3. package/dist/cache.d.ts +58 -0
  4. package/dist/cache.js +303 -0
  5. package/dist/config.cjs +182 -0
  6. package/dist/config.d.cts +248 -0
  7. package/dist/config.d.ts +248 -0
  8. package/dist/config.js +172 -0
  9. package/dist/filters.cjs +16 -0
  10. package/dist/filters.d.cts +3 -0
  11. package/dist/filters.d.ts +3 -0
  12. package/dist/filters.js +12 -0
  13. package/dist/github.cjs +240 -0
  14. package/dist/github.d.cts +46 -0
  15. package/dist/github.d.ts +46 -0
  16. package/dist/github.js +235 -0
  17. package/dist/index.cjs +28 -0
  18. package/dist/index.d.cts +11 -0
  19. package/dist/index.d.ts +11 -0
  20. package/dist/index.js +11 -0
  21. package/dist/reports/context.cjs +8 -0
  22. package/dist/reports/context.d.cts +7 -0
  23. package/dist/reports/context.d.ts +7 -0
  24. package/dist/reports/context.js +5 -0
  25. package/dist/reports/exec.cjs +160 -0
  26. package/dist/reports/exec.d.cts +6 -0
  27. package/dist/reports/exec.d.ts +6 -0
  28. package/dist/reports/exec.js +157 -0
  29. package/dist/reports/index.cjs +21 -0
  30. package/dist/reports/index.d.cts +5 -0
  31. package/dist/reports/index.d.ts +5 -0
  32. package/dist/reports/index.js +5 -0
  33. package/dist/reports/meta.cjs +15 -0
  34. package/dist/reports/meta.d.cts +12 -0
  35. package/dist/reports/meta.d.ts +12 -0
  36. package/dist/reports/meta.js +12 -0
  37. package/dist/reports/personal.cjs +90 -0
  38. package/dist/reports/personal.d.cts +8 -0
  39. package/dist/reports/personal.d.ts +8 -0
  40. package/dist/reports/personal.js +87 -0
  41. package/dist/reports/team.cjs +127 -0
  42. package/dist/reports/team.d.cts +6 -0
  43. package/dist/reports/team.d.ts +6 -0
  44. package/dist/reports/team.js +124 -0
  45. package/dist/reports/types.cjs +2 -0
  46. package/dist/reports/types.d.cts +144 -0
  47. package/dist/reports/types.d.ts +144 -0
  48. package/dist/reports/types.js +1 -0
  49. package/dist/reports/utils.cjs +71 -0
  50. package/dist/reports/utils.d.cts +6 -0
  51. package/dist/reports/utils.d.ts +6 -0
  52. package/dist/reports/utils.js +65 -0
  53. package/dist/repos.cjs +102 -0
  54. package/dist/repos.d.cts +12 -0
  55. package/dist/repos.d.ts +12 -0
  56. package/dist/repos.js +96 -0
  57. package/dist/sync.cjs +360 -0
  58. package/dist/sync.d.cts +24 -0
  59. package/dist/sync.d.ts +24 -0
  60. package/dist/sync.js +357 -0
  61. package/dist/team.cjs +45 -0
  62. package/dist/team.d.cts +10 -0
  63. package/dist/team.d.ts +10 -0
  64. package/dist/team.js +42 -0
  65. package/dist/time.cjs +153 -0
  66. package/dist/time.d.cts +13 -0
  67. package/dist/time.d.ts +13 -0
  68. package/dist/time.js +145 -0
  69. package/dist/types.cjs +2 -0
  70. package/dist/types.d.cts +133 -0
  71. package/dist/types.d.ts +133 -0
  72. package/dist/types.js +1 -0
  73. package/package.json +29 -0
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildExecReport = buildExecReport;
4
+ const utils_1 = require("./utils.cjs");
5
+ const context_1 = require("./context.cjs");
6
+ const filters_1 = require("../filters.cjs");
7
+ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
8
+ function buildExecReport(cache, options) {
9
+ const { pullRequests, commits } = (0, context_1.loadReportData)(cache, options.repos);
10
+ const now = options.meta.generated_at;
11
+ const exclude = options.excludeAuthors;
12
+ const teamMembers = options.teamMembers;
13
+ const prLookup = buildPullRequestLookup(pullRequests);
14
+ const events = cache
15
+ .getEvents({ since: options.timeRange.start, until: options.timeRange.end })
16
+ .filter((event) => options.repos.includes(event.repo));
17
+ const mergedEvents = events.filter((event) => event.type === 'pr_merged' && prAuthorAllowed(prLookup, event, exclude, options.excludeBots));
18
+ const mergedPrs = mergedEvents
19
+ .map((event) => prLookup.get(`${event.repo}#${event.pr_number}`))
20
+ .filter((pr) => Boolean(pr))
21
+ .filter((pr) => options.includeDrafts || !pr.draft);
22
+ const trendBuckets = buildWeeklyBuckets(options.timeRange.start, options.timeRange.end);
23
+ const velocityTrends = trendBuckets.map((bucket) => buildTrendPoint(cache, mergedEvents, prLookup, bucket));
24
+ const cycleTimeTrends = velocityTrends;
25
+ const repoHealth = buildRepoHealth(pullRequests, commits, options.repos, options.thresholds, now);
26
+ const highlights = buildHighlights(mergedPrs, options.thresholds.large_pr_lines, now);
27
+ const teamBreakdown = buildTeamBreakdown(teamMembers, pullRequests, commits, events, exclude, options.excludeBots, options.timeRange.start, options.timeRange.end);
28
+ return {
29
+ type: 'exec',
30
+ meta: options.meta,
31
+ velocity_trends: velocityTrends,
32
+ cycle_time_trends: cycleTimeTrends,
33
+ repo_health: repoHealth,
34
+ highlights,
35
+ team_breakdown: teamBreakdown,
36
+ };
37
+ }
38
+ function buildWeeklyBuckets(start, end) {
39
+ const buckets = [];
40
+ let cursor = start;
41
+ while (cursor < end) {
42
+ const bucketEnd = Math.min(cursor + WEEK_MS, end);
43
+ buckets.push({ start: cursor, end: bucketEnd });
44
+ cursor = bucketEnd;
45
+ }
46
+ return buckets;
47
+ }
48
+ function buildTrendPoint(cache, mergedEvents, prLookup, bucket) {
49
+ const bucketEvents = mergedEvents.filter((event) => event.type === 'pr_merged' &&
50
+ event.merged_at >= bucket.start &&
51
+ event.merged_at <= bucket.end);
52
+ const cycleTimes = bucketEvents
53
+ .map((event) => prLookup.get(`${event.repo}#${event.pr_number}`))
54
+ .filter((pr) => Boolean(pr))
55
+ .map((pr) => (0, utils_1.computeCycleTime)(pr, cache.getEventsForPr(pr.repo, pr.number)))
56
+ .map((metrics) => metrics?.total_time)
57
+ .filter((value) => typeof value === 'number' && value > 0);
58
+ return {
59
+ bucket_start: bucket.start,
60
+ bucket_end: bucket.end,
61
+ prs_merged: bucketEvents.length,
62
+ cycle_time_p50: (0, utils_1.percentile)(cycleTimes, 50),
63
+ };
64
+ }
65
+ function buildRepoHealth(pullRequests, commits, repos, thresholds, now) {
66
+ return repos.map((repo) => {
67
+ const prActivity = pullRequests.filter((pr) => pr.repo === repo).map((pr) => pr.updated_at);
68
+ const commitActivity = commits
69
+ .filter((commit) => commit.repo === repo)
70
+ .map((commit) => commit.committed_at);
71
+ const latest = maxTimestamp([...prActivity, ...commitActivity]);
72
+ const ageDays = latest ? Math.floor((now - latest) / (24 * 60 * 60 * 1000)) : null;
73
+ let status = 'dormant';
74
+ if (ageDays === null) {
75
+ status = 'dormant';
76
+ }
77
+ else if (ageDays <= thresholds.stale_days) {
78
+ status = 'active';
79
+ }
80
+ else if (ageDays <= thresholds.stuck_days) {
81
+ status = 'stale';
82
+ }
83
+ else {
84
+ status = 'dormant';
85
+ }
86
+ return {
87
+ repo,
88
+ status,
89
+ last_activity_at: latest,
90
+ };
91
+ });
92
+ }
93
+ function buildHighlights(mergedPrs, largePrThreshold, now) {
94
+ return mergedPrs
95
+ .map((pr) => {
96
+ const reasons = [];
97
+ if (pr.additions + pr.deletions >= largePrThreshold) {
98
+ reasons.push(`large change (${pr.additions + pr.deletions} lines)`);
99
+ }
100
+ const title = pr.title.toLowerCase();
101
+ if (title.startsWith('feat:') || title.startsWith('fix:')) {
102
+ reasons.push('conventional commit title');
103
+ }
104
+ if (pr.labels.length > 0) {
105
+ reasons.push(`labels: ${pr.labels.slice(0, 3).join(', ')}`);
106
+ }
107
+ if (reasons.length === 0) {
108
+ return null;
109
+ }
110
+ return {
111
+ pr: (0, utils_1.buildPullRequestSummary)(pr, now),
112
+ reasons,
113
+ };
114
+ })
115
+ .filter((entry) => Boolean(entry));
116
+ }
117
+ function buildTeamBreakdown(teamMembers, pullRequests, commits, events, exclude, excludeBots, since, until) {
118
+ const reviewEvents = events.filter((event) => event.type === 'review_submitted');
119
+ return [...teamMembers]
120
+ .filter((member) => !(0, filters_1.isExcludedAuthor)(member, exclude, excludeBots))
121
+ .map((member) => {
122
+ const authored = pullRequests.filter((pr) => pr.author === member &&
123
+ pr.created_at >= since &&
124
+ pr.created_at <= until &&
125
+ !(0, filters_1.isExcludedAuthor)(pr.author, exclude, excludeBots));
126
+ const reviewed = reviewEvents.filter((event) => event.type === 'review_submitted' &&
127
+ event.reviewer === member &&
128
+ !(0, filters_1.isExcludedAuthor)(event.reviewer, exclude, excludeBots));
129
+ const memberCommits = commits.filter((commit) => commit.author === member &&
130
+ commit.committed_at >= since &&
131
+ commit.committed_at <= until &&
132
+ !(0, filters_1.isExcludedAuthor)(commit.author, exclude, excludeBots));
133
+ const linesChanged = memberCommits.reduce((total, commit) => total + commit.additions + commit.deletions, 0);
134
+ return {
135
+ user: member,
136
+ prs_authored: authored.length,
137
+ prs_reviewed: reviewed.length,
138
+ commits: memberCommits.length,
139
+ lines_changed: linesChanged,
140
+ };
141
+ })
142
+ .toSorted((a, b) => a.user.localeCompare(b.user));
143
+ }
144
+ function prAuthorAllowed(lookup, event, exclude, excludeBots) {
145
+ const pr = lookup.get(`${event.repo}#${event.pr_number}`);
146
+ if (!pr) {
147
+ return false;
148
+ }
149
+ return !(0, filters_1.isExcludedAuthor)(pr.author, exclude, excludeBots);
150
+ }
151
+ function maxTimestamp(values) {
152
+ const filtered = values.filter((value) => typeof value === 'number');
153
+ if (filtered.length === 0) {
154
+ return null;
155
+ }
156
+ return Math.max(...filtered);
157
+ }
158
+ function buildPullRequestLookup(pullRequests) {
159
+ return new Map(pullRequests.map((pr) => [`${pr.repo}#${pr.number}`, pr]));
160
+ }
@@ -0,0 +1,6 @@
1
+ import type { Cache } from "../cache.cjs";
2
+ import type { ExecReport, ReportInputs } from "./types.cjs";
3
+ export interface ExecReportOptions extends ReportInputs {
4
+ meta: ExecReport['meta'];
5
+ }
6
+ export declare function buildExecReport(cache: Cache, options: ExecReportOptions): ExecReport;
@@ -0,0 +1,6 @@
1
+ import type { Cache } from "../cache.js";
2
+ import type { ExecReport, ReportInputs } from "./types.js";
3
+ export interface ExecReportOptions extends ReportInputs {
4
+ meta: ExecReport['meta'];
5
+ }
6
+ export declare function buildExecReport(cache: Cache, options: ExecReportOptions): ExecReport;
@@ -0,0 +1,157 @@
1
+ import { buildPullRequestSummary, computeCycleTime, percentile } from "./utils.js";
2
+ import { loadReportData } from "./context.js";
3
+ import { isExcludedAuthor } from "../filters.js";
4
+ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
5
+ export function buildExecReport(cache, options) {
6
+ const { pullRequests, commits } = loadReportData(cache, options.repos);
7
+ const now = options.meta.generated_at;
8
+ const exclude = options.excludeAuthors;
9
+ const teamMembers = options.teamMembers;
10
+ const prLookup = buildPullRequestLookup(pullRequests);
11
+ const events = cache
12
+ .getEvents({ since: options.timeRange.start, until: options.timeRange.end })
13
+ .filter((event) => options.repos.includes(event.repo));
14
+ const mergedEvents = events.filter((event) => event.type === 'pr_merged' && prAuthorAllowed(prLookup, event, exclude, options.excludeBots));
15
+ const mergedPrs = mergedEvents
16
+ .map((event) => prLookup.get(`${event.repo}#${event.pr_number}`))
17
+ .filter((pr) => Boolean(pr))
18
+ .filter((pr) => options.includeDrafts || !pr.draft);
19
+ const trendBuckets = buildWeeklyBuckets(options.timeRange.start, options.timeRange.end);
20
+ const velocityTrends = trendBuckets.map((bucket) => buildTrendPoint(cache, mergedEvents, prLookup, bucket));
21
+ const cycleTimeTrends = velocityTrends;
22
+ const repoHealth = buildRepoHealth(pullRequests, commits, options.repos, options.thresholds, now);
23
+ const highlights = buildHighlights(mergedPrs, options.thresholds.large_pr_lines, now);
24
+ const teamBreakdown = buildTeamBreakdown(teamMembers, pullRequests, commits, events, exclude, options.excludeBots, options.timeRange.start, options.timeRange.end);
25
+ return {
26
+ type: 'exec',
27
+ meta: options.meta,
28
+ velocity_trends: velocityTrends,
29
+ cycle_time_trends: cycleTimeTrends,
30
+ repo_health: repoHealth,
31
+ highlights,
32
+ team_breakdown: teamBreakdown,
33
+ };
34
+ }
35
+ function buildWeeklyBuckets(start, end) {
36
+ const buckets = [];
37
+ let cursor = start;
38
+ while (cursor < end) {
39
+ const bucketEnd = Math.min(cursor + WEEK_MS, end);
40
+ buckets.push({ start: cursor, end: bucketEnd });
41
+ cursor = bucketEnd;
42
+ }
43
+ return buckets;
44
+ }
45
+ function buildTrendPoint(cache, mergedEvents, prLookup, bucket) {
46
+ const bucketEvents = mergedEvents.filter((event) => event.type === 'pr_merged' &&
47
+ event.merged_at >= bucket.start &&
48
+ event.merged_at <= bucket.end);
49
+ const cycleTimes = bucketEvents
50
+ .map((event) => prLookup.get(`${event.repo}#${event.pr_number}`))
51
+ .filter((pr) => Boolean(pr))
52
+ .map((pr) => computeCycleTime(pr, cache.getEventsForPr(pr.repo, pr.number)))
53
+ .map((metrics) => metrics?.total_time)
54
+ .filter((value) => typeof value === 'number' && value > 0);
55
+ return {
56
+ bucket_start: bucket.start,
57
+ bucket_end: bucket.end,
58
+ prs_merged: bucketEvents.length,
59
+ cycle_time_p50: percentile(cycleTimes, 50),
60
+ };
61
+ }
62
+ function buildRepoHealth(pullRequests, commits, repos, thresholds, now) {
63
+ return repos.map((repo) => {
64
+ const prActivity = pullRequests.filter((pr) => pr.repo === repo).map((pr) => pr.updated_at);
65
+ const commitActivity = commits
66
+ .filter((commit) => commit.repo === repo)
67
+ .map((commit) => commit.committed_at);
68
+ const latest = maxTimestamp([...prActivity, ...commitActivity]);
69
+ const ageDays = latest ? Math.floor((now - latest) / (24 * 60 * 60 * 1000)) : null;
70
+ let status = 'dormant';
71
+ if (ageDays === null) {
72
+ status = 'dormant';
73
+ }
74
+ else if (ageDays <= thresholds.stale_days) {
75
+ status = 'active';
76
+ }
77
+ else if (ageDays <= thresholds.stuck_days) {
78
+ status = 'stale';
79
+ }
80
+ else {
81
+ status = 'dormant';
82
+ }
83
+ return {
84
+ repo,
85
+ status,
86
+ last_activity_at: latest,
87
+ };
88
+ });
89
+ }
90
+ function buildHighlights(mergedPrs, largePrThreshold, now) {
91
+ return mergedPrs
92
+ .map((pr) => {
93
+ const reasons = [];
94
+ if (pr.additions + pr.deletions >= largePrThreshold) {
95
+ reasons.push(`large change (${pr.additions + pr.deletions} lines)`);
96
+ }
97
+ const title = pr.title.toLowerCase();
98
+ if (title.startsWith('feat:') || title.startsWith('fix:')) {
99
+ reasons.push('conventional commit title');
100
+ }
101
+ if (pr.labels.length > 0) {
102
+ reasons.push(`labels: ${pr.labels.slice(0, 3).join(', ')}`);
103
+ }
104
+ if (reasons.length === 0) {
105
+ return null;
106
+ }
107
+ return {
108
+ pr: buildPullRequestSummary(pr, now),
109
+ reasons,
110
+ };
111
+ })
112
+ .filter((entry) => Boolean(entry));
113
+ }
114
+ function buildTeamBreakdown(teamMembers, pullRequests, commits, events, exclude, excludeBots, since, until) {
115
+ const reviewEvents = events.filter((event) => event.type === 'review_submitted');
116
+ return [...teamMembers]
117
+ .filter((member) => !isExcludedAuthor(member, exclude, excludeBots))
118
+ .map((member) => {
119
+ const authored = pullRequests.filter((pr) => pr.author === member &&
120
+ pr.created_at >= since &&
121
+ pr.created_at <= until &&
122
+ !isExcludedAuthor(pr.author, exclude, excludeBots));
123
+ const reviewed = reviewEvents.filter((event) => event.type === 'review_submitted' &&
124
+ event.reviewer === member &&
125
+ !isExcludedAuthor(event.reviewer, exclude, excludeBots));
126
+ const memberCommits = commits.filter((commit) => commit.author === member &&
127
+ commit.committed_at >= since &&
128
+ commit.committed_at <= until &&
129
+ !isExcludedAuthor(commit.author, exclude, excludeBots));
130
+ const linesChanged = memberCommits.reduce((total, commit) => total + commit.additions + commit.deletions, 0);
131
+ return {
132
+ user: member,
133
+ prs_authored: authored.length,
134
+ prs_reviewed: reviewed.length,
135
+ commits: memberCommits.length,
136
+ lines_changed: linesChanged,
137
+ };
138
+ })
139
+ .toSorted((a, b) => a.user.localeCompare(b.user));
140
+ }
141
+ function prAuthorAllowed(lookup, event, exclude, excludeBots) {
142
+ const pr = lookup.get(`${event.repo}#${event.pr_number}`);
143
+ if (!pr) {
144
+ return false;
145
+ }
146
+ return !isExcludedAuthor(pr.author, exclude, excludeBots);
147
+ }
148
+ function maxTimestamp(values) {
149
+ const filtered = values.filter((value) => typeof value === 'number');
150
+ if (filtered.length === 0) {
151
+ return null;
152
+ }
153
+ return Math.max(...filtered);
154
+ }
155
+ function buildPullRequestLookup(pullRequests) {
156
+ return new Map(pullRequests.map((pr) => [`${pr.repo}#${pr.number}`, pr]));
157
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types.cjs"), exports);
18
+ __exportStar(require("./personal.cjs"), exports);
19
+ __exportStar(require("./team.cjs"), exports);
20
+ __exportStar(require("./exec.cjs"), exports);
21
+ __exportStar(require("./meta.cjs"), exports);
@@ -0,0 +1,5 @@
1
+ export * from "./types.cjs";
2
+ export * from "./personal.cjs";
3
+ export * from "./team.cjs";
4
+ export * from "./exec.cjs";
5
+ export * from "./meta.cjs";
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export * from "./personal.js";
3
+ export * from "./team.js";
4
+ export * from "./exec.js";
5
+ export * from "./meta.js";
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export * from "./personal.js";
3
+ export * from "./team.js";
4
+ export * from "./exec.js";
5
+ export * from "./meta.js";
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildReportMeta = buildReportMeta;
4
+ function buildReportMeta(options) {
5
+ const generatedAt = options.now ? options.now() : Date.now();
6
+ return {
7
+ audience: options.audience,
8
+ period_label: options.periodLabel,
9
+ start: options.start,
10
+ end: options.end,
11
+ generated_at: generatedAt,
12
+ repos: options.repos,
13
+ data_freshness: options.cache.getFreshness(options.repos),
14
+ };
15
+ }
@@ -0,0 +1,12 @@
1
+ import type { Cache } from "../cache.cjs";
2
+ import type { RepoFullName, Timestamp } from "../types.cjs";
3
+ import type { ReportAudience, ReportMeta } from "./types.cjs";
4
+ export declare function buildReportMeta(options: {
5
+ audience: ReportAudience;
6
+ repos: RepoFullName[];
7
+ periodLabel: string;
8
+ start: Timestamp;
9
+ end: Timestamp;
10
+ cache: Cache;
11
+ now?: () => number;
12
+ }): ReportMeta;
@@ -0,0 +1,12 @@
1
+ import type { Cache } from "../cache.js";
2
+ import type { RepoFullName, Timestamp } from "../types.js";
3
+ import type { ReportAudience, ReportMeta } from "./types.js";
4
+ export declare function buildReportMeta(options: {
5
+ audience: ReportAudience;
6
+ repos: RepoFullName[];
7
+ periodLabel: string;
8
+ start: Timestamp;
9
+ end: Timestamp;
10
+ cache: Cache;
11
+ now?: () => number;
12
+ }): ReportMeta;
@@ -0,0 +1,12 @@
1
+ export function buildReportMeta(options) {
2
+ const generatedAt = options.now ? options.now() : Date.now();
3
+ return {
4
+ audience: options.audience,
5
+ period_label: options.periodLabel,
6
+ start: options.start,
7
+ end: options.end,
8
+ generated_at: generatedAt,
9
+ repos: options.repos,
10
+ data_freshness: options.cache.getFreshness(options.repos),
11
+ };
12
+ }
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildPersonalReport = buildPersonalReport;
4
+ const utils_1 = require("./utils.cjs");
5
+ const context_1 = require("./context.cjs");
6
+ const filters_1 = require("../filters.cjs");
7
+ function buildPersonalReport(cache, options) {
8
+ const { pullRequests, commits } = (0, context_1.loadReportData)(cache, options.repos);
9
+ const now = options.meta.generated_at;
10
+ const exclude = options.excludeAuthors;
11
+ const prLookup = buildPullRequestLookup(pullRequests);
12
+ const events = cache
13
+ .getEvents({ since: options.timeRange.start, until: options.timeRange.end })
14
+ .filter((event) => options.repos.includes(event.repo));
15
+ const authoredPrs = pullRequests.filter((pr) => pr.author === options.user && !(0, filters_1.isExcludedAuthor)(pr.author, exclude, options.excludeBots));
16
+ const authoredSummaries = authoredPrs
17
+ .filter((pr) => pr.created_at >= options.timeRange.start && pr.created_at <= options.timeRange.end)
18
+ .map((pr) => (0, utils_1.buildPullRequestSummary)(pr, now));
19
+ const openWork = authoredPrs
20
+ .filter((pr) => pr.state === 'open')
21
+ .filter((pr) => options.includeDrafts || !pr.draft)
22
+ .map((pr) => (0, utils_1.buildPullRequestSummary)(pr, now));
23
+ const requested = pullRequests
24
+ .filter((pr) => pr.state === 'open')
25
+ .filter((pr) => options.includeDrafts || !pr.draft)
26
+ .filter((pr) => pr.requested_reviewers.includes(options.user))
27
+ .filter((pr) => !(0, filters_1.isExcludedAuthor)(pr.author, exclude, options.excludeBots))
28
+ .map((pr) => (0, utils_1.buildPullRequestSummary)(pr, now));
29
+ const teamMembers = new Set(options.teamMembers);
30
+ const teamPrs = pullRequests
31
+ .filter((pr) => pr.state === 'open')
32
+ .filter((pr) => options.includeDrafts || !pr.draft)
33
+ .filter((pr) => pr.author !== options.user)
34
+ .filter((pr) => teamMembers.has(pr.author))
35
+ .filter((pr) => !(0, filters_1.isExcludedAuthor)(pr.author, exclude, options.excludeBots))
36
+ .map((pr) => (0, utils_1.buildPullRequestSummary)(pr, now));
37
+ const commitsForUser = commits
38
+ .filter((commit) => commit.author === options.user)
39
+ .filter((commit) => commit.committed_at >= options.timeRange.start)
40
+ .filter((commit) => commit.committed_at <= options.timeRange.end)
41
+ .filter((commit) => !(0, filters_1.isExcludedAuthor)(commit.author, exclude, options.excludeBots));
42
+ const commitSummaries = commitsForUser.map((commit) => ({
43
+ repo: commit.repo,
44
+ sha: commit.sha,
45
+ author: commit.author,
46
+ committed_at: commit.committed_at,
47
+ message: commit.message,
48
+ additions: commit.additions,
49
+ deletions: commit.deletions,
50
+ files_changed: commit.files_changed,
51
+ }));
52
+ const summary = buildPersonalSummary(events, prLookup, options.user, commitsForUser);
53
+ return {
54
+ type: options.meta.audience === 'individual' ? 'individual' : 'personal',
55
+ user: options.user,
56
+ meta: options.meta,
57
+ summary,
58
+ review_debt: {
59
+ requested,
60
+ team_prs: teamPrs,
61
+ },
62
+ open_work: {
63
+ open_prs: openWork,
64
+ },
65
+ authored_prs: authoredSummaries,
66
+ commits_detail: commitSummaries,
67
+ };
68
+ }
69
+ function buildPersonalSummary(events, prLookup, user, commits) {
70
+ const opened = events.filter((event) => event.type === 'pr_opened' && event.author === user);
71
+ const merged = events.filter((event) => event.type === 'pr_merged' && prMatchesAuthor(prLookup, event.repo, event.pr_number, user));
72
+ const closed = events.filter((event) => event.type === 'pr_closed' && prMatchesAuthor(prLookup, event.repo, event.pr_number, user));
73
+ const reposTouched = new Set(commits.map((commit) => commit.repo));
74
+ const linesChanged = commits.reduce((total, commit) => total + commit.additions + commit.deletions, 0);
75
+ return {
76
+ prs_opened: opened.length,
77
+ prs_merged: merged.length,
78
+ prs_closed: closed.length,
79
+ commits: commits.length,
80
+ repos_touched: reposTouched.size,
81
+ lines_changed: linesChanged,
82
+ };
83
+ }
84
+ function prMatchesAuthor(lookup, repo, number, user) {
85
+ const pr = lookup.get(`${repo}#${number}`);
86
+ return pr ? pr.author === user : false;
87
+ }
88
+ function buildPullRequestLookup(pullRequests) {
89
+ return new Map(pullRequests.map((pr) => [`${pr.repo}#${pr.number}`, pr]));
90
+ }
@@ -0,0 +1,8 @@
1
+ import type { Cache } from "../cache.cjs";
2
+ import type { GitHubUsername } from "../types.cjs";
3
+ import type { ReportInputs, PersonalReport } from "./types.cjs";
4
+ export interface PersonalReportOptions extends ReportInputs {
5
+ user: GitHubUsername;
6
+ meta: PersonalReport['meta'];
7
+ }
8
+ export declare function buildPersonalReport(cache: Cache, options: PersonalReportOptions): PersonalReport;
@@ -0,0 +1,8 @@
1
+ import type { Cache } from "../cache.js";
2
+ import type { GitHubUsername } from "../types.js";
3
+ import type { ReportInputs, PersonalReport } from "./types.js";
4
+ export interface PersonalReportOptions extends ReportInputs {
5
+ user: GitHubUsername;
6
+ meta: PersonalReport['meta'];
7
+ }
8
+ export declare function buildPersonalReport(cache: Cache, options: PersonalReportOptions): PersonalReport;
@@ -0,0 +1,87 @@
1
+ import { buildPullRequestSummary } from "./utils.js";
2
+ import { loadReportData } from "./context.js";
3
+ import { isExcludedAuthor } from "../filters.js";
4
+ export function buildPersonalReport(cache, options) {
5
+ const { pullRequests, commits } = loadReportData(cache, options.repos);
6
+ const now = options.meta.generated_at;
7
+ const exclude = options.excludeAuthors;
8
+ const prLookup = buildPullRequestLookup(pullRequests);
9
+ const events = cache
10
+ .getEvents({ since: options.timeRange.start, until: options.timeRange.end })
11
+ .filter((event) => options.repos.includes(event.repo));
12
+ const authoredPrs = pullRequests.filter((pr) => pr.author === options.user && !isExcludedAuthor(pr.author, exclude, options.excludeBots));
13
+ const authoredSummaries = authoredPrs
14
+ .filter((pr) => pr.created_at >= options.timeRange.start && pr.created_at <= options.timeRange.end)
15
+ .map((pr) => buildPullRequestSummary(pr, now));
16
+ const openWork = authoredPrs
17
+ .filter((pr) => pr.state === 'open')
18
+ .filter((pr) => options.includeDrafts || !pr.draft)
19
+ .map((pr) => buildPullRequestSummary(pr, now));
20
+ const requested = pullRequests
21
+ .filter((pr) => pr.state === 'open')
22
+ .filter((pr) => options.includeDrafts || !pr.draft)
23
+ .filter((pr) => pr.requested_reviewers.includes(options.user))
24
+ .filter((pr) => !isExcludedAuthor(pr.author, exclude, options.excludeBots))
25
+ .map((pr) => buildPullRequestSummary(pr, now));
26
+ const teamMembers = new Set(options.teamMembers);
27
+ const teamPrs = pullRequests
28
+ .filter((pr) => pr.state === 'open')
29
+ .filter((pr) => options.includeDrafts || !pr.draft)
30
+ .filter((pr) => pr.author !== options.user)
31
+ .filter((pr) => teamMembers.has(pr.author))
32
+ .filter((pr) => !isExcludedAuthor(pr.author, exclude, options.excludeBots))
33
+ .map((pr) => buildPullRequestSummary(pr, now));
34
+ const commitsForUser = commits
35
+ .filter((commit) => commit.author === options.user)
36
+ .filter((commit) => commit.committed_at >= options.timeRange.start)
37
+ .filter((commit) => commit.committed_at <= options.timeRange.end)
38
+ .filter((commit) => !isExcludedAuthor(commit.author, exclude, options.excludeBots));
39
+ const commitSummaries = commitsForUser.map((commit) => ({
40
+ repo: commit.repo,
41
+ sha: commit.sha,
42
+ author: commit.author,
43
+ committed_at: commit.committed_at,
44
+ message: commit.message,
45
+ additions: commit.additions,
46
+ deletions: commit.deletions,
47
+ files_changed: commit.files_changed,
48
+ }));
49
+ const summary = buildPersonalSummary(events, prLookup, options.user, commitsForUser);
50
+ return {
51
+ type: options.meta.audience === 'individual' ? 'individual' : 'personal',
52
+ user: options.user,
53
+ meta: options.meta,
54
+ summary,
55
+ review_debt: {
56
+ requested,
57
+ team_prs: teamPrs,
58
+ },
59
+ open_work: {
60
+ open_prs: openWork,
61
+ },
62
+ authored_prs: authoredSummaries,
63
+ commits_detail: commitSummaries,
64
+ };
65
+ }
66
+ function buildPersonalSummary(events, prLookup, user, commits) {
67
+ const opened = events.filter((event) => event.type === 'pr_opened' && event.author === user);
68
+ const merged = events.filter((event) => event.type === 'pr_merged' && prMatchesAuthor(prLookup, event.repo, event.pr_number, user));
69
+ const closed = events.filter((event) => event.type === 'pr_closed' && prMatchesAuthor(prLookup, event.repo, event.pr_number, user));
70
+ const reposTouched = new Set(commits.map((commit) => commit.repo));
71
+ const linesChanged = commits.reduce((total, commit) => total + commit.additions + commit.deletions, 0);
72
+ return {
73
+ prs_opened: opened.length,
74
+ prs_merged: merged.length,
75
+ prs_closed: closed.length,
76
+ commits: commits.length,
77
+ repos_touched: reposTouched.size,
78
+ lines_changed: linesChanged,
79
+ };
80
+ }
81
+ function prMatchesAuthor(lookup, repo, number, user) {
82
+ const pr = lookup.get(`${repo}#${number}`);
83
+ return pr ? pr.author === user : false;
84
+ }
85
+ function buildPullRequestLookup(pullRequests) {
86
+ return new Map(pullRequests.map((pr) => [`${pr.repo}#${pr.number}`, pr]));
87
+ }