@gitkraken/core-gitlens 0.5.103 → 0.5.105

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 (99) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/dist/git/models/issue.d.ts +78 -0
  3. package/dist/git/models/issue.d.ts.map +1 -1
  4. package/dist/git/models/issue.js.map +1 -1
  5. package/dist/plus/git-github/api/config.d.ts +12 -0
  6. package/dist/plus/git-github/api/config.d.ts.map +1 -1
  7. package/dist/plus/git-github/api/config.js +12 -1
  8. package/dist/plus/git-github/api/config.js.map +1 -1
  9. package/dist/plus/git-github/api/github.d.ts +77 -7
  10. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  11. package/dist/plus/git-github/api/github.js +241 -120
  12. package/dist/plus/git-github/api/github.js.map +1 -1
  13. package/dist/plus/git-github/api/issueSearchQuery.d.ts +70 -0
  14. package/dist/plus/git-github/api/issueSearchQuery.d.ts.map +1 -0
  15. package/dist/plus/git-github/api/issueSearchQuery.js +143 -0
  16. package/dist/plus/git-github/api/issueSearchQuery.js.map +1 -0
  17. package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts +1 -0
  18. package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts.map +1 -1
  19. package/dist/plus/integrations/authentication/configuredIntegrationService.js +4 -1
  20. package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -1
  21. package/dist/plus/integrations/index.d.ts +2 -0
  22. package/dist/plus/integrations/index.d.ts.map +1 -1
  23. package/dist/plus/integrations/integrationService.d.ts +39 -2
  24. package/dist/plus/integrations/integrationService.d.ts.map +1 -1
  25. package/dist/plus/integrations/integrationService.js +46 -23
  26. package/dist/plus/integrations/integrationService.js.map +1 -1
  27. package/dist/plus/integrations/manager.d.ts +88 -2
  28. package/dist/plus/integrations/manager.d.ts.map +1 -1
  29. package/dist/plus/integrations/models/gitHostIntegration.d.ts +61 -3
  30. package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
  31. package/dist/plus/integrations/models/gitHostIntegration.js +51 -0
  32. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  33. package/dist/plus/integrations/models/integration.d.ts +3 -46
  34. package/dist/plus/integrations/models/integration.d.ts.map +1 -1
  35. package/dist/plus/integrations/models/integration.js.map +1 -1
  36. package/dist/plus/integrations/models/issueReads.d.ts +70 -0
  37. package/dist/plus/integrations/models/issueReads.d.ts.map +1 -0
  38. package/dist/plus/integrations/models/issueReads.js +2 -0
  39. package/dist/plus/integrations/models/issueReads.js.map +1 -0
  40. package/dist/plus/integrations/providerFilters.d.ts +10 -0
  41. package/dist/plus/integrations/providerFilters.d.ts.map +1 -1
  42. package/dist/plus/integrations/providers/github.d.ts +26 -3
  43. package/dist/plus/integrations/providers/github.d.ts.map +1 -1
  44. package/dist/plus/integrations/providers/github.js +34 -0
  45. package/dist/plus/integrations/providers/github.js.map +1 -1
  46. package/dist/plus/integrations/providers/models.d.ts +22 -0
  47. package/dist/plus/integrations/providers/models.d.ts.map +1 -1
  48. package/dist/plus/integrations/providers/models.js +26 -0
  49. package/dist/plus/integrations/providers/models.js.map +1 -1
  50. package/dist/plus/integrations/reads/broaden.d.ts +10 -0
  51. package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
  52. package/dist/plus/integrations/reads/broaden.js.map +1 -1
  53. package/dist/plus/integrations/reads/counts.d.ts +64 -0
  54. package/dist/plus/integrations/reads/counts.d.ts.map +1 -0
  55. package/dist/plus/integrations/reads/counts.js +164 -0
  56. package/dist/plus/integrations/reads/counts.js.map +1 -0
  57. package/dist/plus/integrations/reads/filters.d.ts +63 -1
  58. package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
  59. package/dist/plus/integrations/reads/filters.js +125 -0
  60. package/dist/plus/integrations/reads/filters.js.map +1 -1
  61. package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
  62. package/dist/plus/integrations/reads/issues.js +22 -41
  63. package/dist/plus/integrations/reads/issues.js.map +1 -1
  64. package/dist/plus/integrations/reads/paging.d.ts +59 -0
  65. package/dist/plus/integrations/reads/paging.d.ts.map +1 -1
  66. package/dist/plus/integrations/reads/paging.js +60 -0
  67. package/dist/plus/integrations/reads/paging.js.map +1 -1
  68. package/dist/plus/integrations/reads/searchIssues.d.ts +55 -0
  69. package/dist/plus/integrations/reads/searchIssues.d.ts.map +1 -0
  70. package/dist/plus/integrations/reads/searchIssues.js +144 -0
  71. package/dist/plus/integrations/reads/searchIssues.js.map +1 -0
  72. package/dist/plus/integrations/reads/warnings.d.ts +25 -1
  73. package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
  74. package/dist/plus/integrations/reads/warnings.js +86 -2
  75. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  76. package/docs/integrations.md +110 -2
  77. package/docs/kepler-read-api-parity.md +57 -0
  78. package/package.json +1 -1
  79. package/src/git/models/issue.ts +81 -0
  80. package/src/plus/git-github/api/config.ts +13 -0
  81. package/src/plus/git-github/api/github.ts +332 -148
  82. package/src/plus/git-github/api/issueSearchQuery.ts +165 -0
  83. package/src/plus/integrations/authentication/configuredIntegrationService.ts +6 -1
  84. package/src/plus/integrations/index.ts +5 -0
  85. package/src/plus/integrations/integrationService.ts +130 -70
  86. package/src/plus/integrations/manager.ts +88 -2
  87. package/src/plus/integrations/models/gitHostIntegration.ts +102 -2
  88. package/src/plus/integrations/models/integration.ts +7 -46
  89. package/src/plus/integrations/models/issueReads.ts +73 -0
  90. package/src/plus/integrations/providerFilters.ts +15 -0
  91. package/src/plus/integrations/providers/github.ts +64 -2
  92. package/src/plus/integrations/providers/models.ts +49 -0
  93. package/src/plus/integrations/reads/broaden.ts +10 -0
  94. package/src/plus/integrations/reads/counts.ts +285 -0
  95. package/src/plus/integrations/reads/filters.ts +162 -1
  96. package/src/plus/integrations/reads/issues.ts +22 -45
  97. package/src/plus/integrations/reads/paging.ts +102 -0
  98. package/src/plus/integrations/reads/searchIssues.ts +252 -0
  99. package/src/plus/integrations/reads/warnings.ts +114 -4
