paperclip-github-plugin 0.8.0 → 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/README.md CHANGED
@@ -62,7 +62,7 @@ If a company already has a Paperclip project bound to a GitHub repository worksp
62
62
 
63
63
  ### Status sync with delivery context
64
64
 
65
- The plugin does more than mirror issue text. It looks at linked pull requests, mergeability, CI, review threads, and trusted new GitHub comments so imported Paperclip issues can reflect where the work actually is. When GitHub links an issue to a pull request in another repository, GitHub Sync now follows that pull request's actual repository for status checks, review-thread state, and deep links instead of assuming the issue repository. When sync closes an imported issue as `done` or `cancelled`, it also clears any pending Paperclip review or approval execution state so the host accepts the terminal transition cleanly.
65
+ The plugin does more than mirror issue text. It looks at linked pull requests, mergeability, CI, review decisions, review threads, and trusted new GitHub comments so imported Paperclip issues can reflect where the work actually is. When GitHub links an issue to a pull request in another repository, GitHub Sync now follows that pull request's actual repository for status checks, review state, and deep links instead of assuming the issue repository. When sync closes an imported issue as `done` or `cancelled`, it also clears any pending Paperclip review or approval execution state so the host accepts the terminal transition cleanly.
66
66
 
67
67
  ### Company KPI dashboard
68
68
 
@@ -78,7 +78,7 @@ Each mapped project can expose a **Pull Requests** entry in the sidebar that ope
78
78
 
79
79
  Paperclip issue linkage on the queue prefers the GitHub issue that the pull request closes, so imported GitHub issues and delivery work stay connected in the same project view. If a pull request has no closing-issue-backed link yet, the queue falls back to the Paperclip issue created directly from that pull request and updates the table immediately when that create action returns.
80
80
 
81
- Those pull-request-created Paperclip issues also stay in the scheduled/manual sync loop even when the pull request does not close a GitHub issue. GitHub Sync checks their CI, merge state, and review threads so new failures or unresolved feedback move the Paperclip issue back into active work. The same durable PR link is written when an agent creates a PR through the plugin tool with `paperclipIssueId`, when an authenticated agent records a `gh`-created PR through the agent API route with `paperclipIssueId`, or when an operator manually links an unlinked issue from the issue page.
81
+ Those pull-request-created Paperclip issues also stay in the scheduled/manual sync loop even when the pull request does not close a GitHub issue. GitHub Sync checks their CI, merge state, review decision, and review threads so new failures or requested feedback move the Paperclip issue back into active work. The same durable PR link is written when an agent creates a PR through the plugin tool with `paperclipIssueId`, when an authenticated agent records a `gh`-created PR through the agent API route with `paperclipIssueId`, or when an operator manually links an unlinked issue from the issue page.
82
82
 
83
83
  The issue detail panel and sync-created comment annotations also preserve cross-repository linked pull requests, showing those PRs with their real repository path so operators land in the right place on GitHub.
84
84
 
@@ -155,8 +155,8 @@ When the local Paperclip API is available, the plugin also syncs labels by name,
155
155
  | Open issue with no linked pull request, created by a repository maintainer | `todo` on first import |
156
156
  | Open issue with no linked pull request | Configured default status, which defaults to `backlog` |
157
157
  | Open issue with a linked pull request and unfinished CI | `in_progress` |
158
- | Open issue with failing CI, a non-mergeable linked pull request, or unresolved review threads | `todo`, or `in_progress` when GitHub Sync can hand the work back to an executor |
159
- | Open issue with green CI, a merge-ready linked pull request, and all review threads resolved | `in_review` |
158
+ | Open issue with failing CI, a non-mergeable linked pull request, requested changes, or unresolved review threads | `todo`, or `in_progress` when GitHub Sync can hand the work back to an executor |
159
+ | Open issue with green CI, a merge-ready linked pull request, no requested changes, and all review threads resolved | `in_review` |
160
160
  | Closed issue completed as finished work | `done` |
161
161
  | Closed issue closed as `not_planned` or `duplicate` | `cancelled` |
162
162
 
@@ -165,7 +165,7 @@ Additional behavior:
165
165
  - Open issues with no linked pull request that are created by a verified repository maintainer/admin bypass the default imported status and start in `todo`.
166
166
  - When Paperclip board access is connected for a company, the advanced assignee dropdowns list both company agents and `Me` for the connected board user.
167
167
  - Newly imported issues that finish sync in `todo` and are assigned to an agent enqueue an assignee wakeup so the agent can pick them up promptly.
