paperclip-github-plugin 0.8.2 → 0.8.3

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
@@ -613,6 +613,7 @@ function requiresPaperclipBoardAccess(value) {
613
613
  }
614
614
 
615
615
  // src/worker.ts
616
+ var GITHUB_SYNC_BASE_ORIGIN_KIND = `plugin:${GITHUB_SYNC_PLUGIN_ID}`;
616
617
  var GITHUB_ISSUE_ORIGIN_KIND = `plugin:${GITHUB_SYNC_PLUGIN_ID}:github-issue`;
617
618
  var GITHUB_PULL_REQUEST_ORIGIN_KIND = `plugin:${GITHUB_SYNC_PLUGIN_ID}:github-pull-request`;
618
619
  var SETTINGS_SCOPE = {
@@ -5201,6 +5202,9 @@ function isHealthyMaintainerWaitTransition(params) {
5201
5202
  const { currentStatus, nextStatus, syncContext } = params;
5202
5203
  return nextStatus === "in_review" && (currentStatus === "done" || currentStatus === "in_review") && syncContext.executionState === null && syncContext.executionPolicy !== null;
5203
5204
  }
5205
+ function shouldPreserveImportedTriageAssignee(params) {
5206
+ return params.wasImportedThisRun && params.maintainerAuthoredImportedIssue === true && params.currentStatus === "backlog" && params.nextStatus === "todo";
5207
+ }
5204
5208
  function doesPaperclipIssueAssigneeMatch(currentAssignee, nextAssignee) {
5205
5209
  return isSamePaperclipIssueAssigneePrincipal(currentAssignee, nextAssignee);
5206
5210
  }
@@ -6365,6 +6369,18 @@ function extractImportedGitHubIssueUrlFromDescription(description) {
6365
6369
  }
6366
6370
  return normalizeGitHubIssueHtmlUrl(legacyMatch[1]);
6367
6371
  }