@@ -0,0 +1,64 @@
1
+ import type { IssueSearchCriteria } from '../../../git/models/issue.js';
2
+ import type { IntegrationIds } from '../constants.js';
3
+ import type { ProviderReposInput } from '../providers/models.js';
4
+ import type { ProviderResult } from '../results.js';
5
+ import type { ProviderReadContext } from './context.js';
6
+ /**
7
+ * One scope to count.
8
+ *
9
+ * `key` is caller-owned and echoed back verbatim, so a batch result needs no positional matching by the consumer.
10
+ * It never reaches the provider query — the aliases in the upstream document are generated — so it can be any
11
+ * string the caller finds meaningful.
12
+ */
13
+ export interface IssueCountScope {
14
+ key: string;
15
+ /** Repositories to count over. Combines with `org`, exactly as in `searchIssuesPage`. */
16
+ repos?: ProviderReposInput;
17
+ org?: string;
18
+ /** Same criteria model as `searchIssuesPage`, validated against the same capability table. */
19
+ criteria?: IssueSearchCriteria;
20
+ }
21
+ /** The count for one {@link IssueCountScope}, echoed back under the caller's own `key`. */
22
+ export interface IssueCountResult {
23
+ key: string;
24
+ /**
25
+ * Total matches the provider reports. `undefined` when the provider didn't report one for this scope — NEVER
26
+ * zero, which is a real answer. Render the difference: an unreported count means "unknown", and showing it as
27
+ * 0 would tell the user this filter matches nothing.
28
+ */
29
+ count?: number;
30
+ /**
31
+ * True when `count` exceeds the provider's own result ceiling, so a full read CANNOT return everything no
32
+ * matter how it is paged. This is the signal to warn before starting an expensive fetch.
33
+ */
34
+ exceedsProviderLimit: boolean;
35
+ /** The ceiling itself, when the provider declares one. */
36
+ providerLimit?: number;
37
+ }
38
+ /**
39
+ * Counts issues for many scopes without fetching any — the probe behind a "this will fetch ~N issues" preview.
40
+ *
41
+ * A distinct method rather than a `countOnly` flag on `searchIssuesPage`, because transferring ZERO issues is the
42
+ * entire value: a flag would return a paged result whose `items`, `cursor` and `hasMore` are all meaningless, and
43
+ * every consumer would have to know which fields to ignore. A separate method has a return type that only
44
+ * describes counts.
45
+ *
46
+ * Scopes are batched into as few upstream requests as possible ({@link issueCountChunkSize} each). A chunk that
47
+ * fails warns and drops only its own scopes, leaving the successful chunks' counts intact — so a partial answer is
48
+ * still useful — with `fetchFailed` set. A scope refused for its own reasons (unscoped, inexpressible criteria) is
49
+ * likewise isolated: its siblings are still counted.
50
+ *
51
+ * Cheap is not free: each chunk is a network request, so a caller driving this from UI state is expected to
52
+ * debounce and cache.
53
+ */
54
+ export declare function countIssues(ctx: ProviderReadContext, options: {
55
+ providerId: IntegrationIds;
56
+ scopes: readonly IssueCountScope[];
57
+ connectionId?: string;
58
+ /**
59
+ * Explicit self-managed host domain. Used only when the requested connection has no configured domain;
60
+ * it must come from the trusted authentication configuration, not repository or remote data.
61
+ */
62
+ domain?: string;
63
+ }): Promise<ProviderResult<IssueCountResult>>;
64
+ //# sourceMappingURL=counts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counts.d.ts","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/counts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAGxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAqB,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,eAAe,CAAC;AAOrE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAoBxD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,yFAAyF;IACzF,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAC/B;AAED,2FAA2F;AAC3F,MAAM,WAAW,gBAAgB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAChC,GAAG,EAAE,mBAAmB,EACxB,OAAO,EAAE;IACR,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,GACC,OAAO,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,CA4H3C"}
@@ -0,0 +1,164 @@
1
+ import { chunk } from '../../../utils/array.js';
2
+ import { mapBounded } from '../../../utils/promise.js';
3
+ import { providerFanOutConcurrency } from '../constants.js';
4
+ import { providersMetadata } from '../providers/models.js';
5
+ import { appendDedupedWarning } from '../results.js';
6
+ import { isGitHostIntegration, isIssuesHostIntegrationId, warnOnMissingSessionForDomain, } from '../utils/integration.utils.js';
7
+ import { runCaptured } from './drains.js';
8
+ import { resolveIssueSearchCriteria, resolveIssueSearchScope } from './filters.js';
9
+ import { gitHostOnlySurfaceWarning, issuesUnsupportedWarning, otherWarning, unsupportedIssueSearchCriteriaWarning, } from './warnings.js';
10
+ /**
11
+ * How many scopes go into one upstream request.
12
+ *
13
+ * Measured against the live GitHub API, 30 aliased counts cost a single rate-limit point and ~2s, and 60 cost the
14
+ * same point and roughly twice as long — so the constraint isn't rate limit, it's latency and the provider's
15
+ * (uncontracted, so not to be leaned on) query-complexity limits. 25 keeps a chunk comfortably inside both while
16
+ * still collapsing a realistic filter matrix into one or two requests.
17
+ */
18
+ const issueCountChunkSize = 25;
19
+ /**
20
+ * Counts issues for many scopes without fetching any — the probe behind a "this will fetch ~N issues" preview.
21
+ *
22
+ * A distinct method rather than a `countOnly` flag on `searchIssuesPage`, because transferring ZERO issues is the
23
+ * entire value: a flag would return a paged result whose `items`, `cursor` and `hasMore` are all meaningless, and
24
+ * every consumer would have to know which fields to ignore. A separate method has a return type that only
25
+ * describes counts.
26
+ *
27
+ * Scopes are batched into as few upstream requests as possible ({@link issueCountChunkSize} each). A chunk that
28
+ * fails warns and drops only its own scopes, leaving the successful chunks' counts intact — so a partial answer is
29
+ * still useful — with `fetchFailed` set. A scope refused for its own reasons (unscoped, inexpressible criteria) is
30
+ * likewise isolated: its siblings are still counted.
31
+ *
32
+ * Cheap is not free: each chunk is a network request, so a caller driving this from UI state is expected to
33
+ * debounce and cache.
34
+ */
35
+ export async function countIssues(ctx, options) {
36
+ const refused = (warning) => ({
37
+ items: [],
38
+ warnings: [warning],
39
+ fetchFailed: true,
40
+ });
41
+ if (isIssuesHostIntegrationId(options.providerId)) {
42
+ return refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'Issue counts'));
43
+ }
44
+ // Nothing was asked for, so nothing is missing: an empty success, not a refusal.
45
+ if (options.scopes.length === 0)
46
+ return { items: [], warnings: [] };
47
+ const duplicateKey = findDuplicateKey(options.scopes);
48
+ if (duplicateKey != null) {
49
+ // Refuses the whole call rather than deduping: `key` exists so the caller can match results without
50
+ // positional bookkeeping, and two results under one key make that ambiguous for EVERY scope, not just the
51
+ // repeated one.
52
+ return refused(otherWarning(options.providerId, undefined, options.connectionId, `Duplicate issue count scope key '${duplicateKey}'; keys identify results, so each must be unique.`));
53
+ }
54
+ const integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);
55
+ if (integration == null) {
56
+ // A supplied connection or domain that no longer resolves is a broken target, not an empty account.
57
+ const early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);
58
+ return { items: [], warnings: early.warnings, fetchFailed: early.fetchFailed || undefined };
59
+ }
60
+ if (!isGitHostIntegration(integration)) {
61
+ return refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'Issue counts'));
62
+ }
63
+ const domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);
64
+ const warnOnMissingSession = warnOnMissingSessionForDomain(options.providerId, options.domain);
65
+ if (!integration.supportsIssues) {
66
+ return refused(issuesUnsupportedWarning(options.providerId, domain, options.connectionId));
67
+ }
68
+ const providerLimit = providersMetadata[options.providerId]?.issueSearchResultLimit;
69
+ const warnings = [];
70
+ let fetchFailed = false;
71
+ // Validate every scope first, so a refusal costs no request at all and the countable ones are still batched
72
+ // together.
73
+ const countable = [];
74
+ for (const scope of options.scopes) {
75
+ const warning = rejectScope(options.providerId, domain, options.connectionId, scope);
76
+ if (warning != null) {
77
+ // `push`, not `appendDedupedWarning`: every rejection message embeds the scope's own key, and duplicate
78
+ // keys were already refused above, so no two of these can ever collapse — deduping them would only pay
79
+ // the O(n²) key comparison to prove it.
80
+ warnings.push(warning);
81
+ fetchFailed = true;
82
+ continue;
83
+ }
84
+ countable.push(scope);
85
+ }
86
+ // Chunks are independent requests over their own slice of scopes — nothing in one reads what another produced,
87
+ // and `runCaptured` never throws — so they run concurrently, bounded like every other fan-out on the facade.
88
+ // Sequentially they would spend exactly the resource this probe is tuned to conserve: latency, ~2s per chunk.
89
+ // `mapBounded` returns in input order, so `items` and `warnings` stay in scope order.
90
+ const batches = await mapBounded(chunk(countable, issueCountChunkSize), providerFanOutConcurrency, batch => runCaptured(options.providerId, domain, options.connectionId, () => integration.countIssuesResult(batch.map(s => ({
91
+ repos: s.repos,
92
+ org: s.org,
93
+ criteria: s.criteria,
94
+ })), undefined, options.connectionId), { warnOnMissingSession: warnOnMissingSession }).then(result => ({ batch: batch, ...result })));
95
+ const items = [];
96
+ for (const { batch, value, warning } of batches) {
97
+ if (warning != null) {
98
+ appendDedupedWarning(warnings, warning);
99
+ }
100
+ if (value == null) {
101
+ // This batch contributes nothing, but the batches around it still do. Drop only these scopes.
102
+ fetchFailed = true;
103
+ continue;
104
+ }
105
+ for (let i = 0; i < batch.length; i++) {
106
+ const count = value[i];
107
+ items.push({
108
+ key: batch[i].key,
109
+ count: count,
110
+ // Only a reported count can exceed a declared ceiling; unknown-vs-limit is not a comparison.
111
+ exceedsProviderLimit: count != null && providerLimit != null && count > providerLimit,
112
+ providerLimit: providerLimit,
113
+ });
114
+ }
115
+ }
116
+ // A provider with no count support returns `undefined` with no error, which lands as an empty `items` and no
117
+ // warning. Say so explicitly rather than letting it read as "every scope matched nothing".
118
+ if (items.length === 0 && warnings.length === 0) {
119
+ return refused(unsupportedIssueSearchCriteriaWarning(options.providerId, domain, options.connectionId, {
120
+ reason: 'unsupported-search',
121
+ }));
122
+ }
123
+ return { items: items, warnings: warnings, fetchFailed: fetchFailed || undefined };
124
+ }
125
+ /** The first key that appears twice, or `undefined` when every key is unique. */
126
+ function findDuplicateKey(scopes) {
127
+ const seen = new Set();
128
+ for (const scope of scopes) {
129
+ if (seen.has(scope.key))
130
+ return scope.key;
131
+ seen.add(scope.key);
132
+ }
133
+ return undefined;
134
+ }
135
+ /**
136
+ * Why one scope can't be counted, as the warning to report — or `undefined` when it can.
137
+ *
138
+ * The scope and criteria rules come from {@link resolveIssueSearchScope} / {@link resolveIssueSearchCriteria},
139
+ * the same validators `searchIssuesPage` uses, so a count always previews the constraints the read would apply.
140
+ * That is not cosmetic: a count computed under different constraints is a WRONG number rather than a missing
141
+ * one, which is worse. Only the wording is local, because these name the offending scope's key.
142
+ */
143
+ function rejectScope(providerId, domain, connectionId, scope) {
144
+ const scoping = resolveIssueSearchScope(scope.repos, scope.org, scope.criteria);
145
+ switch (scoping.rejection) {
146
+ case 'repo-ids':
147
+ return otherWarning(providerId, domain, connectionId, `Issue count scope '${scope.key}' cannot be scoped by repository id; pass repository descriptors (namespace + name) instead.`);
148
+ case 'unscoped':
149
+ return otherWarning(providerId, domain, connectionId, `Issue count scope '${scope.key}' is unscoped; pass \`repos\`, \`org\`, or a relationship to the current user. \`any-assignee\` and \`unassigned\` are not scopes.`);
150
+ }
151
+ const resolved = resolveIssueSearchCriteria(providerId, scope.criteria);
152
+ if (resolved.rejection != null) {
153
+ return unsupportedIssueSearchCriteriaWarning(providerId, domain, connectionId, resolved.rejection);
154
+ }
155
+ // Count-only, with no counterpart in the read: a relationship set is an OR across several searches, which one
156
+ // count can't express — summing them would double-count anything matching two, and taking the max would
157
+ // under-report. Rather than answer with a wrong number, ask the caller to count each relationship as its own
158
+ // scope, where the keys make the OR explicit.
159
+ if ((scope.criteria?.relationships?.length ?? 0) > 1) {
160
+ return otherWarning(providerId, domain, connectionId, `Issue count scope '${scope.key}' requests several relationships, which a single count can't express (they are OR-ed, so overlapping matches would be double-counted); pass one scope per relationship.`);
161
+ }
162
+ return undefined;
163
+ }
164
+ //# sourceMappingURL=counts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counts.js","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/counts.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { IntegrationIds } from '../constants.js';
2
- import type { IssueFilter, PullRequestFilter } from '../providerFilters.js';
2
+ import type { IssueFilter, IssueSearchCapabilities, IssueSearchCriteria, PullRequestFilter } from '../providerFilters.js';
3
+ import type { ProviderRepoInput, ProviderReposInput } from '../providers/models.js';
3
4
  /**
4
5
  * Validation of a caller-supplied relationship filter set against what a provider can express server-side.
5
6
  *
@@ -43,6 +44,61 @@ export declare function resolveAccountWidePullRequestFilters(id: IntegrationIds,
43
44
  * `[Assignee]` asked for.
44
45
  */
