@gitkraken/core-gitlens 0.9.3 → 0.9.4

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/dist/plus/integrations/integrationService.d.ts +6 -1
  3. package/dist/plus/integrations/integrationService.js.map +1 -1
  4. package/dist/plus/integrations/manager.d.ts +6 -1
  5. package/dist/plus/integrations/models/issueReads.d.ts +14 -0
  6. package/dist/plus/integrations/providers/bitbucket/models.d.ts +5 -5
  7. package/dist/plus/integrations/providers/bitbucket/models.js +2 -2
  8. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  9. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  10. package/dist/plus/integrations/providers/jira-server.d.ts +2 -2
  11. package/dist/plus/integrations/providers/jira-server.js +9 -3
  12. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  13. package/dist/plus/integrations/providers/jira.d.ts +6 -1
  14. package/dist/plus/integrations/providers/jira.js +19 -3
  15. package/dist/plus/integrations/providers/jira.js.map +1 -1
  16. package/dist/plus/integrations/providers/linear.js +8 -2
  17. package/dist/plus/integrations/providers/linear.js.map +1 -1
  18. package/dist/plus/integrations/providers/models.d.ts +19 -1
  19. package/dist/plus/integrations/providers/models.js +14 -5
  20. package/dist/plus/integrations/providers/models.js.map +1 -1
  21. package/dist/plus/integrations/providers/providersApi.d.ts +8 -1
  22. package/dist/plus/integrations/providers/providersApi.js +3 -0
  23. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  24. package/dist/plus/integrations/providers/trello.js +1 -0
  25. package/dist/plus/integrations/providers/trello.js.map +1 -1
  26. package/dist/plus/integrations/reads/cursors.d.ts +8 -0
  27. package/dist/plus/integrations/reads/cursors.js +5 -3
  28. package/dist/plus/integrations/reads/cursors.js.map +1 -1
  29. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  30. package/dist/plus/integrations/reads/filters.d.ts +8 -0
  31. package/dist/plus/integrations/reads/filters.js +2 -1
  32. package/dist/plus/integrations/reads/filters.js.map +1 -1
  33. package/dist/plus/integrations/reads/issueTracker.d.ts +9 -1
  34. package/dist/plus/integrations/reads/issueTracker.js +20 -3
  35. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  36. package/docs/integrations.md +4 -0
  37. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"issueTracker.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueTracker.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { IssueShape, IssueSorting } from '@gitlens/git/models/issue.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport { assessCollectionMetadata, mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { IssueFilter, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata, parsePageCursor } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from './cursors.js';\nimport { runCaptured } from './drains.js';\nimport { projectKey, resourceIdForProject, resourceLabel, resourceMatchesOrg } from './hierarchy.utils.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tincompleteReadWarning,\n\tissueTrackerOnlySurfaceWarning,\n\totherWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedIssueSortWarning,\n} from './warnings.js';\n\nexport async function listIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\torg?: string;\n\t\tproject?: string;\n\t\tfilters?: IssueFilter[];\n\t\t/** Broadens the read to every assignee. Scopes to user-assigned issues when omitted. */\n\t\tincludeAllAssignees?: boolean;\n\t\t/**\n\t\t * How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the\n\t\t * tracker can express it.\n\t\t *\n\t\t * Validated against `getSupportedFilters().issues`' sibling `issueSorts` — a tracker reports there, not\n\t\t * under the account-wide table, because resource -> project IS its only issue surface. A key it can't\n\t\t * express refuses the read rather than serving a differently-ordered list.\n\t\t *\n\t\t * One page can span SEVERAL projects, whose issues are merged here, so a key no normalized issue carries\n\t\t * (`priority`, `dueDate`, `resolved`) is refused for a multi-project page even though the tracker orders by\n\t\t * it perfectly well within one project.\n\t\t *\n\t\t * Unlike the git-host reads this one is safe to change mid-pagination: its cursor windows PROJECTS and\n\t\t * drains each to exhaustion, so which projects a round covers doesn't depend on how their issues are\n\t\t * ordered. Only the order within a page changes.\n\t\t */\n\t\tsort?: IssueSorting;\n\t\tforceSync?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\titemsPerPage?: number;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed tracker host. Used to select the instance when the requested connection has no\n\t\t * configured domain; it must come from the trusted authentication configuration, never from repository\n\t\t * or remote data. Ignored for the cloud trackers.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Pagination is opt-in: only window the projects when the caller actually asked to page. A caller that\n\t// passes none of page/cursor/itemsPerPage keeps the \"aggregate every matched project\" contract, so an\n\t// existing consumer that doesn't inspect `hasMore` never silently loses projects past a default window.\n\tconst compositeCursor = parseIssueTrackerPageCursor(options.cursor);\n\tconst paginated =\n\t\tcompositeCursor?.unpaged !== true &&\n\t\t(options.page != null || options.cursor != null || options.itemsPerPage != null);\n\t// A composite cursor can carry retries from earlier project windows alongside the next untouched window.\n\t// `currentPage` remains the caller-facing position; `nextPage` is the window this round should advance.\n\tconst page = Math.max(\n\t\t1,\n\t\tMath.trunc(compositeCursor?.currentPage ?? parsePageCursor(options.cursor) ?? options.page ?? 1),\n\t);\n\tconst projectsPerPage = Math.max(1, Math.trunc(options.itemsPerPage ?? 20));\n\n\tconst items: IssueShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst emptyPage = (\n\t\tfetchFailed?: boolean,\n\t\ttruncated?: boolean,\n\t\tretry?: {\n\t\t\tnextPage?: number;\n\t\t\tpages?: readonly number[];\n\t\t\tprojects?: readonly string[];\n\t\t\tcompletedProjects?: readonly string[];\n\t\t},\n\t): ProviderPagedResult<IssueShape> => {\n\t\tconst cursor =\n\t\t\tretry != null\n\t\t\t\t? toIssueTrackerPageCursor({\n\t\t\t\t\t\tcurrentPage: page + 1,\n\t\t\t\t\t\tunpaged: !paginated,\n\t\t\t\t\t\tnextPage: retry.nextPage,\n\t\t\t\t\t\tretryPages: retry.pages ?? [],\n\t\t\t\t\t\tretryProjects: retry.projects ?? [],\n\t\t\t\t\t\tcompletedProjects: retry.completedProjects ?? [],\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\titems: items,\n\t\t\twarnings: warnings,\n\t\t\tpage: { currentPage: page, itemsPerPage: items.length, truncated: truncated || undefined },\n\t\t\t// Retry-only state is deliberately manual: advertising it as forward progress would make a\n\t\t\t// persistent provider failure spin forever in a normal `while (hasMore)` consumer. An untouched\n\t\t\t// project window remains normal forward progress even when this window failed completely.\n\t\t\thasMore: retry?.nextPage != null && cursor != null,\n\t\t\tcursor: cursor,\n\t\t\tfetchFailed: fetchFailed || undefined,\n\t\t};\n\t};\n\tconst normalWindowPage = paginated\n\t\t? (compositeCursor?.nextPage ?? (compositeCursor == null ? page : undefined))\n\t\t: undefined;\n\tconst priorRetryPages = compositeCursor?.retryPages ?? [];\n\tconst priorRetryProjects = compositeCursor?.retryProjects ?? [];\n\tconst priorCompletedProjects = compositeCursor?.completedProjects ?? [];\n\tconst trackCompletedProjects =\n\t\tpriorCompletedProjects.length > 0 || priorRetryPages.length > 0 || compositeCursor?.completedProjects != null;\n\tconst retryPagesForDiscoveryFailure = (): number[] => [\n\t\t...priorRetryPages,\n\t\t...(normalWindowPage != null ? [normalWindowPage] : compositeCursor == null ? [1] : []),\n\t];\n\tconst discoveryFailureRetry = (): {\n\t\tpages: readonly number[];\n\t\tprojects: readonly string[];\n\t\tcompletedProjects: readonly string[];\n\t} => ({\n\t\tpages: retryPagesForDiscoveryFailure(),\n\t\tprojects: priorRetryProjects,\n\t\tcompletedProjects: priorCompletedProjects,\n\t});\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\twarnings.push(...early.warnings);\n\t\treturn emptyPage(early.fetchFailed);\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\t// Before any upstream request: the discovery fan-outs below (resources, projects, per-resource accounts) are\n\t// three round trips, and an order this tracker can't express refuses the read whatever they return. Placed after\n\t// `domain` only because every warning carries it.\n\tconst resolvedSort = resolveIssueSort(providersMetadata[options.providerId]?.supportedIssueSorts, options.sort);\n\tif (resolvedSort.rejection != null) {\n\t\twarnings.push(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst sort = resolvedSort.sort;\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst { value: resources, warning: resourcesWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getResourcesForUserResult(options.connectionId),\n\t);\n\tif (resourcesWarning != null) {\n\t\twarnings.push(resourcesWarning);\n\t}\n\tif (resources == null || resources.length === 0) {\n\t\tconst fetchFailed = resourcesWarning != null && resources == null;\n\t\treturn emptyPage(fetchFailed, false, fetchFailed ? discoveryFailureRetry() : undefined);\n\t}\n\n\tconst scopedResources =\n\t\toptions.org != null ? resources.filter(r => resourceMatchesOrg(r, options.org!)) : resources;\n\tif (scopedResources.length === 0) {\n\t\treturn emptyPage();\n\t}\n\n\tconst { value: projectsResult, warning: projectsWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getProjectsForResourcesWithMetadataResult(scopedResources, options.connectionId),\n\t);\n\tif (projectsWarning != null) {\n\t\twarnings.push(projectsWarning);\n\t}\n\t// Partial project discovery: continue with the resources that succeeded, but surface per-resource\n\t// failures as warnings and remember to mark the page fetchFailed so the caller knows some issues may be\n\t// missing. `projectDiscoveryFailed`/`projectDiscoveryTruncated` are OR-ed into the page's\n\t// fetchFailed/truncated at every return below (a truncated-but-not-failed discovery, e.g. a paging\n\t// backstop, still means the project set is incomplete).\n\tconst projectDiscoveryAssessment = mergeAssessmentInto(\n\t\twarnings,\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tprojectsResult?.metadata,\n\t);\n\tconst projectDiscoveryFailed = projectDiscoveryAssessment.fetchFailed;\n\tconst projectDiscoveryTruncated = projectDiscoveryAssessment.truncated;\n\tconst projects = projectsResult?.values;\n\tif (projects == null || projects.length === 0) {\n\t\tconst fetchFailed = (projectsWarning != null && projectsResult == null) || projectDiscoveryFailed;\n\t\treturn emptyPage(\n\t\t\tfetchFailed,\n\t\t\tprojectDiscoveryTruncated,\n\t\t\tfetchFailed || projectDiscoveryTruncated ? discoveryFailureRetry() : undefined,\n\t\t);\n\t}\n\n\tconst matchedProjects =\n\t\toptions.project != null ? projects.filter(p => resourceMatchesOrg(p, options.project!)) : projects;\n\n\t// Validate the requested filters against what this provider supports (e.g. Linear/Trello support only\n\t// Assignee). An unsupported filter must not silently degrade — Linear/Trello ignore the requested type\n\t// and apply Assignee regardless — so warn + fetchFailed instead of returning a differently-scoped set.\n\tif (options.filters?.length) {\n\t\tconst supported = providersMetadata[options.providerId]?.supportedIssueFilters;\n\t\tconst allSupported = supported != null && options.filters.every(f => supported.includes(f));\n\t\tif (!allSupported) {\n\t\t\twarnings.push(\n\t\t\t\totherWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`One or more requested issue filters are not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t}\n\n\t// `includeAllAssignees` drops the user scope, but a user-relative filter (Author/Mention) is meaningless\n\t// without a user. Passing both to the provider degrades silently: Jira, seeing no user, falls through to\n\t// an unscoped project fetch and returns EVERY issue instead of the requested author's/mentions. Reject\n\t// the incompatible combination up front rather than publishing a differently-scoped set as the result.\n\tif (options.includeAllAssignees === true && options.filters?.some(f => f !== IssueFilter.Assignee)) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`\\`includeAllAssignees\\` cannot be combined with an author/mention filter for '${options.providerId}' (those filters require a user scope).`,\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// Scope to the current user's assigned issues unless the caller broadens to all assignees. Resolve the\n\t// handle from each resource's own account (multi-account safe), capturing any error so its kind\n\t// (e.g. auth) is preserved rather than collapsed to a generic warning.\n\tlet usersByResourceId: Map<string, string> | undefined;\n\t// The same accounts keyed by the same resource, holding the provider's stable id rather than the handle. A\n\t// separate map rather than a widened value because the two are resolved and consumed independently: a\n\t// provider may carry an account with no id, and `scopedProjectsWithUsers` gates on the HANDLE alone, so\n\t// folding them together would drop a resource whose id happened to be missing.\n\tlet userIdsByResourceId: Map<string, string> | undefined;\n\tlet accountLookupFailed = false;\n\tif (options.includeAllAssignees !== true) {\n\t\tusersByResourceId = new Map<string, string>();\n\t\tuserIdsByResourceId = new Map<string, string>();\n\t\tconst accounts = await mapBounded(scopedResources, providerFanOutConcurrency, async resource => ({\n\t\t\tresource: resource,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tintegration.getAccountForResourceResult(resource, options.connectionId),\n\t\t\t)),\n\t\t}));\n\n\t\tfor (const { resource, value: account, warning: accountWarning } of accounts) {\n\t\t\tconst user = account?.username ?? account?.name ?? undefined;\n\t\t\tif (user != null) {\n\t\t\t\tconst resourceKey = resourceIdForProject(resource) ?? resource.key;\n\t\t\t\tusersByResourceId.set(resourceKey, user);\n\t\t\t\t// Empty-checked, not just null-checked: an account shape declares `id` as a plain string, and a\n\t\t\t\t// provider with nothing to put there sets `''` rather than omitting it — which would otherwise\n\t\t\t\t// reach a user-field query as an empty identity and match nothing.\n\t\t\t\tif (account?.id != null && account.id !== '') {\n\t\t\t\t\tuserIdsByResourceId.set(resourceKey, account.id);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Deduped: a captured `accountWarning` carries only the provider error, with no resource in its\n\t\t\t// message, so one expired token across N resources would otherwise repeat verbatim N times. The\n\t\t\t// synthesized fallback names the resource, so those stay distinct on their own.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\taccountWarning ??\n\t\t\t\t\totherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Could not resolve the current user for '${resourceLabel(resource)}'; skipping that resource to avoid returning issues assigned to others.`,\n\t\t\t\t\t),\n\t\t\t);\n\t\t\taccountLookupFailed = true;\n\t\t}\n\t}\n\n\tconst fallbackUserForUnscopedProject =\n\t\tscopedResources.length === 1 && usersByResourceId?.size === 1\n\t\t\t? usersByResourceId.values().next().value\n\t\t\t: undefined;\n\tconst userForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\tif (resourceId != null) {\n\t\t\tconst user = usersByResourceId?.get(resourceId);\n\t\t\tif (user != null) {\n\t\t\t\treturn user;\n\t\t\t}\n\t\t}\n\n\t\t// Some providers/tests return project descriptors without their parent resource id. When we have only\n\t\t// one scoped resource, re-use that sole resolved user rather than silently dropping every project.\n\t\treturn fallbackUserForUnscopedProject;\n\t};\n\t// Deliberately NOT falling back the way `userForProject` does. That fallback exists so a project with no\n\t// parent resource id still gets scoped rather than dropped, and the handle is what the drop is judged on;\n\t// the id is optional everywhere it is read, so a miss degrades to the handle instead of widening the read.\n\tconst userIdForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\treturn resourceId != null ? userIdsByResourceId?.get(resourceId) : undefined;\n\t};\n\n\tconst retryProjectKeys = new Set<string>();\n\tconst completedProjectKeys = new Set(priorCompletedProjects);\n\tfor (const retryProject of priorRetryProjects) {\n\t\tcompletedProjectKeys.delete(retryProject);\n\t}\n\tconst scopedProjectsWithUsers =\n\t\tusersByResourceId != null\n\t\t\t? matchedProjects.filter(project => {\n\t\t\t\t\tif (userForProject(project) != null) return true;\n\n\t\t\t\t\tconst key = projectKey(project);\n\t\t\t\t\tretryProjectKeys.add(key);\n\t\t\t\t\tcompletedProjectKeys.delete(key);\n\t\t\t\t\treturn false;\n\t\t\t\t})\n\t\t\t: matchedProjects;\n\n\t// Select the untouched window plus any earlier windows/projects that explicitly need retrying. Keys\n\t// include the parent resource so two trackers can reuse the same project id without collapsing.\n\tconst projectsByRetryKey = new Map(scopedProjectsWithUsers.map(project => [projectKey(project), project]));\n\tconst scopedProjectsByKey = new Map<string, ResourceDescriptor>();\n\tconst addScopedProject = (project: ResourceDescriptor): void => {\n\t\tconst key = projectKey(project);\n\t\tif (completedProjectKeys.has(key)) return;\n\n\t\tscopedProjectsByKey.set(key, project);\n\t};\n\tfor (const retryProject of priorRetryProjects) {\n\t\tconst project = projectsByRetryKey.get(retryProject);\n\t\tif (project != null) {\n\t\t\taddScopedProject(project);\n\t\t} else if (projectDiscoveryFailed || projectDiscoveryTruncated) {\n\t\t\tretryProjectKeys.add(retryProject);\n\t\t}\n\t}\n\tconst windowPages = new Set<number>(priorRetryPages);\n\tif (normalWindowPage != null) {\n\t\twindowPages.add(normalWindowPage);\n\t}\n\tif (!paginated && (projectDiscoveryFailed || projectDiscoveryTruncated)) {\n\t\twindowPages.add(1);\n\t}\n\tif (paginated) {\n\t\tfor (const windowPage of windowPages) {\n\t\t\tconst windowStart = (windowPage - 1) * projectsPerPage;\n\t\t\tfor (const project of scopedProjectsWithUsers.slice(windowStart, windowStart + projectsPerPage)) {\n\t\t\t\taddScopedProject(project);\n\t\t\t}\n\t\t}\n\t} else if (compositeCursor == null || priorRetryPages.length > 0) {\n\t\t// An unpaged retry-only cursor should re-read only the explicitly failed projects already added\n\t\t// above. Discovery retries still rescan the aggregate set so newly recovered projects are included.\n\t\tfor (const project of scopedProjectsWithUsers) {\n\t\t\taddScopedProject(project);\n\t\t}\n\t}\n\tconst scopedProjects = [...scopedProjectsByKey.values()];\n\tconst furthestWindowPage = windowPages.size > 0 ? Math.max(...windowPages) : normalWindowPage;\n\tconst normalWindowEnd =\n\t\tfurthestWindowPage != null ? furthestWindowPage * projectsPerPage : scopedProjectsWithUsers.length;\n\tconst nextPage =\n\t\tpaginated && furthestWindowPage != null && scopedProjectsWithUsers.length > normalWindowEnd\n\t\t\t? furthestWindowPage + 1\n\t\t\t: undefined;\n\tconst retryWindowPages = (): number[] => {\n\t\tif (!projectDiscoveryFailed && !projectDiscoveryTruncated && retryProjectKeys.size === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst pages = new Set(windowPages);\n\t\tif (pages.size === 0) {\n\t\t\t// A terminal retry has no untouched next window to identify its origin. Keep a manual window\n\t\t\t// marker so a later partial discovery can reconcile against the projects already emitted.\n\t\t\tpages.add(paginated ? Math.max(1, page - 1) : 1);\n\t\t}\n\t\treturn [...pages];\n\t};\n\tif (scopedProjects.length === 0) {\n\t\t// The discovered projects didn't intersect the requested filter/window, or every matching resource\n\t\t// failed user resolution. If discovery or account lookup was partial, the empty result is not a\n\t\t// proven-empty account — carry `fetchFailed` so the caller knows issues may be missing.\n\t\treturn emptyPage(projectDiscoveryFailed || accountLookupFailed, projectDiscoveryTruncated, {\n\t\t\tnextPage: nextPage,\n\t\t\tpages: retryWindowPages(),\n\t\t\tprojects: [...retryProjectKeys],\n\t\t\tcompletedProjects: [...completedProjectKeys],\n\t\t});\n\t}\n\n\t// A read spanning several projects is a merge, so it can only honor a key a normalized issue carries. Bound here\n\t// rather than next to the key's validation above, because the project count is only known now — while still\n\t// before any project read, so a refusal costs no request. Refused rather than served as concatenated\n\t// per-project runs, which would look ordered without being so.\n\t//\n\t// Counted over every project the READ covers, not the `scopedProjects` window this page happens to hold: the\n\t// window is `itemsPerPage` projects wide, so counting it would refuse a 20-project page and then serve the\n\t// final 1-project page of the same paginated call — the same key answered two ways, with the earlier pages'\n\t// issues missing from a read that reported success.\n\t//\n\t// Projects are not the only thing a tracker read merges: a user-scoped read asking for several relationships\n\t// issues one provider query PER relationship and unions them within each project (Jira runs one JQL drain per\n\t// filter and folds them into one map, in filter order), so a single-project page of two filters is just as\n\t// concatenated as a two-project one. Counting only projects published those runs under the requested key.\n\t// Not gated on the provider: the two trackers that don't fan out (Linear, Trello) declare `Assignee` as their\n\t// only supported filter, so a multi-filter read is already refused above and can never reach this.\n\tconst mergesFilters = options.includeAllAssignees !== true && (options.filters?.length ?? 0) > 1;\n\tconst ordering = toIssueOrdering(sort, scopedProjectsWithUsers.length > 1 || mergesFilters);\n\tif (ordering.unmergeable != null) {\n\t\twarnings.push(\n\t\t\tunmergeableIssueSortWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tordering.unmergeable,\n\t\t\t\tscopedProjectsWithUsers.length > 1 ? 'projects' : 'filters',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst perProject = await mapBounded(scopedProjects, providerFanOutConcurrency, async project => ({\n\t\tproject: project,\n\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\tintegration.getIssuesForProjectWithTruncationResult(\n\t\t\t\tproject,\n\t\t\t\t{\n\t\t\t\t\tuser: userForProject(project),\n\t\t\t\t\tuserId: userIdForProject(project),\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tsort: sort,\n\t\t\t\t},\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t)),\n\t}));\n\n\t// Partial project discovery means some projects' issues are missing from this page; propagate it so the\n\t// page reports fetchFailed even when every discovered project's own read succeeded.\n\tlet fetchFailed = projectDiscoveryFailed || accountLookupFailed;\n\tlet truncationRecovery: 'narrow-scope' | 'none' | undefined = projectDiscoveryTruncated ? 'none' : undefined;\n\tlet drainMetadata: CollectionMetadata | undefined;\n\tfor (const { project, value: result, warning } of perProject) {\n\t\tconst key = projectKey(project);\n\t\tconst retryProject = result == null;\n\t\tif (warning != null) {\n\t\t\t// Deduped: the per-project warning is built from the provider error alone and names no project, so a\n\t\t\t// single failing token repeats verbatim once per project in the window.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t\tfetchFailed = true;\n\t\t\ttruncationRecovery = 'none';\n\t\t}\n\t\t// A thrown/unsupported read (e.g. Linear not-implemented) surfaces as a warning with no value;\n\t\t// mark the aggregate as fetchFailed so an empty result isn't mistaken for \"no issues\".\n\t\tif (result == null) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (result != null) {\n\t\t\titems.push(...result.values);\n\t\t\tif (result.truncated) {\n\t\t\t\tconst recovery = result.recovery;\n\t\t\t\ttruncationRecovery = truncationRecovery == null || truncationRecovery === recovery ? recovery : 'none';\n\t\t\t}\n\t\t\tif (result.metadata != null) {\n\t\t\t\tdrainMetadata = mergeCollectionMetadata(drainMetadata, result.metadata);\n\t\t\t}\n\t\t}\n\t\tif (retryProject) {\n\t\t\tretryProjectKeys.add(key);\n\t\t\tcompletedProjectKeys.delete(key);\n\t\t} else {\n\t\t\t// A usable partial/truncated project is emitted once and remains structurally incomplete.\n\t\t\t// Re-running the same project cursor would normally return the same prefix and duplicate it\n\t\t\t// across facade pages; only a project that returned no value is safe to retry automatically.\n\t\t\tcompletedProjectKeys.add(key);\n\t\t}\n\t}\n\n\t// Each provider query arrived ordered by the tracker; the union of them — across projects, and across the\n\t// per-relationship queries a multi-filter read fans out into — is not, and the union is what this read\n\t// publishes. A no-op when nothing merged, since the tracker already ordered that single run.\n\tconst orderedItems = ordering.order(items);\n\n\tconst projectReadReportedTruncation = truncationRecovery != null;\n\tconst drainAssessment = assessCollectionMetadata(options.providerId, domain, options.connectionId, drainMetadata);\n\tfor (const warning of drainAssessment.warnings) {\n\t\t// A project result already supplies the recovery semantics. Replace metadata's unstructured fallback with\n\t\t// the structured warning below, while retaining specific failures and omissions.\n\t\tif (projectReadReportedTruncation && !drainAssessment.fetchFailed && warning.omission == null) continue;\n\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\tfetchFailed = fetchFailed || drainAssessment.fetchFailed;\n\tif (drainAssessment.truncated) {\n\t\ttruncationRecovery = 'none';\n\t}\n\n\tconst projectTruncated = truncationRecovery != null;\n\tconst scopeTooLarge = !fetchFailed && truncationRecovery === 'narrow-scope';\n\tif (scopeTooLarge || (!fetchFailed && projectTruncated && !warnings.some(warning => warning.omission != null))) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tincompleteReadWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tscopeTooLarge\n\t\t\t\t\t? 'Some projects hold more issues than one read can return; narrow the scope to read the rest.'\n\t\t\t\t\t: 'Some issues were omitted; the provider returned an incomplete result.',\n\t\t\t\tscopeTooLarge ? 'scope-too-large' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier project asserts the read succeeded; a later one may since have failed.\n\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\n\tconst retryPages = retryWindowPages();\n\tconst cursor = toIssueTrackerPageCursor({\n\t\tcurrentPage: page + 1,\n\t\tunpaged: !paginated,\n\t\tnextPage: nextPage,\n\t\tretryPages: retryPages,\n\t\tretryProjects: [...retryProjectKeys],\n\t\tcompletedProjects:\n\t\t\t(trackCompletedProjects ||\n\t\t\t\tprojectDiscoveryFailed ||\n\t\t\t\tprojectDiscoveryTruncated ||\n\t\t\t\tretryProjectKeys.size > 0) &&\n\t\t\t(retryPages.length > 0 || nextPage != null)\n\t\t\t\t? [...completedProjectKeys]\n\t\t\t\t: [],\n\t});\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: { currentPage: page, itemsPerPage: orderedItems.length, truncated: projectTruncated || undefined },\n\t\t// Failed-project retries alone are manual. Only an untouched project window is automatic progress.\n\t\thasMore: nextPage != null && cursor != null,\n\t\tcursor: cursor,\n\t\tfetchFailed: fetchFailed || undefined,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0BA,eAAsB,2BACrB,KACA,SAoC2C;CAI3C,MAAM,kBAAkB,4BAA4B,QAAQ,MAAM;CAClE,MAAM,YACL,iBAAiB,YAAY,SAC5B,QAAQ,QAAQ,QAAQ,QAAQ,UAAU,QAAQ,QAAQ,gBAAgB;CAG5E,MAAM,OAAO,KAAK,IACjB,GACA,KAAK,MAAM,iBAAiB,eAAe,gBAAgB,QAAQ,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAChG;CACA,MAAM,kBAAkB,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,gBAAgB,EAAE,CAAC;CAE1E,MAAM,QAAsB,CAAC;CAC7B,MAAM,WAA8B,CAAC;CACrC,MAAM,aACL,aACA,WACA,UAMqC;EACrC,MAAM,SACL,SAAS,OACN,yBAAyB;GACzB,aAAa,OAAO;GACpB,SAAS,CAAC;GACV,UAAU,MAAM;GAChB,YAAY,MAAM,SAAS,CAAC;GAC5B,eAAe,MAAM,YAAY,CAAC;GAClC,mBAAmB,MAAM,qBAAqB,CAAC;EAChD,CAAC,IACA,KAAA;EACJ,OAAO;GACC;GACG;GACV,MAAM;IAAE,aAAa;IAAM,cAAc,MAAM;IAAQ,WAAW,aAAa,KAAA;GAAU;GAIzF,SAAS,OAAO,YAAY,QAAQ,UAAU;GACtC;GACR,aAAa,eAAe,KAAA;EAC7B;CACD;CACA,MAAM,mBAAmB,YACrB,iBAAiB,aAAa,mBAAmB,OAAO,OAAO,KAAA,KAChE,KAAA;CACH,MAAM,kBAAkB,iBAAiB,cAAc,CAAC;CACxD,MAAM,qBAAqB,iBAAiB,iBAAiB,CAAC;CAC9D,MAAM,yBAAyB,iBAAiB,qBAAqB,CAAC;CACtE,MAAM,yBACL,uBAAuB,SAAS,KAAK,gBAAgB,SAAS,KAAK,iBAAiB,qBAAqB;CAC1G,MAAM,sCAAgD,CACrD,GAAG,iBACH,GAAI,oBAAoB,OAAO,CAAC,gBAAgB,IAAI,mBAAmB,OAAO,CAAC,CAAC,IAAI,CAAC,CACtF;CACA,MAAM,+BAIA;EACL,OAAO,8BAA8B;EACrC,UAAU;EACV,mBAAmB;CACpB;CAEA,IAAI,CAAC,0BAA0B,QAAQ,UAAU,GAAG;EACnD,SAAS,KACR,+BAA+B,QAAQ,YAAY,QAAQ,cAAc,6BAA6B,CACvG;EACA,OAAO,UAAU,IAAI;CACtB;CAEA,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAAM;EAExB,MAAM,QAAQ,IAAI,8BAA8B,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EACxG,SAAS,KAAK,GAAG,MAAM,QAAQ;EAC/B,OAAO,UAAU,MAAM,WAAW;CACnC;CACA,IAAI,CAAC,oBAAoB,WAAW,GAAG;EACtC,SAAS,KACR,+BAA+B,QAAQ,YAAY,QAAQ,cAAc,6BAA6B,CACvG;EACA,OAAO,UAAU,IAAI;CACtB;CAEA,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAKtG,MAAM,eAAe,iBAAiB,kBAAkB,QAAQ,WAAW,EAAE,qBAAqB,QAAQ,IAAI;CAC9G,IAAI,aAAa,aAAa,MAAM;EACnC,SAAS,KACR,4BAA4B,QAAQ,YAAY,QAAQ,QAAQ,cAAc,aAAa,SAAS,CACrG;EACA,OAAO,UAAU,IAAI;CACtB;CAEA,MAAM,OAAO,aAAa;CAE1B,MAAM,IAAI,wBAAwB,aAAa,QAAQ,WAAW,QAAQ,YAAY;CAEtF,MAAM,EAAE,OAAO,WAAW,SAAS,qBAAqB,MAAM,YAC7D,QAAQ,YACR,QACA,QAAQ,oBACF,YAAY,0BAA0B,QAAQ,YAAY,CACjE;CACA,IAAI,oBAAoB,MACvB,SAAS,KAAK,gBAAgB;CAE/B,IAAI,aAAa,QAAQ,UAAU,WAAW,GAAG;EAChD,MAAM,cAAc,oBAAoB,QAAQ,aAAa;EAC7D,OAAO,UAAU,aAAa,OAAO,cAAc,sBAAsB,IAAI,KAAA,CAAS;CACvF;CAEA,MAAM,kBACL,QAAQ,OAAO,OAAO,UAAU,QAAO,MAAK,mBAAmB,GAAG,QAAQ,GAAI,CAAC,IAAI;CACpF,IAAI,gBAAgB,WAAW,GAC9B,OAAO,UAAU;CAGlB,MAAM,EAAE,OAAO,gBAAgB,SAAS,oBAAoB,MAAM,YACjE,QAAQ,YACR,QACA,QAAQ,oBACF,YAAY,0CAA0C,iBAAiB,QAAQ,YAAY,CAClG;CACA,IAAI,mBAAmB,MACtB,SAAS,KAAK,eAAe;CAO9B,MAAM,6BAA6B,oBAClC,UACA,QAAQ,YACR,QACA,QAAQ,cACR,gBAAgB,QACjB;CACA,MAAM,yBAAyB,2BAA2B;CAC1D,MAAM,4BAA4B,2BAA2B;CAC7D,MAAM,WAAW,gBAAgB;CACjC,IAAI,YAAY,QAAQ,SAAS,WAAW,GAAG;EAC9C,MAAM,cAAe,mBAAmB,QAAQ,kBAAkB,QAAS;EAC3E,OAAO,UACN,aACA,2BACA,eAAe,4BAA4B,sBAAsB,IAAI,KAAA,CACtE;CACD;CAEA,MAAM,kBACL,QAAQ,WAAW,OAAO,SAAS,QAAO,MAAK,mBAAmB,GAAG,QAAQ,OAAQ,CAAC,IAAI;CAK3F,IAAI,QAAQ,SAAS,QAAQ;EAC5B,MAAM,YAAY,kBAAkB,QAAQ,WAAW,EAAE;EAEzD,IAAI,EADiB,aAAa,QAAQ,QAAQ,QAAQ,OAAM,MAAK,UAAU,SAAS,CAAC,CAAC,IACvE;GAClB,SAAS,KACR,aACC,QAAQ,YACR,QACA,QAAQ,cACR,6DAA6D,QAAQ,WAAW,GACjF,CACD;GACA,OAAO,UAAU,IAAI;EACtB;CACD;CAMA,IAAI,QAAQ,wBAAwB,QAAQ,QAAQ,SAAS,MAAK,MAAK,MAAA,UAA0B,GAAG;EACnG,SAAS,KACR,aACC,QAAQ,YACR,QACA,QAAQ,cACR,iFAAiF,QAAQ,WAAW,wCACrG,CACD;EACA,OAAO,UAAU,IAAI;CACtB;CAKA,IAAI;CAKJ,IAAI;CACJ,IAAI,sBAAsB;CAC1B,IAAI,QAAQ,wBAAwB,MAAM;EACzC,oCAAoB,IAAI,IAAoB;EAC5C,sCAAsB,IAAI,IAAoB;EAC9C,MAAM,WAAW,MAAM,WAAW,iBAAA,GAA4C,OAAM,cAAa;GACtF;GACV,GAAI,MAAM,YAAY,QAAQ,YAAY,QAAQ,QAAQ,oBACzD,YAAY,4BAA4B,UAAU,QAAQ,YAAY,CACvE;EACD,EAAE;EAEF,KAAK,MAAM,EAAE,UAAU,OAAO,SAAS,SAAS,oBAAoB,UAAU;GAC7E,MAAM,OAAO,SAAS,YAAY,SAAS,QAAQ,KAAA;GACnD,IAAI,QAAQ,MAAM;IACjB,MAAM,cAAc,qBAAqB,QAAQ,KAAK,SAAS;IAC/D,kBAAkB,IAAI,aAAa,IAAI;IAIvC,IAAI,SAAS,MAAM,QAAQ,QAAQ,OAAO,IACzC,oBAAoB,IAAI,aAAa,QAAQ,EAAE;IAEhD;GACD;GAKA,qBACC,UACA,kBACC,aACC,QAAQ,YACR,QACA,QAAQ,cACR,2CAA2C,cAAc,QAAQ,EAAE,wEACpE,CACF;GACA,sBAAsB;EACvB;CACD;CAEA,MAAM,iCACL,gBAAgB,WAAW,KAAK,mBAAmB,SAAS,IACzD,kBAAkB,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,QAClC,KAAA;CACJ,MAAM,kBAAkB,YAAoD;EAC3E,MAAM,aAAa,qBAAqB,OAAO;EAC/C,IAAI,cAAc,MAAM;GACvB,MAAM,OAAO,mBAAmB,IAAI,UAAU;GAC9C,IAAI,QAAQ,MACX,OAAO;EAET;EAIA,OAAO;CACR;CAIA,MAAM,oBAAoB,YAAoD;EAC7E,MAAM,aAAa,qBAAqB,OAAO;EAC/C,OAAO,cAAc,OAAO,qBAAqB,IAAI,UAAU,IAAI,KAAA;CACpE;CAEA,MAAM,mCAAmB,IAAI,IAAY;CACzC,MAAM,uBAAuB,IAAI,IAAI,sBAAsB;CAC3D,KAAK,MAAM,gBAAgB,oBAC1B,qBAAqB,OAAO,YAAY;CAEzC,MAAM,0BACL,qBAAqB,OAClB,gBAAgB,QAAO,YAAW;EAClC,IAAI,eAAe,OAAO,KAAK,MAAM,OAAO;EAE5C,MAAM,MAAM,WAAW,OAAO;EAC9B,iBAAiB,IAAI,GAAG;EACxB,qBAAqB,OAAO,GAAG;EAC/B,OAAO;CACR,CAAC,IACA;CAIJ,MAAM,qBAAqB,IAAI,IAAI,wBAAwB,KAAI,YAAW,CAAC,WAAW,OAAO,GAAG,OAAO,CAAC,CAAC;CACzG,MAAM,sCAAsB,IAAI,IAAgC;CAChE,MAAM,oBAAoB,YAAsC;EAC/D,MAAM,MAAM,WAAW,OAAO;EAC9B,IAAI,qBAAqB,IAAI,GAAG,GAAG;EAEnC,oBAAoB,IAAI,KAAK,OAAO;CACrC;CACA,KAAK,MAAM,gBAAgB,oBAAoB;EAC9C,MAAM,UAAU,mBAAmB,IAAI,YAAY;EACnD,IAAI,WAAW,MACd,iBAAiB,OAAO;OAClB,IAAI,0BAA0B,2BACpC,iBAAiB,IAAI,YAAY;CAEnC;CACA,MAAM,cAAc,IAAI,IAAY,eAAe;CACnD,IAAI,oBAAoB,MACvB,YAAY,IAAI,gBAAgB;CAEjC,IAAI,CAAC,cAAc,0BAA0B,4BAC5C,YAAY,IAAI,CAAC;CAElB,IAAI,WACH,KAAK,MAAM,cAAc,aAAa;EACrC,MAAM,eAAe,aAAa,KAAK;EACvC,KAAK,MAAM,WAAW,wBAAwB,MAAM,aAAa,cAAc,eAAe,GAC7F,iBAAiB,OAAO;CAE1B;MACM,IAAI,mBAAmB,QAAQ,gBAAgB,SAAS,GAG9D,KAAK,MAAM,WAAW,yBACrB,iBAAiB,OAAO;CAG1B,MAAM,iBAAiB,CAAC,GAAG,oBAAoB,OAAO,CAAC;CACvD,MAAM,qBAAqB,YAAY,OAAO,IAAI,KAAK,IAAI,GAAG,WAAW,IAAI;CAC7E,MAAM,kBACL,sBAAsB,OAAO,qBAAqB,kBAAkB,wBAAwB;CAC7F,MAAM,WACL,aAAa,sBAAsB,QAAQ,wBAAwB,SAAS,kBACzE,qBAAqB,IACrB,KAAA;CACJ,MAAM,yBAAmC;EACxC,IAAI,CAAC,0BAA0B,CAAC,6BAA6B,iBAAiB,SAAS,GACtF,OAAO,CAAC;EAGT,MAAM,QAAQ,IAAI,IAAI,WAAW;EACjC,IAAI,MAAM,SAAS,GAGlB,MAAM,IAAI,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;EAEhD,OAAO,CAAC,GAAG,KAAK;CACjB;CACA,IAAI,eAAe,WAAW,GAI7B,OAAO,UAAU,0BAA0B,qBAAqB,2BAA2B;EAChF;EACV,OAAO,iBAAiB;EACxB,UAAU,CAAC,GAAG,gBAAgB;EAC9B,mBAAmB,CAAC,GAAG,oBAAoB;CAC5C,CAAC;CAmBF,MAAM,gBAAgB,QAAQ,wBAAwB,SAAS,QAAQ,SAAS,UAAU,KAAK;CAC/F,MAAM,WAAW,gBAAgB,MAAM,wBAAwB,SAAS,KAAK,aAAa;CAC1F,IAAI,SAAS,eAAe,MAAM;EACjC,SAAS,KACR,4BACC,QAAQ,YACR,QACA,QAAQ,cACR,SAAS,aACT,wBAAwB,SAAS,IAAI,aAAa,SACnD,CACD;EACA,OAAO,UAAU,IAAI;CACtB;CAEA,MAAM,aAAa,MAAM,WAAW,gBAAA,GAA2C,OAAM,aAAY;EACvF;EACT,GAAI,MAAM,YAAY,QAAQ,YAAY,QAAQ,QAAQ,oBACzD,YAAY,wCACX,SACA;GACC,MAAM,eAAe,OAAO;GAC5B,QAAQ,iBAAiB,OAAO;GAChC,SAAS,QAAQ;GACX;EACP,GACA,QAAQ,YACT,CACD;CACD,EAAE;CAIF,IAAI,cAAc,0BAA0B;CAC5C,IAAI,qBAA0D,4BAA4B,SAAS,KAAA;CACnG,IAAI;CACJ,KAAK,MAAM,EAAE,SAAS,OAAO,QAAQ,aAAa,YAAY;EAC7D,MAAM,MAAM,WAAW,OAAO;EAC9B,MAAM,eAAe,UAAU;EAC/B,IAAI,WAAW,MAAM;GAGpB,qBAAqB,UAAU,OAAO;GACtC,cAAc;GACd,qBAAqB;EACtB;EAGA,IAAI,UAAU,MACb,cAAc;EAEf,IAAI,UAAU,MAAM;GACnB,MAAM,KAAK,GAAG,OAAO,MAAM;GAC3B,IAAI,OAAO,WAAW;IACrB,MAAM,WAAW,OAAO;IACxB,qBAAqB,sBAAsB,QAAQ,uBAAuB,WAAW,WAAW;GACjG;GACA,IAAI,OAAO,YAAY,MACtB,gBAAgB,wBAAwB,eAAe,OAAO,QAAQ;EAExE;EACA,IAAI,cAAc;GACjB,iBAAiB,IAAI,GAAG;GACxB,qBAAqB,OAAO,GAAG;EAChC,OAIC,qBAAqB,IAAI,GAAG;CAE9B;CAKA,MAAM,eAAe,SAAS,MAAM,KAAK;CAEzC,MAAM,gCAAgC,sBAAsB;CAC5D,MAAM,kBAAkB,yBAAyB,QAAQ,YAAY,QAAQ,QAAQ,cAAc,aAAa;CAChH,KAAK,MAAM,WAAW,gBAAgB,UAAU;EAG/C,IAAI,iCAAiC,CAAC,gBAAgB,eAAe,QAAQ,YAAY,MAAM;EAE/F,qBAAqB,UAAU,OAAO;CACvC;CACA,cAAc,eAAe,gBAAgB;CAC7C,IAAI,gBAAgB,WACnB,qBAAqB;CAGtB,MAAM,mBAAmB,sBAAsB;CAC/C,MAAM,gBAAgB,CAAC,eAAe,uBAAuB;CAC7D,IAAI,iBAAkB,CAAC,eAAe,oBAAoB,CAAC,SAAS,MAAK,YAAW,QAAQ,YAAY,IAAI,GAC3G,qBACC,UACA,sBACC,QAAQ,YACR,QACA,QAAQ,cACR,gBACG,gGACA,yEACH,gBAAgB,oBAAoB,WACrC,CACD;CAGD,8BAA8B,UAAU,WAAW;CAEnD,MAAM,aAAa,iBAAiB;CACpC,MAAM,SAAS,yBAAyB;EACvC,aAAa,OAAO;EACpB,SAAS,CAAC;EACA;EACE;EACZ,eAAe,CAAC,GAAG,gBAAgB;EACnC,oBACE,0BACA,0BACA,6BACA,iBAAiB,OAAO,OACxB,WAAW,SAAS,KAAK,YAAY,QACnC,CAAC,GAAG,oBAAoB,IACxB,CAAC;CACN,CAAC;CACD,OAAO;EACN,OAAO;EACG;EACV,MAAM;GAAE,aAAa;GAAM,cAAc,aAAa;GAAQ,WAAW,oBAAoB,KAAA;EAAU;EAEvG,SAAS,YAAY,QAAQ,UAAU;EAC/B;EACR,aAAa,eAAe,KAAA;CAC7B;AACD"}
