@smwb/onreel-git-mcp 0.3.0 → 0.4.0

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.
@@ -2,6 +2,10 @@ import {
2
2
  defaultSecretSink,
3
3
  redactSecretText
4
4
  } from "../chunk-L7CPE2SO.js";
5
+ import {
6
+ issueLinkSchema,
7
+ timestampSchema
8
+ } from "../chunk-4ZGRXIVA.js";
5
9
 
6
10
  // src/tracker/tracker.const.ts
7
11
  var TRACKER_TIMEOUT_MS = 15e3;
@@ -211,6 +215,21 @@ var GITHUB_PRIORITY_LABELS = {
211
215
  "priority: blocker": "blocker"
212
216
  };
213
217
  var ALL_STATUSES = ["open", "in_progress", "done", "declined"];
218
+ var GITLAB_MERGE_REQUEST_STATES = {
219
+ opened: "open",
220
+ locked: "open",
221
+ merged: "merged",
222
+ closed: "closed"
223
+ };
224
+ var GITLAB_DRAFT_PREFIX = "Draft: ";
225
+ var GITLAB_DRAFT_PATTERN = /^\s*(?:\[draft\]|\(draft\)|draft:)\s*/i;
226
+ var SOURCECRAFT_PULL_STATUSES = {
227
+ draft: "draft",
228
+ open: "open",
229
+ merging: "open",
230
+ merged: "merged",
231
+ discarded: "closed"
232
+ };
214
233
 
215
234
  // src/tracker/http.lib.ts