45
46
  export declare function resolveAccountWideIssueFilters(id: IntegrationIds, filters: IssueFilter[] | undefined): ResolvedFilters<IssueFilter>;
47
+ /** Why {@link resolveIssueSearchCriteria} refused a criteria set, so the caller can word the refusal exactly. */
48
+ export type IssueSearchCriteriaRejection =
49
+ /** The provider has no filtered issue search at all. */
50
+ {
51
+ reason: 'unsupported-search';
52
+ }
53
+ /** The provider has one, but can't express these criteria server-side. */
54
+ | {
55
+ reason: 'unsupported-criteria';
56
+ criteria: string[];
57
+ }
58
+ /** `any-assignee` and `unassigned` partition the scope between them; asking for both asks for nothing. */
59
+ | {
60
+ reason: 'contradictory-relationships';
61
+ };
62
+ /**
63
+ * Validates a filtered issue search's criteria against {@link ProviderMetadata.supportedIssueSearch}.
64
+ *
65
+ * All-or-nothing like its three siblings above, and for the same reason: a criterion dropped because the
66
+ * provider can't express it would serve a WIDER result than was asked for, and narrowing the returned page
67
+ * afterward would leave `items` describing a different result set than the `hasMore`/`cursor` the provider
68
+ * produced with it. So an inexpressible set is refused whole and the caller surfaces a warning.
69
+ *
70
+ * Unlike the sibling validators this also rejects a set that is internally contradictory, because this criteria
71
+ * model is the only one with two members that partition the scope between them (`any-assignee` ∪ `unassigned` =
72
+ * everything, ∩ = nothing). Silently keeping one would answer a question the caller didn't ask.
73
+ *
74
+ * `undefined` (or an empty relationship list plus no other criterion) is a valid UNNARROWED search of the given
75
+ * scope, not an error — the caller's `repos`/`org` is what bounds it.
76
+ */
77
+ export declare function resolveIssueSearchCriteria(id: IntegrationIds, criteria: IssueSearchCriteria | undefined): {
78
+ rejection?: IssueSearchCriteriaRejection;
79
+ };
80
+ /** Why a filtered issue search's scope was refused, or `undefined` when it is usable. */
81
+ export type IssueSearchScopeRejection =
82
+ /** No repositories, no org, and no user-relative relationship: a search of the whole host. */
83
+ 'unscoped'
84
+ /** Repositories given as ids. A search names repositories by PATH, so ids can't express a scope. */
85
+ | 'repo-ids';
86
+ /**
87
+ * Validates that a filtered issue search is scoped at all, and narrows `repos` to the descriptor form the
88
+ * provider query needs.
89
+ *
90
+ * Shared by `searchIssuesPage` and `countIssues` because a count computed under different constraints than the
91
+ * read it previews is a WRONG number rather than a missing one. The rejection is returned as a reason code, not a
92
+ * warning: the two callers word it differently (whole-read vs naming the offending scope's key), and wording is
93
+ * the warning layer's business.
94
+ *
95
+ * The two rejections are mutually exclusive — `repo-ids` requires repositories and `unscoped` requires none — so
96
+ * the order they're checked in cannot change the outcome.
97
+ */
98
+ export declare function resolveIssueSearchScope(repos: ProviderReposInput | undefined, org: string | undefined, criteria: IssueSearchCriteria | undefined): {
99
+ rejection?: IssueSearchScopeRejection;
100
+ repos?: ProviderRepoInput[];
101
+ };
46
102
  /**
47
103
  * The filters `listPullRequestsPage`/`listIssuesPage` (and the sweeps) accept for a provider, so a caller can
48
104
  * narrow to what the provider can express BEFORE issuing the read.
@@ -67,6 +123,11 @@ export declare function resolveAccountWideIssueFilters(id: IntegrationIds, filte
67
123
  * {@link IntegrationService.listIssueTrackerIssuesPage} validates against, and leaves `issuesAccountWide`
68
124
  * empty. Reading a tracker's capability off `issuesAccountWide` therefore under-reports it.
69
125
  *
126
+ * `issueSearch` is a third, wider surface: the FILTERED issue search (`searchIssuesPage`, and the `countIssues`
127
+ * probe over the same criteria), which is not bound to the user at all. It is a record of per-criterion flags
128
+ * rather than a list, because its criteria aren't a single kind. An empty `relationships` means the provider has
129
+ * no filtered issue search — hide the surface, not just its chips.
130
+ *
70
131
  * Note this is a CAPABILITY table — "what the provider can express" — not a recommendation. A consumer
71
132
  * matching another tool's behavior may deliberately pass fewer filters than are listed here (or none, where an
72
133
  * already-scoped read would only be narrowed by them). Intersecting against this table is what keeps a
@@ -77,5 +138,6 @@ export declare function getSupportedFilters(providerId: IntegrationIds): {
77
138
  pullRequestsAccountWide: PullRequestFilter[];
78
139
  issues: IssueFilter[];
79
140
  issuesAccountWide: IssueFilter[];
141
+ issueSearch: IssueSearchCapabilities;
80
142
  };
81
143
  //# sourceMappingURL=filters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/filters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG5E;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACxC,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,iBAAiB,EAAE,GAAG,SAAS,GACtC,eAAe,CAAC,iBAAiB,CAAC,CAOpC;AAED;;;GAGG;AACH,wBAAgB,oCAAoC,CACnD,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,iBAAiB,EAAE,GAAG,SAAS,GACtC,eAAe,CAAC,iBAAiB,CAAC,CAOpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAC7C,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,WAAW,EAAE,GAAG,SAAS,GAChC,eAAe,CAAC,WAAW,CAAC,CAO9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,cAAc,GAAG;IAChE,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,uBAAuB,EAAE,iBAAiB,EAAE,CAAC;IAC7C,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,iBAAiB,EAAE,WAAW,EAAE,CAAC;CACjC,CAQA"}
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/filters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EACX,WAAW,EACX,uBAAuB,EACvB,mBAAmB,EAEnB,iBAAiB,EACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGpF;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACxC,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,iBAAiB,EAAE,GAAG,SAAS,GACtC,eAAe,CAAC,iBAAiB,CAAC,CAOpC;AAED;;;GAGG;AACH,wBAAgB,oCAAoC,CACnD,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,iBAAiB,EAAE,GAAG,SAAS,GACtC,eAAe,CAAC,iBAAiB,CAAC,CAOpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAC7C,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,WAAW,EAAE,GAAG,SAAS,GAChC,eAAe,CAAC,WAAW,CAAC,CAO9B;AAED,iHAAiH;AACjH,MAAM,MAAM,4BAA4B;AACvC,wDAAwD;AACtD;IAAE,MAAM,EAAE,oBAAoB,CAAA;CAAE;AAClC,0EAA0E;GACxE;IAAE,MAAM,EAAE,sBAAsB,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE;AACxD,0GAA0G;GACxG;IAAE,MAAM,EAAE,6BAA6B,CAAA;CAAE,CAAC;AAE7C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CACzC,EAAE,EAAE,cAAc,EAClB,QAAQ,EAAE,mBAAmB,GAAG,SAAS,GACvC;IAAE,SAAS,CAAC,EAAE,4BAA4B,CAAA;CAAE,CA4C9C;AAyBD,yFAAyF;AACzF,MAAM,MAAM,yBAAyB;AACpC,8FAA8F;AAC5F,UAAU;AACZ,oGAAoG;GAClG,UAAU,CAAC;AAEd;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,kBAAkB,GAAG,SAAS,EACrC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,QAAQ,EAAE,mBAAmB,GAAG,SAAS,GACvC;IAAE,SAAS,CAAC,EAAE,yBAAyB,CAAC;IAAC,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAYxE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,cAAc,GAAG;IAChE,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,uBAAuB,EAAE,iBAAiB,EAAE,CAAC;IAC7C,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,WAAW,EAAE,uBAAuB,CAAC;CACrC,CAuBA"}
@@ -46,6 +46,111 @@ export function resolveAccountWideIssueFilters(id, filters) {
46
46
  return { unsupported: true };
47
47
  return { filters: filters, unsupported: false };
48
48
  }
49
+ /**
50
+ * Validates a filtered issue search's criteria against {@link ProviderMetadata.supportedIssueSearch}.
51
+ *
52
+ * All-or-nothing like its three siblings above, and for the same reason: a criterion dropped because the
53
+ * provider can't express it would serve a WIDER result than was asked for, and narrowing the returned page
54
+ * afterward would leave `items` describing a different result set than the `hasMore`/`cursor` the provider
55
+ * produced with it. So an inexpressible set is refused whole and the caller surfaces a warning.
56
+ *
57
+ * Unlike the sibling validators this also rejects a set that is internally contradictory, because this criteria
58
+ * model is the only one with two members that partition the scope between them (`any-assignee` ∪ `unassigned` =
59
+ * everything, ∩ = nothing). Silently keeping one would answer a question the caller didn't ask.
60
+ *
61
+ * `undefined` (or an empty relationship list plus no other criterion) is a valid UNNARROWED search of the given
62
+ * scope, not an error — the caller's `repos`/`org` is what bounds it.
63
+ */
64
+ export function resolveIssueSearchCriteria(id, criteria) {
65
+ const supported = providersMetadata[id]?.supportedIssueSearch;
66
+ if (supported == null)
67
+ return { rejection: { reason: 'unsupported-search' } };
68
+ if (criteria == null)
69
+ return {};
70
+ const relationships = criteria.relationships;
71
+ if (relationships?.includes('any-assignee') && relationships.includes('unassigned')) {
72
+ return { rejection: { reason: 'contradictory-relationships' } };
73
+ }
74
+ const unsupported = [];
75
+ for (const relationship of relationships ?? []) {
76
+ if (!supported.relationships.includes(relationship)) {
77
+ unsupported.push(`relationships:${relationship}`);
78
+ }
79
+ }
80
+ // Only a criterion the caller actually SET can be unsupported. `false`/empty means "don't narrow on this",
81
+ // which every provider can honor by doing nothing, so it must not be validated as a request.
82
+ if (criteria.text != null && criteria.text.trim().length > 0 && !supported.text) {
83
+ unsupported.push('text');
84
+ }
85
+ if (criteria.labels?.length && !supported.labels) {
86
+ unsupported.push('labels');
87
+ }
88
+ if (criteria.milestone != null && !supported.milestone) {
89
+ unsupported.push('milestone');
90
+ }
91
+ if (criteria.updatedAfter != null && !supported.updatedAfter) {
92
+ unsupported.push('updatedAfter');
93
+ }
94
+ if (criteria.createdAfter != null && !supported.createdAfter) {
95
+ unsupported.push('createdAfter');
96
+ }
97
+ if (criteria.withoutLinkedPullRequest === true && !supported.withoutLinkedPullRequest) {
98
+ unsupported.push('withoutLinkedPullRequest');
99
+ }
100
+ // `'open'` is every provider's own default, so asking for it needs no state capability.
101
+ if (criteria.state != null && criteria.state !== 'open' && !supported.states) {
102
+ unsupported.push('state');
103
+ }
104
+ if (unsupported.length > 0)
105
+ return { rejection: { reason: 'unsupported-criteria', criteria: unsupported } };
106
+ return {};
107
+ }
108
+ /**
109
+ * The relationships that bound a filtered issue search to the CURRENT USER, and so can stand in for a
110
+ * repository/org scope.
111
+ *
112
+ * Derived from the union rather than spelled out per call site, because the distinction it encodes is the whole
113
+ * reason the scope rule exists: `any-assignee` and `unassigned` are deliberately absent — they describe the
114
+ * ISSUE, not the caller, so neither reduces the search to anyone's own world (measured: unscoped `no:assignee`
115
+ * matches ~45 million issues on GitHub).
116
+ */
117
+ const userScopingIssueSearchRelationships = ['authored', 'assigned', 'mentioned'];
118
+ /** What a provider with NO filtered issue search reports: nothing expressible, spelled out rather than absent. */
119
+ const unsupportedIssueSearchCapabilities = {
120
+ relationships: [],
121
+ text: false,
122
+ labels: false,
123
+ milestone: false,
124
+ updatedAfter: false,
125
+ createdAfter: false,
126
+ withoutLinkedPullRequest: false,
127
+ states: false,
128
+ };
129
+ /**
130
+ * Validates that a filtered issue search is scoped at all, and narrows `repos` to the descriptor form the
131
+ * provider query needs.
132
+ *
133
+ * Shared by `searchIssuesPage` and `countIssues` because a count computed under different constraints than the
134
+ * read it previews is a WRONG number rather than a missing one. The rejection is returned as a reason code, not a
135
+ * warning: the two callers word it differently (whole-read vs naming the offending scope's key), and wording is
136
+ * the warning layer's business.
137
+ *
138
+ * The two rejections are mutually exclusive — `repo-ids` requires repositories and `unscoped` requires none — so
139
+ * the order they're checked in cannot change the outcome.
140
+ */
141
+ export function resolveIssueSearchScope(repos, org, criteria) {
142
+ if (repos?.length) {
143
+ // `ProviderReposInput` is a union of descriptor and id arrays; only the descriptor form is usable here.
144
+ if (repos.some(r => typeof r === 'string' || typeof r === 'number'))
145
+ return { rejection: 'repo-ids' };
146
+ return { repos: repos };
147
+ }
148
+ if (org != null && org.length > 0)
149
+ return {};
150
+ if (criteria?.relationships?.some(r => userScopingIssueSearchRelationships.includes(r)) === true)
151
+ return {};
152
+ return { rejection: 'unscoped' };
153
+ }
49
154
  /**
50
155
  * The filters `listPullRequestsPage`/`listIssuesPage` (and the sweeps) accept for a provider, so a caller can
51
156
  * narrow to what the provider can express BEFORE issuing the read.
@@ -70,6 +175,11 @@ export function resolveAccountWideIssueFilters(id, filters) {
70
175
  * {@link IntegrationService.listIssueTrackerIssuesPage} validates against, and leaves `issuesAccountWide`
71
176
  * empty. Reading a tracker's capability off `issuesAccountWide` therefore under-reports it.
72
177
  *
178
+ * `issueSearch` is a third, wider surface: the FILTERED issue search (`searchIssuesPage`, and the `countIssues`
179
+ * probe over the same criteria), which is not bound to the user at all. It is a record of per-criterion flags
180
+ * rather than a list, because its criteria aren't a single kind. An empty `relationships` means the provider has
181
+ * no filtered issue search — hide the surface, not just its chips.
182
+ *
73
183
  * Note this is a CAPABILITY table — "what the provider can express" — not a recommendation. A consumer
74
184
  * matching another tool's behavior may deliberately pass fewer filters than are listed here (or none, where an
75
185
  * already-scoped read would only be narrowed by them). Intersecting against this table is what keeps a
@@ -77,11 +187,26 @@ export function resolveAccountWideIssueFilters(id, filters) {
77
187
  */