1
+ {"version":3,"file":"issueTracker.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueTracker.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport { assessCollectionMetadata, mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { IssueFilter, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata, parsePageCursor } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from './cursors.js';\nimport { runCaptured } from './drains.js';\nimport { projectKey, resourceIdForProject, resourceLabel, resourceMatchesOrg } from './hierarchy.utils.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tincompleteReadWarning,\n\tissueTrackerOnlySurfaceWarning,\n\totherWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedIssueSortWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\nexport async function listIssueTrackerIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\torg?: string;\n\t\tproject?: string;\n\t\tfilters?: IssueFilter[];\n\t\t/** Broadens the read to every assignee. Scopes to user-assigned issues when omitted. */\n\t\tincludeAllAssignees?: boolean;\n\t\t/**\n\t\t * How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the\n\t\t * tracker can express it.\n\t\t *\n\t\t * Validated against `getSupportedFilters().issues`' sibling `issueSorts` — a tracker reports there, not\n\t\t * under the account-wide table, because resource -> project IS its only issue surface. A key it can't\n\t\t * express refuses the read rather than serving a differently-ordered list.\n\t\t *\n\t\t * One page can span SEVERAL projects, whose issues are merged here, so a key no normalized issue carries\n\t\t * (`priority`, `dueDate`, `resolved`) is refused for a multi-project page even though the tracker orders by\n\t\t * it perfectly well within one project.\n\t\t *\n\t\t * Unlike the git-host reads this one is safe to change mid-pagination: its cursor windows PROJECTS and\n\t\t * drains each to exhaustion, so which projects a round covers doesn't depend on how their issues are\n\t\t * ordered. Only the order within a page changes.\n\t\t */\n\t\tsort?: IssueSorting;\n\t\t/**\n\t\t * Which issue states to read. Omitted reads open issues only.\n\t\t *\n\t\t * Applied server-side by every project read, so it narrows each project's drain rather than the merged page.\n\t\t * Only a tracker declaring `ProviderMetadata.supportsIssueStates` can express anything but `'open'`; asking\n\t\t * another for `'closed'`/`'all'` refuses the read rather than serving its open issues.\n\t\t */\n\t\tstate?: IssueStateFilter;\n\t\tforceSync?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\titemsPerPage?: number;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed tracker host. Used to select the instance when the requested connection has no\n\t\t * configured domain; it must come from the trusted authentication configuration, never from repository\n\t\t * or remote data. Ignored for the cloud trackers.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Pagination is opt-in: only window the projects when the caller actually asked to page. A caller that\n\t// passes none of page/cursor/itemsPerPage keeps the \"aggregate every matched project\" contract, so an\n\t// existing consumer that doesn't inspect `hasMore` never silently loses projects past a default window.\n\tconst compositeCursor = parseIssueTrackerPageCursor(options.cursor);\n\tconst paginated =\n\t\tcompositeCursor?.unpaged !== true &&\n\t\t(options.page != null || options.cursor != null || options.itemsPerPage != null);\n\t// A composite cursor can carry retries from earlier project windows alongside the next untouched window.\n\t// `currentPage` remains the caller-facing position; `nextPage` is the window this round should advance.\n\tconst page = Math.max(\n\t\t1,\n\t\tMath.trunc(compositeCursor?.currentPage ?? parsePageCursor(options.cursor) ?? options.page ?? 1),\n\t);\n\tconst projectsPerPage = Math.max(1, Math.trunc(options.itemsPerPage ?? 20));\n\n\tconst items: IssueShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst emptyPage = (\n\t\tfetchFailed?: boolean,\n\t\ttruncated?: boolean,\n\t\tretry?: {\n\t\t\tnextPage?: number;\n\t\t\tpages?: readonly number[];\n\t\t\tprojects?: readonly string[];\n\t\t\tcompletedProjects?: readonly string[];\n\t\t},\n\t): ProviderPagedResult<IssueShape> => {\n\t\tconst cursor =\n\t\t\tretry != null\n\t\t\t\t? toIssueTrackerPageCursor({\n\t\t\t\t\t\tcurrentPage: page + 1,\n\t\t\t\t\t\tunpaged: !paginated,\n\t\t\t\t\t\tnextPage: retry.nextPage,\n\t\t\t\t\t\tretryPages: retry.pages ?? [],\n\t\t\t\t\t\tretryProjects: retry.projects ?? [],\n\t\t\t\t\t\tcompletedProjects: retry.completedProjects ?? [],\n\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\titems: items,\n\t\t\twarnings: warnings,\n\t\t\tpage: { currentPage: page, itemsPerPage: items.length, truncated: truncated || undefined },\n\t\t\t// Retry-only state is deliberately manual: advertising it as forward progress would make a\n\t\t\t// persistent provider failure spin forever in a normal `while (hasMore)` consumer. An untouched\n\t\t\t// project window remains normal forward progress even when this window failed completely.\n\t\t\thasMore: retry?.nextPage != null && cursor != null,\n\t\t\tcursor: cursor,\n\t\t\tfetchFailed: fetchFailed || undefined,\n\t\t};\n\t};\n\tconst normalWindowPage = paginated\n\t\t? (compositeCursor?.nextPage ?? (compositeCursor == null ? page : undefined))\n\t\t: undefined;\n\tconst priorRetryPages = compositeCursor?.retryPages ?? [];\n\tconst priorRetryProjects = compositeCursor?.retryProjects ?? [];\n\tconst priorCompletedProjects = compositeCursor?.completedProjects ?? [];\n\tconst trackCompletedProjects =\n\t\tpriorCompletedProjects.length > 0 || priorRetryPages.length > 0 || compositeCursor?.completedProjects != null;\n\tconst retryPagesForDiscoveryFailure = (): number[] => [\n\t\t...priorRetryPages,\n\t\t...(normalWindowPage != null ? [normalWindowPage] : compositeCursor == null ? [1] : []),\n\t];\n\tconst discoveryFailureRetry = (): {\n\t\tpages: readonly number[];\n\t\tprojects: readonly string[];\n\t\tcompletedProjects: readonly string[];\n\t} => ({\n\t\tpages: retryPagesForDiscoveryFailure(),\n\t\tprojects: priorRetryProjects,\n\t\tcompletedProjects: priorCompletedProjects,\n\t});\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\twarnings.push(...early.warnings);\n\t\treturn emptyPage(early.fetchFailed);\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\twarnings.push(\n\t\t\tissueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, 'Issue-tracker project reads'),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\t// Before any upstream request: the discovery fan-outs below (resources, projects, per-resource accounts) are\n\t// three round trips, and an order this tracker can't express refuses the read whatever they return. Placed after\n\t// `domain` only because every warning carries it.\n\tconst resolvedSort = resolveIssueSort(providersMetadata[options.providerId]?.supportedIssueSorts, options.sort);\n\tif (resolvedSort.rejection != null) {\n\t\twarnings.push(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst sort = resolvedSort.sort;\n\n\t// Refused before any request for the same reason as the sort: a state the tracker can't express would otherwise\n\t// be dropped by its project reads, and their open issues published as the requested state. A value outside the\n\t// vocabulary is refused too, since `toProviderIssueStates` would read it as omitted, which means open.\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t\t? `Issue state '${options.state}' is not supported by '${options.providerId}'.`\n\t\t\t\t\t: `Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// A cursor resumed under another state than it was minted for skips what it already covered, whose other-state\n\t// issues were never read. A narrowed read only mints composite cursors, which record their state, so any other\n\t// cursor came from an open read.\n\tconst cursorState =\n\t\tcompositeCursor != null ? (compositeCursor.state ?? 'open') : options.cursor != null ? 'open' : undefined;\n\tif (cursorState != null && cursorState !== (options.state ?? 'open')) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'The cursor belongs to a read of another issue state; start again without it.',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst { value: resources, warning: resourcesWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getResourcesForUserResult(options.connectionId),\n\t);\n\tif (resourcesWarning != null) {\n\t\twarnings.push(resourcesWarning);\n\t}\n\tif (resources == null || resources.length === 0) {\n\t\tconst fetchFailed = resourcesWarning != null && resources == null;\n\t\treturn emptyPage(fetchFailed, false, fetchFailed ? discoveryFailureRetry() : undefined);\n\t}\n\n\tconst scopedResources =\n\t\toptions.org != null ? resources.filter(r => resourceMatchesOrg(r, options.org!)) : resources;\n\tif (scopedResources.length === 0) {\n\t\treturn emptyPage();\n\t}\n\n\tconst { value: projectsResult, warning: projectsWarning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() => integration.getProjectsForResourcesWithMetadataResult(scopedResources, options.connectionId),\n\t);\n\tif (projectsWarning != null) {\n\t\twarnings.push(projectsWarning);\n\t}\n\t// Partial project discovery: continue with the resources that succeeded, but surface per-resource\n\t// failures as warnings and remember to mark the page fetchFailed so the caller knows some issues may be\n\t// missing. `projectDiscoveryFailed`/`projectDiscoveryTruncated` are OR-ed into the page's\n\t// fetchFailed/truncated at every return below (a truncated-but-not-failed discovery, e.g. a paging\n\t// backstop, still means the project set is incomplete).\n\tconst projectDiscoveryAssessment = mergeAssessmentInto(\n\t\twarnings,\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tprojectsResult?.metadata,\n\t);\n\tconst projectDiscoveryFailed = projectDiscoveryAssessment.fetchFailed;\n\tconst projectDiscoveryTruncated = projectDiscoveryAssessment.truncated;\n\tconst projects = projectsResult?.values;\n\tif (projects == null || projects.length === 0) {\n\t\tconst fetchFailed = (projectsWarning != null && projectsResult == null) || projectDiscoveryFailed;\n\t\treturn emptyPage(\n\t\t\tfetchFailed,\n\t\t\tprojectDiscoveryTruncated,\n\t\t\tfetchFailed || projectDiscoveryTruncated ? discoveryFailureRetry() : undefined,\n\t\t);\n\t}\n\n\tconst matchedProjects =\n\t\toptions.project != null ? projects.filter(p => resourceMatchesOrg(p, options.project!)) : projects;\n\n\t// Validate the requested filters against what this provider supports (e.g. Linear/Trello support only\n\t// Assignee). An unsupported filter must not silently degrade — Linear/Trello ignore the requested type\n\t// and apply Assignee regardless — so warn + fetchFailed instead of returning a differently-scoped set.\n\tif (options.filters?.length) {\n\t\tconst supported = providersMetadata[options.providerId]?.supportedIssueFilters;\n\t\tconst allSupported = supported != null && options.filters.every(f => supported.includes(f));\n\t\tif (!allSupported) {\n\t\t\twarnings.push(\n\t\t\t\totherWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`One or more requested issue filters are not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn emptyPage(true);\n\t\t}\n\t}\n\n\t// `includeAllAssignees` drops the user scope, but a user-relative filter (Author/Mention) is meaningless\n\t// without a user. Passing both to the provider degrades silently: Jira, seeing no user, falls through to\n\t// an unscoped project fetch and returns EVERY issue instead of the requested author's/mentions. Reject\n\t// the incompatible combination up front rather than publishing a differently-scoped set as the result.\n\tif (options.includeAllAssignees === true && options.filters?.some(f => f !== IssueFilter.Assignee)) {\n\t\twarnings.push(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`\\`includeAllAssignees\\` cannot be combined with an author/mention filter for '${options.providerId}' (those filters require a user scope).`,\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\t// Scope to the current user's assigned issues unless the caller broadens to all assignees. Resolve the\n\t// handle from each resource's own account (multi-account safe), capturing any error so its kind\n\t// (e.g. auth) is preserved rather than collapsed to a generic warning.\n\tlet usersByResourceId: Map<string, string> | undefined;\n\t// The same accounts keyed by the same resource, holding the provider's stable id rather than the handle. A\n\t// separate map rather than a widened value because the two are resolved and consumed independently: a\n\t// provider may carry an account with no id, and `scopedProjectsWithUsers` gates on the HANDLE alone, so\n\t// folding them together would drop a resource whose id happened to be missing.\n\tlet userIdsByResourceId: Map<string, string> | undefined;\n\tlet accountLookupFailed = false;\n\tif (options.includeAllAssignees !== true) {\n\t\tusersByResourceId = new Map<string, string>();\n\t\tuserIdsByResourceId = new Map<string, string>();\n\t\tconst accounts = await mapBounded(scopedResources, providerFanOutConcurrency, async resource => ({\n\t\t\tresource: resource,\n\t\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\t\tintegration.getAccountForResourceResult(resource, options.connectionId),\n\t\t\t)),\n\t\t}));\n\n\t\tfor (const { resource, value: account, warning: accountWarning } of accounts) {\n\t\t\tconst user = account?.username ?? account?.name ?? undefined;\n\t\t\tif (user != null) {\n\t\t\t\tconst resourceKey = resourceIdForProject(resource) ?? resource.key;\n\t\t\t\tusersByResourceId.set(resourceKey, user);\n\t\t\t\t// Empty-checked, not just null-checked: an account shape declares `id` as a plain string, and a\n\t\t\t\t// provider with nothing to put there sets `''` rather than omitting it — which would otherwise\n\t\t\t\t// reach a user-field query as an empty identity and match nothing.\n\t\t\t\tif (account?.id != null && account.id !== '') {\n\t\t\t\t\tuserIdsByResourceId.set(resourceKey, account.id);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Deduped: a captured `accountWarning` carries only the provider error, with no resource in its\n\t\t\t// message, so one expired token across N resources would otherwise repeat verbatim N times. The\n\t\t\t// synthesized fallback names the resource, so those stay distinct on their own.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\taccountWarning ??\n\t\t\t\t\totherWarning(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t`Could not resolve the current user for '${resourceLabel(resource)}'; skipping that resource to avoid returning issues assigned to others.`,\n\t\t\t\t\t),\n\t\t\t);\n\t\t\taccountLookupFailed = true;\n\t\t}\n\t}\n\n\tconst fallbackUserForUnscopedProject =\n\t\tscopedResources.length === 1 && usersByResourceId?.size === 1\n\t\t\t? usersByResourceId.values().next().value\n\t\t\t: undefined;\n\tconst userForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\tif (resourceId != null) {\n\t\t\tconst user = usersByResourceId?.get(resourceId);\n\t\t\tif (user != null) {\n\t\t\t\treturn user;\n\t\t\t}\n\t\t}\n\n\t\t// Some providers/tests return project descriptors without their parent resource id. When we have only\n\t\t// one scoped resource, re-use that sole resolved user rather than silently dropping every project.\n\t\treturn fallbackUserForUnscopedProject;\n\t};\n\t// Deliberately NOT falling back the way `userForProject` does. That fallback exists so a project with no\n\t// parent resource id still gets scoped rather than dropped, and the handle is what the drop is judged on;\n\t// the id is optional everywhere it is read, so a miss degrades to the handle instead of widening the read.\n\tconst userIdForProject = (project: ResourceDescriptor): string | undefined => {\n\t\tconst resourceId = resourceIdForProject(project);\n\t\treturn resourceId != null ? userIdsByResourceId?.get(resourceId) : undefined;\n\t};\n\n\tconst retryProjectKeys = new Set<string>();\n\tconst completedProjectKeys = new Set(priorCompletedProjects);\n\tfor (const retryProject of priorRetryProjects) {\n\t\tcompletedProjectKeys.delete(retryProject);\n\t}\n\tconst scopedProjectsWithUsers =\n\t\tusersByResourceId != null\n\t\t\t? matchedProjects.filter(project => {\n\t\t\t\t\tif (userForProject(project) != null) return true;\n\n\t\t\t\t\tconst key = projectKey(project);\n\t\t\t\t\tretryProjectKeys.add(key);\n\t\t\t\t\tcompletedProjectKeys.delete(key);\n\t\t\t\t\treturn false;\n\t\t\t\t})\n\t\t\t: matchedProjects;\n\n\t// Select the untouched window plus any earlier windows/projects that explicitly need retrying. Keys\n\t// include the parent resource so two trackers can reuse the same project id without collapsing.\n\tconst projectsByRetryKey = new Map(scopedProjectsWithUsers.map(project => [projectKey(project), project]));\n\tconst scopedProjectsByKey = new Map<string, ResourceDescriptor>();\n\tconst addScopedProject = (project: ResourceDescriptor): void => {\n\t\tconst key = projectKey(project);\n\t\tif (completedProjectKeys.has(key)) return;\n\n\t\tscopedProjectsByKey.set(key, project);\n\t};\n\tfor (const retryProject of priorRetryProjects) {\n\t\tconst project = projectsByRetryKey.get(retryProject);\n\t\tif (project != null) {\n\t\t\taddScopedProject(project);\n\t\t} else if (projectDiscoveryFailed || projectDiscoveryTruncated) {\n\t\t\tretryProjectKeys.add(retryProject);\n\t\t}\n\t}\n\tconst windowPages = new Set<number>(priorRetryPages);\n\tif (normalWindowPage != null) {\n\t\twindowPages.add(normalWindowPage);\n\t}\n\tif (!paginated && (projectDiscoveryFailed || projectDiscoveryTruncated)) {\n\t\twindowPages.add(1);\n\t}\n\tif (paginated) {\n\t\tfor (const windowPage of windowPages) {\n\t\t\tconst windowStart = (windowPage - 1) * projectsPerPage;\n\t\t\tfor (const project of scopedProjectsWithUsers.slice(windowStart, windowStart + projectsPerPage)) {\n\t\t\t\taddScopedProject(project);\n\t\t\t}\n\t\t}\n\t} else if (compositeCursor == null || priorRetryPages.length > 0) {\n\t\t// An unpaged retry-only cursor should re-read only the explicitly failed projects already added\n\t\t// above. Discovery retries still rescan the aggregate set so newly recovered projects are included.\n\t\tfor (const project of scopedProjectsWithUsers) {\n\t\t\taddScopedProject(project);\n\t\t}\n\t}\n\tconst scopedProjects = [...scopedProjectsByKey.values()];\n\tconst furthestWindowPage = windowPages.size > 0 ? Math.max(...windowPages) : normalWindowPage;\n\tconst normalWindowEnd =\n\t\tfurthestWindowPage != null ? furthestWindowPage * projectsPerPage : scopedProjectsWithUsers.length;\n\tconst nextPage =\n\t\tpaginated && furthestWindowPage != null && scopedProjectsWithUsers.length > normalWindowEnd\n\t\t\t? furthestWindowPage + 1\n\t\t\t: undefined;\n\tconst retryWindowPages = (): number[] => {\n\t\tif (!projectDiscoveryFailed && !projectDiscoveryTruncated && retryProjectKeys.size === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst pages = new Set(windowPages);\n\t\tif (pages.size === 0) {\n\t\t\t// A terminal retry has no untouched next window to identify its origin. Keep a manual window\n\t\t\t// marker so a later partial discovery can reconcile against the projects already emitted.\n\t\t\tpages.add(paginated ? Math.max(1, page - 1) : 1);\n\t\t}\n\t\treturn [...pages];\n\t};\n\tif (scopedProjects.length === 0) {\n\t\t// The discovered projects didn't intersect the requested filter/window, or every matching resource\n\t\t// failed user resolution. If discovery or account lookup was partial, the empty result is not a\n\t\t// proven-empty account — carry `fetchFailed` so the caller knows issues may be missing.\n\t\treturn emptyPage(projectDiscoveryFailed || accountLookupFailed, projectDiscoveryTruncated, {\n\t\t\tnextPage: nextPage,\n\t\t\tpages: retryWindowPages(),\n\t\t\tprojects: [...retryProjectKeys],\n\t\t\tcompletedProjects: [...completedProjectKeys],\n\t\t});\n\t}\n\n\t// A read spanning several projects is a merge, so it can only honor a key a normalized issue carries. Bound here\n\t// rather than next to the key's validation above, because the project count is only known now — while still\n\t// before any project read, so a refusal costs no request. Refused rather than served as concatenated\n\t// per-project runs, which would look ordered without being so.\n\t//\n\t// Counted over every project the READ covers, not the `scopedProjects` window this page happens to hold: the\n\t// window is `itemsPerPage` projects wide, so counting it would refuse a 20-project page and then serve the\n\t// final 1-project page of the same paginated call — the same key answered two ways, with the earlier pages'\n\t// issues missing from a read that reported success.\n\t//\n\t// Projects are not the only thing a tracker read merges: a user-scoped read asking for several relationships\n\t// issues one provider query PER relationship and unions them within each project (Jira runs one JQL drain per\n\t// filter and folds them into one map, in filter order), so a single-project page of two filters is just as\n\t// concatenated as a two-project one. Counting only projects published those runs under the requested key.\n\t// Not gated on the provider: the two trackers that don't fan out (Linear, Trello) declare `Assignee` as their\n\t// only supported filter, so a multi-filter read is already refused above and can never reach this.\n\tconst mergesFilters = options.includeAllAssignees !== true && (options.filters?.length ?? 0) > 1;\n\tconst ordering = toIssueOrdering(sort, scopedProjectsWithUsers.length > 1 || mergesFilters);\n\tif (ordering.unmergeable != null) {\n\t\twarnings.push(\n\t\t\tunmergeableIssueSortWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tordering.unmergeable,\n\t\t\t\tscopedProjectsWithUsers.length > 1 ? 'projects' : 'filters',\n\t\t\t),\n\t\t);\n\t\treturn emptyPage(true);\n\t}\n\n\tconst perProject = await mapBounded(scopedProjects, providerFanOutConcurrency, async project => ({\n\t\tproject: project,\n\t\t...(await runCaptured(options.providerId, domain, options.connectionId, () =>\n\t\t\tintegration.getIssuesForProjectWithTruncationResult(\n\t\t\t\tproject,\n\t\t\t\t{\n\t\t\t\t\tuser: userForProject(project),\n\t\t\t\t\tuserId: userIdForProject(project),\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tsort: sort,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t},\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t)),\n\t}));\n\n\t// Partial project discovery means some projects' issues are missing from this page; propagate it so the\n\t// page reports fetchFailed even when every discovered project's own read succeeded.\n\tlet fetchFailed = projectDiscoveryFailed || accountLookupFailed;\n\tlet truncationRecovery: 'narrow-scope' | 'none' | undefined = projectDiscoveryTruncated ? 'none' : undefined;\n\tlet drainMetadata: CollectionMetadata | undefined;\n\tfor (const { project, value: result, warning } of perProject) {\n\t\tconst key = projectKey(project);\n\t\tconst retryProject = result == null;\n\t\tif (warning != null) {\n\t\t\t// Deduped: the per-project warning is built from the provider error alone and names no project, so a\n\t\t\t// single failing token repeats verbatim once per project in the window.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t\tfetchFailed = true;\n\t\t\ttruncationRecovery = 'none';\n\t\t}\n\t\t// A thrown/unsupported read (e.g. Linear not-implemented) surfaces as a warning with no value;\n\t\t// mark the aggregate as fetchFailed so an empty result isn't mistaken for \"no issues\".\n\t\tif (result == null) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (result != null) {\n\t\t\titems.push(...result.values);\n\t\t\tif (result.truncated) {\n\t\t\t\tconst recovery = result.recovery;\n\t\t\t\ttruncationRecovery = truncationRecovery == null || truncationRecovery === recovery ? recovery : 'none';\n\t\t\t}\n\t\t\tif (result.metadata != null) {\n\t\t\t\tdrainMetadata = mergeCollectionMetadata(drainMetadata, result.metadata);\n\t\t\t}\n\t\t}\n\t\tif (retryProject) {\n\t\t\tretryProjectKeys.add(key);\n\t\t\tcompletedProjectKeys.delete(key);\n\t\t} else {\n\t\t\t// A usable partial/truncated project is emitted once and remains structurally incomplete.\n\t\t\t// Re-running the same project cursor would normally return the same prefix and duplicate it\n\t\t\t// across facade pages; only a project that returned no value is safe to retry automatically.\n\t\t\tcompletedProjectKeys.add(key);\n\t\t}\n\t}\n\n\t// Each provider query arrived ordered by the tracker; the union of them — across projects, and across the\n\t// per-relationship queries a multi-filter read fans out into — is not, and the union is what this read\n\t// publishes. A no-op when nothing merged, since the tracker already ordered that single run.\n\tconst orderedItems = ordering.order(items);\n\n\tconst projectReadReportedTruncation = truncationRecovery != null;\n\tconst drainAssessment = assessCollectionMetadata(options.providerId, domain, options.connectionId, drainMetadata);\n\tfor (const warning of drainAssessment.warnings) {\n\t\t// A project result already supplies the recovery semantics. Replace metadata's unstructured fallback with\n\t\t// the structured warning below, while retaining specific failures and omissions.\n\t\tif (projectReadReportedTruncation && !drainAssessment.fetchFailed && warning.omission == null) continue;\n\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\tfetchFailed = fetchFailed || drainAssessment.fetchFailed;\n\tif (drainAssessment.truncated) {\n\t\ttruncationRecovery = 'none';\n\t}\n\n\tconst projectTruncated = truncationRecovery != null;\n\tconst scopeTooLarge = !fetchFailed && truncationRecovery === 'narrow-scope';\n\tif (scopeTooLarge || (!fetchFailed && projectTruncated && !warnings.some(warning => warning.omission != null))) {\n\t\tappendDedupedWarning(\n\t\t\twarnings,\n\t\t\tincompleteReadWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\tscopeTooLarge\n\t\t\t\t\t? 'Some projects hold more issues than one read can return; narrow the scope to read the rest.'\n\t\t\t\t\t: 'Some issues were omitted; the provider returned an incomplete result.',\n\t\t\t\tscopeTooLarge ? 'scope-too-large' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier project asserts the read succeeded; a later one may since have failed.\n\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\n\tconst retryPages = retryWindowPages();\n\tconst cursor = toIssueTrackerPageCursor({\n\t\tcurrentPage: page + 1,\n\t\tunpaged: !paginated,\n\t\tnextPage: nextPage,\n\t\tretryPages: retryPages,\n\t\tretryProjects: [...retryProjectKeys],\n\t\tcompletedProjects:\n\t\t\t(trackCompletedProjects ||\n\t\t\t\tprojectDiscoveryFailed ||\n\t\t\t\tprojectDiscoveryTruncated ||\n\t\t\t\tretryProjectKeys.size > 0) &&\n\t\t\t(retryPages.length > 0 || nextPage != null)\n\t\t\t\t? [...completedProjectKeys]\n\t\t\t\t: [],\n\t\tstate: options.state,\n\t});\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: { currentPage: page, itemsPerPage: orderedItems.length, truncated: projectTruncated || undefined },\n\t\t// Failed-project retries alone are manual. Only an untouched project window is automatic progress.\n\t\thasMore: nextPage != null && cursor != null,\n\t\tcursor: cursor,\n\t\tfetchFailed: fetchFailed || undefined,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0BA,MAAM,oBAAiD;CAAC;CAAQ;CAAU;AAAK;AAE/E,eAAsB,2BACrB,KACA,SA4C2C;CAI3C,MAAM,kBAAkB,4BAA4B,QAAQ,MAAM;CAClE,MAAM,YACL,iBAAiB,YAAY,SAC5B,QAAQ,QAAQ,QAAQ,QAAQ,UAAU,QAAQ,QAAQ,gBAAgB;CAG5E,MAAM,OAAO,KAAK,IACjB,GACA,KAAK,MAAM,iBAAiB,eAAe,gBAAgB,QAAQ,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAChG;CACA,MAAM,kBAAkB,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,gBAAgB,EAAE,CAAC;CAE1E,MAAM,QAAsB,CAAC;CAC7B,MAAM,WAA8B,CAAC;CACrC,MAAM,aACL,aACA,WACA,UAMqC;EACrC,MAAM,SACL,SAAS,OACN,yBAAyB;GACzB,aAAa,OAAO;GACpB,SAAS,CAAC;GACV,UAAU,MAAM;GAChB,YAAY,MAAM,SAAS,CAAC;GAC5B,eAAe,MAAM,YAAY,CAAC;GAClC,mBAAmB,MAAM,qBAAqB,CAAC;GAC/C,OAAO,QAAQ;EAChB,CAAC,IACA,KAAA;EACJ,OAAO;GACC;GACG;GACV,MAAM;IAAE,aAAa;IAAM,cAAc,MAAM;IAAQ,WAAW,aAAa,KAAA;GAAU;GAIzF,SAAS,OAAO,YAAY,QAAQ,UAAU;GACtC;GACR,aAAa,eAAe,KAAA;EAC7B;CACD;CACA,MAAM,mBAAmB,YACrB,iBAAiB,aAAa,mBAAmB,OAAO,OAAO,KAAA,KAChE,KAAA;CACH,MAAM,kBAAkB,iBAAiB,cAAc,CAAC;CACxD,MAAM,qBAAqB,iBAAiB,iBAAiB,CAAC;CAC9D,MAAM,yBAAyB,iBAAiB,qBAAqB,CAAC;CACtE,MAAM,yBACL,uBAAuB,SAAS,KAAK,gBAAgB,SAAS,KAAK,iBAAiB,qBAAqB;CAC1G,MAAM,sCAAgD,CACrD,GAAG,iBACH,GAAI,oBAAoB,OAAO,CAAC,gBAAgB,IAAI,mBAAmB,OAAO,CAAC,CAAC,IAAI,CAAC,CACtF;CACA,MAAM,+BAIA;EACL,OAAO,8BAA8B;EACrC,UAAU;EACV,mBAAmB;CACpB;CAEA,IAAI,CAAC,0BAA0B,QAAQ,UAAU,GAAG;EACnD,SAAS,KACR,+BAA+B,QAAQ,YAAY,QAAQ,cAAc,6BAA6B,CACvG;EACA,OAAO,UAAU,IAAI;CACtB;CAEA,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAAM;EAExB,MAAM,QAAQ,IAAI,8BAA8B,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EACxG,SAAS,KAAK,GAAG,MAAM,QAAQ;EAC/B,OAAO,UAAU,MAAM,WAAW;CACnC;CACA,IAAI,CAAC,oBAAoB,WAAW,GAAG;EACtC,SAAS,KACR,+BAA+B,QAAQ,YAAY,QAAQ,cAAc,6BAA6B,CACvG;EACA,OAAO,UAAU,IAAI;CACtB;CAEA,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAKtG,MAAM,eAAe,iBAAiB,kBAAkB,QAAQ,WAAW,EAAE,qBAAqB,QAAQ,IAAI;CAC9G,IAAI,aAAa,aAAa,MAAM;EACnC,SAAS,KACR,4BAA4B,QAAQ,YAAY,QAAQ,QAAQ,cAAc,aAAa,SAAS,CACrG;EACA,OAAO,UAAU,IAAI;CACtB;CAEA,MAAM,OAAO,aAAa;CAK1B,IACC,QAAQ,SAAS,SAChB,CAAC,kBAAkB,SAAS,QAAQ,KAAK,KACxC,QAAQ,UAAU,UAAU,CAAC,kBAAkB,QAAQ,WAAW,EAAE,sBACrE;EACD,SAAS,KACR,aACC,QAAQ,YACR,QACA,QAAQ,cACR,kBAAkB,SAAS,QAAQ,KAAK,IACrC,gBAAgB,QAAQ,MAAM,yBAAyB,QAAQ,WAAW,MAC1E,wCAAwC,kBAAkB,KAAK,IAAI,EAAE,EACzE,CACD;EACA,OAAO,UAAU,IAAI;CACtB;CAKA,MAAM,cACL,mBAAmB,OAAQ,gBAAgB,SAAS,SAAU,QAAQ,UAAU,OAAO,SAAS,KAAA;CACjG,IAAI,eAAe,QAAQ,iBAAiB,QAAQ,SAAS,SAAS;EACrE,SAAS,KACR,aACC,QAAQ,YACR,QACA,QAAQ,cACR,8EACD,CACD;EACA,OAAO,UAAU,IAAI;CACtB;CAEA,MAAM,IAAI,wBAAwB,aAAa,QAAQ,WAAW,QAAQ,YAAY;CAEtF,MAAM,EAAE,OAAO,WAAW,SAAS,qBAAqB,MAAM,YAC7D,QAAQ,YACR,QACA,QAAQ,oBACF,YAAY,0BAA0B,QAAQ,YAAY,CACjE;CACA,IAAI,oBAAoB,MACvB,SAAS,KAAK,gBAAgB;CAE/B,IAAI,aAAa,QAAQ,UAAU,WAAW,GAAG;EAChD,MAAM,cAAc,oBAAoB,QAAQ,aAAa;EAC7D,OAAO,UAAU,aAAa,OAAO,cAAc,sBAAsB,IAAI,KAAA,CAAS;CACvF;CAEA,MAAM,kBACL,QAAQ,OAAO,OAAO,UAAU,QAAO,MAAK,mBAAmB,GAAG,QAAQ,GAAI,CAAC,IAAI;CACpF,IAAI,gBAAgB,WAAW,GAC9B,OAAO,UAAU;CAGlB,MAAM,EAAE,OAAO,gBAAgB,SAAS,oBAAoB,MAAM,YACjE,QAAQ,YACR,QACA,QAAQ,oBACF,YAAY,0CAA0C,iBAAiB,QAAQ,YAAY,CAClG;CACA,IAAI,mBAAmB,MACtB,SAAS,KAAK,eAAe;CAO9B,MAAM,6BAA6B,oBAClC,UACA,QAAQ,YACR,QACA,QAAQ,cACR,gBAAgB,QACjB;CACA,MAAM,yBAAyB,2BAA2B;CAC1D,MAAM,4BAA4B,2BAA2B;CAC7D,MAAM,WAAW,gBAAgB;CACjC,IAAI,YAAY,QAAQ,SAAS,WAAW,GAAG;EAC9C,MAAM,cAAe,mBAAmB,QAAQ,kBAAkB,QAAS;EAC3E,OAAO,UACN,aACA,2BACA,eAAe,4BAA4B,sBAAsB,IAAI,KAAA,CACtE;CACD;CAEA,MAAM,kBACL,QAAQ,WAAW,OAAO,SAAS,QAAO,MAAK,mBAAmB,GAAG,QAAQ,OAAQ,CAAC,IAAI;CAK3F,IAAI,QAAQ,SAAS,QAAQ;EAC5B,MAAM,YAAY,kBAAkB,QAAQ,WAAW,EAAE;EAEzD,IAAI,EADiB,aAAa,QAAQ,QAAQ,QAAQ,OAAM,MAAK,UAAU,SAAS,CAAC,CAAC,IACvE;GAClB,SAAS,KACR,aACC,QAAQ,YACR,QACA,QAAQ,cACR,6DAA6D,QAAQ,WAAW,GACjF,CACD;GACA,OAAO,UAAU,IAAI;EACtB;CACD;CAMA,IAAI,QAAQ,wBAAwB,QAAQ,QAAQ,SAAS,MAAK,MAAK,MAAA,UAA0B,GAAG;EACnG,SAAS,KACR,aACC,QAAQ,YACR,QACA,QAAQ,cACR,iFAAiF,QAAQ,WAAW,wCACrG,CACD;EACA,OAAO,UAAU,IAAI;CACtB;CAKA,IAAI;CAKJ,IAAI;CACJ,IAAI,sBAAsB;CAC1B,IAAI,QAAQ,wBAAwB,MAAM;EACzC,oCAAoB,IAAI,IAAoB;EAC5C,sCAAsB,IAAI,IAAoB;EAC9C,MAAM,WAAW,MAAM,WAAW,iBAAA,GAA4C,OAAM,cAAa;GACtF;GACV,GAAI,MAAM,YAAY,QAAQ,YAAY,QAAQ,QAAQ,oBACzD,YAAY,4BAA4B,UAAU,QAAQ,YAAY,CACvE;EACD,EAAE;EAEF,KAAK,MAAM,EAAE,UAAU,OAAO,SAAS,SAAS,oBAAoB,UAAU;GAC7E,MAAM,OAAO,SAAS,YAAY,SAAS,QAAQ,KAAA;GACnD,IAAI,QAAQ,MAAM;IACjB,MAAM,cAAc,qBAAqB,QAAQ,KAAK,SAAS;IAC/D,kBAAkB,IAAI,aAAa,IAAI;IAIvC,IAAI,SAAS,MAAM,QAAQ,QAAQ,OAAO,IACzC,oBAAoB,IAAI,aAAa,QAAQ,EAAE;IAEhD;GACD;GAKA,qBACC,UACA,kBACC,aACC,QAAQ,YACR,QACA,QAAQ,cACR,2CAA2C,cAAc,QAAQ,EAAE,wEACpE,CACF;GACA,sBAAsB;EACvB;CACD;CAEA,MAAM,iCACL,gBAAgB,WAAW,KAAK,mBAAmB,SAAS,IACzD,kBAAkB,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,QAClC,KAAA;CACJ,MAAM,kBAAkB,YAAoD;EAC3E,MAAM,aAAa,qBAAqB,OAAO;EAC/C,IAAI,cAAc,MAAM;GACvB,MAAM,OAAO,mBAAmB,IAAI,UAAU;GAC9C,IAAI,QAAQ,MACX,OAAO;EAET;EAIA,OAAO;CACR;CAIA,MAAM,oBAAoB,YAAoD;EAC7E,MAAM,aAAa,qBAAqB,OAAO;EAC/C,OAAO,cAAc,OAAO,qBAAqB,IAAI,UAAU,IAAI,KAAA;CACpE;CAEA,MAAM,mCAAmB,IAAI,IAAY;CACzC,MAAM,uBAAuB,IAAI,IAAI,sBAAsB;CAC3D,KAAK,MAAM,gBAAgB,oBAC1B,qBAAqB,OAAO,YAAY;CAEzC,MAAM,0BACL,qBAAqB,OAClB,gBAAgB,QAAO,YAAW;EAClC,IAAI,eAAe,OAAO,KAAK,MAAM,OAAO;EAE5C,MAAM,MAAM,WAAW,OAAO;EAC9B,iBAAiB,IAAI,GAAG;EACxB,qBAAqB,OAAO,GAAG;EAC/B,OAAO;CACR,CAAC,IACA;CAIJ,MAAM,qBAAqB,IAAI,IAAI,wBAAwB,KAAI,YAAW,CAAC,WAAW,OAAO,GAAG,OAAO,CAAC,CAAC;CACzG,MAAM,sCAAsB,IAAI,IAAgC;CAChE,MAAM,oBAAoB,YAAsC;EAC/D,MAAM,MAAM,WAAW,OAAO;EAC9B,IAAI,qBAAqB,IAAI,GAAG,GAAG;EAEnC,oBAAoB,IAAI,KAAK,OAAO;CACrC;CACA,KAAK,MAAM,gBAAgB,oBAAoB;EAC9C,MAAM,UAAU,mBAAmB,IAAI,YAAY;EACnD,IAAI,WAAW,MACd,iBAAiB,OAAO;OAClB,IAAI,0BAA0B,2BACpC,iBAAiB,IAAI,YAAY;CAEnC;CACA,MAAM,cAAc,IAAI,IAAY,eAAe;CACnD,IAAI,oBAAoB,MACvB,YAAY,IAAI,gBAAgB;CAEjC,IAAI,CAAC,cAAc,0BAA0B,4BAC5C,YAAY,IAAI,CAAC;CAElB,IAAI,WACH,KAAK,MAAM,cAAc,aAAa;EACrC,MAAM,eAAe,aAAa,KAAK;EACvC,KAAK,MAAM,WAAW,wBAAwB,MAAM,aAAa,cAAc,eAAe,GAC7F,iBAAiB,OAAO;CAE1B;MACM,IAAI,mBAAmB,QAAQ,gBAAgB,SAAS,GAG9D,KAAK,MAAM,WAAW,yBACrB,iBAAiB,OAAO;CAG1B,MAAM,iBAAiB,CAAC,GAAG,oBAAoB,OAAO,CAAC;CACvD,MAAM,qBAAqB,YAAY,OAAO,IAAI,KAAK,IAAI,GAAG,WAAW,IAAI;CAC7E,MAAM,kBACL,sBAAsB,OAAO,qBAAqB,kBAAkB,wBAAwB;CAC7F,MAAM,WACL,aAAa,sBAAsB,QAAQ,wBAAwB,SAAS,kBACzE,qBAAqB,IACrB,KAAA;CACJ,MAAM,yBAAmC;EACxC,IAAI,CAAC,0BAA0B,CAAC,6BAA6B,iBAAiB,SAAS,GACtF,OAAO,CAAC;EAGT,MAAM,QAAQ,IAAI,IAAI,WAAW;EACjC,IAAI,MAAM,SAAS,GAGlB,MAAM,IAAI,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;EAEhD,OAAO,CAAC,GAAG,KAAK;CACjB;CACA,IAAI,eAAe,WAAW,GAI7B,OAAO,UAAU,0BAA0B,qBAAqB,2BAA2B;EAChF;EACV,OAAO,iBAAiB;EACxB,UAAU,CAAC,GAAG,gBAAgB;EAC9B,mBAAmB,CAAC,GAAG,oBAAoB;CAC5C,CAAC;CAmBF,MAAM,gBAAgB,QAAQ,wBAAwB,SAAS,QAAQ,SAAS,UAAU,KAAK;CAC/F,MAAM,WAAW,gBAAgB,MAAM,wBAAwB,SAAS,KAAK,aAAa;CAC1F,IAAI,SAAS,eAAe,MAAM;EACjC,SAAS,KACR,4BACC,QAAQ,YACR,QACA,QAAQ,cACR,SAAS,aACT,wBAAwB,SAAS,IAAI,aAAa,SACnD,CACD;EACA,OAAO,UAAU,IAAI;CACtB;CAEA,MAAM,aAAa,MAAM,WAAW,gBAAA,GAA2C,OAAM,aAAY;EACvF;EACT,GAAI,MAAM,YAAY,QAAQ,YAAY,QAAQ,QAAQ,oBACzD,YAAY,wCACX,SACA;GACC,MAAM,eAAe,OAAO;GAC5B,QAAQ,iBAAiB,OAAO;GAChC,SAAS,QAAQ;GACX;GACN,OAAO,QAAQ;EAChB,GACA,QAAQ,YACT,CACD;CACD,EAAE;CAIF,IAAI,cAAc,0BAA0B;CAC5C,IAAI,qBAA0D,4BAA4B,SAAS,KAAA;CACnG,IAAI;CACJ,KAAK,MAAM,EAAE,SAAS,OAAO,QAAQ,aAAa,YAAY;EAC7D,MAAM,MAAM,WAAW,OAAO;EAC9B,MAAM,eAAe,UAAU;EAC/B,IAAI,WAAW,MAAM;GAGpB,qBAAqB,UAAU,OAAO;GACtC,cAAc;GACd,qBAAqB;EACtB;EAGA,IAAI,UAAU,MACb,cAAc;EAEf,IAAI,UAAU,MAAM;GACnB,MAAM,KAAK,GAAG,OAAO,MAAM;GAC3B,IAAI,OAAO,WAAW;IACrB,MAAM,WAAW,OAAO;IACxB,qBAAqB,sBAAsB,QAAQ,uBAAuB,WAAW,WAAW;GACjG;GACA,IAAI,OAAO,YAAY,MACtB,gBAAgB,wBAAwB,eAAe,OAAO,QAAQ;EAExE;EACA,IAAI,cAAc;GACjB,iBAAiB,IAAI,GAAG;GACxB,qBAAqB,OAAO,GAAG;EAChC,OAIC,qBAAqB,IAAI,GAAG;CAE9B;CAKA,MAAM,eAAe,SAAS,MAAM,KAAK;CAEzC,MAAM,gCAAgC,sBAAsB;CAC5D,MAAM,kBAAkB,yBAAyB,QAAQ,YAAY,QAAQ,QAAQ,cAAc,aAAa;CAChH,KAAK,MAAM,WAAW,gBAAgB,UAAU;EAG/C,IAAI,iCAAiC,CAAC,gBAAgB,eAAe,QAAQ,YAAY,MAAM;EAE/F,qBAAqB,UAAU,OAAO;CACvC;CACA,cAAc,eAAe,gBAAgB;CAC7C,IAAI,gBAAgB,WACnB,qBAAqB;CAGtB,MAAM,mBAAmB,sBAAsB;CAC/C,MAAM,gBAAgB,CAAC,eAAe,uBAAuB;CAC7D,IAAI,iBAAkB,CAAC,eAAe,oBAAoB,CAAC,SAAS,MAAK,YAAW,QAAQ,YAAY,IAAI,GAC3G,qBACC,UACA,sBACC,QAAQ,YACR,QACA,QAAQ,cACR,gBACG,gGACA,yEACH,gBAAgB,oBAAoB,WACrC,CACD;CAGD,8BAA8B,UAAU,WAAW;CAEnD,MAAM,aAAa,iBAAiB;CACpC,MAAM,SAAS,yBAAyB;EACvC,aAAa,OAAO;EACpB,SAAS,CAAC;EACA;EACE;EACZ,eAAe,CAAC,GAAG,gBAAgB;EACnC,oBACE,0BACA,0BACA,6BACA,iBAAiB,OAAO,OACxB,WAAW,SAAS,KAAK,YAAY,QACnC,CAAC,GAAG,oBAAoB,IACxB,CAAC;EACL,OAAO,QAAQ;CAChB,CAAC;CACD,OAAO;EACN,OAAO;EACG;EACV,MAAM;GAAE,aAAa;GAAM,cAAc,aAAa;GAAQ,WAAW,oBAAoB,KAAA;EAAU;EAEvG,SAAS,YAAY,QAAQ,UAAU;EAC/B;EACR,aAAa,eAAe,KAAA;CAC7B;AACD"}
@@ -651,6 +651,10 @@ const filters = wanted.filter(f => capability.includes(f));
651
651
  can't name. Reported as per-criterion flags rather than a list, and **always present** — a provider with no
652
652
  filtered issue search reports empty `relationships` and all-false flags, which is the signal to hide the
653
653
  surface rather than individual chips.
654
+ - `issueStates` — whether the issue-tracker reads take a `state` other than open (`'closed'`, `'all'`). True for
655
+ Jira, Jira Data Center and Linear, which narrow server-side on every page of their drains; false for Trello,
656
+ which can only read open cards and refuses the other two (warning + `fetchFailed`). An omitted `state` reads
657
+ open issues on every tracker.
654
658
 
655
659
  It's a _capability_ table, not a recommendation: passing fewer filters than listed is fine.
656
660
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gitkraken/core-gitlens",
3
3
  "description": "GitLens core — shared Git / AI / GitHub primitives for internal GitKraken consumption",
4
- "version": "0.9.3",
4
+ "version": "0.9.4",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "GitKraken",
@@ -239,7 +239,7 @@
239
239
  }
240
240
  },
241
241
  "dependencies": {
242
- "@gitkraken/provider-apis": "0.60.0",
242
+ "@gitkraken/provider-apis": "0.61.0",
243
243
  "@octokit/graphql": "9.0.4",
244
244
  "@octokit/request": "10.0.14",
245
245
  "@octokit/request-error": "7.1.1",