paperclip-github-plugin 0.9.5 → 0.9.7

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
@@ -94,6 +94,8 @@ If a Paperclip issue was created locally or by an agent workflow before GitHub S
94
94
 
95
95
  Manual GitHub issue links are added to the same import registry and issue-link entity used by normal sync, so future syncs update the Paperclip issue from the GitHub issue. Manual pull request links are added to the PR-link entity used by the project Pull Requests page, so future syncs monitor PR status even when there is no closing GitHub issue.
96
96
 
97
+ When a Paperclip issue is linked to a GitHub issue and also has older direct pull request links, the GitHub issue remains the status source of truth. Direct pull request links only drive status for PR-only Paperclip issues, which prevents stale merged PR metadata from closing work while the GitHub issue and its current linked PR are still open.
98
+
97
99
  Operators can unlink a linked Paperclip issue from the GitHub detail surface when they intentionally want GitHub Sync to stop updating it. Agent-facing tools and native agent API routes can create durable issue and pull request links, but they do not expose an unlink operation; internal sync repair may still tombstone a link when GitHub transfers an issue to an unmapped repository.
98
100
 
99
101
  ### Agent workflows built in
package/dist/manifest.js CHANGED
@@ -642,7 +642,7 @@ var PULL_REQUEST_ASSET_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_I
642
642
  var require2 = createRequire(import.meta.url);
643
643
  var packageJson = require2("../package.json");
644
644
  var SCHEDULE_TICK_CRON = "* * * * *";