168
- - For linked pull requests, GitHub Sync treats merge-conflict, behind-branch, blocked, draft, and unstable merge states as executor work, while merge-ready states such as `CLEAN` and `HAS_HOOKS` can move work into `in_review` when CI and review threads are also clear.
168
+ - For linked pull requests, GitHub Sync treats merge-conflict, behind-branch, blocked, draft, unstable merge states, and requested-changes review decisions as executor work, while merge-ready states such as `CLEAN` and `HAS_HOOKS` can move work into `in_review` when CI is green and there are no requested changes or unresolved review threads.
169
169
  - When sync moves work into `in_review`, GitHub Sync first follows the Paperclip issue execution policy's current reviewer or approver when that stage is visible on the issue. If Paperclip exposes an internal review or approval stage but not yet the participant, the plugin falls back to the configured reviewer or approver handoff assignee. If the transition is only a healthy linked-PR wait with no visible internal review or approval stage, GitHub Sync leaves the issue unassigned so it can wait on normal maintainer review without waking an internal owner.
170
170
  - When sync moves work back into active execution, GitHub Sync first follows the Paperclip issue execution policy `returnAssignee` when it is available. Otherwise it falls back to the configured executor handoff assignee and then to the default imported assignee.
171
171
  - Sync-driven handoffs to agent assignees best-effort enqueue an explicit wakeup so the next reviewer, approver, or executor can pick the issue up even when their agent is not running heartbeats.
package/dist/manifest.js CHANGED
@@ -535,7 +535,7 @@ var COMPANY_METRIC_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_ID}/a
535
535
  var require2 = createRequire(import.meta.url);
536
536
  var packageJson = require2("../package.json");
537
537
  var SCHEDULE_TICK_CRON = "* * * * *";
