paperclip-github-plugin 0.7.5 → 0.8.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/worker.js CHANGED
@@ -223,6 +223,21 @@ var GITHUB_AGENT_TOOLS = [
223
223
  }
224
224
  }
225
225
  },
226
+ {
227
+ name: "assign_to_current_user",
228
+ displayName: "Assign To Current User",
229
+ description: "Assign a GitHub issue to the GitHub user authenticated by the saved token, preserving any existing assignees.",
230
+ parametersSchema: {
231
+ type: "object",
232
+ additionalProperties: false,
233
+ ...issueTargetSchema,
234
+ properties: {
235
+ repository: repositoryProperty,
236
+ issueNumber: issueNumberProperty,
237
+ paperclipIssueId: paperclipIssueIdProperty
238
+ }
239
+ }
240
+ },
226
241
  {
227
242
  name: "add_issue_comment",
228
243
  displayName: "Add Issue Comment",
@@ -797,6 +812,7 @@ var GITHUB_PULL_REQUEST_CI_CONTEXTS_QUERY = `
797
812
  pullRequest(number: $pullRequestNumber) {
798
813
  mergeable
799
814
  mergeStateStatus
815
+ reviewDecision
800
816
  statusCheckRollup {
801
817
  contexts(first: 100, after: $after) {
802
818
  pageInfo {
@@ -862,6 +878,7 @@ var GITHUB_REPOSITORY_OPEN_PULL_REQUEST_STATUSES_QUERY = `
862
878
  number
863
879
  mergeable
864
880
  mergeStateStatus
881
+ reviewDecision
865
882
  reviewThreads(first: 100) {
866
883
  pageInfo {
867
884
  hasNextPage
@@ -902,6 +919,7 @@ var GITHUB_PROJECT_PULL_REQUEST_BASE_FIELDS = `
902
919
  state
903
920
  mergeable
904
921
  mergeStateStatus
922
+ reviewDecision
905
923
  createdAt
906
924
  updatedAt
907
925
  baseRefName
@@ -988,6 +1006,7 @@ var GITHUB_PROJECT_PULL_REQUEST_METRICS_FIELDS = `
988
1006
  number
989
1007
  mergeable
990
1008
  mergeStateStatus
1009
+ reviewDecision
991
1010
  baseRefName
992
1011
  reviews(first: 100) {
993
1012
  pageInfo {
@@ -2243,6 +2262,40 @@ function doesImportedIssueMatchTarget(issue, target) {
2243
2262
  }
2244
2263
  return target.issueId !== void 0 && issue.paperclipIssueId === target.issueId || target.githubIssueId !== void 0 && issue.githubIssueId === target.githubIssueId || target.githubIssueNumber !== void 0 && issue.githubIssueNumber === target.githubIssueNumber;
2245
2264
  }
2265
+ function resolvePaperclipIssueOriginGitHubIssueLink(issue, companyId) {
2266
+ if (issue?.originKind !== GITHUB_ISSUE_ORIGIN_KIND || typeof issue.originId !== "string") {
2267
+ return null;
2268
+ }
2269
+ const reference = parseGitHubIssueHtmlUrl(issue.originId);
2270
+ if (!reference) {
2271
+ return null;
2272
+ }
2273
+ return {
2274
+ source: "issue_origin",
2275
+ companyId,
2276
+ paperclipProjectId: issue.projectId ?? void 0,
2277
+ repositoryUrl: reference.repositoryUrl,
2278
+ githubIssueNumber: reference.issueNumber,
2279
+ githubIssueUrl: reference.issueUrl,
2280
+ linkedPullRequestNumbers: [],
2281
+ linkedPullRequests: []
2282
+ };
2283
+ }
2284
+ function resolvePaperclipIssueOriginGitHubPullRequestLink(issue) {
2285
+ if (issue?.originKind !== GITHUB_PULL_REQUEST_ORIGIN_KIND || typeof issue.originId !== "string") {
2286
+ return null;
2287
+ }
2288
+ const reference = parseGitHubPullRequestHtmlUrl(issue.originId);
2289
+ if (!reference) {
2290
+ return null;
2291
+ }
2292
+ return {
2293
+ source: "issue_origin",
2294
+ repositoryUrl: reference.repositoryUrl,
2295
+ githubPullRequestNumber: reference.pullRequestNumber,
2296
+ githubPullRequestUrl: reference.pullRequestUrl
2297
+ };
2298
+ }
2246
2299
  async function resolvePaperclipIssueGitHubLink(ctx, issueId, companyId, options = {}) {
2247
2300
  const linkRecords = options.linkRecords ?? await listGitHubIssueLinkRecords(ctx, {
2248
2301
  paperclipIssueId: issueId
@@ -2287,6 +2340,10 @@ async function resolvePaperclipIssueGitHubLink(ctx, issueId, companyId, options
2287
2340
  }
2288
2341
  }
2289
2342
  const issue = options.paperclipIssue ?? await ctx.issues.get(issueId, companyId);
2343
+ const issueOriginLink = resolvePaperclipIssueOriginGitHubIssueLink(issue, companyId);
2344
+ if (issueOriginLink) {
2345
+ return await hydrateRecoveredPaperclipIssueGitHubLink(ctx, issueId, issueOriginLink) ?? issueOriginLink;
2346
+ }
2290
2347
  const githubIssueUrl = extractImportedGitHubIssueUrlFromDescription(issue?.description);
2291
2348
  const githubIssueReference = githubIssueUrl ? parseGitHubIssueHtmlUrl(githubIssueUrl) : null;
2292
2349
  if (!githubIssueReference) {
@@ -2794,26 +2851,45 @@ async function buildIssueGitHubDetails(ctx, input) {
2794
2851
  const safeLeftTimestamp = Number.isFinite(leftTimestamp) ? leftTimestamp : 0;
2795
2852
  return safeRightTimestamp - safeLeftTimestamp;
2796
2853
  })[0];
2797
- if (!pullRequestLink) {
2854
+ if (pullRequestLink) {
2855
+ return {
2856
+ paperclipIssueId: issueId,
2857
+ kind: "pull_request",
2858
+ source: "pull_request_entity",
2859
+ repositoryUrl: pullRequestLink.data.repositoryUrl,
2860
+ githubPullRequestNumber: pullRequestLink.data.githubPullRequestNumber,
2861
+ githubPullRequestUrl: pullRequestLink.data.githubPullRequestUrl,
2862
+ githubPullRequestState: pullRequestLink.data.githubPullRequestState,
2863
+ title: pullRequestLink.data.title,
2864
+ linkedPullRequestNumbers: [pullRequestLink.data.githubPullRequestNumber],
2865
+ linkedPullRequests: [
2866
+ {
2867
+ number: pullRequestLink.data.githubPullRequestNumber,
2868
+ repositoryUrl: pullRequestLink.data.repositoryUrl
2869
+ }
2870
+ ],
2871
+ syncedAt: pullRequestLink.data.syncedAt
2872
+ };
2873
+ }
2874
+ const paperclipIssue = await ctx.issues.get(issueId, companyId);
2875
+ const pullRequestOriginLink = resolvePaperclipIssueOriginGitHubPullRequestLink(paperclipIssue);
2876
+ if (!pullRequestOriginLink) {
2798
2877
  return null;
2799
2878
  }
2800
2879
  return {
2801
2880
  paperclipIssueId: issueId,
2802
2881
  kind: "pull_request",
2803
- source: "pull_request_entity",
2804
- repositoryUrl: pullRequestLink.data.repositoryUrl,
2805
- githubPullRequestNumber: pullRequestLink.data.githubPullRequestNumber,
2806
- githubPullRequestUrl: pullRequestLink.data.githubPullRequestUrl,
2807
- githubPullRequestState: pullRequestLink.data.githubPullRequestState,
2808
- title: pullRequestLink.data.title,
2809
- linkedPullRequestNumbers: [pullRequestLink.data.githubPullRequestNumber],
2882
+ source: pullRequestOriginLink.source,
2883
+ repositoryUrl: pullRequestOriginLink.repositoryUrl,
2884
+ githubPullRequestNumber: pullRequestOriginLink.githubPullRequestNumber,
2885
+ githubPullRequestUrl: pullRequestOriginLink.githubPullRequestUrl,
2886
+ linkedPullRequestNumbers: [pullRequestOriginLink.githubPullRequestNumber],
2810
2887
  linkedPullRequests: [
2811
2888
  {
2812
- number: pullRequestLink.data.githubPullRequestNumber,
2813
- repositoryUrl: pullRequestLink.data.repositoryUrl
2889
+ number: pullRequestOriginLink.githubPullRequestNumber,
2890
+ repositoryUrl: pullRequestOriginLink.repositoryUrl
2814
2891
  }
2815
- ],
2816
- syncedAt: pullRequestLink.data.syncedAt
2892
+ ]
2817
2893
  };
2818
2894
  }
2819
2895
  async function resolveIssueByIdentifier(ctx, input) {
@@ -5463,11 +5539,23 @@ function normalizeGitHubPullRequestMergeStateStatus(value) {
5463
5539
  return "unknown";
5464
5540
  }
5465
5541
  }
5542
+ function normalizeGitHubPullRequestReviewDecision(value) {
5543
+ switch (typeof value === "string" ? value.trim().toLowerCase() : "") {
5544
+ case "approved":
5545
+ return "approved";
5546
+ case "changes_requested":
5547
+ return "changes_requested";
5548
+ case "review_required":
5549
+ return "review_required";
5550
+ default:
5551
+ return "unknown";
5552
+ }
5553
+ }
5466
5554
  function isGitHubPullRequestActionRequiredForSync(pullRequest) {
5467
- return pullRequest.mergeability === "conflicting" || ACTION_REQUIRED_GITHUB_PULL_REQUEST_MERGE_STATE_STATUSES.has(pullRequest.mergeStateStatus);
5555
+ return pullRequest.reviewDecision === "changes_requested" || pullRequest.mergeability === "conflicting" || ACTION_REQUIRED_GITHUB_PULL_REQUEST_MERGE_STATE_STATUSES.has(pullRequest.mergeStateStatus);
5468
5556
  }
5469
5557
  function isGitHubPullRequestReviewReadyForSync(pullRequest) {
5470
- if (pullRequest.ciState !== "green" || pullRequest.hasUnresolvedReviewThreads) {
5558
+ if (pullRequest.ciState !== "green" || pullRequest.hasUnresolvedReviewThreads || pullRequest.reviewDecision === "changes_requested") {
5471
5559
  return false;
5472
5560
  }
5473
5561
  return REVIEW_READY_GITHUB_PULL_REQUEST_MERGE_STATE_STATUSES.has(pullRequest.mergeStateStatus);
@@ -5500,6 +5588,9 @@ function listGitHubPullRequestSyncBlockingConditions(pullRequest) {
5500
5588
  if (pullRequest.hasUnresolvedReviewThreads) {
5501
5589
  conditions.push("unresolved review threads");
5502
5590
  }
5591
+ if (pullRequest.reviewDecision === "changes_requested") {
5592
+ conditions.push("requested changes");
5593
+ }
5503
5594
  return conditions;
5504
5595
  }
5505
5596
  function tryBuildGitHubPullRequestStatusSnapshotFromBatchNode(node, repository) {
@@ -5517,7 +5608,8 @@ function tryBuildGitHubPullRequestStatusSnapshotFromBatchNode(node, repository)
5517
5608
  hasUnresolvedReviewThreads: reviewThreadSummary.unresolvedReviewThreads > 0,
5518
5609
  ciState,
5519
5610
  mergeability: normalizeGitHubPullRequestMergeability(node.mergeable),
5520
- mergeStateStatus: normalizeGitHubPullRequestMergeStateStatus(node.mergeStateStatus)
5611
+ mergeStateStatus: normalizeGitHubPullRequestMergeStateStatus(node.mergeStateStatus),
5612
+ reviewDecision: normalizeGitHubPullRequestReviewDecision(node.reviewDecision)
5521
5613
  };
5522
5614
  }
5523
5615
  function tryBuildGitHubPullRequestCiStateFromBatchNode(node) {
@@ -5574,6 +5666,7 @@ async function getGitHubPullRequestCiSnapshot(octokit, repository, pullRequestNu
5574
5666
  const contexts = [];
5575
5667
  let mergeability = "unknown";
5576
5668
  let mergeStateStatus = "unknown";
5669
+ let reviewDecision = "unknown";
5577
5670
  let after;
5578
5671
  do {
5579
5672
  const response = await octokit.graphql(GITHUB_PULL_REQUEST_CI_CONTEXTS_QUERY, {
@@ -5584,6 +5677,7 @@ async function getGitHubPullRequestCiSnapshot(octokit, repository, pullRequestNu
5584
5677
  });
5585
5678
  mergeability = normalizeGitHubPullRequestMergeability(response.repository?.pullRequest?.mergeable);
5586
5679
  mergeStateStatus = normalizeGitHubPullRequestMergeStateStatus(response.repository?.pullRequest?.mergeStateStatus);
5680
+ reviewDecision = normalizeGitHubPullRequestReviewDecision(response.repository?.pullRequest?.reviewDecision);
5587
5681
  const connection = response.repository?.pullRequest?.statusCheckRollup?.contexts;
5588
5682
  const nodes = connection?.nodes ?? [];
5589
5683
  for (const node of nodes) {
@@ -5610,7 +5704,8 @@ async function getGitHubPullRequestCiSnapshot(octokit, repository, pullRequestNu
5610
5704
  return {
5611
5705
  ciState: classifyGitHubPullRequestCiState(contexts),
5612
5706
  mergeability,
5613
- mergeStateStatus
5707
+ mergeStateStatus,
5708
+ reviewDecision
5614
5709
  };
5615
5710
  }
5616
5711
  async function getGitHubPullRequestStatusSnapshot(octokit, repository, pullRequestNumber, pullRequestStatusCache, options) {
@@ -5624,14 +5719,15 @@ async function getGitHubPullRequestStatusSnapshot(octokit, repository, pullReque
5624
5719
  setCachedGitHubPullRequestStatusSnapshot(pullRequestStatusCache, cachedSnapshot);
5625
5720
  return cachedSnapshot;
5626
5721
  }
5627
- if (options?.reviewThreadSummary && options.ciState && options.mergeability !== void 0 && options.mergeStateStatus !== void 0) {
5722
+ if (options?.reviewThreadSummary && options.ciState && options.mergeability !== void 0 && options.mergeStateStatus !== void 0 && options.reviewDecision !== void 0) {
5628
5723
  const snapshot = cacheGitHubPullRequestStatusSnapshot(repository, {
5629
5724
  number: pullRequestNumber,
5630
5725
  repositoryUrl: repository.url,
5631
5726
  hasUnresolvedReviewThreads: options.reviewThreadSummary.unresolvedReviewThreads > 0,
5632
5727
  ciState: options.ciState,
5633
5728
  mergeability: options.mergeability,
5634
- mergeStateStatus: options.mergeStateStatus
5729
+ mergeStateStatus: options.mergeStateStatus,
5730
+ reviewDecision: options.reviewDecision
5635
5731
  });
5636
5732
  setCachedGitHubPullRequestStatusSnapshot(pullRequestStatusCache, snapshot);
5637
5733
  return snapshot;
@@ -5645,10 +5741,11 @@ async function getGitHubPullRequestStatusSnapshot(octokit, repository, pullReque
5645
5741
  const loadSnapshotPromise = (async () => {
5646
5742
  const [reviewThreadSummary, ciSnapshot] = await Promise.all([
5647
5743
  options?.reviewThreadSummary ?? getOrLoadCachedGitHubPullRequestReviewThreadSummary(octokit, repository, pullRequestNumber),
5648
- options?.ciState && options.mergeability !== void 0 && options.mergeStateStatus !== void 0 ? {
5744
+ options?.ciState && options.mergeability !== void 0 && options.mergeStateStatus !== void 0 && options.reviewDecision !== void 0 ? {
5649
5745
  ciState: options.ciState,
5650
5746
  mergeability: options.mergeability,
5651
- mergeStateStatus: options.mergeStateStatus
5747
+ mergeStateStatus: options.mergeStateStatus,
5748
+ reviewDecision: options.reviewDecision
5652
5749
  } : getGitHubPullRequestCiSnapshot(octokit, repository, pullRequestNumber)
5653
5750
  ]);
5654
5751
  return cacheGitHubPullRequestStatusSnapshot(repository, {
@@ -5657,7 +5754,8 @@ async function getGitHubPullRequestStatusSnapshot(octokit, repository, pullReque
5657
5754
  hasUnresolvedReviewThreads: reviewThreadSummary.unresolvedReviewThreads > 0,
5658
5755
  ciState: ciSnapshot.ciState,
5659
5756
  mergeability: ciSnapshot.mergeability,
5660
- mergeStateStatus: ciSnapshot.mergeStateStatus
5757
+ mergeStateStatus: ciSnapshot.mergeStateStatus,
5758
+ reviewDecision: ciSnapshot.reviewDecision
5661
5759
  });
5662
5760
  })();
5663
5761
  activeGitHubPullRequestStatusSnapshotPromiseCache.set(cacheKey, loadSnapshotPromise);
@@ -10326,6 +10424,7 @@ async function buildProjectPullRequestSummaryRecord(octokit, repository, node, i
10326
10424
  });
10327
10425
  const inlineMergeability = normalizeGitHubPullRequestMergeability(node.mergeable);
10328
10426
  const inlineMergeStateStatus = normalizeGitHubPullRequestMergeStateStatus(node.mergeStateStatus);
10427
+ const inlineReviewDecision = normalizeGitHubPullRequestReviewDecision(node.reviewDecision);
10329
10428
  const [reviewThreadSummary, reviewSummary, statusSnapshot, behindBy] = await Promise.all([
10330
10429
  getOrLoadCachedGitHubPullRequestReviewThreadSummary(
10331
10430
  octokit,
@@ -10343,7 +10442,8 @@ async function buildProjectPullRequestSummaryRecord(octokit, repository, node, i
10343
10442
  reviewThreadSummary: inlineReviewThreadSummary,
10344
10443
  ciState: inlineCiState,
10345
10444
  mergeability: inlineMergeability,
10346
- mergeStateStatus: inlineMergeStateStatus
10445
+ mergeStateStatus: inlineMergeStateStatus,
10446
+ reviewDecision: inlineReviewDecision
10347
10447
  }),
10348
10448
  getGitHubPullRequestBehindCount(octokit, repository, {
10349
10449
  baseBranch: node.baseRefName,
@@ -10483,6 +10583,7 @@ async function buildProjectPullRequestMetricCounts(octokit, repository, node, pu
10483
10583
  });
10484
10584
  const inlineMergeability = normalizeGitHubPullRequestMergeability(node.mergeable);
10485
10585
  const inlineMergeStateStatus = normalizeGitHubPullRequestMergeStateStatus(node.mergeStateStatus);
10586
+ const inlineReviewDecision = normalizeGitHubPullRequestReviewDecision(node.reviewDecision);
10486
10587
  const [reviewThreadSummary, reviewSummary, statusSnapshot] = await Promise.all([
10487
10588
  getOrLoadCachedGitHubPullRequestReviewThreadSummary(
10488
10589
  octokit,
@@ -10500,7 +10601,8 @@ async function buildProjectPullRequestMetricCounts(octokit, repository, node, pu
10500
10601
  reviewThreadSummary: inlineReviewThreadSummary,
10501
10602
  ciState: inlineCiState,
10502
10603
  mergeability: inlineMergeability,
10503
- mergeStateStatus: inlineMergeStateStatus
10604
+ mergeStateStatus: inlineMergeStateStatus,
10605
+ reviewDecision: inlineReviewDecision
10504
10606
  })
10505
10607
  ]);
10506
10608
  const checksStatus = statusSnapshot.ciState === "green" ? "passed" : statusSnapshot.ciState === "red" ? "failed" : "pending";
@@ -13106,6 +13208,66 @@ function registerGitHubAgentTools(ctx) {
13106
13208
  );
13107
13209
  })
13108
13210
  );
13211
+ ctx.tools.register(
13212
+ "assign_to_current_user",
13213
+ getGitHubAgentToolDeclaration("assign_to_current_user"),
13214
+ async (params, runCtx) => executeGitHubTool(async () => {
13215
+ const input = getToolInputRecord(params);
13216
+ const target = await resolveGitHubIssueToolTarget(ctx, runCtx, input);
13217
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
13218
+ const [currentResponse, authenticatedUserResponse] = await Promise.all([
13219
+ octokit.rest.issues.get({
13220
+ owner: target.repository.owner,
13221
+ repo: target.repository.repo,
13222
+ issue_number: target.issueNumber,
13223
+ headers: {
13224
+ "X-GitHub-Api-Version": GITHUB_API_VERSION
13225
+ }
13226
+ }),
13227
+ octokit.rest.users.getAuthenticated({
13228
+ headers: {
13229
+ "X-GitHub-Api-Version": GITHUB_API_VERSION
13230
+ }
13231
+ })
13232
+ ]);
13233
+ const assignedLogin = normalizeOptionalToolString(authenticatedUserResponse.data.login);
13234
+ if (!assignedLogin) {
13235
+ throw new Error("GitHub did not return the login for the saved token.");
13236
+ }
13237
+ const currentAssignees = (currentResponse.data.assignees ?? []).map((assignee) => assignee?.login ?? "").filter(Boolean);
13238
+ const nextAssignees = mergeNamedValues(currentAssignees, {
13239
+ setValues: [],
13240
+ addValues: [assignedLogin],
13241
+ removeValues: []
13242
+ });
13243
+ const alreadyAssigned = nextAssignees.length === currentAssignees.length && nextAssignees.every((assignee, index) => assignee.toLowerCase() === currentAssignees[index]?.toLowerCase());
13244
+ const updatedResponse = alreadyAssigned ? currentResponse : await octokit.rest.issues.update({
13245
+ owner: target.repository.owner,
13246
+ repo: target.repository.repo,
13247
+ issue_number: target.issueNumber,
13248
+ assignees: nextAssignees,
13249
+ headers: {
13250
+ "X-GitHub-Api-Version": GITHUB_API_VERSION
13251
+ }
13252
+ });
13253
+ const updatedIssue = normalizeGitHubIssueRecord(updatedResponse.data);
13254
+ return buildToolSuccessResult(
13255
+ alreadyAssigned ? `GitHub issue #${updatedIssue.number} in ${formatRepositoryLabel(target.repository)} was already assigned to ${assignedLogin}.` : `Assigned GitHub issue #${updatedIssue.number} in ${formatRepositoryLabel(target.repository)} to ${assignedLogin}.`,
13256
+ {
13257
+ repository: target.repository.url,
13258
+ assignedLogin,
13259
+ issue: {
13260
+ number: updatedIssue.number,
13261
+ title: updatedIssue.title,
13262
+ url: updatedIssue.htmlUrl,
13263
+ state: updatedIssue.state,
13264
+ labels: normalizeGitHubIssueLabels(updatedResponse.data.labels),
13265
+ assignees: (updatedResponse.data.assignees ?? []).map((assignee) => assignee?.login ?? "").filter(Boolean)
13266
+ }
13267
+ }
13268
+ );
13269
+ })
13270
+ );
13109
13271
  ctx.tools.register(
13110
13272
  "add_issue_comment",
13111
13273
  getGitHubAgentToolDeclaration("add_issue_comment"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-github-plugin",
3
- "version": "0.7.5",
3
+ "version": "0.8.1",
4
4
  "description": "Paperclip plugin for synchronizing GitHub issues into Paperclip projects.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@octokit/rest": "^22.0.1",
44
- "@paperclipai/plugin-sdk": "^2026.427.0",
44
+ "@paperclipai/plugin-sdk": "^2026.428.0",
45
45
  "react": "^19.2.5",
46
46
  "react-markdown": "^10.1.0",
47
47
  "rehype-raw": "^7.0.0",