216
235
  async function requestJson(url, request) {
@@ -299,12 +318,232 @@ function listTrackers() {
299
318
  return [...definitions.values()];
300
319
  }
301
320
 
302
- // src/tracker/github.lib.ts
321
+ // src/tracker/pull.schema.ts
322
+ import { z } from "zod";
323
+ var pullRequestStatusCategorySchema = z.enum(["draft", "open", "merged", "closed"]);
324
+ var pullRequestFilterSchema = z.enum(["status", "author", "source", "target", "labels", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"]);
325
+ var pullRequestSortFieldSchema = z.enum(["created", "updated", "title"]);
326
+ var mergeMethodSchema = z.enum(["merge", "squash", "rebase"]);
327
+ var pullRequestSchema = z.object({
328
+ /** Номер PR в своём трекере: GitHub number, GitLab iid, SourceCraft slug; для клиента непрозрачная строка. */
329
+ key: z.string().min(1).max(200),
330
+ title: z.string().min(1).max(1024),
331
+ description: z.string().max(1048576).optional(),
332
+ status: z.object({ category: pullRequestStatusCategorySchema, name: z.string().min(1).max(200) }),
333
+ /** Автор — логин или slug трекера. */
334
+ author: z.string().min(1).max(200).optional(),
335
+ /** Ветка-источник и целевая ветка — имя без refs/heads/. */
336
+ source: z.string().min(1).max(500),
337
+ target: z.string().min(1).max(500),
338
+ /** Коммит головы источника. */
339
+ headSha: z.string().regex(/^[0-9a-f]{7,64}$/).optional(),
340
+ labels: z.array(z.string().min(1).max(200)).max(100),
341
+ /** Запрошенные ревьюеры; нет — трекер их в PR не отдаёт. */
342
+ reviewers: z.array(z.string().min(1).max(200)).max(100).optional(),
343
+ links: z.array(issueLinkSchema).max(100),
344
+ createdAt: timestampSchema.optional(),
345
+ updatedAt: timestampSchema.optional(),
346
+ mergedAt: timestampSchema.optional(),
347
+ closedAt: timestampSchema.optional()
348
+ });
349
+
350
+ // src/tracker/github-pull.lib.ts
303
351
  var DEFAULT_API = "https://api.github.com";
304
352
  var TARGET = /^[\w.-]+\/[\w.-]+$/;
305
- var NUMBER = /^[1-9]\d{0,14}$/;
306
- var LIST_SUBJECT = "\u0417\u0430\u0434\u0430\u0447\u0438 GitHub";
307
- var COMMENTS_SUBJECT = "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitHub";
353
+ var NUMBER = /^[1-9]\d{0,19}$/;
354
+ var SHA = /^[0-9a-f]{7,64}$/;
355
+ var LIST_SUBJECT = "PR GitHub";
356
+ var COMMENTS_SUBJECT = "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 PR GitHub";
357
+ var PULL_CAPABILITIES = {
358
+ list: {
359
+ filters: pullRequestFilterSchema.options,
360
+ clientSide: ["status", "author", "labels", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"],
361
+ sorts: ["created", "updated"]
362
+ },
363
+ comments: { filters: COMMENT_FILTERS, clientSide: [], order: ["asc"] },
364
+ create: true,
365
+ createDraft: true,
366
+ update: ["title", "description", "target"],
367
+ statuses: ["open", "closed"],
368
+ comment: true,
369
+ merge: ["merge", "squash", "rebase"],
370
+ deleteSourceOnMerge: false
371
+ };
372
+ function githubPulls() {
373
+ return {
374
+ capabilities: PULL_CAPABILITIES,
375
+ create: (context) => createGithubPullAdapter(context)
376
+ };
377
+ }
378
+ function createGithubPullAdapter({ connection, fetch }) {
379
+ const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
380
+ if (!TARGET.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
381
+ const api = baseUrl(connection.url, DEFAULT_API);
382
+ const root = `${api}/repos/${target}/pulls`;
383
+ const issues = `${api}/repos/${target}/issues`;
384
+ const owner = target.split("/")[0] ?? target;
385
+ const headers = {
386
+ accept: "application/vnd.github+json",
387
+ "x-github-api-version": "2022-11-28",
388
+ ...connection.token === void 0 || connection.token === "" ? {} : { authorization: `Bearer ${connection.token}` }
389
+ };
390
+ const missing = (key) => ({ failure: "not_found", message: `\u0412 GitHub \u043D\u0435\u0442 PR ${key}.` });
391
+ const number = (key) => {
392
+ if (!NUMBER.test(key)) throw new TrackerError("not_found", `\u0412 GitHub \u043D\u0435\u0442 PR ${key}.`);
393
+ return key;
394
+ };
395
+ const getPull = async (key) => pullOf((await requestJson(`${root}/${number(key)}`, { fetch, headers, missing: missing(key) })).body);
396
+ return {
397
+ list: async (query) => {
398
+ assertListQuery(LIST_SUBJECT, PULL_CAPABILITIES.list, query, pullRequestFilterSchema.options);
399
+ const status = usedStatuses(query.status);
400
+ const page = pageOf(query.cursor);
401
+ const params = new URLSearchParams({ state: stateFilter(status), per_page: String(query.limit), page: String(page) });
402
+ if (query.source !== void 0) params.set("head", `${owner}:${query.source}`);
403
+ if (query.target !== void 0) params.set("base", query.target);
404
+ if (query.sort !== void 0) {
405
+ params.set("sort", query.sort.field);
406
+ params.set("direction", query.sort.direction ?? "desc");
407
+ }
408
+ const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
409
+ const items = (Array.isArray(body) ? body : []).map(pullOf).filter((pull) => matchesPage(pull, query, status));
410
+ return { items, ...nextOf(responseHeaders.get("link"), page) };
411
+ },
412
+ get: (key) => getPull(key),
413
+ create: async (input) => pullOf((await requestJson(root, { fetch, headers, method: "POST", body: createBody(input) })).body),
414
+ update: async (key, patch) => {
415
+ if (patch.status === "draft") throw new TrackerError("unsupported", "PR GitHub: \u043F\u0435\u0440\u0435\u0432\u043E\u0434 \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0447\u0435\u0440\u0435\u0437 REST.");
416
+ const current = patch.status === "open" ? await getPull(key) : void 0;
417
+ if (current?.status.category === "draft") throw new TrackerError("unsupported", "PR GitHub: \u0441\u043D\u044F\u0442\u0438\u0435 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u0447\u0435\u0440\u0435\u0437 REST.");
418
+ if (current !== void 0 && current.status.category === "open" && patch.title === void 0 && patch.description === void 0 && patch.target === void 0) return current;
419
+ return pullOf((await requestJson(`${root}/${number(key)}`, { fetch, headers, method: "PATCH", body: patchBody(patch), missing: missing(key) })).body);
420
+ },
421
+ merge: async (key, input) => {
422
+ if (input.deleteSource === true) throw new TrackerError("unsupported", "PR GitHub: \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0432\u0435\u0442\u043A\u0438-\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430 \u043F\u0440\u0438 \u0441\u043B\u0438\u044F\u043D\u0438\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E.");
423
+ if (input.method !== void 0 && !PULL_CAPABILITIES.merge.includes(input.method)) {
424
+ throw new TrackerError("unsupported", `PR GitHub: \u0441\u043B\u0438\u044F\u043D\u0438\u0435 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C ${input.method} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
425
+ }
426
+ await requestJson(`${root}/${number(key)}/merge`, { fetch, headers, method: "PUT", body: mergeBody(input), conflictOn: [405, 409], missing: missing(key) });
427
+ return getPull(key);
428
+ },
429
+ comments: async (key, query) => {
430
+ assertCommentQuery(COMMENTS_SUBJECT, PULL_CAPABILITIES.comments, query);
431
+ const page = pageOf(query.cursor);
432
+ const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
433
+ if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
434
+ const { body, headers: responseHeaders } = await requestJson(`${issues}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missing: missing(key) });
435
+ return { items: (Array.isArray(body) ? body : []).map(commentOf), ...nextOf(responseHeaders.get("link"), page) };
436
+ },
437
+ comment: async (key, body) => commentOf((await requestJson(`${issues}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missing: missing(key) })).body)
438
+ };
439
+ }
440
+ function pageOf(cursor) {
441
+ const page = cursor === void 0 ? 1 : Number(cursor);
442
+ return Number.isInteger(page) && page >= 1 ? page : 1;
443
+ }
444
+ function nextOf(link, page) {
445
+ return link !== null && /rel="next"/.test(link) ? { nextCursor: String(page + 1) } : {};
446
+ }
447
+ function stateFilter(status) {
448
+ if (status === void 0) return "all";
449
+ if (status.every((item) => item === "draft" || item === "open")) return "open";
450
+ if (status.every((item) => item === "merged" || item === "closed")) return "closed";
451
+ return "all";
452
+ }
453
+ function matchesPage(pull, query, status) {
454
+ if (status !== void 0 && !status.includes(pull.status.category)) return false;
455
+ if (query.author !== void 0 && pull.author !== query.author) return false;
456
+ if (query.labels !== void 0 && !query.labels.every((label) => pull.labels.includes(label))) return false;
457
+ if (query.search !== void 0 && !containsText([pull.title, pull.description], query.search)) return false;
458
+ return inTimeRange(pull.createdAt, query.createdAfter, query.createdBefore) && inTimeRange(pull.updatedAt, query.updatedAfter, query.updatedBefore);
459
+ }
460
+ function usedStatuses(status) {
461
+ if (status === void 0 || status.length === 0) return void 0;
462
+ for (const item of status) {
463
+ if (!pullRequestStatusCategorySchema.options.includes(item)) {
464
+ throw new TrackerError("invalid_request", `PR GitHub: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F \u0441\u0442\u0430\u0442\u0443\u0441\u0430 ${String(item)}.`);
465
+ }
466
+ }
467
+ return status;
468
+ }
469
+ function createBody(input) {
470
+ return {
471
+ title: input.title,
472
+ head: input.source,
473
+ base: input.target,
474
+ ...input.description === void 0 ? {} : { body: input.description },
475
+ ...input.draft === true ? { draft: true } : {}
476
+ };
477
+ }
478
+ function patchBody(patch) {
479
+ return {
480
+ ...patch.title === void 0 ? {} : { title: patch.title },
481
+ ...patch.description === void 0 ? {} : { body: patch.description },
482
+ ...patch.target === void 0 ? {} : { base: patch.target },
483
+ ...patch.status === void 0 ? {} : { state: patch.status === "closed" ? "closed" : "open" }
484
+ };
485
+ }
486
+ function mergeBody(input) {
487
+ const lines = input.message?.split("\n") ?? [];
488
+ return {
489
+ ...input.method === void 0 ? {} : { merge_method: input.method },
490
+ ...input.headSha === void 0 ? {} : { sha: input.headSha },
491
+ ...input.message === void 0 ? {} : { commit_title: lines[0] ?? "" },
492
+ ...lines.length > 1 ? { commit_message: lines.slice(1).join("\n") } : {}
493
+ };
494
+ }
495
+ function pullOf(raw) {
496
+ const item = isRecord(raw) ? raw : {};
497
+ const head = isRecord(item.head) ? item.head : {};
498
+ const base = isRecord(item.base) ? item.base : {};
499
+ const key = typeof item.number === "number" ? String(item.number) : "?";
500
+ const description = text(item.body);
501
+ const author = isRecord(item.user) ? text(item.user.login) : void 0;
502
+ const sha = text(head.sha);
503
+ const url = text(item.html_url);
504
+ const createdAt = toTimestamp(item.created_at);
505
+ const updatedAt = toTimestamp(item.updated_at);
506
+ const mergedAt = toTimestamp(item.merged_at);
507
+ const closedAt = toTimestamp(item.closed_at);
508
+ const closed = item.state === "closed";
509
+ const merged = item.merged_at !== void 0 && item.merged_at !== null;
510
+ const category = closed ? merged ? "merged" : "closed" : item.draft === true ? "draft" : "open";
511
+ return {
512
+ key,
513
+ title: text(item.title) ?? key,
514
+ ...description === void 0 ? {} : { description },
515
+ status: { category, name: category },
516
+ ...author === void 0 ? {} : { author },
517
+ source: text(head.ref) ?? "?",
518
+ target: text(base.ref) ?? "?",
519
+ ...sha !== void 0 && SHA.test(sha) ? { headSha: sha } : {},
520
+ labels: Array.isArray(item.labels) ? stringList(item.labels.map((label) => isRecord(label) ? label.name : label)) : [],
521
+ reviewers: logins(item.requested_reviewers),
522
+ links: url === void 0 ? [] : [{ url, title: "GitHub" }],
523
+ ...createdAt === void 0 ? {} : { createdAt },
524
+ ...updatedAt === void 0 ? {} : { updatedAt },
525
+ ...mergedAt === void 0 ? {} : { mergedAt },
526
+ ...closedAt === void 0 ? {} : { closedAt }
527
+ };
528
+ }
529
+ function logins(value) {
530
+ if (!Array.isArray(value)) return [];
531
+ const names = value.map((item) => isRecord(item) ? text(item.login) : void 0);
532
+ return [...new Set(names.filter((name) => name !== void 0))];
533
+ }
534
+ function commentOf(raw) {
535
+ const item = isRecord(raw) ? raw : {};
536
+ const author = isRecord(item.user) ? text(item.user.login) : void 0;
537
+ const createdAt = toTimestamp(item.created_at);
538
+ return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
539
+ }
540
+
541
+ // src/tracker/github.lib.ts
542
+ var DEFAULT_API2 = "https://api.github.com";
543
+ var TARGET2 = /^[\w.-]+\/[\w.-]+$/;
544
+ var NUMBER2 = /^[1-9]\d{0,14}$/;
545
+ var LIST_SUBJECT2 = "\u0417\u0430\u0434\u0430\u0447\u0438 GitHub";
546
+ var COMMENTS_SUBJECT2 = "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitHub";
308
547
  function githubTracker() {
309
548
  const issues = {
310
549
  list: { filters: ISSUE_FILTERS, clientSide: ["status"], sorts: ["created", "updated", "comments"] },
@@ -322,13 +561,14 @@ function githubTracker() {
322
561
  features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: true, statuses: ["open", "in_progress", "done", "declined"] }
323
562
  },
324
563
  create: (context) => createGithubAdapter(context, issues),
325
- issues
564
+ issues,
565
+ pulls: githubPulls()
326
566
  };
327
567
  }
328
568
  function createGithubAdapter({ connection, fetch }, capabilities) {
329
569
  const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
330
- if (!TARGET.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
331
- const api = baseUrl(connection.url, DEFAULT_API);
570
+ if (!TARGET2.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
571
+ const api = baseUrl(connection.url, DEFAULT_API2);
332
572
  const root = `${api}/repos/${target}/issues`;
333
573
  const headers = {
334
574
  accept: "application/vnd.github+json",
@@ -336,7 +576,7 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
336
576
  ...connection.token === void 0 || connection.token === "" ? {} : { authorization: `Bearer ${connection.token}` }
337
577
  };
338
578
  const number = (key) => {
339
- if (!NUMBER.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitHub \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
579
+ if (!NUMBER2.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitHub \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
340
580
  return key;
341
581
  };
342
582
  const rawOf = async (key) => {
@@ -345,9 +585,9 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
345
585
  };
346
586
  return {
347
587
  list: async (query) => {
348
- assertListQuery(LIST_SUBJECT, capabilities.list, query, ISSUE_FILTERS);
588
+ assertListQuery(LIST_SUBJECT2, capabilities.list, query, ISSUE_FILTERS);
349
589
  assertCsvLabels(query.labels);
350
- const page = pageOf(query.cursor);
590
+ const page = pageOf2(query.cursor);
351
591
  const search = needsSearch(query);
352
592
  const params = search ? searchParams(target, query, page) : issuesParams(query, page);
353
593
  const { body, headers: responseHeaders } = await requestJson(`${search ? `${api}/search/issues` : root}?${params.toString()}`, { fetch, headers });
@@ -366,25 +606,25 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
366
606
  },
367
607
  update: async (key, patch) => {
368
608
  const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : issueOf(await rawOf(key));
369
- const response = await requestJson(`${root}/${number(key)}`, { fetch, headers, method: "PATCH", body: patchBody(patch, current), missingIsIssue: true });
609
+ const response = await requestJson(`${root}/${number(key)}`, { fetch, headers, method: "PATCH", body: patchBody2(patch, current), missingIsIssue: true });
370
610
  return issueOf(response.body);
371
611
  },
372
612
  comments: async (key, query) => {
373
- assertCommentQuery(COMMENTS_SUBJECT, capabilities.comments, query);
374
- const page = pageOf(query.cursor);
613
+ assertCommentQuery(COMMENTS_SUBJECT2, capabilities.comments, query);
614
+ const page = pageOf2(query.cursor);
375
615
  const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
376
616
  if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
377
617
  const { body, headers: responseHeaders } = await requestJson(`${root}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missingIsIssue: true });
378
- return { items: (Array.isArray(body) ? body : []).map(commentOf), .../rel="next"/.test(responseHeaders.get("link") ?? "") ? { nextCursor: String(page + 1) } : {} };
618
+ return { items: (Array.isArray(body) ? body : []).map(commentOf2), .../rel="next"/.test(responseHeaders.get("link") ?? "") ? { nextCursor: String(page + 1) } : {} };
379
619
  },
380
- comment: async (key, body) => commentOf((await requestJson(`${root}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
620
+ comment: async (key, body) => commentOf2((await requestJson(`${root}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
381
621
  };
382
622
  }
383
- function pageOf(cursor) {
623
+ function pageOf2(cursor) {
384
624
  const page = cursor === void 0 ? 1 : Number(cursor);
385
625
  return Number.isInteger(page) && page >= 1 ? page : 1;
386
626
  }
387
- function stateFilter(status) {
627
+ function stateFilter2(status) {
388
628
  if (status === void 0) return "all";
389
629
  if (status.every((item) => item === "open" || item === "in_progress")) return "open";
390
630
  if (status.every((item) => item === "done" || item === "declined")) return "closed";
@@ -394,10 +634,10 @@ function needsSearch(query) {
394
634
  return query.search !== void 0 || query.createdAfter !== void 0 || query.createdBefore !== void 0 || query.updatedBefore !== void 0;
395
635
  }
396
636
  function assertCsvLabels(labels) {
397
- if (labels?.some((label) => label.includes(",")) === true) throw new TrackerError("invalid_request", `${LIST_SUBJECT}: \u043C\u0435\u0442\u043A\u0430 \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u0430 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u0437\u0430\u043F\u044F\u0442\u0443\u044E.`);
637
+ if (labels?.some((label) => label.includes(",")) === true) throw new TrackerError("invalid_request", `${LIST_SUBJECT2}: \u043C\u0435\u0442\u043A\u0430 \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u0430 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u0437\u0430\u043F\u044F\u0442\u0443\u044E.`);
398
638
  }
399
639
  function issuesParams(query, page) {
400
- const params = new URLSearchParams({ state: stateFilter(query.status), per_page: String(query.limit), page: String(page) });
640
+ const params = new URLSearchParams({ state: stateFilter2(query.status), per_page: String(query.limit), page: String(page) });
401
641
  if (query.labels !== void 0 && query.labels.length > 0) params.set("labels", query.labels.join(","));
402
642
  if (query.assignee !== void 0) params.set("assignee", query.assignee ?? "none");
403
643
  if (query.author !== void 0) params.set("creator", query.author);
@@ -456,7 +696,7 @@ function composeLabels(plain, priority, status) {
456
696
  const priorityLabel = priority === void 0 ? void 0 : priorityName(GITHUB_PRIORITY_LABELS, priority);
457
697
  return [...plain, ...priorityLabel === void 0 ? [] : [priorityLabel], ...status === "in_progress" ? [GITHUB_IN_PROGRESS_LABEL] : []];
458
698
  }
459
- function patchBody(patch, current) {
699
+ function patchBody2(patch, current) {
460
700
  const status = patch.status ?? current?.status.category;
461
701
  const touchesLabels = patch.labels !== void 0 || patch.priority !== void 0 || patch.status !== void 0;
462
702
  return {
@@ -492,22 +732,216 @@ function issueOf(raw) {
492
732
  ...updatedAt === void 0 ? {} : { updatedAt }
493
733
  };
494
734
  }
495
- function commentOf(raw) {
735
+ function commentOf2(raw) {
496
736
  const item = isRecord(raw) ? raw : {};
497
737
  const author = isRecord(item.user) ? text(item.user.login) : void 0;
498
738
  const createdAt = toTimestamp(item.created_at);
499
739
  return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
500
740
  }
501
741
 
502
- // src/tracker/gitlab.lib.ts
742
+ // src/tracker/gitlab-pull.lib.ts
503
743
  var DEFAULT_URL = "https://gitlab.com";
504
- var TARGET2 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
505
- var IID = /^[1-9]\d{0,14}$/;
744
+ var TARGET3 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
745
+ var IID = /^[1-9]\d{0,19}$/;
746
+ var SHA2 = /^[0-9a-f]{7,64}$/;
747
+ var PULL_CAPABILITIES2 = {
748
+ list: { filters: pullRequestFilterSchema.options, clientSide: ["status"], sorts: ["created", "updated", "title"] },
749
+ comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] },
750
+ create: true,
751
+ createDraft: true,
752
+ update: ["title", "description", "target"],
753
+ statuses: ["draft", "open", "closed"],
754
+ comment: true,
755
+ merge: ["merge", "squash"],
756
+ deleteSourceOnMerge: true
757
+ };
758
+ var ORDER_BY = { created: "created_at", updated: "updated_at", title: "title" };
759
+ function gitlabPulls() {
760
+ return {
761
+ capabilities: PULL_CAPABILITIES2,
762
+ create: (context) => createGitlabPullAdapter(context)
763
+ };
764
+ }
765
+ function createGitlabPullAdapter({ connection, fetch }) {
766
+ const target = requireTarget(connection.target, "\u043F\u0443\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430 GitLab, group/project, \u0438\u043B\u0438 \u0435\u0433\u043E id");
767
+ if (!TARGET3.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
768
+ const root = `${baseUrl(connection.url, DEFAULT_URL)}/api/v4/projects/${encodeURIComponent(target)}/merge_requests`;
769
+ const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
770
+ const iid = (key) => {
771
+ if (!IID.test(key)) throw new TrackerError("not_found", `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.`);
772
+ return key;
773
+ };
774
+ const currentOf = async (key) => pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
775
+ return {
776
+ list: async (query) => {
777
+ assertListQuery("PR GitLab", PULL_CAPABILITIES2.list, query, pullRequestFilterSchema.options);
778
+ const status = usedStatuses2(query.status);
779
+ const page = pageOf3(query.cursor);
780
+ const params = new URLSearchParams({
781
+ state: stateFilter3(status),
782
+ per_page: String(query.limit),
783
+ page: String(page),
784
+ order_by: query.sort === void 0 ? "created_at" : ORDER_BY[query.sort.field] ?? "created_at",
785
+ sort: query.sort?.direction ?? "desc"
786
+ });
787
+ if (query.labels !== void 0 && query.labels.length > 0) {
788
+ if (query.labels.some((label) => label.includes(","))) throw new TrackerError("invalid_request", "PR GitLab: \u043C\u0435\u0442\u043A\u0430 \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u0430 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u0437\u0430\u043F\u044F\u0442\u0443\u044E.");
789
+ params.set("labels", query.labels.join(","));
790
+ }
791
+ if (query.author !== void 0) params.set("author_username", query.author);
792
+ if (query.source !== void 0) params.set("source_branch", query.source);
793
+ if (query.target !== void 0) params.set("target_branch", query.target);
794
+ if (query.search !== void 0) params.set("search", query.search);
795
+ if (query.createdAfter !== void 0) params.set("created_after", isoTime(query.createdAfter));
796
+ if (query.createdBefore !== void 0) params.set("created_before", isoTime(query.createdBefore));
797
+ if (query.updatedAfter !== void 0) params.set("updated_after", isoTime(query.updatedAfter));
798
+ if (query.updatedBefore !== void 0) params.set("updated_before", isoTime(query.updatedBefore));
799
+ const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
800
+ const next = responseHeaders.get("x-next-page");
801
+ const items = (Array.isArray(body) ? body : []).map(pullOf2);
802
+ return {
803
+ items: status === void 0 ? items : items.filter((pr) => status.includes(pr.status.category) === true),
804
+ ...next === null || next === "" ? {} : { nextCursor: next }
805
+ };
806
+ },
807
+ get: currentOf,
808
+ create: async (input) => {
809
+ const body = {
810
+ source_branch: input.source,
811
+ target_branch: input.target,
812
+ title: (input.draft === true ? GITLAB_DRAFT_PREFIX : "") + input.title,
813
+ ...input.description === void 0 ? {} : { description: input.description }
814
+ };
815
+ return pullOf2((await requestJson(root, { fetch, headers, method: "POST", body, conflictOn: [409] })).body);
816
+ },
817
+ update: async (key, patch) => {
818
+ const current = patch.status === void 0 ? void 0 : await currentOf(key);
819
+ if (patch.status === "draft" && current?.status.category === "closed") {
820
+ throw new TrackerError("unsupported", "PR GitLab: \u043F\u0435\u0440\u0435\u0432\u043E\u0434 \u0437\u0430\u043A\u0440\u044B\u0442\u043E\u0433\u043E MR \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D.");
821
+ }
822
+ const body = patchBody3(patch, current);
823
+ if (Object.keys(body).length === 0) return current ?? pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
824
+ return pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, method: "PUT", body, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
825
+ },
826
+ merge: async (key, input) => {
827
+ if (input.method !== void 0 && !PULL_CAPABILITIES2.merge.includes(input.method)) {
828
+ throw new TrackerError("unsupported", `PR GitLab: \u0441\u043B\u0438\u044F\u043D\u0438\u0435 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C ${input.method} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
829
+ }
830
+ const body = {
831
+ // squash: true ставит squash; false GitLab игнорирует — при method 'merge' действует настройка MR/проекта.
832
+ ...input.method === "squash" ? { squash: true } : {},
833
+ ...input.message === void 0 ? {} : input.method === "squash" ? { squash_commit_message: input.message } : { merge_commit_message: input.message },
834
+ ...input.deleteSource === void 0 ? {} : { should_remove_source_branch: input.deleteSource },
835
+ ...input.headSha === void 0 ? {} : { sha: input.headSha }
836
+ };
837
+ return pullOf2((await requestJson(`${root}/${iid(key)}/merge`, { fetch, headers, method: "PUT", body, conflictOn: [405, 409, 422], missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
838
+ },
839
+ comments: async (key, query) => {
840
+ assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", PULL_CAPABILITIES2.comments, query);
841
+ const page = pageOf3(query.cursor);
842
+ const params = new URLSearchParams({ sort: query.order ?? "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
843
+ const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } });
844
+ const next = responseHeaders.get("x-next-page");
845
+ const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true && inTimeRange(toTimestamp(note.updated_at), query.updatedAfter, void 0)).map(commentOf3);
846
+ return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
847
+ },
848
+ comment: async (key, body) => commentOf3((await requestJson(`${root}/${iid(key)}/notes`, { fetch, headers, method: "POST", body: { body }, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body)
849
+ };
850
+ }
851
+ function pageOf3(cursor) {
852
+ const page = cursor === void 0 ? 1 : Number(cursor);
853
+ return Number.isInteger(page) && page >= 1 ? page : 1;
854
+ }
855
+ function stateFilter3(status) {
856
+ if (status === void 0) return "all";
857
+ if (status.every((item) => item === "draft" || item === "open")) return "opened";
858
+ if (status.every((item) => item === "merged")) return "merged";
859
+ if (status.every((item) => item === "closed")) return "closed";
860
+ return "all";
861
+ }
862
+ function patchBody3(patch, current) {
863
+ const body = {};
864
+ if (patch.description !== void 0) body.description = patch.description;
865
+ if (patch.target !== void 0) body.target_branch = patch.target;
866
+ if (patch.status === "closed" && current?.status.category !== "closed") body.state_event = "close";
867
+ if (patch.status === "open" && current?.status.category === "closed") body.state_event = "reopen";
868
+ if (patch.status === "draft" || patch.status === "open") {
869
+ const title = patch.title ?? current?.title ?? "";
870
+ const base = title.replace(GITLAB_DRAFT_PATTERN, "");
871
+ const next = patch.status === "draft" ? GITLAB_DRAFT_PREFIX + base : current?.status.category === "draft" || title !== base ? base : patch.title;
872
+ if (next !== void 0 && next !== current?.title) body.title = next;
873
+ } else if (patch.title !== void 0 && patch.title !== current?.title) {
874
+ body.title = patch.title;
875
+ }
876
+ return body;
877
+ }
878
+ function usedStatuses2(status) {
879
+ if (status === void 0 || status.length === 0) return void 0;
880
+ for (const item of status) {
881
+ if (!pullRequestStatusCategorySchema.options.includes(item)) {
882
+ throw new TrackerError("invalid_request", `PR GitLab: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F \u0441\u0442\u0430\u0442\u0443\u0441\u0430 ${String(item)}.`);
883
+ }
884
+ }
885
+ return status;
886
+ }
887
+ function labelNames(value) {
888
+ if (!Array.isArray(value)) return [];
889
+ const names = value.map((item) => typeof item === "string" ? item : isRecord(item) ? text(item.name) : void 0);
890
+ return [...new Set(names.filter((name) => name !== void 0 && name !== ""))];
891
+ }
892
+ function usernames(value) {
893
+ if (!Array.isArray(value)) return [];
894
+ const names = value.map((item) => isRecord(item) ? text(item.username) : void 0);
895
+ return [...new Set(names.filter((name) => name !== void 0 && name !== ""))];
896
+ }
897
+ function pullOf2(raw) {
898
+ const item = isRecord(raw) ? raw : {};
899
+ const state = text(item.state) ?? "opened";
900
+ const draft = item.draft === true;
901
+ const category = lookup(GITLAB_MERGE_REQUEST_STATES, state, "open");
902
+ const key = typeof item.iid === "number" ? String(item.iid) : "?";
903
+ const description = text(item.description);
904
+ const author = isRecord(item.author) ? text(item.author.username) : void 0;
905
+ const sha = text(item.sha);
906
+ const url = text(item.web_url);
907
+ const createdAt = toTimestamp(item.created_at);
908
+ const updatedAt = toTimestamp(item.updated_at);
909
+ const mergedAt = toTimestamp(item.merged_at);
910
+ const closedAt = toTimestamp(item.closed_at);
911
+ return {
912
+ key,
913
+ title: text(item.title) ?? key,
914
+ ...description === void 0 ? {} : { description },
915
+ status: { category: draft && category === "open" ? "draft" : category, name: draft && category === "open" ? "draft" : state },
916
+ ...author === void 0 ? {} : { author },
917
+ source: text(item.source_branch) ?? "?",
918
+ target: text(item.target_branch) ?? "?",
919
+ ...sha !== void 0 && SHA2.test(sha) ? { headSha: sha } : {},
920
+ labels: labelNames(item.labels),
921
+ reviewers: usernames(item.reviewers),
922
+ links: url === void 0 ? [] : [{ url, title: "GitLab" }],
923
+ ...createdAt === void 0 ? {} : { createdAt },
924
+ ...updatedAt === void 0 ? {} : { updatedAt },
925
+ ...mergedAt === void 0 ? {} : { mergedAt },
926
+ ...closedAt === void 0 ? {} : { closedAt }
927
+ };
928
+ }
929
+ function commentOf3(raw) {
930
+ const item = isRecord(raw) ? raw : {};
931
+ const author = isRecord(item.author) ? text(item.author.username) : void 0;
932
+ const createdAt = toTimestamp(item.created_at);
933
+ return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
934
+ }
935
+
936
+ // src/tracker/gitlab.lib.ts
937
+ var DEFAULT_URL2 = "https://gitlab.com";
938
+ var TARGET4 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
939
+ var IID2 = /^[1-9]\d{0,14}$/;
506
940
  var ISSUE_CAPABILITIES = {
507
941
  list: { filters: ISSUE_FILTERS, clientSide: ["status"], sorts: ["created", "updated", "priority", "title"] },
508
942
  comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] }
509
943
  };
510
- var ORDER_BY = {
944
+ var ORDER_BY2 = {
511
945
  created: "created_at",
512
946
  updated: "updated_at",
513
947
  priority: "priority",
@@ -526,28 +960,29 @@ function gitlabTracker() {
526
960
  features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
527
961
  },
528
962
  create: (context) => createGitlabAdapter(context),
529
- issues: ISSUE_CAPABILITIES
963
+ issues: ISSUE_CAPABILITIES,
964
+ pulls: gitlabPulls()
530
965
  };
531
966
  }
532
967
  function createGitlabAdapter({ connection, fetch }) {
533
968
  const target = requireTarget(connection.target, "\u043F\u0443\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430 GitLab, group/project, \u0438\u043B\u0438 \u0435\u0433\u043E id");
534
- if (!TARGET2.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
535
- const root = `${baseUrl(connection.url, DEFAULT_URL)}/api/v4/projects/${encodeURIComponent(target)}/issues`;
969
+ if (!TARGET4.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
970
+ const root = `${baseUrl(connection.url, DEFAULT_URL2)}/api/v4/projects/${encodeURIComponent(target)}/issues`;
536
971
  const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
537
972
  const iid = (key) => {
538
- if (!IID.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
973
+ if (!IID2.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
539
974
  return key;
540
975
  };
541
976
  const currentOf = async (key) => issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missingIsIssue: true })).body);
542
977
  return {
543
978
  list: async (query) => {
544
979
  assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 GitLab", ISSUE_CAPABILITIES.list, query, ISSUE_FILTERS);
545
- const page = pageOf2(query.cursor);
980
+ const page = pageOf4(query.cursor);
546
981
  const params = new URLSearchParams({
547
- state: stateFilter2(query.status),
982
+ state: stateFilter4(query.status),
548
983
  per_page: String(query.limit),
549
984
  page: String(page),
550
- order_by: query.sort === void 0 ? "created_at" : ORDER_BY[query.sort.field] ?? "created_at",
985
+ order_by: query.sort === void 0 ? "created_at" : ORDER_BY2[query.sort.field] ?? "created_at",
551
986
  sort: query.sort?.direction ?? "desc"
552
987
  });
553
988
  if (query.labels !== void 0 && query.labels.length > 0) {
@@ -577,25 +1012,25 @@ function createGitlabAdapter({ connection, fetch }) {
577
1012
  },
578
1013
  update: async (key, patch) => {
579
1014
  const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : await currentOf(key);
580
- return issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, method: "PUT", body: patchBody2(patch, current), missingIsIssue: true })).body);
1015
+ return issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, method: "PUT", body: patchBody4(patch, current), missingIsIssue: true })).body);
581
1016
  },
582
1017
  comments: async (key, query) => {
583
1018
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", ISSUE_CAPABILITIES.comments, query);
584
- const page = pageOf2(query.cursor);
1019
+ const page = pageOf4(query.cursor);
585
1020
  const params = new URLSearchParams({ sort: query.order ?? "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
586
1021
  const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, headers, missingIsIssue: true });
587
1022
  const next = responseHeaders.get("x-next-page");
588
- const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true && inTimeRange(toTimestamp(note.updated_at), query.updatedAfter, void 0)).map(commentOf2);
1023
+ const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true && inTimeRange(toTimestamp(note.updated_at), query.updatedAfter, void 0)).map(commentOf4);
589
1024
  return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
590
1025
  },
591
- comment: async (key, body) => commentOf2((await requestJson(`${root}/${iid(key)}/notes`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
1026
+ comment: async (key, body) => commentOf4((await requestJson(`${root}/${iid(key)}/notes`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
592
1027
  };
593
1028
  }
594
- function pageOf2(cursor) {
1029
+ function pageOf4(cursor) {
595
1030
  const page = cursor === void 0 ? 1 : Number(cursor);
596
1031
  return Number.isInteger(page) && page >= 1 ? page : 1;
597
1032
  }
598
- function stateFilter2(status) {
1033
+ function stateFilter4(status) {
599
1034
  if (status === void 0) return "all";
600
1035
  if (status.every((item) => item === "open" || item === "in_progress")) return "opened";
601
1036
  if (status.every((item) => item === "done" || item === "declined")) return "closed";
@@ -605,7 +1040,7 @@ function composeLabels2(plain, priority, status) {
605
1040
  const priorityLabel = priority === void 0 ? void 0 : priorityName(GITLAB_PRIORITY_LABELS, priority);
606
1041
  return [...plain, ...priorityLabel === void 0 ? [] : [priorityLabel], ...status === "in_progress" ? [GITLAB_IN_PROGRESS_LABEL] : []];
607
1042
  }
608
- function patchBody2(patch, current) {
1043
+ function patchBody4(patch, current) {
609
1044
  const status = patch.status ?? current?.status.category;
610
1045
  const touchesLabels = patch.labels !== void 0 || patch.priority !== void 0 || patch.status !== void 0;
611
1046
  if (patch.status === "declined") throw new TrackerError("invalid_request", "\u0412 GitLab \u043D\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \xAB\u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0430\xBB.");
@@ -643,7 +1078,7 @@ function issueOf2(raw) {
643
1078
  ...updatedAt === void 0 ? {} : { updatedAt }
644
1079
  };
645
1080
  }
646
- function commentOf2(raw) {
1081
+ function commentOf4(raw) {
647
1082
  const item = isRecord(raw) ? raw : {};
648
1083
  const author = isRecord(item.author) ? text(item.author.username) : void 0;
649
1084
  const createdAt = toTimestamp(item.created_at);
@@ -748,9 +1183,9 @@ function createJiraAdapter({ connection, fetch }) {
748
1183
  const items = raw.filter((item) => inTimeRange(toTimestamp(isRecord(item) ? item.updated : void 0), query.updatedAfter, void 0));
749
1184
  const total = typeof record.total === "number" ? record.total : 0;
750
1185
  const at = typeof record.startAt === "number" ? record.startAt : 0;
751
- return { items: items.map(commentOf3), ...at + raw.length < total && raw.length > 0 ? { nextCursor: String(at + raw.length) } : {} };
1186
+ return { items: items.map(commentOf5), ...at + raw.length < total && raw.length > 0 ? { nextCursor: String(at + raw.length) } : {} };
752
1187
  },
753
- comment: async (value, body) => commentOf3((await requestJson(`${api}/issue/${key(value)}/comment`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
1188
+ comment: async (value, body) => commentOf5((await requestJson(`${api}/issue/${key(value)}/comment`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
754
1189
  };
755
1190
  }
756
1191
  function listJql(project, query) {
@@ -822,7 +1257,7 @@ function issueOf3(raw, site) {
822
1257
  ...updatedAt === void 0 ? {} : { updatedAt }
823
1258
  };
824
1259
  }
825
- function commentOf3(raw) {
1260
+ function commentOf5(raw) {
826
1261
  const item = isRecord(raw) ? raw : {};
827
1262
  const author = isRecord(item.author) ? text(item.author.displayName) : void 0;
828
1263
  const createdAt = toTimestamp(item.created);
@@ -909,11 +1344,212 @@ function toComment(row) {
909
1344
  return { id: String(row.id), ...row.author === void 0 ? {} : { author: row.author }, body: row.body, createdAt: row.createdAt };
910
1345
  }
911
1346
 
1347
+ // src/tracker/sourcecraft-pull.lib.ts
1348
+ var DEFAULT_API3 = "https://api.sourcecraft.tech";
1349
+ var TARGET5 = /^[\w.-]+\/[\w.-]+$/;
1350
+ var SLUG = /^[\w-]{1,100}$/;
1351
+ var SHA3 = /^[0-9a-f]{7,64}$/;
1352
+ var PULL_SORT_FIELDS_UPSTREAM = {
1353
+ created: "created_at",
1354
+ updated: "updated_at",
1355
+ title: "title"
1356
+ };
1357
+ var PULL_CAPABILITIES3 = {
1358
+ list: {
1359
+ filters: pullRequestFilterSchema.options,
1360
+ clientSide: ["labels", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"],
1361
+ sorts: ["created", "updated", "title"]
1362
+ },
1363
+ comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] },
1364
+ create: true,
1365
+ createDraft: true,
1366
+ update: ["title", "description", "target"],
1367
+ statuses: ["draft", "open", "closed"],
1368
+ comment: true,
1369
+ merge: ["merge", "squash", "rebase"],
1370
+ deleteSourceOnMerge: true
1371
+ };
1372
+ function sourcecraftPulls() {
1373
+ return { capabilities: PULL_CAPABILITIES3, create: (context) => createPullAdapter(context) };
1374
+ }
1375
+ function createPullAdapter({ connection, fetch }) {
1376
+ const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
1377
+ if (!TARGET5.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
1378
+ const api = baseUrl(connection.url, DEFAULT_API3);
1379
+ const root = `${api}/repos/${target}/pulls`;
1380
+ const headers = { authorization: `Bearer ${connection.token ?? ""}` };
1381
+ const missing = (key) => ({ failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 PR ${key}.` });
1382
+ const slug = (key) => {
1383
+ if (!SLUG.test(key)) throw new TrackerError("not_found", `\u0412 SourceCraft \u043D\u0435\u0442 PR ${key}.`);
1384
+ return key;
1385
+ };
1386
+ const getPull = async (key) => pullOf3((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missing: missing(key) })).body);
1387
+ return {
1388
+ list: async (query) => {
1389
+ assertListQuery("PR SourceCraft", PULL_CAPABILITIES3.list, query, pullRequestFilterSchema.options);
1390
+ const status = usedStatuses3(query.status);
1391
+ const params = new URLSearchParams({ page_size: String(query.limit) });
1392
+ if (query.cursor !== void 0) params.set("page_token", query.cursor);
1393
+ const filter = pullFilterOf(query, status);
1394
+ if (filter !== void 0) params.set("filter", filter);
1395
+ if (query.sort !== void 0) params.set("sort_by", `${query.sort.direction === "asc" ? "" : "-"}${PULL_SORT_FIELDS_UPSTREAM[query.sort.field]}`);
1396
+ const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
1397
+ const record = isRecord(body) ? body : {};
1398
+ const pulls = (Array.isArray(record.pull_requests) ? record.pull_requests : []).map(pullOf3).filter((pull) => matchesPage2(pull, query));
1399
+ return { items: pulls, ...nextOf2(record.next_page_token) };
1400
+ },
1401
+ get: (key) => getPull(key),
1402
+ create: async (input) => pullOf3((await requestJson(root, { fetch, headers, method: "POST", body: createBody2(input) })).body),
1403
+ update: async (key, patch) => {
1404
+ const id = slug(key);
1405
+ let updated;
1406
+ if (patch.title !== void 0 || patch.description !== void 0 || patch.target !== void 0) {
1407
+ const body = {
1408
+ ...patch.title === void 0 ? {} : { title: patch.title },
1409
+ ...patch.description === void 0 ? {} : { description: patch.description },
1410
+ ...patch.target === void 0 ? {} : { target_branch: patch.target }
1411
+ };
1412
+ updated = (await requestJson(`${root}/${id}`, { fetch, headers, method: "PATCH", body, missing: missing(key) })).body;
1413
+ }
1414
+ if (patch.status !== void 0) {
1415
+ const action = statusActionOf(patch.status, (await getPull(key)).status.category);
1416
+ if (action !== void 0) await requestJson(`${root}/${id}/${action}`, { fetch, headers, method: "POST", missing: missing(key) });
1417
+ return getPull(key);
1418
+ }
1419
+ return pullOf3(updated ?? (await requestJson(`${root}/${id}`, { fetch, headers, missing: missing(key) })).body);
1420
+ },
1421
+ merge: async (key, input) => {
1422
+ const id = slug(key);
1423
+ if (input.message !== void 0) throw new TrackerError("unsupported", "PR SourceCraft: \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u0430 \u0441\u043B\u0438\u044F\u043D\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.");
1424
+ if (input.method !== void 0 && !PULL_CAPABILITIES3.merge.includes(input.method)) {
1425
+ throw new TrackerError("unsupported", `PR SourceCraft: \u0441\u043B\u0438\u044F\u043D\u0438\u0435 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C ${input.method} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
1426
+ }
1427
+ if (input.headSha !== void 0) {
1428
+ const current = await getPull(key);
1429
+ if ((current.headSha ?? "").toLowerCase() !== input.headSha.toLowerCase()) {
1430
+ throw new TrackerError("conflict", `\u0413\u043E\u043B\u043E\u0432\u0430 PR ${key} \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0430\u0441\u044C: \u043E\u0436\u0438\u0434\u0430\u043B\u0441\u044F ${input.headSha}, \u0441\u0435\u0439\u0447\u0430\u0441 ${current.headSha ?? "\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D"}.`);
1431
+ }
1432
+ }
1433
+ const { body } = await requestJson(`${root}/${id}/merge`, {
1434
+ fetch,
1435
+ headers,
1436
+ method: "POST",
1437
+ // squash/rebase по умолчанию наследуются из настройки репозитория — без method не шлём явно false.
1438
+ body: {
1439
+ ...input.method === void 0 ? {} : { squash: input.method === "squash", rebase: input.method === "rebase" },
1440
+ ...input.deleteSource === void 0 ? {} : { delete_branch: input.deleteSource }
1441
+ },
1442
+ missing: missing(key)
1443
+ });
1444
+ const operation = isRecord(body) ? body : {};
1445
+ if (operation.status === "failed" || operation.status === "cancel") {
1446
+ const errorMessage = isRecord(operation.error) ? text(operation.error.message) : void 0;
1447
+ throw new TrackerError("conflict", `\u0421\u043B\u0438\u044F\u043D\u0438\u0435 PR ${key} \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u043E: ${redactSecretText(errorMessage ?? String(operation.status))}.`);
1448
+ }
1449
+ return getPull(key);
1450
+ },
1451
+ comments: async (key, query) => {
1452
+ const id = slug(key);
1453
+ assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 PR SourceCraft", PULL_CAPABILITIES3.comments, query);
1454
+ const params = new URLSearchParams({ page_size: String(query.limit) });
1455
+ if (query.cursor !== void 0) params.set("page_token", query.cursor);
1456
+ if (query.order !== void 0) params.set("sort_by", query.order === "asc" ? "created_at" : "-created_at");
1457
+ const { body } = await requestJson(`${root}/${id}/comments?${params.toString()}`, { fetch, headers, missing: missing(key) });
1458
+ const record = isRecord(body) ? body : {};
1459
+ const rawComments = Array.isArray(record.pull_request_comments) ? record.pull_request_comments : [];
1460
+ const visible = rawComments.filter((raw) => !(isRecord(raw) && raw.is_deleted === true));
1461
+ const filtered = query.updatedAfter === void 0 ? visible : visible.filter((raw) => inTimeRange(toTimestamp(isRecord(raw) ? raw.updated_at : void 0), query.updatedAfter, void 0));
1462
+ return { items: filtered.map(commentOf6), ...nextOf2(record.next_page_token) };
1463
+ },
1464
+ comment: async (key, body) => commentOf6((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missing: missing(key) })).body)
1465
+ };
1466
+ }
1467
+ function createBody2(input) {
1468
+ return {
1469
+ title: input.title,
1470
+ source_branch: input.source,
1471
+ target_branch: input.target,
1472
+ ...input.description === void 0 ? {} : { description: input.description },
1473
+ publish: input.draft !== true
1474
+ };
1475
+ }
1476
+ function statusActionOf(requested, current) {
1477
+ if (current === requested) return void 0;
1478
+ if (requested === "draft") return "draft";
1479
+ if (requested === "open") return current === "draft" ? "publish" : "reopen";
1480
+ return "discard";
1481
+ }
1482
+ function usedStatuses3(status) {
1483
+ if (status === void 0 || status.length === 0) return void 0;
1484
+ for (const item of status) {
1485
+ if (!pullRequestStatusCategorySchema.options.includes(item)) {
1486
+ throw new TrackerError("invalid_request", `PR SourceCraft: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F \u0441\u0442\u0430\u0442\u0443\u0441\u0430 ${String(item)}.`);
1487
+ }
1488
+ }
1489
+ return status;
1490
+ }
1491
+ function matchesPage2(pull, query) {
1492
+ if (query.labels !== void 0 && !query.labels.every((label) => pull.labels.includes(label))) return false;
1493
+ if (query.search !== void 0 && !containsText([pull.title, pull.description], query.search)) return false;
1494
+ return inTimeRange(pull.createdAt, query.createdAfter, query.createdBefore) && inTimeRange(pull.updatedAt, query.updatedAfter, query.updatedBefore);
1495
+ }
1496
+ function pullFilterOf(query, status) {
1497
+ const clauses = [];
1498
+ if (status !== void 0) {
1499
+ const slugs = [...new Set(keysFor(SOURCECRAFT_PULL_STATUSES, status))];
1500
+ clauses.push(`status in (${slugs.join(", ")})`);
1501
+ }
1502
+ if (query.author !== void 0) clauses.push(`author_slug=${quoted(query.author)}`);
1503
+ if (query.source !== void 0) clauses.push(`source_branch=${quoted(query.source)}`);
1504
+ if (query.target !== void 0) clauses.push(`target_branch=${quoted(query.target)}`);
1505
+ return clauses.length === 0 ? void 0 : clauses.join(" and ");
1506
+ }
1507
+ function nextOf2(token) {
1508
+ const value = text(token);
1509
+ return value === void 0 ? {} : { nextCursor: value };
1510
+ }
1511
+ function pullOf3(raw) {
1512
+ const item = isRecord(raw) ? raw : {};
1513
+ const source = isRecord(item.source) ? item.source : {};
1514
+ const target = isRecord(item.target) ? item.target : {};
1515
+ const headSha = text(source.sha);
1516
+ const description = text(item.description);
1517
+ const author = isRecord(item.author) ? text(item.author.slug) : void 0;
1518
+ const createdAt = toTimestamp(item.created_at);
1519
+ const updatedAt = toTimestamp(item.updated_at);
1520
+ const mergedAt = toTimestamp(item.merged_at);
1521
+ const closedAt = toTimestamp(item.closed_at);
1522
+ const key = text(item.slug) ?? "?";
1523
+ const statusName = text(item.status) ?? "open";
1524
+ return {
1525
+ key,
1526
+ title: text(item.title) ?? key,
1527
+ ...description === void 0 ? {} : { description },
1528
+ status: { category: lookup(SOURCECRAFT_PULL_STATUSES, statusName, "open"), name: statusName },
1529
+ ...author === void 0 ? {} : { author },
1530
+ source: text(source.ref) ?? text(item.source_branch) ?? "?",
1531
+ target: text(target.ref) ?? text(item.target_branch) ?? "?",
1532
+ ...headSha !== void 0 && SHA3.test(headSha) ? { headSha } : {},
1533
+ labels: Array.isArray(item.labels) ? stringList(item.labels.map((label) => isRecord(label) ? label.name : void 0)) : [],
1534
+ links: [],
1535
+ ...createdAt === void 0 ? {} : { createdAt },
1536
+ ...updatedAt === void 0 ? {} : { updatedAt },
1537
+ ...mergedAt === void 0 ? {} : { mergedAt },
1538
+ ...closedAt === void 0 ? {} : { closedAt }
1539
+ };
1540
+ }
1541
+ function commentOf6(raw) {
1542
+ const item = isRecord(raw) ? raw : {};
1543
+ const author = isRecord(item.author) ? text(item.author.slug) : void 0;
1544
+ const createdAt = toTimestamp(item.created_at);
1545
+ return { id: text(item.id) ?? "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
1546
+ }
1547
+
912
1548
  // src/tracker/sourcecraft.lib.ts
913
- var DEFAULT_API2 = "https://api.sourcecraft.tech";
1549
+ var DEFAULT_API4 = "https://api.sourcecraft.tech";
914
1550
  var DEFAULT_WEB = "https://sourcecraft.dev";
915
- var TARGET3 = /^[\w.-]+\/[\w.-]+$/;
916
- var SLUG = /^[\w-]{1,100}$/;
1551
+ var TARGET6 = /^[\w.-]+\/[\w.-]+$/;
1552
+ var SLUG2 = /^[\w-]{1,100}$/;
917
1553
  var ISSUE_SORT_FIELDS_UPSTREAM = {
918
1554
  created: "created_at",
919
1555
  updated: "updated_at",
@@ -937,18 +1573,19 @@ function sourcecraftTracker() {
937
1573
  features: { create: true, update: true, comment: true, priority: true, labels: false, assignee: false, statuses: [...ALL_STATUSES] }
938
1574
  },
939
1575
  issues: ISSUE_CAPABILITIES4,
940
- create: (context) => createSourceCraftAdapter(context)
1576
+ create: (context) => createSourceCraftAdapter(context),
1577
+ pulls: sourcecraftPulls()
941
1578
  };
942
1579
  }
943
1580
  function createSourceCraftAdapter({ connection, fetch }) {
944
1581
  const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
945
- if (!TARGET3.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
946
- const api = baseUrl(connection.url, DEFAULT_API2);
1582
+ if (!TARGET6.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
1583
+ const api = baseUrl(connection.url, DEFAULT_API4);
947
1584
  const root = `${api}/repos/${target}/issues`;
948
1585
  const headers = { authorization: `Bearer ${connection.token ?? ""}` };
949
1586
  const web = connection.url === void 0 ? `${DEFAULT_WEB}/${target}/issues` : void 0;
950
1587
  const slug = (key) => {
951
- if (!SLUG.test(key)) throw new TrackerError("unknown_issue", `\u0412 SourceCraft \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
1588
+ if (!SLUG2.test(key)) throw new TrackerError("unknown_issue", `\u0412 SourceCraft \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
952
1589
  return key;
953
1590
  };
954
1591
  const toIssue2 = (raw) => issueOf4(raw, web);
@@ -965,14 +1602,14 @@ function createSourceCraftAdapter({ connection, fetch }) {
965
1602
  let issues = (Array.isArray(record.issues) ? record.issues : []).map(toIssue2);
966
1603
  if (query.status !== void 0) issues = issues.filter((issue) => query.status?.includes(issue.status.category) === true);
967
1604
  if (query.assignee === null) issues = issues.filter((issue) => issue.assignee === void 0);
968
- return { items: issues, ...nextOf(record.next_page_token) };
1605
+ return { items: issues, ...nextOf3(record.next_page_token) };
969
1606
  },
970
1607
  get: async (key) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missingIsIssue: true })).body),
971
1608
  create: async (input) => {
972
1609
  const body = { title: input.title, ...input.description === void 0 ? {} : { description: input.description }, ...input.priority === void 0 ? {} : { priority: input.priority } };
973
1610
  return toIssue2((await requestJson(root, { fetch, headers, method: "POST", body })).body);
974
1611
  },
975
- update: async (key, patch) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, method: "PATCH", body: patchBody3(patch), missingIsIssue: true })).body),
1612
+ update: async (key, patch) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, method: "PATCH", body: patchBody5(patch), missingIsIssue: true })).body),
976
1613
  comments: async (key, query) => {
977
1614
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 SourceCraft", ISSUE_CAPABILITIES4.comments, query);
978
1615
  const params = new URLSearchParams({ page_size: String(query.limit) });
@@ -982,12 +1619,12 @@ function createSourceCraftAdapter({ connection, fetch }) {
982
1619
  const record = isRecord(body) ? body : {};
983
1620
  const rawComments = Array.isArray(record.issue_comments) ? record.issue_comments : [];
984
1621
  const filtered = query.updatedAfter === void 0 ? rawComments : rawComments.filter((raw) => inTimeRange(toTimestamp(isRecord(raw) ? raw.updated_at : void 0), query.updatedAfter, void 0));
985
- return { items: filtered.map(commentOf4), ...nextOf(record.next_page_token) };
1622
+ return { items: filtered.map(commentOf7), ...nextOf3(record.next_page_token) };
986
1623
  },
987
- comment: async (key, body) => commentOf4((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
1624
+ comment: async (key, body) => commentOf7((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
988
1625
  };
989
1626
  }
990
- function patchBody3(patch) {
1627
+ function patchBody5(patch) {
991
1628
  return {
992
1629
  ...patch.title === void 0 ? {} : { title: patch.title },
993
1630
  ...patch.description === void 0 ? {} : { description: patch.description },
@@ -995,7 +1632,7 @@ function patchBody3(patch) {
995
1632
  ...patch.priority === void 0 ? {} : { priority: patch.priority }
996
1633
  };
997
1634
  }
998
- function nextOf(token) {
1635
+ function nextOf3(token) {
999
1636
  const value = text(token);
1000
1637
  return value === void 0 ? {} : { nextCursor: value };
1001
1638
  }
@@ -1044,7 +1681,7 @@ function issueOf4(raw, web) {
1044
1681
  ...updatedAt === void 0 ? {} : { updatedAt }
1045
1682
  };
1046
1683
  }
1047
- function commentOf4(raw) {
1684
+ function commentOf7(raw) {
1048
1685
  const item = isRecord(raw) ? raw : {};
1049
1686
  const author = isRecord(item.author) ? text(item.author.slug) : void 0;
1050
1687
  const createdAt = toTimestamp(item.created_at);
@@ -1107,13 +1744,22 @@ function writeTrackerToken(dataDir, projectId, token, sink = defaultSecretSink)
1107
1744
  function deleteTrackerToken(dataDir, projectId, sink = defaultSecretSink) {
1108
1745
  return writeTrackerToken(dataDir, projectId, "", sink);
1109
1746
  }
1747
+
1748
+ // src/tracker/pull.lib.ts
1749
+ function pullRequestAdapterOf(definition, context) {
1750
+ if (definition.pulls === void 0) throw new TrackerError("unsupported", `\u0422\u0440\u0435\u043A\u0435\u0440 ${definition.descriptor.title} \u043D\u0435 \u0432\u0435\u0434\u0451\u0442 pull request-\u044B.`);
1751
+ return definition.pulls.create(context);
1752
+ }
1110
1753
  export {
1111
1754
  ALL_STATUSES,
1112
1755
  COMMENT_FILTERS,
1113
1756
  GITHUB_CLOSE_REASONS,
1114
1757
  GITHUB_IN_PROGRESS_LABEL,
1115
1758
  GITHUB_PRIORITY_LABELS,
1759
+ GITLAB_DRAFT_PATTERN,
1760
+ GITLAB_DRAFT_PREFIX,
1116
1761
  GITLAB_IN_PROGRESS_LABEL,
1762
+ GITLAB_MERGE_REQUEST_STATES,
1117
1763
  GITLAB_PRIORITY_LABELS,
1118
1764
  ISSUE_FILTERS,
1119
1765
  ISSUE_SORT_FIELDS,
@@ -1123,6 +1769,7 @@ export {
1123
1769
  JIRA_STATUS_CATEGORIES,
1124
1770
  LEGACY_ISSUE_CAPABILITIES,
1125
1771
  SOURCECRAFT_PRIORITIES,
1772
+ SOURCECRAFT_PULL_STATUSES,
1126
1773
  SOURCECRAFT_STATUSES,
1127
1774
  SOURCECRAFT_STATUS_FILTER,
1128
1775
  SOURCECRAFT_STATUS_SLUGS,
@@ -1139,7 +1786,9 @@ export {
1139
1786
  containsText,
1140
1787
  deleteTrackerToken,
1141
1788
  findTracker,
1789
+ githubPulls,
1142
1790
  githubTracker,
1791
+ gitlabPulls,
1143
1792
  gitlabTracker,
1144
1793
  hasTrackerToken,
1145
1794
  inTimeRange,
@@ -1151,14 +1800,21 @@ export {
1151
1800
  listTrackers,
1152
1801
  localTracker,
1153
1802
  lookup,
1803
+ mergeMethodSchema,
1154
1804
  nameOf,
1155
1805
  priorityName,
1806
+ pullRequestAdapterOf,
1807
+ pullRequestFilterSchema,
1808
+ pullRequestSchema,
1809
+ pullRequestSortFieldSchema,
1810
+ pullRequestStatusCategorySchema,
1156
1811
  quoted,
1157
1812
  readTrackerToken,
1158
1813
  registerBuiltinTrackers,
1159
1814
  registerTracker,
1160
1815
  requestJson,
1161
1816
  requireTarget,
1817
+ sourcecraftPulls,
1162
1818
  sourcecraftTracker,
1163
1819
  stringList,
1164
1820
  text,