538
- var MANIFEST_VERSION = "0.8.0"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
538
+ var MANIFEST_VERSION = "0.8.1"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
539
539
  var manifest = {
540
540
  id: GITHUB_SYNC_PLUGIN_ID,
541
541
  apiVersion: 1,
package/dist/worker.js CHANGED
@@ -812,6 +812,7 @@ var GITHUB_PULL_REQUEST_CI_CONTEXTS_QUERY = `
812
812
  pullRequest(number: $pullRequestNumber) {
813
813
  mergeable
814
814
  mergeStateStatus
815
+ reviewDecision
815
816
  statusCheckRollup {
816
817
  contexts(first: 100, after: $after) {
817
818
  pageInfo {
@@ -877,6 +878,7 @@ var GITHUB_REPOSITORY_OPEN_PULL_REQUEST_STATUSES_QUERY = `
877
878
  number
878
879
  mergeable
879
880
  mergeStateStatus
881
+ reviewDecision
880
882
  reviewThreads(first: 100) {
881
883
  pageInfo {
882
884
  hasNextPage
@@ -917,6 +919,7 @@ var GITHUB_PROJECT_PULL_REQUEST_BASE_FIELDS = `
917
919
  state
918
920
  mergeable
919
921
  mergeStateStatus
922
+ reviewDecision
920
923
  createdAt
921
924
  updatedAt
922
925
  baseRefName
@@ -1003,6 +1006,7 @@ var GITHUB_PROJECT_PULL_REQUEST_METRICS_FIELDS = `
1003
1006
  number
1004
1007
  mergeable
1005
1008
  mergeStateStatus
1009
+ reviewDecision
1006
1010
  baseRefName
1007
1011
  reviews(first: 100) {
1008
1012
  pageInfo {
@@ -5535,11 +5539,23 @@ function normalizeGitHubPullRequestMergeStateStatus(value) {
5535
5539
  return "unknown";
5536
5540
  }
5537
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
+ }
5538
5554
  function isGitHubPullRequestActionRequiredForSync(pullRequest) {
5539
- 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);
5540
5556
  }
5541
5557
  function isGitHubPullRequestReviewReadyForSync(pullRequest) {
5542
- if (pullRequest.ciState !== "green" || pullRequest.hasUnresolvedReviewThreads) {
5558
+ if (pullRequest.ciState !== "green" || pullRequest.hasUnresolvedReviewThreads || pullRequest.reviewDecision === "changes_requested") {
5543
5559
  return false;
5544
5560
  }
5545
5561
  return REVIEW_READY_GITHUB_PULL_REQUEST_MERGE_STATE_STATUSES.has(pullRequest.mergeStateStatus);
@@ -5572,6 +5588,9 @@ function listGitHubPullRequestSyncBlockingConditions(pullRequest) {
5572
5588
  if (pullRequest.hasUnresolvedReviewThreads) {
5573
5589
  conditions.push("unresolved review threads");
5574
5590
  }
5591
+ if (pullRequest.reviewDecision === "changes_requested") {
5592
+ conditions.push("requested changes");
5593
+ }
5575
5594
  return conditions;
5576
5595
  }
5577
5596
  function tryBuildGitHubPullRequestStatusSnapshotFromBatchNode(node, repository) {
@@ -5589,7 +5608,8 @@ function tryBuildGitHubPullRequestStatusSnapshotFromBatchNode(node, repository)
5589
5608
  hasUnresolvedReviewThreads: reviewThreadSummary.unresolvedReviewThreads > 0,
5590
5609
  ciState,
5591
5610
  mergeability: normalizeGitHubPullRequestMergeability(node.mergeable),
5592
- mergeStateStatus: normalizeGitHubPullRequestMergeStateStatus(node.mergeStateStatus)
5611
+ mergeStateStatus: normalizeGitHubPullRequestMergeStateStatus(node.mergeStateStatus),
5612
+ reviewDecision: normalizeGitHubPullRequestReviewDecision(node.reviewDecision)
5593
5613
  };
5594
5614
  }
5595
5615
  function tryBuildGitHubPullRequestCiStateFromBatchNode(node) {
@@ -5646,6 +5666,7 @@ async function getGitHubPullRequestCiSnapshot(octokit, repository, pullRequestNu
5646
5666
  const contexts = [];
5647
5667
  let mergeability = "unknown";
5648
5668
  let mergeStateStatus = "unknown";
5669
+ let reviewDecision = "unknown";
5649
5670
  let after;
5650
5671
  do {
5651
5672
  const response = await octokit.graphql(GITHUB_PULL_REQUEST_CI_CONTEXTS_QUERY, {
@@ -5656,6 +5677,7 @@ async function getGitHubPullRequestCiSnapshot(octokit, repository, pullRequestNu
5656
5677
  });
5657
5678
  mergeability = normalizeGitHubPullRequestMergeability(response.repository?.pullRequest?.mergeable);
5658
5679
  mergeStateStatus = normalizeGitHubPullRequestMergeStateStatus(response.repository?.pullRequest?.mergeStateStatus);
5680
+ reviewDecision = normalizeGitHubPullRequestReviewDecision(response.repository?.pullRequest?.reviewDecision);
5659
5681
  const connection = response.repository?.pullRequest?.statusCheckRollup?.contexts;
5660
5682
  const nodes = connection?.nodes ?? [];
5661
5683
  for (const node of nodes) {
@@ -5682,7 +5704,8 @@ async function getGitHubPullRequestCiSnapshot(octokit, repository, pullRequestNu
5682
5704
  return {
5683
5705
  ciState: classifyGitHubPullRequestCiState(contexts),
5684
5706
  mergeability,
5685
- mergeStateStatus
5707
+ mergeStateStatus,
5708
+ reviewDecision
5686
5709
  };
5687
5710
  }
5688
5711
  async function getGitHubPullRequestStatusSnapshot(octokit, repository, pullRequestNumber, pullRequestStatusCache, options) {
@@ -5696,14 +5719,15 @@ async function getGitHubPullRequestStatusSnapshot(octokit, repository, pullReque
5696
5719
  setCachedGitHubPullRequestStatusSnapshot(pullRequestStatusCache, cachedSnapshot);
5697
5720
  return cachedSnapshot;
5698
5721
  }
5699
- 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) {
5700
5723
  const snapshot = cacheGitHubPullRequestStatusSnapshot(repository, {
5701
5724
  number: pullRequestNumber,
5702
5725
  repositoryUrl: repository.url,
5703
5726
  hasUnresolvedReviewThreads: options.reviewThreadSummary.unresolvedReviewThreads > 0,
5704
5727
  ciState: options.ciState,
5705
5728
  mergeability: options.mergeability,
5706
- mergeStateStatus: options.mergeStateStatus
5729
+ mergeStateStatus: options.mergeStateStatus,
5730
+ reviewDecision: options.reviewDecision
5707
5731
  });
5708
5732
  setCachedGitHubPullRequestStatusSnapshot(pullRequestStatusCache, snapshot);
5709
5733
  return snapshot;
@@ -5717,10 +5741,11 @@ async function getGitHubPullRequestStatusSnapshot(octokit, repository, pullReque
5717
5741
  const loadSnapshotPromise = (async () => {
5718
5742
  const [reviewThreadSummary, ciSnapshot] = await Promise.all([
5719
5743
  options?.reviewThreadSummary ?? getOrLoadCachedGitHubPullRequestReviewThreadSummary(octokit, repository, pullRequestNumber),
5720
- 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 ? {
5721
5745
  ciState: options.ciState,
5722
5746
  mergeability: options.mergeability,
5723
- mergeStateStatus: options.mergeStateStatus
5747
+ mergeStateStatus: options.mergeStateStatus,
5748
+ reviewDecision: options.reviewDecision
5724
5749
  } : getGitHubPullRequestCiSnapshot(octokit, repository, pullRequestNumber)
5725
5750
  ]);
5726
5751
  return cacheGitHubPullRequestStatusSnapshot(repository, {
@@ -5729,7 +5754,8 @@ async function getGitHubPullRequestStatusSnapshot(octokit, repository, pullReque
5729
5754
  hasUnresolvedReviewThreads: reviewThreadSummary.unresolvedReviewThreads > 0,
5730
5755
  ciState: ciSnapshot.ciState,
5731
5756
  mergeability: ciSnapshot.mergeability,
5732
- mergeStateStatus: ciSnapshot.mergeStateStatus
5757
+ mergeStateStatus: ciSnapshot.mergeStateStatus,
5758
+ reviewDecision: ciSnapshot.reviewDecision
5733
5759
  });
5734
5760
  })();
5735
5761
  activeGitHubPullRequestStatusSnapshotPromiseCache.set(cacheKey, loadSnapshotPromise);
@@ -10398,6 +10424,7 @@ async function buildProjectPullRequestSummaryRecord(octokit, repository, node, i
10398
10424
  });
10399
10425
  const inlineMergeability = normalizeGitHubPullRequestMergeability(node.mergeable);
10400
10426
  const inlineMergeStateStatus = normalizeGitHubPullRequestMergeStateStatus(node.mergeStateStatus);
10427
+ const inlineReviewDecision = normalizeGitHubPullRequestReviewDecision(node.reviewDecision);
10401
10428
  const [reviewThreadSummary, reviewSummary, statusSnapshot, behindBy] = await Promise.all([
10402
10429
  getOrLoadCachedGitHubPullRequestReviewThreadSummary(
10403
10430
  octokit,
@@ -10415,7 +10442,8 @@ async function buildProjectPullRequestSummaryRecord(octokit, repository, node, i
10415
10442
  reviewThreadSummary: inlineReviewThreadSummary,
10416
10443
  ciState: inlineCiState,
10417
10444
  mergeability: inlineMergeability,
10418
- mergeStateStatus: inlineMergeStateStatus
10445
+ mergeStateStatus: inlineMergeStateStatus,
10446
+ reviewDecision: inlineReviewDecision
10419
10447
  }),
10420
10448
  getGitHubPullRequestBehindCount(octokit, repository, {
10421
10449
  baseBranch: node.baseRefName,
@@ -10555,6 +10583,7 @@ async function buildProjectPullRequestMetricCounts(octokit, repository, node, pu
10555
10583
  });
10556
10584
  const inlineMergeability = normalizeGitHubPullRequestMergeability(node.mergeable);
10557
10585
  const inlineMergeStateStatus = normalizeGitHubPullRequestMergeStateStatus(node.mergeStateStatus);
10586
+ const inlineReviewDecision = normalizeGitHubPullRequestReviewDecision(node.reviewDecision);
10558
10587
  const [reviewThreadSummary, reviewSummary, statusSnapshot] = await Promise.all([
10559
10588
  getOrLoadCachedGitHubPullRequestReviewThreadSummary(
10560
10589
  octokit,
@@ -10572,7 +10601,8 @@ async function buildProjectPullRequestMetricCounts(octokit, repository, node, pu
10572
10601
  reviewThreadSummary: inlineReviewThreadSummary,
10573
10602
  ciState: inlineCiState,
10574
10603
  mergeability: inlineMergeability,
10575
- mergeStateStatus: inlineMergeStateStatus
10604
+ mergeStateStatus: inlineMergeStateStatus,
10605
+ reviewDecision: inlineReviewDecision
10576
10606
  })
10577
10607
  ]);
10578
10608
  const checksStatus = statusSnapshot.ciState === "green" ? "passed" : statusSnapshot.ciState === "red" ? "failed" : "pending";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-github-plugin",
3
- "version": "0.8.0",
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",