78
188
  export function getSupportedFilters(providerId) {
79
189
  const metadata = providersMetadata[providerId];
190
+ const issueSearch = metadata?.supportedIssueSearch;
80
191
  return {
81
192
  pullRequests: [...(metadata?.supportedPullRequestFilters ?? [])],
82
193
  pullRequestsAccountWide: [...(metadata?.supportedAccountWidePullRequestFilters ?? [])],
83
194
  issues: [...(metadata?.supportedIssueFilters ?? [])],
84
195
  issuesAccountWide: [...(metadata?.supportedAccountWideIssueFilters ?? [])],
196
+ // Always an object, never `undefined`: a provider WITHOUT a filtered issue search reports one whose
197
+ // `relationships` is empty and whose flags are all false, so a consumer reads capabilities the same way
198
+ // for every provider (and an empty `relationships` is the signal to hide the surface itself).
199
+ //
200
+ // Spread over the all-false baseline rather than defaulting each flag: a criterion added to
201
+ // `IssueSearchCapabilities` then can't be forgotten here and silently reported as `undefined` (which a
202
+ // consumer's `if (caps.x)` would read as unsupported — right answer, wrong reason, and untyped).
203
+ // `supportedIssueSearch` is typed as the complete shape, so no member can arrive as an explicit undefined.
204
+ issueSearch: {
205
+ ...unsupportedIssueSearchCapabilities,
206
+ ...issueSearch,
207
+ // Copied, so mutating the result can't corrupt the metadata table.
208
+ relationships: [...(issueSearch?.relationships ?? [])],
209
+ },
85
210
  };
86
211
  }
