@mastra/github-signals 0.0.0-agent-learning-fetch-again-20260701152039

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1385 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var promises = require('fs/promises');
5
+ var os = require('os');
6
+ var path = require('path');
7
+ var util = require('util');
8
+ var signals = require('@mastra/core/signals');
9
+ var tools = require('@mastra/core/tools');
10
+ var z = require('zod');
11
+
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ var z__default = /*#__PURE__*/_interopDefault(z);
15
+
16
+ // src/index.ts
17
+ var _execFileAsync;
18
+ async function execFileAsync(file, args, options) {
19
+ if (!_execFileAsync) {
20
+ const cp = await import('child_process');
21
+ _execFileAsync = util.promisify(cp.execFile);
22
+ }
23
+ return _execFileAsync(file, args, options);
24
+ }
25
+ var GITHUB_SUBSCRIBE_PR_TAG = "github-subscribe-pr";
26
+ var GITHUB_UNSUBSCRIBE_PR_TAG = "github-unsubscribe-pr";
27
+ var GITHUB_SYNC_STATUS_TAG = "github-sync-status";
28
+ var GITHUB_SIGNALS_METADATA_KEY = "githubSignals";
29
+ var DEFAULT_AUTHORIZED_PERMISSIONS = ["admin", "maintain", "write"];
30
+ var DEFAULT_AUTHORIZED_BOTS = ["coderabbitai[bot]", "devin-ai-integration[bot]"];
31
+ var PERMISSION_CACHE_TTL_MS = 5 * 60 * 1e3;
32
+ var AUTHOR_GATED_NOTIFICATION_KINDS = /* @__PURE__ */ new Set(["pull-request-activity", "pull-request-review-activity"]);
33
+ var createGithubTool = tools.createTool;
34
+ function isPlainObject(value) {
35
+ return typeof value === "object" && value !== null && !Array.isArray(value);
36
+ }
37
+ function readString(value) {
38
+ return typeof value === "string" && value.length > 0 ? value : void 0;
39
+ }
40
+ function readNumber(value) {
41
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
42
+ if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
43
+ return void 0;
44
+ }
45
+ function stableJson(value) {
46
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
47
+ if (isPlainObject(value)) {
48
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
49
+ }
50
+ return JSON.stringify(value);
51
+ }
52
+ function snapshotHash(value) {
53
+ return crypto.createHash("sha256").update(stableJson(value)).digest("hex");
54
+ }
55
+ function resolveHomePath(path$1) {
56
+ return path$1.startsWith("~/") ? path.join(os.homedir(), path$1.slice(2)) : path$1;
57
+ }
58
+ async function getGitcrawlDbPath() {
59
+ if (process.env.GITCRAWL_DB_PATH) return resolveHomePath(process.env.GITCRAWL_DB_PATH);
60
+ const configPath = process.env.GITCRAWL_CONFIG_PATH ?? path.join(os.homedir(), ".config", "gitcrawl", "config.toml");
61
+ try {
62
+ const config = await promises.readFile(resolveHomePath(configPath), "utf8");
63
+ const match = /^\s*db_path\s*=\s*['\"]([^'\"]+)['\"]/m.exec(config);
64
+ if (match?.[1]) return resolveHomePath(match[1]);
65
+ } catch {
66
+ }
67
+ return path.join(os.homedir(), ".config", "gitcrawl", "gitcrawl.db");
68
+ }
69
+ async function queryGitcrawlDb(sql) {
70
+ const dbPath = await getGitcrawlDbPath();
71
+ const { stdout } = await execFileAsync("sqlite3", ["-json", dbPath, sql], { maxBuffer: 10 * 1024 * 1024 });
72
+ return JSON.parse(stdout || "[]");
73
+ }
74
+ function sqlString(value) {
75
+ return `'${value.replaceAll("'", "''")}'`;
76
+ }
77
+ function getSignalMetadata(message) {
78
+ if (message.role !== "signal") return void 0;
79
+ const signal = message.content.metadata?.signal;
80
+ return isPlainObject(signal) ? signal : void 0;
81
+ }
82
+ function getGithubMetadata(threadMetadata) {
83
+ const mastra = isPlainObject(threadMetadata?.mastra) ? threadMetadata.mastra : {};
84
+ const githubSignals = isPlainObject(mastra[GITHUB_SIGNALS_METADATA_KEY]) ? mastra[GITHUB_SIGNALS_METADATA_KEY] : {};
85
+ const rawSubscriptions = Array.isArray(githubSignals.subscriptions) ? githubSignals.subscriptions : [];
86
+ const subscriptions = [];
87
+ for (const rawSubscription of rawSubscriptions) {
88
+ if (!isPlainObject(rawSubscription)) continue;
89
+ const owner = readString(rawSubscription.owner);
90
+ const repo = readString(rawSubscription.repo);
91
+ const number = readNumber(rawSubscription.number);
92
+ const subscribedAt = readString(rawSubscription.subscribedAt);
93
+ const updatedAt = readString(rawSubscription.updatedAt);
94
+ const lastSubscribeSignalId = readString(rawSubscription.lastSubscribeSignalId);
95
+ if (!owner || !repo || !number || !subscribedAt || !updatedAt || !lastSubscribeSignalId) continue;
96
+ subscriptions.push({
97
+ owner,
98
+ repo,
99
+ number,
100
+ subscribedAt,
101
+ updatedAt,
102
+ lastSubscribeSignalId,
103
+ ...readString(rawSubscription.lastSyncAt) ? { lastSyncAt: readString(rawSubscription.lastSyncAt) } : {},
104
+ ...rawSubscription.lastSyncStatus === "success" || rawSubscription.lastSyncStatus === "error" || rawSubscription.lastSyncStatus === "skipped" ? { lastSyncStatus: rawSubscription.lastSyncStatus } : {},
105
+ ...readString(rawSubscription.lastSyncError) ? { lastSyncError: readString(rawSubscription.lastSyncError) } : {},
106
+ ...readString(rawSubscription.lastObservedGithubUpdatedAt) ? { lastObservedGithubUpdatedAt: readString(rawSubscription.lastObservedGithubUpdatedAt) } : {},
107
+ ...readString(rawSubscription.lastObservedContentHash) ? { lastObservedContentHash: readString(rawSubscription.lastObservedContentHash) } : {},
108
+ ...readString(rawSubscription.lastObservedThreadContentHash) ? { lastObservedThreadContentHash: readString(rawSubscription.lastObservedThreadContentHash) } : {},
109
+ ...readString(rawSubscription.lastObservedHeadSha) ? { lastObservedHeadSha: readString(rawSubscription.lastObservedHeadSha) } : {},
110
+ ...readString(rawSubscription.lastObservedState) ? { lastObservedState: readString(rawSubscription.lastObservedState) } : {},
111
+ ...readString(rawSubscription.lastObservedMergeableState) ? { lastObservedMergeableState: readString(rawSubscription.lastObservedMergeableState) } : {},
112
+ ...readString(rawSubscription.lastObservedCiState) ? { lastObservedCiState: readString(rawSubscription.lastObservedCiState) } : {},
113
+ ...readString(rawSubscription.lastObservedReviewStateHash) ? { lastObservedReviewStateHash: readString(rawSubscription.lastObservedReviewStateHash) } : {},
114
+ ...readString(rawSubscription.lastNotificationAt) ? { lastNotificationAt: readString(rawSubscription.lastNotificationAt) } : {},
115
+ ...readString(rawSubscription.lastNotificationKind) ? { lastNotificationKind: readString(rawSubscription.lastNotificationKind) } : {},
116
+ ...rawSubscription.lastNotificationPriority === "medium" || rawSubscription.lastNotificationPriority === "high" ? { lastNotificationPriority: rawSubscription.lastNotificationPriority } : {},
117
+ ...readString(rawSubscription.lastNotificationSummary) ? { lastNotificationSummary: readString(rawSubscription.lastNotificationSummary) } : {}
118
+ });
119
+ }
120
+ return {
121
+ subscriptions,
122
+ ...githubSignals.subscriptionHintShown === true ? { subscriptionHintShown: true } : {}
123
+ };
124
+ }
125
+ function setGithubMetadata(threadMetadata, githubSignals) {
126
+ const existing = threadMetadata ?? {};
127
+ const mastra = isPlainObject(existing.mastra) ? existing.mastra : {};
128
+ const existingGithubSignals = isPlainObject(mastra[GITHUB_SIGNALS_METADATA_KEY]) ? mastra[GITHUB_SIGNALS_METADATA_KEY] : {};
129
+ return {
130
+ ...existing,
131
+ mastra: {
132
+ ...mastra,
133
+ [GITHUB_SIGNALS_METADATA_KEY]: {
134
+ ...existingGithubSignals,
135
+ ...githubSignals
136
+ }
137
+ }
138
+ };
139
+ }
140
+ function getFailingChecks(snapshot) {
141
+ return (snapshot.checks ?? []).filter((check) => check.conclusion === "failure" || check.conclusion === "timed_out");
142
+ }
143
+ function getPendingChecks(snapshot) {
144
+ return (snapshot.checks ?? []).filter((check) => check.status && check.status !== "completed");
145
+ }
146
+ function getPrLabel(subscription, snapshot) {
147
+ const pr = `${subscription.owner}/${subscription.repo}#${subscription.number}`;
148
+ return snapshot?.title ? `${pr}: ${snapshot.title}` : pr;
149
+ }
150
+ function getMergedNotificationSummary(label) {
151
+ return `${label} was merged. This thread has been automatically unsubscribed from this PR. Resubscribe if you still need updates.`;
152
+ }
153
+ function stripBlocks(text, open, close) {
154
+ const haystack = text.toLowerCase();
155
+ const openLower = open.toLowerCase();
156
+ const closeLower = close.toLowerCase();
157
+ let result = "";
158
+ let cursor = 0;
159
+ for (; ; ) {
160
+ const start = haystack.indexOf(openLower, cursor);
161
+ if (start === -1) {
162
+ result += text.slice(cursor);
163
+ return result;
164
+ }
165
+ result += text.slice(cursor, start);
166
+ const end = haystack.indexOf(closeLower, start + openLower.length);
167
+ if (end === -1) return result;
168
+ cursor = end + closeLower.length;
169
+ }
170
+ }
171
+ var CODE_TOKEN_PREFIX = "\0CODE";
172
+ var CODE_TOKEN_SUFFIX = "\0";
173
+ function preserveMarkdownCode(text) {
174
+ const preserved = [];
175
+ const stash = (match) => {
176
+ const token = `${CODE_TOKEN_PREFIX}${preserved.length}${CODE_TOKEN_SUFFIX}`;
177
+ preserved.push(match);
178
+ return token;
179
+ };
180
+ const protectedText = text.replace(/```[\s\S]*?```/g, stash).replace(/(`{2,})(?!`)[\s\S]*?[^`]\1(?!`)/g, stash).replace(/`[^`\n]*`/g, stash);
181
+ return {
182
+ text: protectedText,
183
+ restore: (sanitized) => sanitized.replace(/\u0000CODE(\d+)\u0000/g, (_, index) => preserved[Number(index)] ?? "")
184
+ };
185
+ }
186
+ function sanitizeCommentText(body) {
187
+ const { text: protectedBody, restore } = preserveMarkdownCode(body);
188
+ let text = stripBlocks(protectedBody, "<!--", "-->");
189
+ text = stripBlocks(text, "<details", "</details>");
190
+ const stripped = text.replace(/<\/?[a-zA-Z][^<>]*>/g, "").replace(/<[!/a-zA-Z][\s\S]*$/g, "").replace(/</g, "").replace(/\n{3,}/g, "\n\n").trim();
191
+ return restore(stripped);
192
+ }
193
+ function sanitizeCommentBody(body) {
194
+ if (body === void 0) return void 0;
195
+ const sanitized = sanitizeCommentText(body);
196
+ return sanitized.length > 0 ? sanitized : void 0;
197
+ }
198
+ function getCommentExcerpt(body) {
199
+ const excerpt = sanitizeCommentText(body).replace(/\s+/g, " ").trim();
200
+ return excerpt.length > 240 ? `${excerpt.slice(0, 237)}...` : excerpt;
201
+ }
202
+ function getCommentNotificationSummary(pr, snapshot) {
203
+ if (!snapshot.latestCommentAuthor || !snapshot.latestCommentBody) return void 0;
204
+ return `${snapshot.latestCommentAuthor} commented on ${pr}: ${getCommentExcerpt(snapshot.latestCommentBody)}`;
205
+ }
206
+ var githubActivityNotificationPriority = {
207
+ high: 0,
208
+ medium: 1
209
+ };
210
+ function getGithubActivityNotificationRank(notification) {
211
+ return notification.kind === "pull-request-activity" ? 0 : 1;
212
+ }
213
+ function compareGithubActivityNotifications(a, b) {
214
+ if (!a && !b) return 0;
215
+ if (!a) return 1;
216
+ if (!b) return -1;
217
+ const priorityComparison = githubActivityNotificationPriority[a.priority] - githubActivityNotificationPriority[b.priority];
218
+ if (priorityComparison !== 0) return priorityComparison;
219
+ return getGithubActivityNotificationRank(a) - getGithubActivityNotificationRank(b);
220
+ }
221
+ function classifyGithubCommentActivityNotification(input) {
222
+ if (isBotOnlyActivity(input.snapshot)) return void 0;
223
+ const pr = `${input.subscription.owner}/${input.subscription.repo}#${input.subscription.number}`;
224
+ const summary = getCommentNotificationSummary(pr, input.snapshot);
225
+ if (!summary) return void 0;
226
+ return { kind: "pull-request-activity", priority: "high", summary };
227
+ }
228
+ function getCheckUpdatedTime(check) {
229
+ const value = check.updatedAt ? Date.parse(check.updatedAt) : Number.NaN;
230
+ return Number.isFinite(value) ? value : 0;
231
+ }
232
+ function getCheckKey(check) {
233
+ return `${check.name || "check"}:${check.detailsUrl || check.workflowName || ""}`;
234
+ }
235
+ function normalizeGithubChecksForSnapshot(input) {
236
+ const latestCheckUpdatedAt = input.checkRows.reduce(
237
+ (latest, check) => Math.max(latest, getCheckUpdatedTime(check)),
238
+ 0
239
+ );
240
+ const rows = [
241
+ ...input.checkRows,
242
+ ...input.workflowRows.filter(
243
+ (workflow) => input.checkRows.length === 0 || getCheckUpdatedTime(workflow) >= latestCheckUpdatedAt
244
+ )
245
+ ];
246
+ const byKey = /* @__PURE__ */ new Map();
247
+ for (const row of rows) {
248
+ const key = getCheckKey(row);
249
+ const existing = byKey.get(key);
250
+ if (!existing) {
251
+ byKey.set(key, row);
252
+ continue;
253
+ }
254
+ const rowTime = getCheckUpdatedTime(row);
255
+ const existingTime = getCheckUpdatedTime(existing);
256
+ if (rowTime > existingTime || rowTime === existingTime && existing.source === "workflow" && row.source === "check") {
257
+ byKey.set(key, row);
258
+ }
259
+ }
260
+ return [...byKey.values()].map(({ source: _source, ...check }) => check).sort((a, b) => `${a.name}:${a.detailsUrl ?? ""}`.localeCompare(`${b.name}:${b.detailsUrl ?? ""}`));
261
+ }
262
+ function isBotOnlyActivity(snapshot) {
263
+ return snapshot.latestCommentIsBot === true && (!snapshot.ciState || snapshot.ciState === "unknown");
264
+ }
265
+ function stringifyEvidence(value) {
266
+ if (typeof value === "string") return value;
267
+ try {
268
+ return JSON.stringify(value);
269
+ } catch {
270
+ return "";
271
+ }
272
+ }
273
+ function detectPrWorkEvidence(input) {
274
+ const evidence = [
275
+ input.text ?? "",
276
+ ...(input.toolCalls ?? []).map((toolCall) => `${toolCall.toolName} ${stringifyEvidence(toolCall.args)}`)
277
+ ].join("\n");
278
+ if (!evidence.trim()) return void 0;
279
+ const url = /github\.com\/([^\s/#]+)\/([^\s/#]+)\/pull\/(\d+)/i.exec(evidence);
280
+ if (url?.[1] && url[2] && url[3]) return { owner: url[1], repo: url[2], number: Number(url[3]) };
281
+ const repoRef = /\b([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)#(\d+)\b/.exec(evidence);
282
+ if (repoRef?.[1] && repoRef[2] && repoRef[3])
283
+ return { owner: repoRef[1], repo: repoRef[2], number: Number(repoRef[3]) };
284
+ const ghCommand = /\bgh\s+(?:pr\s+(?:view|checks|status|comment|diff|checkout)|run\s+(?:rerun|view))\b/i.test(
285
+ evidence
286
+ );
287
+ if (!ghCommand) return void 0;
288
+ const numberMatch = /(?:^|\s)#?(\d{2,})(?:\s|$)/.exec(evidence);
289
+ return numberMatch?.[1] ? { number: Number(numberMatch[1]) } : void 0;
290
+ }
291
+ function classifyGithubActivityNotification(input) {
292
+ const pr = `${input.subscription.owner}/${input.subscription.repo}#${input.subscription.number}`;
293
+ const label = getPrLabel(input.subscription, input.snapshot);
294
+ if (input.snapshot.state && input.subscription.lastObservedState !== input.snapshot.state) {
295
+ if (input.snapshot.state === "merged")
296
+ return {
297
+ kind: "pull-request-merged",
298
+ priority: "high",
299
+ summary: getMergedNotificationSummary(label)
300
+ };
301
+ if (input.snapshot.state === "closed")
302
+ return { kind: "pull-request-closed", priority: "high", summary: `${label} was closed` };
303
+ if (input.subscription.lastObservedState && input.snapshot.state === "open")
304
+ return { kind: "pull-request-reopened", priority: "medium", summary: `${label} was reopened` };
305
+ }
306
+ const failingChecks = getFailingChecks(input.snapshot);
307
+ if (input.snapshot.ciState === "failure" && input.subscription.lastObservedCiState !== "failure") {
308
+ const names = failingChecks.slice(0, 3).map((check) => check.name).join(", ");
309
+ return {
310
+ kind: "pull-request-ci-failure",
311
+ priority: "high",
312
+ summary: `${pr} has failing CI${names ? `: ${names}` : ""}`
313
+ };
314
+ }
315
+ if (input.snapshot.mergeableState === "dirty" && input.subscription.lastObservedMergeableState !== "dirty") {
316
+ return {
317
+ kind: "pull-request-conflict",
318
+ priority: "high",
319
+ summary: `${pr} has merge conflicts${input.snapshot.title ? `: ${input.snapshot.title}` : ""}`
320
+ };
321
+ }
322
+ if (input.snapshot.mergeableState && input.subscription.lastObservedMergeableState === "dirty" && input.snapshot.mergeableState !== "dirty") {
323
+ return {
324
+ kind: "pull-request-conflict-resolved",
325
+ priority: "medium",
326
+ summary: `${pr} merge conflicts were resolved`
327
+ };
328
+ }
329
+ if (input.snapshot.mergeableState === "dirty") return void 0;
330
+ if (input.snapshot.ciState === "success" && input.subscription.lastObservedCiState && input.subscription.lastObservedCiState !== "success") {
331
+ return { kind: "pull-request-ci-recovered", priority: "medium", summary: `${pr} CI recovered` };
332
+ }
333
+ if (input.snapshot.reviewStateHash && input.subscription.lastObservedReviewStateHash && input.snapshot.reviewStateHash !== input.subscription.lastObservedReviewStateHash && (input.snapshot.unresolvedReviewThreads ?? 0) > 0) {
334
+ return {
335
+ kind: "pull-request-review-activity",
336
+ priority: "medium",
337
+ summary: `${pr} has ${input.snapshot.unresolvedReviewThreads} unresolved review thread${input.snapshot.unresolvedReviewThreads === 1 ? "" : "s"}`
338
+ };
339
+ }
340
+ const pendingChecks = getPendingChecks(input.snapshot);
341
+ if (input.snapshot.ciState === "pending" && input.subscription.lastObservedCiState !== "pending" && pendingChecks.length > 0) {
342
+ const names = pendingChecks.slice(0, 3).map((check) => check.name).join(", ");
343
+ return {
344
+ kind: "pull-request-ci-pending",
345
+ priority: "medium",
346
+ summary: `${pr} has CI still running${names ? `: ${names}` : ""}`
347
+ };
348
+ }
349
+ if (input.snapshot.ciState === "pending" && input.subscription.lastObservedCiState === "pending") return void 0;
350
+ if (isBotOnlyActivity(input.snapshot)) return void 0;
351
+ const commentSummary = getCommentNotificationSummary(pr, input.snapshot);
352
+ return {
353
+ kind: "pull-request-activity",
354
+ priority: commentSummary ? "high" : "medium",
355
+ summary: commentSummary ?? `${pr} has new activity${input.snapshot.title ? `: ${input.snapshot.title}` : ""}`
356
+ };
357
+ }
358
+ function classifyGithubBaselineNotification(input) {
359
+ const pr = `${input.subscription.owner}/${input.subscription.repo}#${input.subscription.number}`;
360
+ const failingChecks = getFailingChecks(input.snapshot);
361
+ const reviewCount = input.snapshot.unresolvedReviewThreads ?? 0;
362
+ const high = input.snapshot.ciState === "failure" || input.snapshot.mergeableState === "dirty";
363
+ const details = [
364
+ input.snapshot.state ? `state: ${input.snapshot.state}` : void 0,
365
+ input.snapshot.ciState && input.snapshot.ciState !== "unknown" ? `CI: ${input.snapshot.ciState}` : void 0,
366
+ input.snapshot.mergeableState ? `mergeability: ${input.snapshot.mergeableState}` : void 0,
367
+ reviewCount > 0 ? `${reviewCount} unresolved review thread${reviewCount === 1 ? "" : "s"}` : void 0,
368
+ failingChecks.length > 0 ? `failing: ${failingChecks.slice(0, 3).map((check) => check.name).join(", ")}` : void 0
369
+ ].filter(Boolean);
370
+ return {
371
+ kind: "pull-request-baseline",
372
+ priority: high ? "high" : "medium",
373
+ summary: `${pr} subscribed${input.snapshot.title ? `: ${input.snapshot.title}` : ""}${details.length ? ` (${details.join("; ")})` : ""}`
374
+ };
375
+ }
376
+ function applySnapshotCursor(subscription, snapshot) {
377
+ if (snapshot.githubUpdatedAt) subscription.lastObservedGithubUpdatedAt = snapshot.githubUpdatedAt;
378
+ if (snapshot.contentHash) subscription.lastObservedContentHash = snapshot.contentHash;
379
+ if (snapshot.threadContentHash) subscription.lastObservedThreadContentHash = snapshot.threadContentHash;
380
+ if (snapshot.headSha) subscription.lastObservedHeadSha = snapshot.headSha;
381
+ if (snapshot.state) subscription.lastObservedState = snapshot.state;
382
+ if (snapshot.mergeableState) subscription.lastObservedMergeableState = snapshot.mergeableState;
383
+ if (snapshot.ciState) subscription.lastObservedCiState = snapshot.ciState;
384
+ if (snapshot.reviewStateHash) subscription.lastObservedReviewStateHash = snapshot.reviewStateHash;
385
+ }
386
+ function parseGitHubRemoteUrl(remoteUrl) {
387
+ const trimmed = remoteUrl.trim().replace(/\.git$/, "");
388
+ const httpsMatch = /^https:\/\/github\.com\/([^/]+)\/([^/]+)$/.exec(trimmed);
389
+ if (httpsMatch?.[1] && httpsMatch[2]) return { owner: httpsMatch[1], repo: httpsMatch[2] };
390
+ const sshMatch = /^git@github\.com:([^/]+)\/([^/]+)$/.exec(trimmed);
391
+ if (sshMatch?.[1] && sshMatch[2]) return { owner: sshMatch[1], repo: sshMatch[2] };
392
+ return void 0;
393
+ }
394
+ var GitRemoteRepositoryResolver = class {
395
+ async resolveRepository(input) {
396
+ try {
397
+ const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], {
398
+ cwd: input.cwd,
399
+ signal: input.abortSignal
400
+ });
401
+ return parseGitHubRemoteUrl(stdout);
402
+ } catch {
403
+ return void 0;
404
+ }
405
+ }
406
+ };
407
+ var GitcrawlSyncClient = class {
408
+ #command;
409
+ constructor(options = {}) {
410
+ this.#command = options.command ?? "gitcrawl";
411
+ }
412
+ async syncPullRequest(input) {
413
+ try {
414
+ const args = [
415
+ "sync",
416
+ `${input.owner}/${input.repo}`,
417
+ "--numbers",
418
+ String(input.number),
419
+ ...input.includeComments === false ? [] : ["--include-comments"],
420
+ "--with",
421
+ "pr-details",
422
+ "--json"
423
+ ];
424
+ const { stdout, stderr } = await execFileAsync(this.#command, args, {
425
+ cwd: input.cwd,
426
+ signal: input.abortSignal,
427
+ maxBuffer: 10 * 1024 * 1024
428
+ });
429
+ return { ok: true, stdout, stderr };
430
+ } catch (error) {
431
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
432
+ }
433
+ }
434
+ async getPullRequestSnapshot(input) {
435
+ try {
436
+ const { stdout } = await execFileAsync(
437
+ this.#command,
438
+ ["threads", `${input.owner}/${input.repo}`, "--numbers", String(input.number), "--json"],
439
+ {
440
+ cwd: input.cwd,
441
+ signal: input.abortSignal,
442
+ maxBuffer: 10 * 1024 * 1024
443
+ }
444
+ );
445
+ const parsed = JSON.parse(stdout);
446
+ const thread = parsed.threads?.find((item) => readNumber(item.number) === input.number);
447
+ if (!thread) return void 0;
448
+ const owner = sqlString(input.owner);
449
+ const repo = sqlString(input.repo);
450
+ const number = input.number;
451
+ const [threadDetails] = await queryGitcrawlDb(`select t.state, t.closed_at_gh, t.merged_at_gh
452
+ from threads t
453
+ join repositories r on r.id=t.repo_id
454
+ where r.owner=${owner} and r.name=${repo} and t.number=${number}
455
+ limit 1`);
456
+ const [details] = await queryGitcrawlDb(`select d.head_sha, d.head_ref, d.mergeable_state,
457
+ json_extract(d.raw_json, '$.merged_at') as merged_at
458
+ from pull_request_details d
459
+ join threads t on t.id=d.thread_id
460
+ join repositories r on r.id=t.repo_id
461
+ where r.owner=${owner} and r.name=${repo} and t.number=${number}
462
+ limit 1`);
463
+ const headSha = readString(details?.head_sha);
464
+ const checkRows = await queryGitcrawlDb(`select c.name, c.status, c.conclusion, c.workflow_name, c.details_url,
465
+ coalesce(c.completed_at, c.started_at, c.fetched_at) as updated_at
466
+ from pull_request_checks c
467
+ join threads t on t.id=c.thread_id
468
+ join repositories r on r.id=t.repo_id
469
+ where r.owner=${owner} and r.name=${repo} and t.number=${number}${headSha ? ` and json_extract(c.raw_json, '$.head_sha')=${sqlString(headSha)}` : ""}`);
470
+ const workflowRows = details?.head_sha ? await queryGitcrawlDb(`select workflow_name, status, conclusion, html_url, updated_at_gh
471
+ from github_workflow_runs w
472
+ join repositories r on r.id=w.repo_id
473
+ where r.owner=${owner} and r.name=${repo} and w.head_sha=${sqlString(details.head_sha)}`) : [];
474
+ const [reviewState] = await queryGitcrawlDb(`select count(*) as unresolved_count,
475
+ max(coalesce(first_comment_updated_at, first_comment_created_at, fetched_at)) as latest_review_thread_at
476
+ from pull_request_review_threads rt
477
+ join threads t on t.id=rt.thread_id
478
+ join repositories r on r.id=t.repo_id
479
+ where r.owner=${owner} and r.name=${repo} and t.number=${number} and rt.is_resolved=0`);
480
+ const latestComments = await queryGitcrawlDb(`select c.author_login, c.author_type, c.is_bot, c.body, json_extract(c.raw_json, '$.html_url') as html_url,
481
+ coalesce(c.updated_at_gh, c.created_at_gh) as updated_at
482
+ from comments c
483
+ join threads t on t.id=c.thread_id
484
+ join repositories r on r.id=t.repo_id
485
+ where r.owner=${owner} and r.name=${repo} and t.number=${number}
486
+ order by coalesce(c.updated_at_gh, c.created_at_gh) desc
487
+ limit 20`);
488
+ const latestComment = latestComments[0];
489
+ const checks = normalizeGithubChecksForSnapshot({
490
+ checkRows: checkRows.map((row) => ({
491
+ source: "check",
492
+ name: readString(row.name) ?? "check",
493
+ status: readString(row.status),
494
+ conclusion: readString(row.conclusion),
495
+ workflowName: readString(row.workflow_name),
496
+ detailsUrl: readString(row.details_url),
497
+ updatedAt: readString(row.updated_at)
498
+ })),
499
+ workflowRows: workflowRows.map((row) => ({
500
+ source: "workflow",
501
+ name: readString(row.workflow_name) ?? "workflow",
502
+ status: readString(row.status),
503
+ conclusion: readString(row.conclusion),
504
+ workflowName: readString(row.workflow_name),
505
+ detailsUrl: readString(row.html_url),
506
+ updatedAt: readString(row.updated_at_gh)
507
+ }))
508
+ });
509
+ const ciState = checks.some((check) => check.conclusion === "failure" || check.conclusion === "timed_out") ? "failure" : checks.some((check) => check.status && check.status !== "completed") ? "pending" : checks.length > 0 ? "success" : "unknown";
510
+ const threadContentHash = readString(thread.content_hash);
511
+ const unresolvedReviewThreads = Number(reviewState?.unresolved_count ?? 0);
512
+ const reviewStateHash = snapshotHash({
513
+ unresolvedReviewThreads,
514
+ latestReviewThreadAt: reviewState?.latest_review_thread_at
515
+ });
516
+ const contentHash = snapshotHash({
517
+ threadContentHash,
518
+ state: thread.state,
519
+ headSha: details?.head_sha,
520
+ mergeableState: details?.mergeable_state,
521
+ ciState,
522
+ reviewStateHash,
523
+ checks: checks.map((check) => ({
524
+ name: check.name,
525
+ status: check.status,
526
+ conclusion: check.conclusion,
527
+ detailsUrl: check.detailsUrl,
528
+ updatedAt: check.updatedAt
529
+ }))
530
+ });
531
+ return {
532
+ title: readString(thread.title),
533
+ state: readString(details?.merged_at) || readString(threadDetails?.merged_at_gh) ? "merged" : readString(threadDetails?.state) ?? readString(thread.state),
534
+ htmlUrl: readString(thread.html_url),
535
+ githubUpdatedAt: readString(thread.updated_at_gh),
536
+ closedAt: readString(threadDetails?.closed_at_gh),
537
+ mergedAt: readString(details?.merged_at) ?? readString(threadDetails?.merged_at_gh),
538
+ threadContentHash,
539
+ contentHash,
540
+ headSha: readString(details?.head_sha),
541
+ headRef: readString(details?.head_ref),
542
+ mergeableState: readString(details?.mergeable_state),
543
+ checks,
544
+ ciState,
545
+ unresolvedReviewThreads,
546
+ reviewStateHash,
547
+ latestReviewThreadAt: readString(reviewState?.latest_review_thread_at),
548
+ latestCommentAuthor: readString(latestComment?.author_login),
549
+ latestCommentAuthorType: readString(latestComment?.author_type),
550
+ latestCommentIsBot: latestComment?.is_bot === 1,
551
+ latestCommentBody: sanitizeCommentBody(readString(latestComment?.body)),
552
+ latestCommentUrl: readString(latestComment?.html_url),
553
+ latestCommentUpdatedAt: readString(latestComment?.updated_at),
554
+ latestComments: latestComments.map((comment) => ({
555
+ author: readString(comment.author_login),
556
+ authorType: readString(comment.author_type),
557
+ isBot: comment.is_bot === 1,
558
+ body: sanitizeCommentBody(readString(comment.body)),
559
+ url: readString(comment.html_url),
560
+ updatedAt: readString(comment.updated_at)
561
+ }))
562
+ };
563
+ } catch {
564
+ return void 0;
565
+ }
566
+ }
567
+ };
568
+ var GithubSignals = class extends signals.SignalProvider {
569
+ id = "github-signals";
570
+ name = "GitHub Signals";
571
+ #ghMastra;
572
+ static signals = {
573
+ subscribeToPR(input) {
574
+ const normalized = typeof input === "number" ? { number: input } : input;
575
+ return {
576
+ type: "reactive",
577
+ tagName: GITHUB_SUBSCRIBE_PR_TAG,
578
+ contents: `Subscribe to GitHub PR #${normalized.number}`,
579
+ attributes: {
580
+ ...normalized.owner ? { owner: normalized.owner } : {},
581
+ ...normalized.repo ? { repo: normalized.repo } : {},
582
+ number: normalized.number
583
+ },
584
+ metadata: {
585
+ github: {
586
+ action: "subscribeToPR",
587
+ ...normalized
588
+ }
589
+ }
590
+ };
591
+ },
592
+ unsubscribeFromPR(input) {
593
+ const normalized = typeof input === "number" ? { number: input } : input;
594
+ return {
595
+ type: "reactive",
596
+ tagName: GITHUB_UNSUBSCRIBE_PR_TAG,
597
+ contents: `Unsubscribe from GitHub PR #${normalized.number}`,
598
+ attributes: {
599
+ ...normalized.owner ? { owner: normalized.owner } : {},
600
+ ...normalized.repo ? { repo: normalized.repo } : {},
601
+ number: normalized.number
602
+ },
603
+ metadata: {
604
+ github: {
605
+ action: "unsubscribeFromPR",
606
+ ...normalized
607
+ }
608
+ }
609
+ };
610
+ }
611
+ };
612
+ #options;
613
+ #syncClient;
614
+ #repositoryResolver;
615
+ #polling = /* @__PURE__ */ new Map();
616
+ #permissionCache = /* @__PURE__ */ new Map();
617
+ #agent;
618
+ #agentOptions = {};
619
+ #subscriptionsChangedHandler;
620
+ #pollingChangedHandler;
621
+ constructor(options = {}) {
622
+ super();
623
+ this.#options = options;
624
+ this.#syncClient = options.syncClient ?? new GitcrawlSyncClient({ command: options.gitcrawlCommand });
625
+ this.#repositoryResolver = options.repositoryResolver ?? new GitRemoteRepositoryResolver();
626
+ if (options.getNotificationStreamOptions) {
627
+ this.#agentOptions = { getNotificationStreamOptions: options.getNotificationStreamOptions };
628
+ }
629
+ }
630
+ /**
631
+ * @deprecated Use `Agent({ signals: [githubSignals] })` instead.
632
+ * Kept for backward compatibility.
633
+ */
634
+ addAgent(agent, options = {}) {
635
+ this.#agent = agent;
636
+ this.#agentOptions = options;
637
+ }
638
+ /**
639
+ * Called by the Agent constructor when this provider is passed via `signals: [...]`.
640
+ * Sets the bidirectional link so the provider can send signals back to the agent.
641
+ */
642
+ connect(agent) {
643
+ super.connect(agent);
644
+ this.#agent = agent;
645
+ }
646
+ getInputProcessors() {
647
+ return [this];
648
+ }
649
+ getOutputProcessors() {
650
+ return [this];
651
+ }
652
+ onSubscriptionsChanged(handler) {
653
+ this.#subscriptionsChangedHandler = handler;
654
+ }
655
+ onPollingChanged(handler) {
656
+ this.#pollingChangedHandler = handler;
657
+ }
658
+ __registerMastra(mastra) {
659
+ super.__registerMastra(mastra);
660
+ this.#ghMastra = mastra;
661
+ }
662
+ async syncThreadNow(input) {
663
+ return this.#pollThread(input, { includeComments: true });
664
+ }
665
+ async subscribeThreadToPR(input) {
666
+ const pr = typeof input.pr === "number" ? { number: input.pr } : input.pr;
667
+ return this.#subscribe({
668
+ id: `github-command-subscribe-${crypto.randomUUID()}`,
669
+ ...pr,
670
+ threadId: input.threadId,
671
+ resourceId: input.resourceId
672
+ });
673
+ }
674
+ async unsubscribeThreadFromPR(input) {
675
+ const pr = typeof input.pr === "number" ? { number: input.pr } : input.pr;
676
+ return this.#unsubscribe({
677
+ id: `github-command-unsubscribe-${crypto.randomUUID()}`,
678
+ ...pr,
679
+ threadId: input.threadId,
680
+ resourceId: input.resourceId
681
+ });
682
+ }
683
+ async startPollingForThread(input, options = {}) {
684
+ const subscriptions = await this.#getThreadSubscriptions(input);
685
+ if (subscriptions.length === 0) {
686
+ this.stopPollingForThread(input);
687
+ return false;
688
+ }
689
+ const key = this.#pollingKey(input);
690
+ for (const [pollingKey, state] of this.#polling.entries()) {
691
+ if (pollingKey === key) continue;
692
+ clearInterval(state.timer);
693
+ this.#polling.delete(pollingKey);
694
+ }
695
+ if (this.#polling.has(key)) return true;
696
+ const runPoll = (pollOptions = {}) => {
697
+ void this.#pollThread(input, pollOptions).catch((error) => {
698
+ console.warn("GitHub PR polling failed:", error);
699
+ });
700
+ };
701
+ const timer = setInterval(() => {
702
+ runPoll({ includeComments: true });
703
+ }, this.#options.pollIntervalMs ?? 3e5);
704
+ if (options.pollImmediately) runPoll({ includeComments: true });
705
+ timer.unref?.();
706
+ this.#polling.set(key, { ...input, timer, running: false });
707
+ return true;
708
+ }
709
+ stopPollingForThread(input) {
710
+ const key = this.#pollingKey(input);
711
+ const state = this.#polling.get(key);
712
+ if (!state) return;
713
+ clearInterval(state.timer);
714
+ this.#polling.delete(key);
715
+ }
716
+ isPollingThread(input) {
717
+ return this.#polling.has(this.#pollingKey(input));
718
+ }
719
+ isPollingThreadRunning(input) {
720
+ return this.#polling.get(this.#pollingKey(input))?.running ?? false;
721
+ }
722
+ getPollIntervalMs() {
723
+ return this.#options.pollIntervalMs ?? 3e5;
724
+ }
725
+ stopAllPolling() {
726
+ for (const state of this.#polling.values()) clearInterval(state.timer);
727
+ this.#polling.clear();
728
+ }
729
+ async pollThreadNow(input) {
730
+ return this.#pollThread(input, { includeComments: true });
731
+ }
732
+ async processInputStep(args) {
733
+ const tools = this.#createTools(args);
734
+ if (args.stepNumber !== 0) return { tools };
735
+ const signal = this.#findLatestGithubSignal(args.messages);
736
+ if (!signal) return { tools };
737
+ const threadContext = this.#getThreadContext(args);
738
+ if (signal.tagName === GITHUB_UNSUBSCRIBE_PR_TAG) {
739
+ const result2 = await this.#unsubscribe({ ...signal, ...threadContext, abortSignal: args.abortSignal });
740
+ await this.#sendStatus(args, result2, {
741
+ status: result2.removed ? "unsubscribed" : "not_subscribed",
742
+ action: "unsubscribeFromPR",
743
+ message: result2.removed ? `Unsubscribed from ${result2.owner}/${result2.repo}#${result2.number}.` : `No GitHub subscription found for ${result2.owner}/${result2.repo}#${result2.number}.`
744
+ });
745
+ return { tools };
746
+ }
747
+ const result = await this.#subscribe({ ...signal, ...threadContext, abortSignal: args.abortSignal });
748
+ if (result.alreadyProcessed) return { tools };
749
+ await this.#sendStatus(args, result, {
750
+ status: result.syncResult?.ok === false ? "sync_error" : "subscribed",
751
+ action: "subscribeToPR",
752
+ message: result.syncResult?.ok === false ? `Subscribed to ${result.owner}/${result.repo}#${result.number}, but gitcrawl sync failed: ${result.syncResult.error}` : `Subscribed to ${result.owner}/${result.repo}#${result.number}.`
753
+ });
754
+ return { tools };
755
+ }
756
+ async processOutputStep(args) {
757
+ const evidence = detectPrWorkEvidence({ text: args.text, toolCalls: args.toolCalls });
758
+ if (!evidence) return;
759
+ const threadContext = this.#getThreadContext(args);
760
+ if (!threadContext.threadId || !threadContext.resourceId) return;
761
+ const { threadStore, loadedThread } = await this.#loadThread(threadContext);
762
+ const githubMetadata = getGithubMetadata(loadedThread.metadata);
763
+ if (githubMetadata.subscriptionHintShown || githubMetadata.subscriptions.length > 0) return;
764
+ let repository;
765
+ try {
766
+ repository = await this.#resolveRepository({
767
+ id: "github-subscription-hint",
768
+ owner: evidence.owner,
769
+ repo: evidence.repo,
770
+ number: evidence.number
771
+ });
772
+ } catch {
773
+ return;
774
+ }
775
+ await threadStore.saveThread({
776
+ thread: {
777
+ ...loadedThread,
778
+ id: threadContext.threadId,
779
+ resourceId: threadContext.resourceId,
780
+ createdAt: loadedThread.createdAt ?? /* @__PURE__ */ new Date(),
781
+ updatedAt: /* @__PURE__ */ new Date(),
782
+ metadata: setGithubMetadata(loadedThread.metadata, { ...githubMetadata, subscriptionHintShown: true })
783
+ }
784
+ });
785
+ await args.sendSignal?.({
786
+ type: "reactive",
787
+ tagName: "system-reminder",
788
+ contents: `Looks like you're working with ${repository.owner}/${repository.repo}#${evidence.number}. Use /github subscribe ${evidence.number} or the github_subscribe_pr tool to follow updates.`,
789
+ attributes: { type: "github-subscription-hint" },
790
+ metadata: {
791
+ github: {
792
+ action: "subscriptionHint",
793
+ owner: repository.owner,
794
+ repo: repository.repo,
795
+ number: evidence.number
796
+ }
797
+ }
798
+ });
799
+ }
800
+ async #resolveThreadStore() {
801
+ if (this.#options.threadStore) return this.#options.threadStore;
802
+ const storage = this.#ghMastra?.getStorage?.();
803
+ const memoryStore = storage?.getStore ? await storage.getStore("memory") : void 0;
804
+ return memoryStore;
805
+ }
806
+ #getThreadContext(args) {
807
+ const memoryContext = args.requestContext?.get("MastraMemory");
808
+ return { threadId: memoryContext?.thread?.id, resourceId: memoryContext?.resourceId };
809
+ }
810
+ #createTools(args) {
811
+ const threadContext = this.#getThreadContext(args);
812
+ const getExecutionThreadContext = (context) => ({
813
+ threadId: context?.agent?.threadId ?? threadContext.threadId,
814
+ resourceId: context?.agent?.resourceId ?? threadContext.resourceId
815
+ });
816
+ return {
817
+ ...args.tools,
818
+ github_subscribe_pr: createGithubTool({
819
+ id: "github_subscribe_pr",
820
+ description: "Subscribe this thread to a GitHub pull request. Syncs only the requested PR with gitcrawl and stores the subscription on the thread.",
821
+ inputSchema: z__default.default.object({
822
+ number: z__default.default.number().int().positive(),
823
+ owner: z__default.default.string().optional(),
824
+ repo: z__default.default.string().optional()
825
+ }),
826
+ execute: async (input, context) => {
827
+ const executionThreadContext = getExecutionThreadContext(context);
828
+ const result = await this.#subscribe({
829
+ id: `github-tool-subscribe-${crypto.randomUUID()}`,
830
+ owner: input.owner,
831
+ repo: input.repo,
832
+ number: input.number,
833
+ threadId: executionThreadContext.threadId,
834
+ resourceId: executionThreadContext.resourceId
835
+ });
836
+ return {
837
+ subscribed: true,
838
+ owner: result.owner,
839
+ repo: result.repo,
840
+ number: result.number,
841
+ syncStatus: result.syncResult?.ok === false ? "error" : result.syncResult ? "success" : void 0,
842
+ message: result.syncResult?.ok === false ? `Subscribed to ${result.owner}/${result.repo}#${result.number}, but gitcrawl sync failed: ${result.syncResult.error}` : `Subscribed to ${result.owner}/${result.repo}#${result.number}.`
843
+ };
844
+ }
845
+ }),
846
+ github_unsubscribe_pr: createGithubTool({
847
+ id: "github_unsubscribe_pr",
848
+ description: "Unsubscribe this thread from a GitHub pull request.",
849
+ inputSchema: z__default.default.object({
850
+ number: z__default.default.number().int().positive(),
851
+ owner: z__default.default.string().optional(),
852
+ repo: z__default.default.string().optional()
853
+ }),
854
+ execute: async (input, context) => {
855
+ const executionThreadContext = getExecutionThreadContext(context);
856
+ const result = await this.#unsubscribe({
857
+ id: `github-tool-unsubscribe-${crypto.randomUUID()}`,
858
+ owner: input.owner,
859
+ repo: input.repo,
860
+ number: input.number,
861
+ threadId: executionThreadContext.threadId,
862
+ resourceId: executionThreadContext.resourceId
863
+ });
864
+ return {
865
+ unsubscribed: result.removed ?? false,
866
+ owner: result.owner,
867
+ repo: result.repo,
868
+ number: result.number,
869
+ remainingSubscriptions: result.remainingSubscriptions,
870
+ message: result.removed ? `Unsubscribed from ${result.owner}/${result.repo}#${result.number}.` : `No GitHub subscription found for ${result.owner}/${result.repo}#${result.number}.`
871
+ };
872
+ }
873
+ })
874
+ };
875
+ }
876
+ async #resolveRepository(input) {
877
+ const resolvedRepository = input.owner && input.repo ? { owner: input.owner, repo: input.repo } : this.#options.owner && this.#options.repo ? { owner: this.#options.owner, repo: this.#options.repo } : await this.#repositoryResolver.resolveRepository({
878
+ cwd: this.#options.cwd,
879
+ abortSignal: input.abortSignal
880
+ });
881
+ if (!resolvedRepository?.owner || !resolvedRepository.repo) {
882
+ throw new Error(
883
+ "GitHub PR subscription requires owner and repo. Run inside a GitHub repo or pass owner and repo."
884
+ );
885
+ }
886
+ return resolvedRepository;
887
+ }
888
+ async #loadThread(input) {
889
+ const threadStore = await this.#resolveThreadStore();
890
+ if (!threadStore) throw new Error("GitHub PR subscription requires memory-backed thread storage.");
891
+ if (!input.threadId || !input.resourceId)
892
+ throw new Error("GitHub PR subscription requires threadId and resourceId.");
893
+ const loadedThread = await threadStore.getThreadById({ threadId: input.threadId, resourceId: input.resourceId }) ?? void 0;
894
+ if (!loadedThread) throw new Error(`Could not load thread ${input.threadId}.`);
895
+ return { threadStore, loadedThread };
896
+ }
897
+ #pollingKey(input) {
898
+ return `${input.resourceId}:${input.threadId}`;
899
+ }
900
+ #getNotificationAgent(_input) {
901
+ if (this.#agent) return this.#agent;
902
+ const agentId = _input?.agentId ?? this.#options.agentId;
903
+ return agentId ? this.#ghMastra?.getAgentById?.(agentId) : void 0;
904
+ }
905
+ async #getThreadSubscriptions(input) {
906
+ const { loadedThread } = await this.#loadThread(input);
907
+ return getGithubMetadata(loadedThread.metadata).subscriptions;
908
+ }
909
+ #notifySubscriptionsChanged(input) {
910
+ this.#subscriptionsChangedHandler?.(input);
911
+ }
912
+ #notifyPollingChanged(input) {
913
+ this.#pollingChangedHandler?.(input);
914
+ }
915
+ async #pollThread(input, options = {}) {
916
+ const key = this.#pollingKey(input);
917
+ const state = this.#polling.get(key);
918
+ if (state?.running) {
919
+ return 0;
920
+ }
921
+ if (state) state.running = true;
922
+ this.#notifyPollingChanged({ threadId: input.threadId, resourceId: input.resourceId, running: true });
923
+ try {
924
+ const { threadStore, loadedThread } = await this.#loadThread(input);
925
+ const githubMetadata = getGithubMetadata(loadedThread.metadata);
926
+ if (githubMetadata.subscriptions.length === 0) {
927
+ this.stopPollingForThread(input);
928
+ return 0;
929
+ }
930
+ const now = (/* @__PURE__ */ new Date()).toISOString();
931
+ const subscriptions = [];
932
+ for (const subscription of githubMetadata.subscriptions) {
933
+ const syncInput = {
934
+ owner: subscription.owner,
935
+ repo: subscription.repo,
936
+ number: subscription.number,
937
+ cwd: this.#options.cwd,
938
+ includeComments: options.includeComments
939
+ };
940
+ const syncResult = await this.#syncClient.syncPullRequest(syncInput);
941
+ let snapshot = syncResult.ok ? await this.#syncClient.getPullRequestSnapshot?.(syncInput) : void 0;
942
+ if (snapshot)
943
+ snapshot = await this.#filterUnauthorizedLatestComment(subscription.owner, subscription.repo, snapshot);
944
+ const nextSubscription = {
945
+ ...subscription,
946
+ updatedAt: now,
947
+ lastSyncAt: now,
948
+ lastSyncStatus: syncResult.ok ? "success" : "error"
949
+ };
950
+ if (syncResult.error) nextSubscription.lastSyncError = syncResult.error;
951
+ else delete nextSubscription.lastSyncError;
952
+ const previousGithubUpdatedAt = subscription.lastObservedGithubUpdatedAt;
953
+ const previousContentHash = subscription.lastObservedContentHash;
954
+ const previousThreadContentHash = subscription.lastObservedThreadContentHash;
955
+ const previousHeadSha = subscription.lastObservedHeadSha;
956
+ const latestCommentChanged = !!previousGithubUpdatedAt && !!snapshot?.latestCommentUpdatedAt && Date.parse(snapshot.latestCommentUpdatedAt) > Date.parse(previousGithubUpdatedAt);
957
+ if (snapshot) applySnapshotCursor(nextSubscription, snapshot);
958
+ const isFirstObservation = syncResult.ok && snapshot && !previousGithubUpdatedAt && !previousContentHash;
959
+ const legacyAggregateChanged = previousContentHash && snapshot?.contentHash && previousContentHash !== snapshot.contentHash && !previousThreadContentHash && !previousHeadSha;
960
+ const changed = isFirstObservation || syncResult.ok && snapshot && (legacyAggregateChanged || latestCommentChanged || previousThreadContentHash && snapshot.threadContentHash && previousThreadContentHash !== snapshot.threadContentHash || previousHeadSha && snapshot.headSha && previousHeadSha !== snapshot.headSha || subscription.lastObservedState && snapshot.state && subscription.lastObservedState !== snapshot.state || subscription.lastObservedMergeableState && snapshot.mergeableState && subscription.lastObservedMergeableState !== snapshot.mergeableState || subscription.lastObservedCiState && snapshot.ciState && subscription.lastObservedCiState !== snapshot.ciState || subscription.lastObservedReviewStateHash && snapshot.reviewStateHash && subscription.lastObservedReviewStateHash !== snapshot.reviewStateHash);
961
+ let shouldKeepSubscription = true;
962
+ if (changed && snapshot) {
963
+ const notifications = await this.#sendActivityNotifications({
964
+ polling: input,
965
+ subscription,
966
+ snapshot,
967
+ previousGithubUpdatedAt,
968
+ previousContentHash,
969
+ latestCommentChanged
970
+ });
971
+ const primaryNotification = notifications[0];
972
+ if (primaryNotification) {
973
+ nextSubscription.lastNotificationAt = now;
974
+ nextSubscription.lastNotificationKind = primaryNotification.kind;
975
+ nextSubscription.lastNotificationPriority = primaryNotification.priority;
976
+ nextSubscription.lastNotificationSummary = primaryNotification.summary;
977
+ shouldKeepSubscription = notifications.every((notification) => notification.kind !== "pull-request-merged");
978
+ }
979
+ }
980
+ if (shouldKeepSubscription) subscriptions.push(nextSubscription);
981
+ }
982
+ await threadStore.saveThread({
983
+ thread: {
984
+ ...loadedThread,
985
+ id: input.threadId,
986
+ resourceId: input.resourceId,
987
+ createdAt: loadedThread.createdAt ?? /* @__PURE__ */ new Date(),
988
+ updatedAt: /* @__PURE__ */ new Date(),
989
+ metadata: setGithubMetadata(loadedThread.metadata, { subscriptions })
990
+ }
991
+ });
992
+ this.#notifySubscriptionsChanged({ threadId: input.threadId, resourceId: input.resourceId, subscriptions });
993
+ if (subscriptions.length === 0) this.stopPollingForThread(input);
994
+ return subscriptions.length;
995
+ } catch (error) {
996
+ throw error;
997
+ } finally {
998
+ const latestState = this.#polling.get(key);
999
+ if (latestState) latestState.running = false;
1000
+ this.#notifyPollingChanged({ threadId: input.threadId, resourceId: input.resourceId, running: false });
1001
+ }
1002
+ }
1003
+ #createGithubNotificationInput(input) {
1004
+ const failingChecks = getFailingChecks(input.snapshot);
1005
+ const pendingChecks = getPendingChecks(input.snapshot);
1006
+ const latestCommentExcerpt = input.snapshot.latestCommentBody ? getCommentExcerpt(input.snapshot.latestCommentBody) : void 0;
1007
+ const latestCommentDedupeSuffix = input.notification.kind === "pull-request-activity" && input.snapshot.latestCommentUrl ? `comment:${input.snapshot.latestCommentUrl}:${input.snapshot.latestCommentUpdatedAt ?? ""}` : input.dedupeSuffix;
1008
+ const notificationInput = {
1009
+ source: "github",
1010
+ kind: input.notification.kind,
1011
+ priority: input.notification.priority,
1012
+ summary: input.notification.summary,
1013
+ dedupeKey: `github:${input.subscription.owner}/${input.subscription.repo}#${input.subscription.number}:${latestCommentDedupeSuffix}`,
1014
+ coalesceKey: `github:${input.subscription.owner}/${input.subscription.repo}#${input.subscription.number}:${input.notification.kind}`,
1015
+ attributes: {
1016
+ owner: input.subscription.owner,
1017
+ repo: input.subscription.repo,
1018
+ number: input.subscription.number,
1019
+ ...input.snapshot.title ? { title: input.snapshot.title } : {},
1020
+ ...input.snapshot.state ? { state: input.snapshot.state } : {},
1021
+ ...input.snapshot.htmlUrl ? { url: input.snapshot.htmlUrl } : {},
1022
+ ...input.snapshot.githubUpdatedAt ? { githubUpdatedAt: input.snapshot.githubUpdatedAt } : {},
1023
+ ...input.previousGithubUpdatedAt ? { previousGithubUpdatedAt: input.previousGithubUpdatedAt } : {},
1024
+ ...input.snapshot.mergeableState ? { mergeableState: input.snapshot.mergeableState } : {},
1025
+ ...input.snapshot.ciState ? { ciState: input.snapshot.ciState } : {},
1026
+ ...input.snapshot.unresolvedReviewThreads !== void 0 ? { unresolvedReviewThreads: input.snapshot.unresolvedReviewThreads } : {},
1027
+ ...input.snapshot.latestCommentAuthor ? { latestCommentAuthor: input.snapshot.latestCommentAuthor } : {},
1028
+ ...latestCommentExcerpt ? { latestCommentExcerpt } : {},
1029
+ ...input.snapshot.latestCommentUrl ? { latestCommentUrl: input.snapshot.latestCommentUrl } : {},
1030
+ ...input.snapshot.latestCommentUpdatedAt ? { latestCommentUpdatedAt: input.snapshot.latestCommentUpdatedAt } : {},
1031
+ ...failingChecks.length > 0 ? { failingChecks: failingChecks.map((check) => check.name).join(", ") } : {},
1032
+ ...pendingChecks.length > 0 ? { pendingChecks: pendingChecks.map((check) => check.name).join(", ") } : {}
1033
+ },
1034
+ metadata: {
1035
+ github: {
1036
+ owner: input.subscription.owner,
1037
+ repo: input.subscription.repo,
1038
+ number: input.subscription.number,
1039
+ title: input.snapshot.title,
1040
+ state: input.snapshot.state,
1041
+ htmlUrl: input.snapshot.htmlUrl,
1042
+ githubUpdatedAt: input.snapshot.githubUpdatedAt,
1043
+ previousGithubUpdatedAt: input.previousGithubUpdatedAt,
1044
+ contentHash: input.snapshot.contentHash,
1045
+ previousContentHash: input.previousContentHash,
1046
+ threadContentHash: input.snapshot.threadContentHash,
1047
+ headSha: input.snapshot.headSha,
1048
+ headRef: input.snapshot.headRef,
1049
+ mergeableState: input.snapshot.mergeableState,
1050
+ ciState: input.snapshot.ciState,
1051
+ closedAt: input.snapshot.closedAt,
1052
+ mergedAt: input.snapshot.mergedAt,
1053
+ unresolvedReviewThreads: input.snapshot.unresolvedReviewThreads,
1054
+ reviewStateHash: input.snapshot.reviewStateHash,
1055
+ latestReviewThreadAt: input.snapshot.latestReviewThreadAt,
1056
+ latestCommentAuthor: input.snapshot.latestCommentAuthor,
1057
+ latestCommentAuthorType: input.snapshot.latestCommentAuthorType,
1058
+ latestCommentIsBot: input.snapshot.latestCommentIsBot,
1059
+ // Intentionally omit the full latestCommentBody here: persisting it verbatim bloats
1060
+ // notification payloads (a single CodeRabbit comment can exceed 100KB) and can overflow
1061
+ // agent context windows when listed. The 240-char latestCommentExcerpt is stored instead.
1062
+ latestCommentExcerpt,
1063
+ latestCommentUrl: input.snapshot.latestCommentUrl,
1064
+ latestCommentUpdatedAt: input.snapshot.latestCommentUpdatedAt,
1065
+ failingChecks,
1066
+ pendingChecks
1067
+ }
1068
+ }
1069
+ };
1070
+ return notificationInput;
1071
+ }
1072
+ async #sendGithubNotification(input) {
1073
+ const notificationInput = this.#createGithubNotificationInput(input);
1074
+ const streamOptions = await this.#agentOptions.getNotificationStreamOptions?.(input.target);
1075
+ await input.agent?.sendNotificationSignal?.(
1076
+ notificationInput,
1077
+ streamOptions ? { ...input.target, ifIdle: { streamOptions } } : input.target
1078
+ );
1079
+ }
1080
+ async #sendBaselineNotification(input) {
1081
+ const agent = this.#getNotificationAgent({});
1082
+ if (!agent?.sendNotificationSignal) return;
1083
+ await this.#sendGithubNotification({
1084
+ agent,
1085
+ subscription: input.subscription,
1086
+ snapshot: input.snapshot,
1087
+ notification: classifyGithubBaselineNotification({ subscription: input.subscription, snapshot: input.snapshot }),
1088
+ target: { resourceId: input.resourceId, threadId: input.threadId },
1089
+ dedupeSuffix: `baseline:${input.subscription.lastSubscribeSignalId}`
1090
+ });
1091
+ }
1092
+ async #isAuthorizedAuthor(owner, repo, user, metadata = {}) {
1093
+ if (!user) return false;
1094
+ const normalizedUser = user.toLowerCase();
1095
+ const isBot = metadata.isBot === true || metadata.authorType?.toLowerCase() === "bot" || normalizedUser.endsWith("[bot]");
1096
+ if (isBot) {
1097
+ const ignoredBots = this.#options.ignoredBots ?? [];
1098
+ if (ignoredBots.some((bot) => bot.toLowerCase() === normalizedUser)) return false;
1099
+ const authorizedBots = this.#options.authorizedBots ?? DEFAULT_AUTHORIZED_BOTS;
1100
+ return authorizedBots.some((bot) => bot.toLowerCase() === normalizedUser);
1101
+ }
1102
+ const permission = await this.#loadAuthorPermission(owner, repo, user);
1103
+ const authorizedPermissions = this.#options.authorizedPermissions ?? DEFAULT_AUTHORIZED_PERMISSIONS;
1104
+ return !!permission && authorizedPermissions.includes(permission);
1105
+ }
1106
+ async #filterUnauthorizedLatestComment(owner, repo, snapshot) {
1107
+ const comments = snapshot.latestComments?.length ? snapshot.latestComments : [
1108
+ {
1109
+ author: snapshot.latestCommentAuthor,
1110
+ authorType: snapshot.latestCommentAuthorType,
1111
+ isBot: snapshot.latestCommentIsBot,
1112
+ body: snapshot.latestCommentBody,
1113
+ url: snapshot.latestCommentUrl,
1114
+ updatedAt: snapshot.latestCommentUpdatedAt
1115
+ }
1116
+ ];
1117
+ if (!comments.some((comment) => comment.author)) return snapshot;
1118
+ if (!comments.some((comment) => comment.body || comment.url || comment.updatedAt)) return snapshot;
1119
+ for (const comment of comments) {
1120
+ if (!await this.#isAuthorizedAuthor(owner, repo, comment.author, {
1121
+ authorType: comment.authorType,
1122
+ isBot: comment.isBot
1123
+ })) {
1124
+ continue;
1125
+ }
1126
+ return {
1127
+ ...snapshot,
1128
+ latestCommentAuthor: comment.author,
1129
+ latestCommentAuthorType: comment.authorType,
1130
+ latestCommentIsBot: comment.isBot,
1131
+ latestCommentBody: comment.body,
1132
+ latestCommentUrl: comment.url,
1133
+ latestCommentUpdatedAt: comment.updatedAt
1134
+ };
1135
+ }
1136
+ return {
1137
+ ...snapshot,
1138
+ latestCommentAuthor: void 0,
1139
+ latestCommentAuthorType: void 0,
1140
+ latestCommentIsBot: void 0,
1141
+ latestCommentBody: void 0,
1142
+ latestCommentUrl: void 0,
1143
+ latestCommentUpdatedAt: void 0
1144
+ };
1145
+ }
1146
+ async #loadAuthorPermission(owner, repo, user) {
1147
+ const cacheKey = `${owner}/${repo}:${user.toLowerCase()}`;
1148
+ const cached = this.#permissionCache.get(cacheKey);
1149
+ if (cached && cached.expiresAt > Date.now()) return cached.permission;
1150
+ if (cached) this.#permissionCache.delete(cacheKey);
1151
+ try {
1152
+ let permission;
1153
+ if (this.#options.permissionResolver) {
1154
+ permission = await this.#options.permissionResolver.getPermission(owner, repo, user);
1155
+ } else {
1156
+ const { stdout } = await execFileAsync("gh", [
1157
+ "api",
1158
+ `repos/${owner}/${repo}/collaborators/${user}/permission`,
1159
+ "--jq",
1160
+ ".permission"
1161
+ ]);
1162
+ const raw = stdout.trim();
1163
+ permission = ["admin", "maintain", "write", "triage", "read", "none"].includes(
1164
+ raw
1165
+ ) ? raw : void 0;
1166
+ }
1167
+ if (permission) {
1168
+ this.#permissionCache.set(cacheKey, { permission, expiresAt: Date.now() + PERMISSION_CACHE_TTL_MS });
1169
+ }
1170
+ return permission;
1171
+ } catch {
1172
+ this.#permissionCache.delete(cacheKey);
1173
+ return void 0;
1174
+ }
1175
+ }
1176
+ async #sendActivityNotifications(input) {
1177
+ const agent = this.#getNotificationAgent(input.polling);
1178
+ if (!agent?.sendNotificationSignal) return [];
1179
+ const notifications = [
1180
+ classifyGithubActivityNotification({
1181
+ subscription: input.subscription,
1182
+ snapshot: input.snapshot
1183
+ })
1184
+ ];
1185
+ if (input.latestCommentChanged && notifications[0]?.kind !== "pull-request-activity") {
1186
+ notifications.push(
1187
+ classifyGithubCommentActivityNotification({
1188
+ subscription: input.subscription,
1189
+ snapshot: input.snapshot
1190
+ })
1191
+ );
1192
+ }
1193
+ const sent = [];
1194
+ const notificationInputs = [];
1195
+ for (const notification of notifications.sort(compareGithubActivityNotifications)) {
1196
+ if (!notification) continue;
1197
+ if (AUTHOR_GATED_NOTIFICATION_KINDS.has(notification.kind)) {
1198
+ const authorized = await this.#isAuthorizedAuthor(
1199
+ input.subscription.owner,
1200
+ input.subscription.repo,
1201
+ input.snapshot.latestCommentAuthor,
1202
+ {
1203
+ authorType: input.snapshot.latestCommentAuthorType,
1204
+ isBot: input.snapshot.latestCommentIsBot
1205
+ }
1206
+ );
1207
+ if (!authorized) continue;
1208
+ }
1209
+ notificationInputs.push(
1210
+ this.#createGithubNotificationInput({
1211
+ subscription: input.subscription,
1212
+ snapshot: input.snapshot,
1213
+ notification,
1214
+ dedupeSuffix: input.snapshot.contentHash ?? input.snapshot.githubUpdatedAt ?? String(Date.now()),
1215
+ previousGithubUpdatedAt: input.previousGithubUpdatedAt,
1216
+ previousContentHash: input.previousContentHash
1217
+ })
1218
+ );
1219
+ sent.push(notification);
1220
+ }
1221
+ if (notificationInputs.length > 0) {
1222
+ const target = { resourceId: input.polling.resourceId, threadId: input.polling.threadId };
1223
+ const streamOptions = await this.#agentOptions.getNotificationStreamOptions?.(target);
1224
+ await agent.sendNotificationSignal(
1225
+ notificationInputs,
1226
+ streamOptions ? { ...target, ifIdle: { streamOptions } } : target
1227
+ );
1228
+ }
1229
+ return sent;
1230
+ }
1231
+ async #subscribe(input) {
1232
+ const { owner, repo } = await this.#resolveRepository(input);
1233
+ const { threadStore, loadedThread } = await this.#loadThread(input);
1234
+ const githubMetadata = getGithubMetadata(loadedThread.metadata);
1235
+ const existingIndex = githubMetadata.subscriptions.findIndex(
1236
+ (subscription2) => subscription2.owner === owner && subscription2.repo === repo && subscription2.number === input.number
1237
+ );
1238
+ const existing = existingIndex >= 0 ? githubMetadata.subscriptions[existingIndex] : void 0;
1239
+ if (existing?.lastSubscribeSignalId === input.id) {
1240
+ return { owner, repo, number: input.number, subscription: existing, alreadyProcessed: true };
1241
+ }
1242
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1243
+ const subscription = {
1244
+ owner,
1245
+ repo,
1246
+ number: input.number,
1247
+ subscribedAt: existing?.subscribedAt ?? now,
1248
+ updatedAt: now,
1249
+ lastSubscribeSignalId: input.id,
1250
+ ...existing?.lastSyncAt ? { lastSyncAt: existing.lastSyncAt } : {},
1251
+ ...existing?.lastSyncStatus ? { lastSyncStatus: existing.lastSyncStatus } : {},
1252
+ ...existing?.lastSyncError ? { lastSyncError: existing.lastSyncError } : {},
1253
+ ...existing?.lastObservedGithubUpdatedAt ? { lastObservedGithubUpdatedAt: existing.lastObservedGithubUpdatedAt } : {},
1254
+ ...existing?.lastObservedContentHash ? { lastObservedContentHash: existing.lastObservedContentHash } : {},
1255
+ ...existing?.lastObservedThreadContentHash ? { lastObservedThreadContentHash: existing.lastObservedThreadContentHash } : {},
1256
+ ...existing?.lastObservedHeadSha ? { lastObservedHeadSha: existing.lastObservedHeadSha } : {},
1257
+ ...existing?.lastObservedState ? { lastObservedState: existing.lastObservedState } : {},
1258
+ ...existing?.lastObservedMergeableState ? { lastObservedMergeableState: existing.lastObservedMergeableState } : {},
1259
+ ...existing?.lastObservedCiState ? { lastObservedCiState: existing.lastObservedCiState } : {},
1260
+ ...existing?.lastObservedReviewStateHash ? { lastObservedReviewStateHash: existing.lastObservedReviewStateHash } : {}
1261
+ };
1262
+ let syncResult;
1263
+ let baselineSnapshot;
1264
+ if (this.#options.syncOnSubscribe !== false) {
1265
+ const syncInput = {
1266
+ owner,
1267
+ repo,
1268
+ number: input.number,
1269
+ cwd: this.#options.cwd,
1270
+ abortSignal: input.abortSignal
1271
+ };
1272
+ syncResult = await this.#syncClient.syncPullRequest(syncInput);
1273
+ subscription.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
1274
+ subscription.lastSyncStatus = syncResult.ok ? "success" : "error";
1275
+ if (syncResult.error) subscription.lastSyncError = syncResult.error;
1276
+ else delete subscription.lastSyncError;
1277
+ const snapshot = syncResult.ok ? await this.#syncClient.getPullRequestSnapshot?.(syncInput) : void 0;
1278
+ baselineSnapshot = snapshot;
1279
+ if (snapshot) applySnapshotCursor(subscription, snapshot);
1280
+ } else {
1281
+ subscription.lastSyncStatus = "skipped";
1282
+ }
1283
+ const subscriptions = [subscription];
1284
+ await threadStore.saveThread({
1285
+ thread: {
1286
+ ...loadedThread,
1287
+ id: input.threadId,
1288
+ resourceId: input.resourceId,
1289
+ createdAt: loadedThread.createdAt ?? /* @__PURE__ */ new Date(),
1290
+ updatedAt: /* @__PURE__ */ new Date(),
1291
+ metadata: setGithubMetadata(loadedThread.metadata, { subscriptions })
1292
+ }
1293
+ });
1294
+ this.#notifySubscriptionsChanged({ threadId: input.threadId, resourceId: input.resourceId, subscriptions });
1295
+ if (baselineSnapshot) {
1296
+ await this.#sendBaselineNotification({
1297
+ threadId: input.threadId,
1298
+ resourceId: input.resourceId,
1299
+ subscription,
1300
+ snapshot: baselineSnapshot
1301
+ });
1302
+ }
1303
+ await this.startPollingForThread({ threadId: input.threadId, resourceId: input.resourceId });
1304
+ return { owner, repo, number: input.number, subscription, syncResult };
1305
+ }
1306
+ async #unsubscribe(input) {
1307
+ const { owner, repo } = await this.#resolveRepository(input);
1308
+ const { threadStore, loadedThread } = await this.#loadThread(input);
1309
+ const githubMetadata = getGithubMetadata(loadedThread.metadata);
1310
+ const subscriptions = githubMetadata.subscriptions.filter(
1311
+ (subscription) => !(subscription.owner === owner && subscription.repo === repo && subscription.number === input.number)
1312
+ );
1313
+ const removed = subscriptions.length !== githubMetadata.subscriptions.length;
1314
+ if (removed) {
1315
+ await threadStore.saveThread({
1316
+ thread: {
1317
+ ...loadedThread,
1318
+ id: input.threadId,
1319
+ resourceId: input.resourceId,
1320
+ createdAt: loadedThread.createdAt ?? /* @__PURE__ */ new Date(),
1321
+ updatedAt: /* @__PURE__ */ new Date(),
1322
+ metadata: setGithubMetadata(loadedThread.metadata, { subscriptions })
1323
+ }
1324
+ });
1325
+ this.#notifySubscriptionsChanged({ threadId: input.threadId, resourceId: input.resourceId, subscriptions });
1326
+ if (subscriptions.length === 0)
1327
+ this.stopPollingForThread({ threadId: input.threadId, resourceId: input.resourceId });
1328
+ }
1329
+ return { owner, repo, number: input.number, removed, remainingSubscriptions: subscriptions.length };
1330
+ }
1331
+ #findLatestGithubSignal(messages) {
1332
+ const message = messages.at(-1);
1333
+ if (!message) return void 0;
1334
+ const signal = getSignalMetadata(message);
1335
+ if (!signal || signal.tagName !== GITHUB_SUBSCRIBE_PR_TAG && signal.tagName !== GITHUB_UNSUBSCRIBE_PR_TAG) {
1336
+ return void 0;
1337
+ }
1338
+ const attributes = isPlainObject(signal.attributes) ? signal.attributes : {};
1339
+ const metadata = isPlainObject(signal.metadata) ? signal.metadata : {};
1340
+ const github = isPlainObject(metadata.github) ? metadata.github : {};
1341
+ const number = readNumber(attributes.number) ?? readNumber(github.number);
1342
+ if (!number) return void 0;
1343
+ return {
1344
+ tagName: String(signal.tagName),
1345
+ id: readString(signal.id) ?? message.id,
1346
+ owner: readString(attributes.owner) ?? readString(github.owner),
1347
+ repo: readString(attributes.repo) ?? readString(github.repo),
1348
+ number
1349
+ };
1350
+ }
1351
+ async #sendStatus(args, signal, status) {
1352
+ await args.sendSignal?.({
1353
+ type: "reactive",
1354
+ tagName: GITHUB_SYNC_STATUS_TAG,
1355
+ contents: status.message,
1356
+ attributes: {
1357
+ status: status.status,
1358
+ owner: signal.owner,
1359
+ repo: signal.repo,
1360
+ number: signal.number
1361
+ },
1362
+ metadata: {
1363
+ github: {
1364
+ action: status.action,
1365
+ status: status.status,
1366
+ owner: signal.owner,
1367
+ repo: signal.repo,
1368
+ number: signal.number
1369
+ }
1370
+ }
1371
+ });
1372
+ }
1373
+ };
1374
+
1375
+ exports.GITHUB_SIGNALS_METADATA_KEY = GITHUB_SIGNALS_METADATA_KEY;
1376
+ exports.GITHUB_SUBSCRIBE_PR_TAG = GITHUB_SUBSCRIBE_PR_TAG;
1377
+ exports.GITHUB_SYNC_STATUS_TAG = GITHUB_SYNC_STATUS_TAG;
1378
+ exports.GITHUB_UNSUBSCRIBE_PR_TAG = GITHUB_UNSUBSCRIBE_PR_TAG;
1379
+ exports.GitRemoteRepositoryResolver = GitRemoteRepositoryResolver;
1380
+ exports.GitcrawlSyncClient = GitcrawlSyncClient;
1381
+ exports.GithubSignals = GithubSignals;
1382
+ exports.normalizeGithubChecksForSnapshot = normalizeGithubChecksForSnapshot;
1383
+ exports.sanitizeCommentText = sanitizeCommentText;
1384
+ //# sourceMappingURL=index.cjs.map
1385
+ //# sourceMappingURL=index.cjs.map