645
- var MANIFEST_VERSION = "0.9.5"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
645
+ var MANIFEST_VERSION = "0.9.7"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
646
646
  var manifest = {
647
647
  id: GITHUB_SYNC_PLUGIN_ID,
648
648
  apiVersion: 1,
package/dist/worker.js CHANGED
@@ -5669,7 +5669,16 @@ function resolveSyncTransitionAssignee(params) {
5669
5669
  }
5670
5670
  function isHealthyMaintainerWaitTransition(params) {
5671
5671
  const { currentStatus, nextStatus, syncContext } = params;
5672
- return nextStatus === "in_review" && (currentStatus === "done" || currentStatus === "in_review") && syncContext.executionState === null && syncContext.executionPolicy !== null;
5672
+ return nextStatus === "in_review" && (currentStatus === "done" || currentStatus === "in_review") && isClearableMaintainerWaitExecutionState(syncContext.executionState);
5673
+ }
5674
+ function isClearableMaintainerWaitExecutionState(executionState) {
5675
+ if (executionState === null) {
5676
+ return true;
5677
+ }
5678
+ if (executionState.currentParticipant !== null || executionState.returnAssignee !== null || executionState.currentStageId !== null || executionState.currentStageIndex !== null || executionState.currentStageType !== null || executionState.completedStageIds.length > 0 || executionState.lastDecisionId || executionState.lastDecisionOutcome) {
5679
+ return false;
5680
+ }
5681
+ return !executionState.status || executionState.status === "idle" || executionState.status === "completed";
5673
5682
  }
5674
5683
  function shouldClearCompletedSyncExecutionPolicy(params) {
5675
5684
  return (params.nextStatus === "done" || params.nextStatus === "cancelled") && (params.syncContext.executionPolicy !== null || params.syncContext.executionState !== null);
@@ -7338,6 +7347,23 @@ async function listGitHubPullRequestIssueLinksForMapping(ctx, mapping, target) {
7338
7347
  }
7339
7348
  return [...recordsByKey.values()];
7340
7349
  }
7350
+ async function listGitHubIssueLinkedPaperclipIssueIdsForMapping(ctx, mapping, importedIssueRecords, target) {
7351
+ const issueIds = new Set(
7352
+ importedIssueRecords.map((record) => record.paperclipIssueId).filter(Boolean)
7353
+ );
7354
+ if (target?.kind === "issue" && target.issueId && target.githubIssueNumber) {
7355
+ issueIds.add(target.issueId);
7356
+ }
7357
+ const issueLinks = await listGitHubIssueLinkRecords(ctx, {
7358
+ ...target?.kind === "issue" && target.issueId ? { paperclipIssueId: target.issueId } : {}
7359
+ });
7360
+ for (const record of issueLinks) {
7361
+ if (doesGitHubIssueLinkRecordMatchMapping(record, mapping) && doesGitHubIssueLinkRecordMatchTarget(record, target)) {
7362
+ issueIds.add(record.paperclipIssueId);
7363
+ }
7364
+ }
7365
+ return issueIds;
7366
+ }
7341
7367
  function doesGitHubIssueLinkRecordMatchMapping(record, mapping) {
7342
7368
  if (record.data.repositoryUrl !== getNormalizedMappingRepositoryUrl(mapping)) {
7343
7369
  return false;
@@ -7375,6 +7401,7 @@ async function listExternalGitHubLinkSyncWork(ctx, mappings, target) {
7375
7401
  const syncableMappings = getSyncableMappingsForTarget(mappings, target);
7376
7402
  const issueLinksByKey = /* @__PURE__ */ new Map();
7377
7403
  const pullRequestLinksByKey = /* @__PURE__ */ new Map();
7404
+ const issueLinkedPaperclipIssueIds = /* @__PURE__ */ new Set();
7378
7405
  const [issueLinks, pullRequestLinks] = await Promise.all([
7379
7406
  listGitHubIssueLinkRecords(ctx, {
7380
7407
  ...target?.kind === "issue" && target.issueId ? { paperclipIssueId: target.issueId } : {}
@@ -7384,7 +7411,11 @@ async function listExternalGitHubLinkSyncWork(ctx, mappings, target) {
7384
7411
  })
7385
7412
  ]);
7386
7413
  for (const record of issueLinks) {
7387
- if (!doesGitHubIssueLinkRecordMatchTarget(record, target) || isGitHubIssueLinkCoveredByMappings(record, syncableMappings)) {
7414
+ if (!doesGitHubIssueLinkRecordMatchTarget(record, target)) {
7415
+ continue;
7416
+ }
7417
+ issueLinkedPaperclipIssueIds.add(record.paperclipIssueId);
7418
+ if (isGitHubIssueLinkCoveredByMappings(record, syncableMappings)) {
7388
7419
  continue;
7389
7420
  }
7390
7421
  issueLinksByKey.set(
@@ -7393,7 +7424,7 @@ async function listExternalGitHubLinkSyncWork(ctx, mappings, target) {
7393
7424
  );
7394
7425
  }
7395
7426
  for (const record of pullRequestLinks) {
7396
- if (!doesGitHubPullRequestLinkRecordMatchTarget(record, target) || isGitHubPullRequestLinkCoveredByMappings(record, syncableMappings)) {
7427
+ if (!doesGitHubPullRequestLinkRecordMatchTarget(record, target) || issueLinkedPaperclipIssueIds.has(record.paperclipIssueId) || isGitHubPullRequestLinkCoveredByMappings(record, syncableMappings)) {
7397
7428
  continue;
7398
7429
  }
7399
7430
  pullRequestLinksByKey.set(
@@ -8973,6 +9004,24 @@ async function updatePaperclipIssueState(ctx, params) {
8973
9004
  });
8974
9005
  if (!payloadResult.failure) {
8975
9006
  issueUpdated = true;
9007
+ if (issuePatch.executionState === null && ctx.issues && typeof ctx.issues.update === "function") {
9008
+ try {
9009
+ await ctx.issues.update(
9010
+ issueId,
9011
+ { executionState: null },
9012
+ companyId
9013
+ );
9014
+ } catch (error) {
9015
+ issueUpdated = false;
9016
+ ctx.logger.warn("GitHub sync could not clear Paperclip issue execution state after the local API update. Falling back to direct issue mutation.", {
9017
+ companyId,
9018
+ issueId,
9019
+ paperclipApiBaseUrl,
9020
+ nextStatus,
9021
+ error: getErrorMessage(error)
9022
+ });
9023
+ }
9024
+ }
8976
9025
  }
8977
9026
  if (payloadResult.failure && (payloadResult.failure.status ?? response.status) !== 404 && (payloadResult.failure.status ?? response.status) !== 405) {
8978
9027
  logPaperclipIssueStatusUpdateFailure(ctx, {
@@ -14115,7 +14164,13 @@ async function performSync(ctx, trigger, options = {}) {
14115
14164
  const importRegistryByIssueId = new Map(
14116
14165
  importedIssueRecords.map((entry) => [entry.githubIssueId, entry])
14117
14166
  );
14118
- const pullRequestLinks = await listGitHubPullRequestIssueLinksForMapping(ctx, mapping, options.target);
14167
+ const githubIssueLinkedPaperclipIssueIds = await listGitHubIssueLinkedPaperclipIssueIdsForMapping(
14168
+ ctx,
14169
+ mapping,
14170
+ importedIssueRecords,
14171
+ options.target
14172
+ );
14173
+ const pullRequestLinks = (await listGitHubPullRequestIssueLinksForMapping(ctx, mapping, options.target)).filter((record) => !githubIssueLinkedPaperclipIssueIds.has(record.paperclipIssueId));
14119
14174
  const ensuredPaperclipIssueIds = /* @__PURE__ */ new Map();
14120
14175
  const trackedIssueIds = /* @__PURE__ */ new Set([
14121
14176
  ...issues.map((issue) => issue.id),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-github-plugin",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "Paperclip plugin for synchronizing GitHub issues into Paperclip projects.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",