87
212
  //# sourceMappingURL=filters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"filters.js","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/filters.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAoB3D;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CACxC,EAAkB,EAClB,OAAwC;IAExC,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAE3E,MAAM,SAAS,GAAG,iBAAiB,CAAC,EAAE,CAAC,EAAE,2BAA2B,CAAC;IACrE,IAAI,SAAS,IAAI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAEjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oCAAoC,CACnD,EAAkB,EAClB,OAAwC;IAExC,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAE3E,MAAM,SAAS,GAAG,iBAAiB,CAAC,EAAE,CAAC,EAAE,sCAAsC,CAAC;IAChF,IAAI,SAAS,IAAI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAEjG,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAC/D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,8BAA8B,CAC7C,EAAkB,EAClB,OAAkC;IAElC,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAE3E,MAAM,SAAS,GAAG,iBAAiB,CAAC,EAAE,CAAC,EAAE,gCAAgC,CAAC;IAC1E,IAAI,SAAS,IAAI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAEjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAA0B;IAM7D,MAAM,QAAQ,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAC/C,OAAO;QACN,YAAY,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,2BAA2B,IAAI,EAAE,CAAC,CAAC;QAChE,uBAAuB,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,sCAAsC,IAAI,EAAE,CAAC,CAAC;QACtF,MAAM,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,qBAAqB,IAAI,EAAE,CAAC,CAAC;QACpD,iBAAiB,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,gCAAgC,IAAI,EAAE,CAAC,CAAC;KAC1E,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"filters.js","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/filters.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/issues.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAG9E,OAAO,KAAK,EAAE,mBAAmB,EAAmB,MAAM,eAAe,CAAC;AAO1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAqBxD,wBAAsB,cAAc,CACnC,GAAG,EAAE,mBAAmB,EACxB,OAAO,EAAE;IACR,UAAU,EAAE,cAAc,CAAC;IAC3B,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,sGAAsG;IACtG,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,2FAA2F;IAC3F,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,GACC,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAwW1C"}