6372
+ function removeGitHubIssueLinkMetadataFromDescription(description) {
6373
+ if (typeof description !== "string") {
6374
+ return void 0;
6375
+ }
6376
+ let next = description;
6377
+ const hiddenMarkerPattern = getHiddenGitHubImportMarkerPattern();
6378
+ while (hiddenMarkerPattern.test(next)) {
6379
+ next = next.replace(hiddenMarkerPattern, "");
6380
+ }
6381
+ next = next.replace(/^\*\s+GitHub issue:\s+\[[^\]]+\]\([^)]+\)\s*$/gm, "").replace(/^Imported from https:\/\/github\.com\/[^\s)]+\/[^\s)]+\/issues\/\d+\.?\s*$/gim, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
6382
+ return next === description ? void 0 : next;
6383
+ }
6368
6384
  function compareImportedPaperclipIssueCreatedAt(left, right) {
6369
6385
  const leftTime = Date.parse(String(left.createdAt ?? ""));
6370
6386
  const rightTime = Date.parse(String(right.createdAt ?? ""));
@@ -6693,6 +6709,7 @@ async function listGitHubIssueLinkRecords(ctx, query = {}) {
6693
6709
  }
6694
6710
  records.push({
6695
6711
  paperclipIssueId: entry.scopeId,
6712
+ ...typeof entry.externalId === "string" && entry.externalId.trim() ? { externalId: entry.externalId.trim() } : {},
6696
6713
  ...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
6697
6714
  ...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {},
6698
6715
  ...typeof entry.title === "string" && entry.title.trim() ? { title: entry.title.trim() } : {},
@@ -6739,6 +6756,7 @@ async function listGitHubPullRequestLinkRecords(ctx, query = {}) {
6739
6756
  }
6740
6757
  records.push({
6741
6758
  paperclipIssueId: entry.scopeId,
6759
+ ...typeof entry.externalId === "string" && entry.externalId.trim() ? { externalId: entry.externalId.trim() } : {},
6742
6760
  ...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
6743
6761
  ...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {},
6744
6762
  ...typeof entry.title === "string" && entry.title.trim() ? { title: entry.title.trim() } : {},
@@ -7147,6 +7165,134 @@ async function linkPaperclipIssueToGitHubPullRequest(ctx, params) {
7147
7165
  githubPullRequestState: pullRequestState
7148
7166
  };
7149
7167
  }
7168
+ function invalidateGitHubLinkCachesForTarget(params) {
7169
+ if (!params.companyId || !params.projectId || !params.repositoryUrl) {
7170
+ return;
7171
+ }
7172
+ const repository = parseRepositoryReference(params.repositoryUrl);
7173
+ if (!repository) {
7174
+ return;
7175
+ }
7176
+ invalidateProjectPullRequestCaches({
7177
+ companyId: params.companyId,
7178
+ projectId: params.projectId,
7179
+ repository
7180
+ });
7181
+ }
7182
+ async function tombstoneGitHubIssueLinkRecord(ctx, record, unlinkedAt) {
7183
+ await ctx.entities.upsert({
7184
+ entityType: ISSUE_LINK_ENTITY_TYPE,
7185
+ scopeKind: "issue",
7186
+ scopeId: record.paperclipIssueId,
7187
+ externalId: record.externalId ?? record.data.githubIssueUrl,
7188
+ title: record.title ?? `GitHub issue #${record.data.githubIssueNumber}`,
7189
+ status: "unlinked",
7190
+ data: {
7191
+ kind: "issue",
7192
+ paperclipIssueId: record.paperclipIssueId,
7193
+ repositoryUrl: record.data.repositoryUrl,
7194
+ githubIssueNumber: record.data.githubIssueNumber,
7195
+ githubIssueUrl: record.data.githubIssueUrl,
7196
+ ...record.data.companyId ? { companyId: record.data.companyId } : {},
7197
+ ...record.data.paperclipProjectId ? { paperclipProjectId: record.data.paperclipProjectId } : {},
7198
+ unlinkedAt
7199
+ }
7200
+ });
7201
+ }
7202
+ async function tombstoneGitHubPullRequestLinkRecord(ctx, record, unlinkedAt) {
7203
+ await ctx.entities.upsert({
7204
+ entityType: PULL_REQUEST_LINK_ENTITY_TYPE,
7205
+ scopeKind: "issue",
7206
+ scopeId: record.paperclipIssueId,
7207
+ externalId: record.externalId ?? record.data.githubPullRequestUrl,
7208
+ title: record.title ?? `GitHub pull request #${record.data.githubPullRequestNumber}`,
7209
+ status: "unlinked",
7210
+ data: {
7211
+ kind: "pull_request",
7212
+ paperclipIssueId: record.paperclipIssueId,
7213
+ repositoryUrl: record.data.repositoryUrl,
7214
+ githubPullRequestNumber: record.data.githubPullRequestNumber,
7215
+ githubPullRequestUrl: record.data.githubPullRequestUrl,
7216
+ ...record.data.companyId ? { companyId: record.data.companyId } : {},
7217
+ ...record.data.paperclipProjectId ? { paperclipProjectId: record.data.paperclipProjectId } : {},
7218
+ unlinkedAt
7219
+ }
7220
+ });
7221
+ }
7222
+ async function unlinkPaperclipIssueFromGitHub(ctx, params) {
7223
+ const companyId = normalizeCompanyId(params.companyId);
7224
+ const issueId = normalizeOptionalString2(params.issueId);
7225
+ if (!companyId || !issueId) {
7226
+ throw new Error("companyId and issueId are required.");
7227
+ }
7228
+ const issue = await ctx.issues.get(issueId, companyId);
7229
+ if (!issue) {
7230
+ throw new Error("Paperclip issue was not found.");
7231
+ }
7232
+ const issueLinkRecords = (await listGitHubIssueLinkRecords(ctx, {
7233
+ paperclipIssueId: issueId
7234
+ })).filter((record) => !record.data.companyId || record.data.companyId === companyId);
7235
+ const pullRequestLinkRecords = (await listGitHubPullRequestLinkRecords(ctx, {
7236
+ paperclipIssueId: issueId
7237
+ })).filter((record) => !record.data.companyId || record.data.companyId === companyId);
7238
+ const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
7239
+ const removedImportRegistryEntries = importRegistry.filter(
7240
+ (entry) => entry.paperclipIssueId === issueId && (!entry.companyId || entry.companyId === companyId)
7241
+ );
7242
+ const nextImportRegistry = importRegistry.filter(
7243
+ (entry) => !(entry.paperclipIssueId === issueId && (!entry.companyId || entry.companyId === companyId))
7244
+ );
7245
+ const unlinkedAt = (/* @__PURE__ */ new Date()).toISOString();
7246
+ for (const record of issueLinkRecords) {
7247
+ await tombstoneGitHubIssueLinkRecord(ctx, record, unlinkedAt);
7248
+ invalidateGitHubLinkCachesForTarget({
7249
+ companyId: record.data.companyId ?? companyId,
7250
+ projectId: record.data.paperclipProjectId ?? issue.projectId ?? void 0,
7251
+ repositoryUrl: record.data.repositoryUrl
7252
+ });
7253
+ }
7254
+ for (const record of pullRequestLinkRecords) {
7255
+ await tombstoneGitHubPullRequestLinkRecord(ctx, record, unlinkedAt);
7256
+ invalidateGitHubLinkCachesForTarget({
7257
+ companyId: record.data.companyId ?? companyId,
7258
+ projectId: record.data.paperclipProjectId ?? issue.projectId ?? void 0,
7259
+ repositoryUrl: record.data.repositoryUrl
7260
+ });
7261
+ }
7262
+ for (const entry of removedImportRegistryEntries) {
7263
+ invalidateGitHubLinkCachesForTarget({
7264
+ companyId: entry.companyId ?? companyId,
7265
+ projectId: entry.paperclipProjectId ?? issue.projectId ?? void 0,
7266
+ repositoryUrl: entry.repositoryUrl
7267
+ });
7268
+ }
7269
+ if (removedImportRegistryEntries.length > 0) {
7270
+ await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextImportRegistry);
7271
+ }
7272
+ const shouldClearGitHubOrigin = issue.originKind === GITHUB_ISSUE_ORIGIN_KIND || issue.originKind === GITHUB_PULL_REQUEST_ORIGIN_KIND;
7273
+ const nextDescription = removeGitHubIssueLinkMetadataFromDescription(issue.description);
7274
+ const issuePatch = {};
7275
+ if (shouldClearGitHubOrigin) {
7276
+ issuePatch.originKind = GITHUB_SYNC_BASE_ORIGIN_KIND;
7277
+ issuePatch.originId = null;
7278
+ issuePatch.originRunId = null;
7279
+ }
7280
+ if (nextDescription !== void 0) {
7281
+ issuePatch.description = nextDescription;
7282
+ }
7283
+ if (Object.keys(issuePatch).length > 0) {
7284
+ await ctx.issues.update(issueId, issuePatch, companyId);
7285
+ }
7286
+ return {
7287
+ paperclipIssueId: issueId,
7288
+ unlinked: issueLinkRecords.length > 0 || pullRequestLinkRecords.length > 0 || removedImportRegistryEntries.length > 0 || shouldClearGitHubOrigin || nextDescription !== void 0,
7289
+ unlinkedIssueLinksCount: issueLinkRecords.length,
7290
+ unlinkedPullRequestLinksCount: pullRequestLinkRecords.length,
7291
+ removedImportRegistryEntriesCount: removedImportRegistryEntries.length,
7292
+ clearedIssueOrigin: shouldClearGitHubOrigin,
7293
+ removedDescriptionMetadata: nextDescription !== void 0
7294
+ };
7295
+ }
7150
7296
  async function upsertStatusTransitionCommentAnnotation(ctx, params) {
7151
7297
  const { issueId, commentId, annotation } = params;
7152
7298
  await ctx.entities.upsert({
@@ -8450,6 +8596,261 @@ async function ensurePaperclipIssueImported(ctx, mapping, advancedSettings, issu
8450
8596
  }
8451
8597
  return createdIssue.id;
8452
8598
  }
8599
+ function getGitHubIssueRepositoryReference(issue) {
8600
+ return parseGitHubIssueHtmlUrl(issue.htmlUrl);
8601
+ }
8602
+ function findTransferredIssueTargetMapping(mappings, params) {
8603
+ const companyId = normalizeCompanyId(params.companyId);
8604
+ const repository = parseRepositoryReference(params.repositoryUrl);
8605
+ if (!companyId || !repository) {
8606
+ return void 0;
8607
+ }
8608
+ return getSyncableMappings(mappings).find((mapping) => {
8609
+ if (normalizeCompanyId(mapping.companyId) !== companyId) {
8610
+ return false;
8611
+ }
8612
+ const mappingRepository = parseRepositoryReference(mapping.repositoryUrl);
8613
+ return Boolean(mappingRepository && areRepositoriesEqual(mappingRepository, repository));
8614
+ });
8615
+ }
8616
+ async function patchPaperclipIssueForGitHubIssueTransfer(ctx, params) {
8617
+ const nextDescription = buildPaperclipIssueDescription(params.githubIssue, []);
8618
+ const nextOriginId = normalizeGitHubIssueHtmlUrl(params.githubIssue.htmlUrl) ?? params.githubIssue.htmlUrl;
8619
+ const descriptionUpdated = normalizeIssueDescriptionValue(params.currentDescription) !== nextDescription;
8620
+ const patch = {
8621
+ projectId: params.projectId,
8622
+ originKind: GITHUB_ISSUE_ORIGIN_KIND,
8623
+ originId: nextOriginId,
8624
+ ...descriptionUpdated ? { description: nextDescription } : {}
8625
+ };
8626
+ let issueUpdated = false;
8627
+ if (params.paperclipApiBaseUrl) {
8628
+ try {
8629
+ const response = await fetchPaperclipApi(
8630
+ getPaperclipIssueEndpoint(params.paperclipApiBaseUrl, params.issueId),
8631
+ {
8632
+ method: "PATCH",
8633
+ headers: {
8634
+ accept: "application/json",
8635
+ "content-type": "application/json"
8636
+ },
8637
+ body: JSON.stringify(patch)
8638
+ },
8639
+ {
8640
+ companyId: params.companyId
8641
+ }
8642
+ );
8643
+ const payloadResult = await readPaperclipApiJsonResponse(response, {
8644
+ operationLabel: "issue update",
8645
+ bodyRequired: false
8646
+ });
8647
+ issueUpdated = !payloadResult.failure;
8648
+ } catch {
8649
+ issueUpdated = false;
8650
+ }
8651
+ }
8652
+ if (!issueUpdated) {
8653
+ await ctx.issues.update(
8654
+ params.issueId,
8655
+ patch,
8656
+ params.companyId
8657
+ );
8658
+ }
8659
+ return {
8660
+ descriptionUpdated
8661
+ };
8662
+ }
8663
+ async function moveImportedIssueToTransferredGitHubMapping(ctx, params) {
8664
+ const companyId = normalizeCompanyId(params.targetMapping.companyId);
8665
+ const projectId = normalizeOptionalString2(params.targetMapping.paperclipProjectId);
8666
+ if (!companyId || !projectId) {
8667
+ return {
8668
+ updatedDescriptionsCount: 0
8669
+ };
8670
+ }
8671
+ const paperclipIssue = await ctx.issues.get(params.importedIssue.paperclipIssueId, companyId);
8672
+ if (!paperclipIssue) {
8673
+ return {
8674
+ updatedDescriptionsCount: 0
8675
+ };
8676
+ }
8677
+ const transferPatchResult = await patchPaperclipIssueForGitHubIssueTransfer(ctx, {
8678
+ companyId,
8679
+ issueId: params.importedIssue.paperclipIssueId,
8680
+ projectId,
8681
+ githubIssue: params.githubIssue,
8682
+ currentDescription: paperclipIssue.description,
8683
+ paperclipApiBaseUrl: params.paperclipApiBaseUrl
8684
+ });
8685
+ const nextGitHubIssueUrl = normalizeGitHubIssueHtmlUrl(params.githubIssue.htmlUrl) ?? params.githubIssue.htmlUrl;
8686
+ const existingLinks = await listGitHubIssueLinkRecords(ctx, {
8687
+ paperclipIssueId: params.importedIssue.paperclipIssueId
8688
+ });
8689
+ const unlinkedAt = (/* @__PURE__ */ new Date()).toISOString();
8690
+ for (const link of existingLinks) {
8691
+ if ((!link.data.companyId || link.data.companyId === companyId) && link.data.githubIssueUrl !== nextGitHubIssueUrl) {
8692
+ await tombstoneGitHubIssueLinkRecord(ctx, link, unlinkedAt);
8693
+ }
8694
+ }
8695
+ await upsertGitHubIssueLinkRecord(
8696
+ ctx,
8697
+ params.targetMapping,
8698
+ params.importedIssue.paperclipIssueId,
8699
+ params.githubIssue,
8700
+ []
8701
+ );
8702
+ upsertImportedIssueRecord(
8703
+ params.nextRegistry,
8704
+ buildImportedIssueRecord(
8705
+ params.targetMapping,
8706
+ params.githubIssue,
8707
+ params.importedIssue.paperclipIssueId,
8708
+ params.importedIssue.importedAt
8709
+ )
8710
+ );
8711
+ invalidateGitHubLinkCachesForTarget({
8712
+ companyId,
8713
+ projectId: params.sourceMapping.paperclipProjectId ?? paperclipIssue.projectId ?? void 0,
8714
+ repositoryUrl: params.sourceMapping.repositoryUrl
8715
+ });
8716
+ invalidateGitHubLinkCachesForTarget({
8717
+ companyId,
8718
+ projectId,
8719
+ repositoryUrl: params.targetMapping.repositoryUrl
8720
+ });
8721
+ return {
8722
+ updatedDescriptionsCount: transferPatchResult.descriptionUpdated ? 1 : 0
8723
+ };
8724
+ }
8725
+ function buildUnmappedTransferredIssueCancellationComment(params) {
8726
+ return `GitHub Sync updated the status from \`${formatPaperclipIssueStatus(params.previousStatus)}\` to \`${formatPaperclipIssueStatus(params.nextStatus)}\` because the linked GitHub issue was transferred to \`${formatRepositoryLabel(params.transferredRepository)}\`, which is not mapped to a Paperclip project. The GitHub link was removed so sync will stop updating this Paperclip issue.`;
8727
+ }
8728
+ async function cancelUnmappedTransferredGitHubIssue(ctx, params) {
8729
+ const companyId = normalizeCompanyId(params.mapping.companyId);
8730
+ if (!companyId) {
8731
+ return {
8732
+ updatedStatusesCount: 0
8733
+ };
8734
+ }
8735
+ const paperclipIssue = await ctx.issues.get(params.importedIssue.paperclipIssueId, companyId);
8736
+ if (!paperclipIssue) {
8737
+ return {
8738
+ updatedStatusesCount: 0
8739
+ };
8740
+ }
8741
+ await unlinkPaperclipIssueFromGitHub(ctx, {
8742
+ companyId,
8743
+ issueId: params.importedIssue.paperclipIssueId
8744
+ });
8745
+ const nextStatus = "cancelled";
8746
+ await updatePaperclipIssueState(ctx, {
8747
+ companyId,
8748
+ issueId: params.importedIssue.paperclipIssueId,
8749
+ currentStatus: paperclipIssue.status,
8750
+ syncContext: getPaperclipIssueSyncContext(paperclipIssue),
8751
+ nextStatus,
8752
+ transitionComment: buildUnmappedTransferredIssueCancellationComment({
8753
+ previousStatus: paperclipIssue.status,
8754
+ nextStatus,
8755
+ transferredRepository: params.transferredRepository
8756
+ }),
8757
+ paperclipApiBaseUrl: params.paperclipApiBaseUrl
8758
+ });
8759
+ return {
8760
+ updatedStatusesCount: paperclipIssue.status === nextStatus ? 0 : 1
8761
+ };
8762
+ }
8763
+ async function fetchTransferredGitHubIssueForImportedRecord(octokit, repository, importedIssue) {
8764
+ if (importedIssue.githubIssueNumber === void 0) {
8765
+ return null;
8766
+ }
8767
+ const response = await octokit.rest.issues.get({
8768
+ owner: repository.owner,
8769
+ repo: repository.repo,
8770
+ issue_number: importedIssue.githubIssueNumber,
8771
+ headers: {
8772
+ accept: "application/vnd.github+json",
8773
+ "X-GitHub-Api-Version": GITHUB_API_VERSION
8774
+ }
8775
+ });
8776
+ const rawIssue = response.data;
8777
+ if (rawIssue.pull_request) {
8778
+ return null;
8779
+ }
8780
+ const githubIssue = normalizeGitHubIssueRecord(rawIssue);
8781
+ const transferredIssueReference = getGitHubIssueRepositoryReference(githubIssue);
8782
+ const transferredRepository = transferredIssueReference ? parseRepositoryReference(transferredIssueReference.repositoryUrl) : void 0;
8783
+ if (!transferredRepository || areRepositoriesEqual(repository, transferredRepository)) {
8784
+ return null;
8785
+ }
8786
+ return {
8787
+ githubIssue,
8788
+ transferredRepository
8789
+ };
8790
+ }
8791
+ async function reconcileTransferredImportedIssues(ctx, params) {
8792
+ let updatedStatusesCount = 0;
8793
+ let updatedDescriptionsCount = 0;
8794
+ for (const importedIssue of params.importedIssues) {
8795
+ if (params.allIssuesById.has(importedIssue.githubIssueId)) {
8796
+ continue;
8797
+ }
8798
+ try {
8799
+ updateSyncFailureContext(params.syncFailureContext, {
8800
+ phase: "evaluating_github_status",
8801
+ repositoryUrl: params.sourceRepository.url,
8802
+ githubIssueNumber: importedIssue.githubIssueNumber
8803
+ });
8804
+ const transfer = await fetchTransferredGitHubIssueForImportedRecord(
8805
+ params.octokit,
8806
+ params.sourceRepository,
8807
+ importedIssue
8808
+ );
8809
+ if (!transfer) {
8810
+ continue;
8811
+ }
8812
+ const targetMapping = findTransferredIssueTargetMapping(params.allMappings, {
8813
+ companyId: params.sourceMapping.companyId,
8814
+ repositoryUrl: transfer.transferredRepository.url
8815
+ });
8816
+ if (targetMapping) {
8817
+ const moveResult = await moveImportedIssueToTransferredGitHubMapping(ctx, {
8818
+ sourceMapping: params.sourceMapping,
8819
+ targetMapping,
8820
+ importedIssue,
8821
+ githubIssue: transfer.githubIssue,
8822
+ nextRegistry: params.nextRegistry,
8823
+ paperclipApiBaseUrl: params.paperclipApiBaseUrl
8824
+ });
8825
+ updatedDescriptionsCount += moveResult.updatedDescriptionsCount;
8826
+ continue;
8827
+ }
8828
+ const cancelResult = await cancelUnmappedTransferredGitHubIssue(ctx, {
8829
+ mapping: params.sourceMapping,
8830
+ importedIssue,
8831
+ transferredRepository: transfer.transferredRepository,
8832
+ paperclipApiBaseUrl: params.paperclipApiBaseUrl
8833
+ });
8834
+ for (let index = params.nextRegistry.length - 1; index >= 0; index -= 1) {
8835
+ const entry = params.nextRegistry[index];
8836
+ if (entry?.paperclipIssueId === importedIssue.paperclipIssueId && (!entry.companyId || entry.companyId === params.sourceMapping.companyId)) {
8837
+ params.nextRegistry.splice(index, 1);
8838
+ }
8839
+ }
8840
+ updatedStatusesCount += cancelResult.updatedStatusesCount;
8841
+ } catch (error) {
8842
+ if (isGitHubRateLimitError(error)) {
8843
+ throw error;
8844
+ }
8845
+ recordRecoverableSyncFailure(ctx, params.failures, error, params.syncFailureContext);
8846
+ continue;
8847
+ }
8848
+ }
8849
+ return {
8850
+ updatedStatusesCount,
8851
+ updatedDescriptionsCount
8852
+ };
8853
+ }
8453
8854
  async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mapping, advancedSettings, allIssuesById, importedIssues, createdIssueIds, availableLabels, paperclipApiBaseUrl, linkedPullRequestsByIssueNumber, issueStatusSnapshotCache, pullRequestStatusCache, repositoryMaintainerCache, syncFailureContext, failures, assertNotCancelled, onGitHubIssueClosed, onProgress) {
8454
8855
  if (!mapping.companyId || !ctx.issues || typeof ctx.issues.get !== "function" || typeof ctx.issues.update !== "function") {
8455
8856
  return {
@@ -8638,7 +9039,13 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
8638
9039
  nextStatus,
8639
9040
  syncContext: paperclipIssueSyncContext
8640
9041
  });
8641
- const nextTransitionAssignee = resolveSyncTransitionAssignee({
9042
+ const shouldPreserveImportedTriageRouting = shouldPreserveImportedTriageAssignee({
9043
+ currentStatus: paperclipIssue.status,
9044
+ nextStatus,
9045
+ wasImportedThisRun,
9046
+ maintainerAuthoredImportedIssue
9047
+ });
9048
+ const nextTransitionAssignee = shouldPreserveImportedTriageRouting ? null : resolveSyncTransitionAssignee({
8642
9049
  currentStatus: paperclipIssue.status,
8643
9050
  nextStatus,
8644
9051
  syncContext: paperclipIssueSyncContext,
@@ -8646,7 +9053,7 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
8646
9053
  });
8647
9054
  const shouldClearTransitionAssignee = nextStatus === "in_review" && (nextTransitionAssignee === null || shouldPreserveMaintainerWaitRouting) && paperclipIssueSyncContext.assignee !== null;
8648
9055
  const nextAssigneeChanged = nextTransitionAssignee ? !doesPaperclipIssueAssigneeMatch(paperclipIssueSyncContext.assignee, nextTransitionAssignee.principal) : false;
8649
- const shouldWakeImportedAssignee = wasImportedThisRun && paperclipIssue.status === nextStatus && nextStatus === "todo" && paperclipIssueSyncContext.assignee?.kind === "agent";
9056
+ const shouldWakeImportedAssignee = wasImportedThisRun && nextStatus === "todo" && (paperclipIssue.status === nextStatus || shouldPreserveImportedTriageRouting) && paperclipIssueSyncContext.assignee?.kind === "agent";
8650
9057
  const shouldWakeTransitionAssignee = paperclipIssue.status !== nextStatus && nextTransitionAssignee?.principal.kind === "agent" && isActionablePaperclipIssueStatus(nextStatus) && (nextAssigneeChanged || paperclipIssue.status !== nextStatus);
8651
9058
  importedIssue.githubIssueNumber = githubIssue.number;
8652
9059
  importedIssue.lastSeenCommentCount = snapshot.commentCount;
@@ -8708,6 +9115,14 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
8708
9115
  paperclipApiBaseUrl
8709
9116
  });
8710
9117
  updatedStatusesCount += 1;
9118
+ if (shouldWakeImportedAssignee) {
9119
+ queuedIssueWakeups.push({
9120
+ assigneeAgentId: paperclipIssueSyncContext.assignee?.kind === "agent" ? paperclipIssueSyncContext.assignee.id : null,
9121
+ paperclipIssueId: importedIssue.paperclipIssueId,
9122
+ reason: IMPORTED_ISSUE_WAKE_REASON,
9123
+ mutation: "import"
9124
+ });
9125
+ }
8711
9126
  if (shouldWakeTransitionAssignee && nextTransitionAssignee?.principal.kind === "agent") {
8712
9127
  queuedIssueWakeups.push({
8713
9128
  assigneeAgentId: nextTransitionAssignee.principal.id,
@@ -12723,6 +13138,22 @@ async function performSync(ctx, trigger, options = {}) {
12723
13138
  await persistRunningProgress(issueIndex === issues.length - 1);
12724
13139
  }
12725
13140
  }
13141
+ const transferReconciliationResult = await reconcileTransferredImportedIssues(ctx, {
13142
+ octokit,
13143
+ sourceMapping: mapping,
13144
+ sourceRepository: repository,
13145
+ allMappings: settings.mappings,
13146
+ importedIssues: [...importRegistryByIssueId.values()].filter(
13147
+ (importedIssue) => doesImportedIssueMatchTarget(importedIssue, options.target)
13148
+ ),
13149
+ allIssuesById,
13150
+ nextRegistry,
13151
+ paperclipApiBaseUrl,
13152
+ syncFailureContext: failureContext,
13153
+ failures: recoverableFailures
13154
+ });
13155
+ updatedStatusesCount += transferReconciliationResult.updatedStatusesCount;
13156
+ updatedDescriptionsCount += transferReconciliationResult.updatedDescriptionsCount;
12726
13157
  const importedIssuesForSynchronization = [...importRegistryByIssueId.values()].filter(
12727
13158
  (importedIssue) => allIssuesById.has(importedIssue.githubIssueId) && doesImportedIssueMatchTarget(importedIssue, options.target)
12728
13159
  );
@@ -14008,6 +14439,18 @@ var plugin = definePlugin({
14008
14439
  requireUnlinked: true
14009
14440
  });
14010
14441
  });
14442
+ ctx.actions.register("issue.unlinkGitHubItem", async (input) => {
14443
+ const record = input && typeof input === "object" ? input : {};
14444
+ const companyId = normalizeCompanyId(record.companyId);
14445
+ const issueId = normalizeOptionalString2(record.issueId);
14446
+ if (!companyId || !issueId) {
14447
+ throw new Error("companyId and issueId are required.");
14448
+ }
14449
+ return unlinkPaperclipIssueFromGitHub(ctx, {
14450
+ companyId,
14451
+ issueId
14452
+ });
14453
+ });
14011
14454
  ctx.actions.register("settings.saveRegistration", async (input) => {
14012
14455
  const previous = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
14013
14456
  const config = await getResolvedConfig(ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-github-plugin",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Paperclip plugin for synchronizing GitHub issues into Paperclip projects.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",