@bretwardjames/ghp-core 0.6.1 → 0.7.1
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/dist/index.cjs +550 -2
- package/dist/index.d.cts +93 -3
- package/dist/index.d.ts +93 -3
- package/dist/index.js +548 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,12 +23,15 @@ __export(queries_exports, {
|
|
|
23
23
|
ISSUE_FOR_UPDATE_QUERY: () => ISSUE_FOR_UPDATE_QUERY,
|
|
24
24
|
ISSUE_NODE_ID_QUERY: () => ISSUE_NODE_ID_QUERY,
|
|
25
25
|
ISSUE_RELATIONSHIPS_QUERY: () => ISSUE_RELATIONSHIPS_QUERY,
|
|
26
|
+
ISSUE_TIMELINE_QUERY: () => ISSUE_TIMELINE_QUERY,
|
|
26
27
|
ISSUE_TYPES_QUERY: () => ISSUE_TYPES_QUERY,
|
|
27
28
|
ISSUE_WITH_PROJECT_ITEMS_QUERY: () => ISSUE_WITH_PROJECT_ITEMS_QUERY,
|
|
28
29
|
LABEL_EXISTS_QUERY: () => LABEL_EXISTS_QUERY,
|
|
29
30
|
PROJECT_FIELDS_QUERY: () => PROJECT_FIELDS_QUERY,
|
|
30
31
|
PROJECT_ITEMS_QUERY: () => PROJECT_ITEMS_QUERY,
|
|
31
32
|
PROJECT_VIEWS_QUERY: () => PROJECT_VIEWS_QUERY,
|
|
33
|
+
PR_AUTHORED_SEARCH_QUERY: () => PR_AUTHORED_SEARCH_QUERY,
|
|
34
|
+
PR_REVIEWS_SEARCH_QUERY: () => PR_REVIEWS_SEARCH_QUERY,
|
|
32
35
|
RECENT_ISSUES_QUERY: () => RECENT_ISSUES_QUERY,
|
|
33
36
|
REMOVE_BLOCKED_BY_MUTATION: () => REMOVE_BLOCKED_BY_MUTATION,
|
|
34
37
|
REMOVE_LABELS_MUTATION: () => REMOVE_LABELS_MUTATION,
|
|
@@ -113,6 +116,7 @@ var PROJECT_ITEMS_QUERY = `
|
|
|
113
116
|
number
|
|
114
117
|
url
|
|
115
118
|
state
|
|
119
|
+
updatedAt
|
|
116
120
|
issueType { name }
|
|
117
121
|
assignees(first: 5) { nodes { login } }
|
|
118
122
|
labels(first: 10) { nodes { name color } }
|
|
@@ -127,6 +131,7 @@ var PROJECT_ITEMS_QUERY = `
|
|
|
127
131
|
number
|
|
128
132
|
url
|
|
129
133
|
state
|
|
134
|
+
updatedAt
|
|
130
135
|
merged
|
|
131
136
|
assignees(first: 5) { nodes { login } }
|
|
132
137
|
labels(first: 10) { nodes { name color } }
|
|
@@ -490,6 +495,162 @@ var ISSUE_WITH_PROJECT_ITEMS_QUERY = `
|
|
|
490
495
|
}
|
|
491
496
|
}
|
|
492
497
|
`;
|
|
498
|
+
var ISSUE_TIMELINE_QUERY = `
|
|
499
|
+
query($owner: String!, $name: String!, $number: Int!, $since: DateTime!) {
|
|
500
|
+
repository(owner: $owner, name: $name) {
|
|
501
|
+
issueOrPullRequest(number: $number) {
|
|
502
|
+
... on Issue {
|
|
503
|
+
timelineItems(first: 100, since: $since) {
|
|
504
|
+
nodes {
|
|
505
|
+
__typename
|
|
506
|
+
... on IssueComment {
|
|
507
|
+
author { login }
|
|
508
|
+
createdAt
|
|
509
|
+
body
|
|
510
|
+
}
|
|
511
|
+
... on LabeledEvent {
|
|
512
|
+
actor { login }
|
|
513
|
+
createdAt
|
|
514
|
+
label { name }
|
|
515
|
+
}
|
|
516
|
+
... on UnlabeledEvent {
|
|
517
|
+
actor { login }
|
|
518
|
+
createdAt
|
|
519
|
+
label { name }
|
|
520
|
+
}
|
|
521
|
+
... on AssignedEvent {
|
|
522
|
+
actor { login }
|
|
523
|
+
createdAt
|
|
524
|
+
assignee { ... on User { login } }
|
|
525
|
+
}
|
|
526
|
+
... on UnassignedEvent {
|
|
527
|
+
actor { login }
|
|
528
|
+
createdAt
|
|
529
|
+
assignee { ... on User { login } }
|
|
530
|
+
}
|
|
531
|
+
... on ClosedEvent {
|
|
532
|
+
actor { login }
|
|
533
|
+
createdAt
|
|
534
|
+
}
|
|
535
|
+
... on ReopenedEvent {
|
|
536
|
+
actor { login }
|
|
537
|
+
createdAt
|
|
538
|
+
}
|
|
539
|
+
... on CrossReferencedEvent {
|
|
540
|
+
actor { login }
|
|
541
|
+
createdAt
|
|
542
|
+
source {
|
|
543
|
+
__typename
|
|
544
|
+
... on PullRequest { number title url }
|
|
545
|
+
... on Issue { number title }
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
... on PullRequest {
|
|
552
|
+
timelineItems(first: 100, since: $since) {
|
|
553
|
+
nodes {
|
|
554
|
+
__typename
|
|
555
|
+
... on IssueComment {
|
|
556
|
+
author { login }
|
|
557
|
+
createdAt
|
|
558
|
+
body
|
|
559
|
+
}
|
|
560
|
+
... on LabeledEvent {
|
|
561
|
+
actor { login }
|
|
562
|
+
createdAt
|
|
563
|
+
label { name }
|
|
564
|
+
}
|
|
565
|
+
... on UnlabeledEvent {
|
|
566
|
+
actor { login }
|
|
567
|
+
createdAt
|
|
568
|
+
label { name }
|
|
569
|
+
}
|
|
570
|
+
... on AssignedEvent {
|
|
571
|
+
actor { login }
|
|
572
|
+
createdAt
|
|
573
|
+
assignee { ... on User { login } }
|
|
574
|
+
}
|
|
575
|
+
... on UnassignedEvent {
|
|
576
|
+
actor { login }
|
|
577
|
+
createdAt
|
|
578
|
+
assignee { ... on User { login } }
|
|
579
|
+
}
|
|
580
|
+
... on ClosedEvent {
|
|
581
|
+
actor { login }
|
|
582
|
+
createdAt
|
|
583
|
+
}
|
|
584
|
+
... on ReopenedEvent {
|
|
585
|
+
actor { login }
|
|
586
|
+
createdAt
|
|
587
|
+
}
|
|
588
|
+
... on CrossReferencedEvent {
|
|
589
|
+
actor { login }
|
|
590
|
+
createdAt
|
|
591
|
+
source {
|
|
592
|
+
__typename
|
|
593
|
+
... on PullRequest { number title url }
|
|
594
|
+
... on Issue { number title }
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
... on PullRequestReview {
|
|
598
|
+
author { login }
|
|
599
|
+
createdAt
|
|
600
|
+
state
|
|
601
|
+
}
|
|
602
|
+
... on ReviewRequestedEvent {
|
|
603
|
+
actor { login }
|
|
604
|
+
createdAt
|
|
605
|
+
requestedReviewer {
|
|
606
|
+
... on User { login }
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
`;
|
|
616
|
+
var PR_REVIEWS_SEARCH_QUERY = `
|
|
617
|
+
query($searchQuery: String!) {
|
|
618
|
+
search(query: $searchQuery, type: ISSUE, first: 50) {
|
|
619
|
+
nodes {
|
|
620
|
+
... on PullRequest {
|
|
621
|
+
number
|
|
622
|
+
title
|
|
623
|
+
url
|
|
624
|
+
author { login }
|
|
625
|
+
reviews(first: 20) {
|
|
626
|
+
nodes {
|
|
627
|
+
author { login }
|
|
628
|
+
state
|
|
629
|
+
submittedAt
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
`;
|
|
637
|
+
var PR_AUTHORED_SEARCH_QUERY = `
|
|
638
|
+
query($searchQuery: String!) {
|
|
639
|
+
search(query: $searchQuery, type: ISSUE, first: 50) {
|
|
640
|
+
nodes {
|
|
641
|
+
... on PullRequest {
|
|
642
|
+
number
|
|
643
|
+
title
|
|
644
|
+
url
|
|
645
|
+
state
|
|
646
|
+
createdAt
|
|
647
|
+
mergedAt
|
|
648
|
+
mergedBy { login }
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
`;
|
|
493
654
|
var ISSUE_RELATIONSHIPS_QUERY = `
|
|
494
655
|
query($owner: String!, $name: String!, $number: Int!) {
|
|
495
656
|
repository(owner: $owner, name: $name) {
|
|
@@ -895,7 +1056,8 @@ var GitHubAPI = class {
|
|
|
895
1056
|
parent,
|
|
896
1057
|
subIssues,
|
|
897
1058
|
blockedBy,
|
|
898
|
-
blocking
|
|
1059
|
+
blocking,
|
|
1060
|
+
updatedAt: content.updatedAt || null
|
|
899
1061
|
};
|
|
900
1062
|
});
|
|
901
1063
|
}
|
|
@@ -1004,7 +1166,8 @@ var GitHubAPI = class {
|
|
|
1004
1166
|
parent: issue.parent,
|
|
1005
1167
|
subIssues: issue.subIssues.nodes,
|
|
1006
1168
|
blockedBy: issue.blockedBy.nodes,
|
|
1007
|
-
blocking: issue.blocking.nodes
|
|
1169
|
+
blocking: issue.blocking.nodes,
|
|
1170
|
+
updatedAt: null
|
|
1008
1171
|
};
|
|
1009
1172
|
} catch (error) {
|
|
1010
1173
|
return null;
|
|
@@ -1566,6 +1729,279 @@ var GitHubAPI = class {
|
|
|
1566
1729
|
return null;
|
|
1567
1730
|
}
|
|
1568
1731
|
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Get recent activity across all project items since a given time.
|
|
1734
|
+
* Multi-pass approach:
|
|
1735
|
+
* Pass 1: Fetch all project items, filter by updatedAt client-side
|
|
1736
|
+
* Pass 2: Fetch timeline events only for items that changed
|
|
1737
|
+
* Pass 3: Search for PRs reviewed by user (not captured in project items)
|
|
1738
|
+
* Pass 4: Search for PRs authored by user (created/merged)
|
|
1739
|
+
*/
|
|
1740
|
+
async getRecentActivity(repo, since, options) {
|
|
1741
|
+
if (!this.graphqlWithAuth) throw new Error("Not authenticated");
|
|
1742
|
+
const projects = await this.getProjects(repo);
|
|
1743
|
+
if (projects.length === 0) return [];
|
|
1744
|
+
const allItems = [];
|
|
1745
|
+
for (const project of projects) {
|
|
1746
|
+
const items = await this.getProjectItems(project.id, project.title);
|
|
1747
|
+
allItems.push(...items);
|
|
1748
|
+
}
|
|
1749
|
+
const sinceMs = since.getTime();
|
|
1750
|
+
const sinceISO = since.toISOString();
|
|
1751
|
+
const sinceDate = sinceISO.split("T")[0];
|
|
1752
|
+
let recentItems = allItems.filter(
|
|
1753
|
+
(item) => item.updatedAt && new Date(item.updatedAt).getTime() >= sinceMs
|
|
1754
|
+
);
|
|
1755
|
+
if (options?.mine && this.username) {
|
|
1756
|
+
recentItems = recentItems.filter(
|
|
1757
|
+
(item) => item.assignees.includes(this.username)
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1761
|
+
recentItems = recentItems.filter((item) => {
|
|
1762
|
+
const key = `${item.repository || ""}#${item.number}`;
|
|
1763
|
+
if (!item.number || seen.has(key)) return false;
|
|
1764
|
+
seen.add(key);
|
|
1765
|
+
return true;
|
|
1766
|
+
});
|
|
1767
|
+
const activities = [];
|
|
1768
|
+
const BATCH_SIZE = 5;
|
|
1769
|
+
for (let i = 0; i < recentItems.length; i += BATCH_SIZE) {
|
|
1770
|
+
const batch = recentItems.slice(i, i + BATCH_SIZE);
|
|
1771
|
+
const results = await Promise.allSettled(
|
|
1772
|
+
batch.map(async (item) => {
|
|
1773
|
+
if (!item.number || !item.repository) return null;
|
|
1774
|
+
const [owner, name] = item.repository.split("/");
|
|
1775
|
+
if (!owner || !name) return null;
|
|
1776
|
+
const events = await this.fetchTimelineEvents(
|
|
1777
|
+
owner,
|
|
1778
|
+
name,
|
|
1779
|
+
item.number,
|
|
1780
|
+
sinceISO
|
|
1781
|
+
);
|
|
1782
|
+
if (events.length === 0) return null;
|
|
1783
|
+
return {
|
|
1784
|
+
issue: {
|
|
1785
|
+
number: item.number,
|
|
1786
|
+
title: item.title,
|
|
1787
|
+
url: item.url || ""
|
|
1788
|
+
},
|
|
1789
|
+
status: item.status,
|
|
1790
|
+
assignees: item.assignees,
|
|
1791
|
+
changes: events
|
|
1792
|
+
};
|
|
1793
|
+
})
|
|
1794
|
+
);
|
|
1795
|
+
for (const result of results) {
|
|
1796
|
+
if (result.status === "fulfilled" && result.value) {
|
|
1797
|
+
activities.push(result.value);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
const activityNumbers = new Set(activities.map((a) => a.issue.number));
|
|
1802
|
+
if (this.username) {
|
|
1803
|
+
const reviewActivities = await this.fetchReviewedPRs(
|
|
1804
|
+
repo,
|
|
1805
|
+
this.username,
|
|
1806
|
+
sinceDate,
|
|
1807
|
+
sinceMs,
|
|
1808
|
+
activityNumbers
|
|
1809
|
+
);
|
|
1810
|
+
for (const a of reviewActivities) {
|
|
1811
|
+
activityNumbers.add(a.issue.number);
|
|
1812
|
+
activities.push(a);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
if (this.username) {
|
|
1816
|
+
const authoredActivities = await this.fetchAuthoredPRs(
|
|
1817
|
+
repo,
|
|
1818
|
+
this.username,
|
|
1819
|
+
sinceDate,
|
|
1820
|
+
sinceMs,
|
|
1821
|
+
activityNumbers
|
|
1822
|
+
);
|
|
1823
|
+
for (const a of authoredActivities) {
|
|
1824
|
+
activityNumbers.add(a.issue.number);
|
|
1825
|
+
activities.push(a);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
activities.sort((a, b) => {
|
|
1829
|
+
const aLatest = a.changes[a.changes.length - 1]?.timestamp || "";
|
|
1830
|
+
const bLatest = b.changes[b.changes.length - 1]?.timestamp || "";
|
|
1831
|
+
return bLatest.localeCompare(aLatest);
|
|
1832
|
+
});
|
|
1833
|
+
return activities;
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Search for PRs the user reviewed that aren't already in the activity list.
|
|
1837
|
+
*/
|
|
1838
|
+
async fetchReviewedPRs(repo, username, sinceDate, sinceMs, seen) {
|
|
1839
|
+
const searchQuery = `repo:${repo.owner}/${repo.name} reviewed-by:${username} updated:>=${sinceDate} is:pr`;
|
|
1840
|
+
const response = await this.graphqlWithRetry(
|
|
1841
|
+
PR_REVIEWS_SEARCH_QUERY,
|
|
1842
|
+
{ searchQuery }
|
|
1843
|
+
);
|
|
1844
|
+
const activities = [];
|
|
1845
|
+
for (const pr of response.search.nodes) {
|
|
1846
|
+
if (seen.has(pr.number)) continue;
|
|
1847
|
+
const recentReviews = pr.reviews.nodes.filter(
|
|
1848
|
+
(r) => r.author?.login === username && new Date(r.submittedAt).getTime() >= sinceMs
|
|
1849
|
+
);
|
|
1850
|
+
if (recentReviews.length === 0) continue;
|
|
1851
|
+
const events = recentReviews.map((r) => {
|
|
1852
|
+
const stateLabel = r.state === "APPROVED" ? "Approved" : r.state === "CHANGES_REQUESTED" ? "Changes requested" : r.state === "COMMENTED" ? "Review comment" : r.state === "DISMISSED" ? "Review dismissed" : r.state;
|
|
1853
|
+
return {
|
|
1854
|
+
type: "review_submitted",
|
|
1855
|
+
actor: username,
|
|
1856
|
+
timestamp: r.submittedAt,
|
|
1857
|
+
details: stateLabel
|
|
1858
|
+
};
|
|
1859
|
+
});
|
|
1860
|
+
activities.push({
|
|
1861
|
+
issue: { number: pr.number, title: pr.title, url: pr.url },
|
|
1862
|
+
status: null,
|
|
1863
|
+
assignees: [],
|
|
1864
|
+
changes: events
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
return activities;
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Search for PRs the user authored that aren't already in the activity list.
|
|
1871
|
+
* Captures PR creation and merges.
|
|
1872
|
+
*/
|
|
1873
|
+
async fetchAuthoredPRs(repo, username, sinceDate, sinceMs, seen) {
|
|
1874
|
+
const searchQuery = `repo:${repo.owner}/${repo.name} author:${username} created:>=${sinceDate} is:pr`;
|
|
1875
|
+
const response = await this.graphqlWithRetry(
|
|
1876
|
+
PR_AUTHORED_SEARCH_QUERY,
|
|
1877
|
+
{ searchQuery }
|
|
1878
|
+
);
|
|
1879
|
+
const activities = [];
|
|
1880
|
+
for (const pr of response.search.nodes) {
|
|
1881
|
+
if (seen.has(pr.number)) continue;
|
|
1882
|
+
const events = [];
|
|
1883
|
+
if (new Date(pr.createdAt).getTime() >= sinceMs) {
|
|
1884
|
+
events.push({
|
|
1885
|
+
type: "pr_created",
|
|
1886
|
+
actor: username,
|
|
1887
|
+
timestamp: pr.createdAt
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
if (pr.mergedAt && new Date(pr.mergedAt).getTime() >= sinceMs) {
|
|
1891
|
+
events.push({
|
|
1892
|
+
type: "pr_merged",
|
|
1893
|
+
actor: pr.mergedBy?.login || username,
|
|
1894
|
+
timestamp: pr.mergedAt
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
if (events.length === 0) continue;
|
|
1898
|
+
activities.push({
|
|
1899
|
+
issue: { number: pr.number, title: pr.title, url: pr.url },
|
|
1900
|
+
status: pr.state === "MERGED" ? "Merged" : pr.state === "OPEN" ? "Open" : "Closed",
|
|
1901
|
+
assignees: [username],
|
|
1902
|
+
changes: events
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
return activities;
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Fetch and normalize timeline events for a single issue/PR
|
|
1909
|
+
*/
|
|
1910
|
+
async fetchTimelineEvents(owner, name, number, since) {
|
|
1911
|
+
if (!this.graphqlWithAuth) throw new Error("Not authenticated");
|
|
1912
|
+
const response = await this.graphqlWithRetry(
|
|
1913
|
+
ISSUE_TIMELINE_QUERY,
|
|
1914
|
+
{ owner, name, number, since }
|
|
1915
|
+
);
|
|
1916
|
+
const item = response.repository.issueOrPullRequest;
|
|
1917
|
+
if (!item) return [];
|
|
1918
|
+
const events = [];
|
|
1919
|
+
for (const node of item.timelineItems.nodes) {
|
|
1920
|
+
const actor = node.actor?.login || node.author?.login || "unknown";
|
|
1921
|
+
const timestamp = node.createdAt || "";
|
|
1922
|
+
switch (node.__typename) {
|
|
1923
|
+
case "IssueComment":
|
|
1924
|
+
events.push({
|
|
1925
|
+
type: "comment",
|
|
1926
|
+
actor,
|
|
1927
|
+
timestamp,
|
|
1928
|
+
details: node.body ? node.body.substring(0, 80) + (node.body.length > 80 ? "..." : "") : void 0
|
|
1929
|
+
});
|
|
1930
|
+
break;
|
|
1931
|
+
case "LabeledEvent":
|
|
1932
|
+
events.push({
|
|
1933
|
+
type: "labeled",
|
|
1934
|
+
actor,
|
|
1935
|
+
timestamp,
|
|
1936
|
+
details: node.label?.name
|
|
1937
|
+
});
|
|
1938
|
+
break;
|
|
1939
|
+
case "UnlabeledEvent":
|
|
1940
|
+
events.push({
|
|
1941
|
+
type: "unlabeled",
|
|
1942
|
+
actor,
|
|
1943
|
+
timestamp,
|
|
1944
|
+
details: node.label?.name
|
|
1945
|
+
});
|
|
1946
|
+
break;
|
|
1947
|
+
case "AssignedEvent":
|
|
1948
|
+
events.push({
|
|
1949
|
+
type: "assigned",
|
|
1950
|
+
actor,
|
|
1951
|
+
timestamp,
|
|
1952
|
+
details: node.assignee?.login
|
|
1953
|
+
});
|
|
1954
|
+
break;
|
|
1955
|
+
case "UnassignedEvent":
|
|
1956
|
+
events.push({
|
|
1957
|
+
type: "unassigned",
|
|
1958
|
+
actor,
|
|
1959
|
+
timestamp,
|
|
1960
|
+
details: node.assignee?.login
|
|
1961
|
+
});
|
|
1962
|
+
break;
|
|
1963
|
+
case "ClosedEvent":
|
|
1964
|
+
events.push({ type: "closed", actor, timestamp });
|
|
1965
|
+
break;
|
|
1966
|
+
case "ReopenedEvent":
|
|
1967
|
+
events.push({ type: "reopened", actor, timestamp });
|
|
1968
|
+
break;
|
|
1969
|
+
case "CrossReferencedEvent": {
|
|
1970
|
+
const source = node.source;
|
|
1971
|
+
if (source) {
|
|
1972
|
+
const ref = source.__typename === "PullRequest" ? `PR #${source.number}: ${source.title}` : `#${source.number}: ${source.title}`;
|
|
1973
|
+
events.push({
|
|
1974
|
+
type: "referenced",
|
|
1975
|
+
actor,
|
|
1976
|
+
timestamp,
|
|
1977
|
+
details: ref
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1982
|
+
case "PullRequestReview": {
|
|
1983
|
+
const stateLabel = node.state === "APPROVED" ? "Approved" : node.state === "CHANGES_REQUESTED" ? "Changes requested" : node.state === "COMMENTED" ? "Review comment" : node.state === "DISMISSED" ? "Review dismissed" : node.state || "Reviewed";
|
|
1984
|
+
events.push({
|
|
1985
|
+
type: "review_submitted",
|
|
1986
|
+
actor,
|
|
1987
|
+
timestamp,
|
|
1988
|
+
details: stateLabel
|
|
1989
|
+
});
|
|
1990
|
+
break;
|
|
1991
|
+
}
|
|
1992
|
+
case "ReviewRequestedEvent": {
|
|
1993
|
+
events.push({
|
|
1994
|
+
type: "review_requested",
|
|
1995
|
+
actor,
|
|
1996
|
+
timestamp,
|
|
1997
|
+
details: node.requestedReviewer?.login
|
|
1998
|
+
});
|
|
1999
|
+
break;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
return events;
|
|
2004
|
+
}
|
|
1569
2005
|
};
|
|
1570
2006
|
|
|
1571
2007
|
// src/branch-linker.ts
|
|
@@ -3637,6 +4073,114 @@ function validateUrl(url) {
|
|
|
3637
4073
|
return url;
|
|
3638
4074
|
}
|
|
3639
4075
|
|
|
4076
|
+
// src/standup.ts
|
|
4077
|
+
function formatStandupText(activities, options) {
|
|
4078
|
+
const { since } = options;
|
|
4079
|
+
const lines = [];
|
|
4080
|
+
const sinceStr = formatRelativeDate(since);
|
|
4081
|
+
const issueCount = activities.length;
|
|
4082
|
+
lines.push(`Since ${sinceStr} \u2014 ${issueCount} issue${issueCount !== 1 ? "s" : ""} changed`);
|
|
4083
|
+
lines.push("");
|
|
4084
|
+
if (activities.length === 0) {
|
|
4085
|
+
lines.push("No activity found in this time window.");
|
|
4086
|
+
return lines.join("\n");
|
|
4087
|
+
}
|
|
4088
|
+
for (const activity of activities) {
|
|
4089
|
+
const statusTag = activity.status ? ` [${activity.status}]` : "";
|
|
4090
|
+
lines.push(`#${activity.issue.number} ${activity.issue.title}${statusTag}`);
|
|
4091
|
+
for (const event of activity.changes) {
|
|
4092
|
+
lines.push(` ${formatEventLine(event)}`);
|
|
4093
|
+
}
|
|
4094
|
+
lines.push("");
|
|
4095
|
+
}
|
|
4096
|
+
return lines.join("\n").trimEnd();
|
|
4097
|
+
}
|
|
4098
|
+
function parseSince(input) {
|
|
4099
|
+
const isoDate = new Date(input);
|
|
4100
|
+
if (!isNaN(isoDate.getTime()) && input.includes("-")) {
|
|
4101
|
+
return isoDate;
|
|
4102
|
+
}
|
|
4103
|
+
const match = input.match(/^(\d+)\s*(h|d|w)$/i);
|
|
4104
|
+
if (!match) {
|
|
4105
|
+
throw new Error(`Invalid duration format: "${input}". Use formats like "24h", "2d", "1w", or an ISO date.`);
|
|
4106
|
+
}
|
|
4107
|
+
const amount = parseInt(match[1], 10);
|
|
4108
|
+
const unit = match[2].toLowerCase();
|
|
4109
|
+
const now = /* @__PURE__ */ new Date();
|
|
4110
|
+
switch (unit) {
|
|
4111
|
+
case "h":
|
|
4112
|
+
return new Date(now.getTime() - amount * 60 * 60 * 1e3);
|
|
4113
|
+
case "d":
|
|
4114
|
+
return new Date(now.getTime() - amount * 24 * 60 * 60 * 1e3);
|
|
4115
|
+
case "w":
|
|
4116
|
+
return new Date(now.getTime() - amount * 7 * 24 * 60 * 60 * 1e3);
|
|
4117
|
+
default:
|
|
4118
|
+
throw new Error(`Unknown duration unit: "${unit}"`);
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
function formatEventLine(event) {
|
|
4122
|
+
const arrow = "\u2197";
|
|
4123
|
+
const timestamp = formatShortTimestamp(event.timestamp);
|
|
4124
|
+
const actor = event.actor;
|
|
4125
|
+
switch (event.type) {
|
|
4126
|
+
case "comment":
|
|
4127
|
+
return `${arrow} Comment by ${actor} (${timestamp})${event.details ? ": " + event.details : ""}`;
|
|
4128
|
+
case "labeled":
|
|
4129
|
+
return `${arrow} Labeled "${event.details}" by ${actor} (${timestamp})`;
|
|
4130
|
+
case "unlabeled":
|
|
4131
|
+
return `${arrow} Unlabeled "${event.details}" by ${actor} (${timestamp})`;
|
|
4132
|
+
case "assigned":
|
|
4133
|
+
return `${arrow} Assigned to ${event.details || actor} (${timestamp})`;
|
|
4134
|
+
case "unassigned":
|
|
4135
|
+
return `${arrow} Unassigned ${event.details || ""} by ${actor} (${timestamp})`;
|
|
4136
|
+
case "closed":
|
|
4137
|
+
return `${arrow} Closed by ${actor} (${timestamp})`;
|
|
4138
|
+
case "reopened":
|
|
4139
|
+
return `${arrow} Reopened by ${actor} (${timestamp})`;
|
|
4140
|
+
case "referenced":
|
|
4141
|
+
return `${arrow} ${event.details} linked by ${actor} (${timestamp})`;
|
|
4142
|
+
case "review_submitted":
|
|
4143
|
+
return `${arrow} ${event.details} by ${actor} (${timestamp})`;
|
|
4144
|
+
case "review_requested":
|
|
4145
|
+
return `${arrow} Review requested from ${event.details || "team"} by ${actor} (${timestamp})`;
|
|
4146
|
+
case "pr_created":
|
|
4147
|
+
return `${arrow} PR created by ${actor} (${timestamp})`;
|
|
4148
|
+
case "pr_merged":
|
|
4149
|
+
return `${arrow} PR merged by ${actor} (${timestamp})`;
|
|
4150
|
+
default:
|
|
4151
|
+
return `${arrow} ${event.type} by ${actor} (${timestamp})`;
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
function formatRelativeDate(date) {
|
|
4155
|
+
const now = /* @__PURE__ */ new Date();
|
|
4156
|
+
const diffMs = now.getTime() - date.getTime();
|
|
4157
|
+
const diffHours = Math.round(diffMs / (1e3 * 60 * 60));
|
|
4158
|
+
const dateStr = date.toLocaleDateString("en-US", {
|
|
4159
|
+
month: "short",
|
|
4160
|
+
day: "numeric",
|
|
4161
|
+
hour: "2-digit",
|
|
4162
|
+
minute: "2-digit"
|
|
4163
|
+
});
|
|
4164
|
+
if (diffHours < 24) {
|
|
4165
|
+
return `${diffHours}h ago (${dateStr})`;
|
|
4166
|
+
} else if (diffHours < 48) {
|
|
4167
|
+
return `yesterday (${dateStr})`;
|
|
4168
|
+
} else {
|
|
4169
|
+
const diffDays = Math.round(diffHours / 24);
|
|
4170
|
+
return `${diffDays} days ago (${dateStr})`;
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
function formatShortTimestamp(isoTimestamp) {
|
|
4174
|
+
if (!isoTimestamp) return "";
|
|
4175
|
+
const date = new Date(isoTimestamp);
|
|
4176
|
+
return date.toLocaleDateString("en-US", {
|
|
4177
|
+
month: "short",
|
|
4178
|
+
day: "numeric",
|
|
4179
|
+
hour: "2-digit",
|
|
4180
|
+
minute: "2-digit"
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
4183
|
+
|
|
3640
4184
|
// src/dashboard/index.ts
|
|
3641
4185
|
import { exec as exec3 } from "child_process";
|
|
3642
4186
|
import { promisify as promisify3 } from "util";
|
|
@@ -5093,6 +5637,7 @@ export {
|
|
|
5093
5637
|
findSessionFile,
|
|
5094
5638
|
formatAction,
|
|
5095
5639
|
formatConflict,
|
|
5640
|
+
formatStandupText,
|
|
5096
5641
|
gatherDashboardData,
|
|
5097
5642
|
generateBranchName,
|
|
5098
5643
|
generateWorktreePath,
|
|
@@ -5150,6 +5695,7 @@ export {
|
|
|
5150
5695
|
parseIssueUrl,
|
|
5151
5696
|
parseRateLimitDelay,
|
|
5152
5697
|
parseSessionLine,
|
|
5698
|
+
parseSince,
|
|
5153
5699
|
pullLatest,
|
|
5154
5700
|
queries_exports as queries,
|
|
5155
5701
|
registerAgent,
|