1
+ {"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/issues.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAG9E,OAAO,KAAK,EAAE,mBAAmB,EAAmB,MAAM,eAAe,CAAC;AAO1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAsBxD,wBAAsB,cAAc,CACnC,GAAG,EAAE,mBAAmB,EACxB,OAAO,EAAE;IACR,UAAU,EAAE,cAAc,CAAC;IAC3B,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,sGAAsG;IACtG,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,2FAA2F;IAC3F,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,GACC,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAgV1C"}
@@ -2,11 +2,11 @@ import { mergeAssessmentInto } from '../collectionMetadata.js';
2
2
  import { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';
3
3
  import { PagingMode, providersMetadata } from '../providers/models.js';
4
4
  import { mergeCollectionMetadata } from '../providers/utils/providerPaging.js';
5
- import { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';
5
+ import { reconcileOmissionsWithFailure } from '../results.js';
6
6
  import { isGitHostIntegration, isIssuesHostIntegrationId, warnOnMissingSessionForDomain, } from '../utils/integration.utils.js';
7
7
  import { runCaptured } from './drains.js';
8
8
  import { resolveAccountWideIssueFilters } from './filters.js';
9
- import { drainToRequestedPage, isPageNumberAdvanceable, pageToCursor, refusedPage, resolveContinuation, resolveCurrentPage, toProviderPageInfo, usableCursor, } from './paging.js';
9
+ import { drainFlatPagesToRequestedPage, drainToRequestedPage, isPageNumberAdvanceable, pageToCursor, refusedPage, resolveContinuation, resolveCurrentPage, toProviderPageInfo, usableCursor, } from './paging.js';
10
10
  import { gitHostOnlySurfaceWarning, issuesUnsupportedWarning, otherWarning, truncationWarning, unsupportedAccountWideIssueFiltersWarning, } from './warnings.js';
11
11
  export async function listIssuesPage(ctx, options) {
12
12
  const page = Math.max(1, options.page ?? 1);
@@ -43,6 +43,11 @@ export async function listIssuesPage(ctx, options) {
43
43
  if (options.filters?.length && options.includeAllAssignees === true) {
44
44
  return refused(otherWarning(options.providerId, domain, options.connectionId, '`filters` and `includeAllAssignees` are contradictory for an account-wide issue read; pass only one.'));
45
45
  }
46
+ // GitHub expresses "any assignee" as `assignee:*`, which is meaningless without a scope: unscoped it
47
+ // matches millions of issues across all of GitHub instead of the user's own world. ANY scope makes it
48
+ // meaningful (one repository, several, or an org) — it is specifically THIS branch, the account-wide read,
49
+ // that has none to offer. Scope the read to repositories (or use `searchIssuesPage`, whose criteria model
50
+ // takes `any-assignee` alongside a `repos`/`org` scope) rather than reading unscoped.
46
51
  if (options.includeAllAssignees === true &&
47
52
  (options.providerId === GitCloudHostIntegrationId.GitHub ||
48
53
  options.providerId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise)) {
@@ -73,46 +78,22 @@ export async function listIssuesPage(ctx, options) {
73
78
  project: options.project,
74
79
  }), { warnOnMissingSession: warnOnMissingSession });
75
80
  const first = await readAccountWidePage(options.cursor);
76
- let value = first.value;
77
81
  const warnings = first.warning != null ? [first.warning] : [];
78
- let allMetadata = value?.metadata;
79
- let pageFetchFailed = first.warning != null && value == null;
80
- let currentPage = value?.page ?? 1;
81
- let currentTruncated = value?.truncated ?? false;
82
- let requestedPageMissing = false;
83
- if (options.cursor == null && page > 1 && value != null) {
84
- // Guard against the empty-cursor sentinel: a provider that claims another page without handing
85
- // back a usable cursor would otherwise be re-read with `'{}'` and answer with page 1 again.
86
- for (let nextCursor = usableCursor(value.cursor); currentPage < page && value.hasMore && nextCursor != null; nextCursor = usableCursor(value.cursor)) {
87
- const next = await readAccountWidePage(nextCursor);
88
- if (next.warning != null) {
89
- appendDedupedWarning(warnings, next.warning);
90
- }
91
- if (next.value == null) {
92
- pageFetchFailed = pageFetchFailed || next.warning != null;
93
- value = undefined;
94
- requestedPageMissing = true;
95
- break;
96
- }
97
- value = next.value;
98
- allMetadata = mergeCollectionMetadata(allMetadata, value.metadata);
99
- currentTruncated = currentTruncated || value.truncated;
100
- currentPage = value.page ?? currentPage + 1;
101
- // A provider that hands back the same cursor isn't advancing; stop rather than loop forever.
102
- if (usableCursor(value.cursor) === nextCursor) {
103
- currentTruncated = true;
104
- break;
105
- }
106
- }
107
- // A numbered page beyond the provider's terminal cursor is genuinely empty. Never return or
108
- // relabel the last available page as the requested one.
109
- if (currentPage < page) {
110
- requestedPageMissing = true;
111
- }
112
- }
113
- else if (options.cursor == null && page > 1) {
114
- requestedPageMissing = true;
115
- }
82
+ // Merged across the walked pages, so a per-scope failure reported on an earlier page isn't lost by
83
+ // paging past it the one thing this read accumulates that the filtered search doesn't.
84
+ let allMetadata = first.value?.metadata;
85
+ const drained = await drainFlatPagesToRequestedPage(first, {
86
+ requestedPage: page,
87
+ suppliedCursor: options.cursor,
88
+ warnings: warnings,
89
+ readPage: readAccountWidePage,
90
+ fold: p => {
91
+ allMetadata = mergeCollectionMetadata(allMetadata, p.metadata);
92
+ },
93
+ });
94
+ const { value, currentPage, requestedPageMissing } = drained;
95
+ const currentTruncated = drained.truncated;
96
+ const pageFetchFailed = drained.fetchFailed;
116
97
  // GitHub, GitLab, and Azure implement an account-wide issue search; a provider that doesn't (Bitbucket
117
98
  // exposes no issues at all, and `supportsIssues` already short-circuits it above) returns `undefined`
118
99
  // with no error. Surface that as an explicit unsupported warning + fetchFailed rather than a silent