@mastra/factory 0.9.0 → 0.10.0-alpha.10
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/CHANGELOG.md +179 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +24 -8
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +1 -0
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/provenance.d.ts +7 -2
- package/dist/integrations/github/provenance.d.ts.map +1 -1
- package/dist/integrations/github/provenance.js +16 -2
- package/dist/integrations/github/provenance.js.map +1 -1
- package/dist/integrations/github/routes.d.ts +3 -0
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +165 -6
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +193 -127
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/linear/routes.d.ts.map +1 -1
- package/dist/integrations/linear/routes.js +50 -0
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +1 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/routes/attention.d.ts +17 -0
- package/dist/routes/attention.d.ts.map +1 -0
- package/dist/routes/attention.js +305 -0
- package/dist/routes/attention.js.map +1 -0
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +11 -2
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/surface.d.ts +2 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +61 -43
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +35 -14
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.js +5 -5
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts +13 -0
- package/dist/rules/dispatch-errors.d.ts.map +1 -0
- package/dist/rules/dispatch-errors.js +77 -0
- package/dist/rules/dispatch-errors.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +9 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +118 -45
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +30 -14
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/terminal-cleanup.d.ts +1 -1
- package/dist/rules/terminal-cleanup.d.ts.map +1 -1
- package/dist/rules/terminal-cleanup.js +2 -2
- package/dist/rules/terminal-cleanup.js.map +1 -1
- package/dist/rules/transition-service.d.ts +2 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +1 -1
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +2 -0
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +9 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/secret-encryption.d.ts +25 -0
- package/dist/secret-encryption.d.ts.map +1 -0
- package/dist/secret-encryption.js +96 -0
- package/dist/secret-encryption.js.map +1 -0
- package/dist/session/factory-session.d.ts +13 -3
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +15 -6
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/first-exec-capture.d.ts +16 -10
- package/dist/session/first-exec-capture.d.ts.map +1 -1
- package/dist/session/first-exec-capture.js +74 -14
- package/dist/session/first-exec-capture.js.map +1 -1
- package/dist/session/first-message-capture.d.ts +14 -10
- package/dist/session/first-message-capture.d.ts.map +1 -1
- package/dist/session/first-message-capture.js +17 -11
- package/dist/session/first-message-capture.js.map +1 -1
- package/dist/session/session-title.d.ts +7 -0
- package/dist/session/session-title.d.ts.map +1 -0
- package/dist/session/session-title.js +14 -0
- package/dist/session/session-title.js.map +1 -0
- package/dist/session/thread-title-mirror.d.ts +35 -0
- package/dist/session/thread-title-mirror.d.ts.map +1 -0
- package/dist/session/thread-title-mirror.js +51 -0
- package/dist/session/thread-title-mirror.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +3 -1
- package/dist/storage/domains/credentials/base.d.ts.map +1 -1
- package/dist/storage/domains/credentials/base.js +33 -15
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.d.ts +3 -1
- package/dist/storage/domains/custom-providers/base.d.ts.map +1 -1
- package/dist/storage/domains/custom-providers/base.js +45 -25
- package/dist/storage/domains/custom-providers/base.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +6 -5
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +21 -13
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/integrations/base.d.ts +3 -1
- package/dist/storage/domains/integrations/base.d.ts.map +1 -1
- package/dist/storage/domains/integrations/base.js +28 -12
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +8 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +14 -2
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +7 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +115 -12
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +497 -43
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/work-item-branch.d.ts +23 -0
- package/dist/work-item-branch.d.ts.map +1 -0
- package/dist/work-item-branch.js +39 -0
- package/dist/work-item-branch.js.map +1 -0
- package/factory-skills/factory-rereview/SKILL.md +2 -0
- package/factory-skills/factory-review/SKILL.md +2 -0
- package/package.json +7 -7
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { changeRequestTargetKey } from "./subscriptions.js";
|
|
2
2
|
import { resolveFactoryGithubRule } from "../../rules/resolve.js";
|
|
3
|
+
import { isTerminalFactoryRuleStage } from "../../rules/types.js";
|
|
3
4
|
import { validateFactoryRuleDecisions } from "../../rules/validation.js";
|
|
5
|
+
import { FACTORY_PULL_REQUEST_RECONCILIATION_KEY } from "../../storage/domains/work-items/base.js";
|
|
4
6
|
//#region src/integrations/github/rules.ts
|
|
5
7
|
const TRUSTED_PERMISSIONS = /* @__PURE__ */ new Set(["write", "admin"]);
|
|
6
8
|
const RULE_TIMEOUT_MS = 5e3;
|
|
@@ -178,124 +180,148 @@ var GithubRules = class {
|
|
|
178
180
|
factoryAuthored: !senderIsResponder && provenance !== null || this.#isFactoryLogin(login)
|
|
179
181
|
});
|
|
180
182
|
if (actor.type === "github" && actor.factoryAuthored && (event === "issueCommentCreated" || event === "issueCommentEdited") && string(issueComment?.body)?.includes(FACTORY_TRIAGE_COMMENT_MARKER)) return { status: "ignored" };
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
+
const evaluate = async (item, ingressIdentity) => {
|
|
184
|
+
const context = {
|
|
185
|
+
tenant: {
|
|
186
|
+
orgId: project.orgId,
|
|
187
|
+
projectId: project.factoryProjectId
|
|
188
|
+
},
|
|
189
|
+
actor,
|
|
190
|
+
ingress: {
|
|
191
|
+
type: "github",
|
|
192
|
+
id: ingressIdentity
|
|
193
|
+
},
|
|
194
|
+
cause: `github.${event}`,
|
|
195
|
+
causalChain: [],
|
|
196
|
+
ruleSetVersion: this.options.rules.version,
|
|
197
|
+
...item ? {
|
|
198
|
+
item: {
|
|
199
|
+
id: item.id,
|
|
200
|
+
source: workItemSource(item),
|
|
201
|
+
sourceKey: workItemSourceKey(item),
|
|
202
|
+
parentWorkItemId: item.parentWorkItemId,
|
|
203
|
+
title: item.title,
|
|
204
|
+
url: item.externalSource?.url ?? null,
|
|
205
|
+
stages: item.stages,
|
|
206
|
+
metadata: item.metadata
|
|
207
|
+
},
|
|
208
|
+
board: item.externalSource?.type === "pull-request" ? "review" : "work",
|
|
209
|
+
itemRevision: item.revision
|
|
210
|
+
} : {},
|
|
211
|
+
event,
|
|
212
|
+
deliveryId: parsed.deliveryId,
|
|
213
|
+
factory: { createdAt: factoryProject.createdAt.toISOString() },
|
|
214
|
+
repository: {
|
|
215
|
+
id: repositoryId,
|
|
216
|
+
fullName: repositoryName
|
|
217
|
+
},
|
|
218
|
+
...issueNumber && string(issue?.title) && string(issue?.html_url) ? { issue: {
|
|
219
|
+
number: issueNumber,
|
|
220
|
+
title: string(issue?.title),
|
|
221
|
+
url: string(issue?.html_url),
|
|
222
|
+
...string(issue?.created_at) ? { createdAt: string(issue?.created_at) } : {},
|
|
223
|
+
...string(issue?.updated_at) ? { updatedAt: string(issue?.updated_at) } : {},
|
|
224
|
+
assignees: actorLogins(issue?.assignees),
|
|
225
|
+
labels: labelNames(issue?.labels),
|
|
226
|
+
...string(issue?.state) === "closed" || string(issue?.state) === "open" ? { state: string(issue?.state) } : {},
|
|
227
|
+
...string(issue?.state_reason) ? { stateReason: string(issue?.state_reason) } : {}
|
|
228
|
+
} } : {},
|
|
229
|
+
...event === "issueEdited" ? { issueChange: {
|
|
230
|
+
title: Boolean(object(changes?.title)),
|
|
231
|
+
body: Boolean(object(changes?.body))
|
|
232
|
+
} } : {},
|
|
233
|
+
...number(issueComment?.id) ? { issueComment: {
|
|
234
|
+
id: number(issueComment?.id),
|
|
235
|
+
...string(issueComment?.body) ? { body: string(issueComment?.body) } : {},
|
|
236
|
+
...string(issueComment?.html_url) ? { url: string(issueComment?.html_url) } : {},
|
|
237
|
+
...string(object(issueComment?.user)?.login) ? { author: string(object(issueComment?.user)?.login) } : {},
|
|
238
|
+
...string(object(issueComment?.user)?.type) ? { authorType: string(object(issueComment?.user)?.type) } : {},
|
|
239
|
+
...string(issueComment?.created_at) ? { createdAt: string(issueComment?.created_at) } : {},
|
|
240
|
+
...string(issueComment?.updated_at) ? { updatedAt: string(issueComment?.updated_at) } : {}
|
|
241
|
+
} } : {},
|
|
242
|
+
...pullRequestNumber && string(pullRequest?.title) && string(pullRequest?.html_url) ? { pullRequest: {
|
|
243
|
+
number: pullRequestNumber,
|
|
244
|
+
title: string(pullRequest?.title),
|
|
245
|
+
url: string(pullRequest?.html_url),
|
|
246
|
+
...string(pullRequest?.created_at) ? { createdAt: string(pullRequest?.created_at) } : {},
|
|
247
|
+
state: string(pullRequest?.state) === "closed" ? "closed" : "open",
|
|
248
|
+
draft: boolean(pullRequest?.draft) ?? false,
|
|
249
|
+
merged: boolean(pullRequest?.merged) ?? false,
|
|
250
|
+
assignees: actorLogins(pullRequest?.assignees),
|
|
251
|
+
requestedReviewers: actorLogins(pullRequest?.requested_reviewers),
|
|
252
|
+
labels: labelNames(pullRequest?.labels),
|
|
253
|
+
headBranch: string(object(pullRequest?.head)?.ref) ?? "",
|
|
254
|
+
baseBranch: string(object(pullRequest?.base)?.ref) ?? ""
|
|
255
|
+
} } : {},
|
|
256
|
+
...reviewRequested && requestedReviewer ? { reviewRequest: {
|
|
257
|
+
reviewer: requestedReviewer,
|
|
258
|
+
factoryReviewer: this.#isFactoryLogin(requestedReviewer)
|
|
259
|
+
} } : {},
|
|
260
|
+
...object(parsed.payload.review) ? { review: {
|
|
261
|
+
id: number(object(parsed.payload.review)?.id) ?? 0,
|
|
262
|
+
state: string(object(parsed.payload.review)?.state) ?? "unknown",
|
|
263
|
+
url: string(object(parsed.payload.review)?.html_url) ?? ""
|
|
264
|
+
} } : {}
|
|
265
|
+
};
|
|
266
|
+
const rule = resolveFactoryGithubRule(this.options.rules, event);
|
|
267
|
+
let decision;
|
|
268
|
+
let decisions = [];
|
|
269
|
+
let outcome = { status: "accepted" };
|
|
270
|
+
try {
|
|
271
|
+
decision = rule ? await withRuleTimeout(Promise.resolve(rule(Object.freeze(context)))) : void 0;
|
|
272
|
+
if (decision?.type === "reject") outcome = {
|
|
273
|
+
status: "rejected",
|
|
274
|
+
code: decision.code,
|
|
275
|
+
reason: decision.reason
|
|
276
|
+
};
|
|
277
|
+
else if (decision) decisions = validateFactoryRuleDecisions([decision]).map((entry) => ({ ...entry }));
|
|
278
|
+
} catch (error) {
|
|
279
|
+
const timedOut = error instanceof Error && error.message === "FACTORY_RULE_TIMEOUT";
|
|
280
|
+
outcome = {
|
|
281
|
+
status: "rejected",
|
|
282
|
+
code: timedOut ? "timeout" : "rule_error",
|
|
283
|
+
reason: timedOut ? "Factory rule evaluation timed out." : error instanceof Error ? error.message.slice(0, 2e3) : "Factory GitHub rule failed."
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return { status: (await this.options.storage.commitRuleEvaluation({
|
|
183
287
|
orgId: project.orgId,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
id: `${installationId}:${parsed.deliveryId}`
|
|
190
|
-
},
|
|
191
|
-
cause: `github.${event}`,
|
|
192
|
-
causalChain: [],
|
|
193
|
-
ruleSetVersion: this.options.rules.version,
|
|
194
|
-
...relatedItem ? {
|
|
195
|
-
item: {
|
|
196
|
-
id: relatedItem.id,
|
|
197
|
-
source: workItemSource(relatedItem),
|
|
198
|
-
sourceKey: workItemSourceKey(relatedItem),
|
|
199
|
-
parentWorkItemId: relatedItem.parentWorkItemId,
|
|
200
|
-
title: relatedItem.title,
|
|
201
|
-
url: relatedItem.externalSource?.url ?? null,
|
|
202
|
-
stages: relatedItem.stages,
|
|
203
|
-
metadata: relatedItem.metadata
|
|
288
|
+
factoryProjectId: project.factoryProjectId,
|
|
289
|
+
workItemId: item?.id ?? null,
|
|
290
|
+
ingress: {
|
|
291
|
+
identity: ingressIdentity,
|
|
292
|
+
triggerType: `github.${event}`
|
|
204
293
|
},
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
fullName: repositoryName
|
|
214
|
-
},
|
|
215
|
-
...issueNumber && string(issue?.title) && string(issue?.html_url) ? { issue: {
|
|
216
|
-
number: issueNumber,
|
|
217
|
-
title: string(issue?.title),
|
|
218
|
-
url: string(issue?.html_url),
|
|
219
|
-
...string(issue?.created_at) ? { createdAt: string(issue?.created_at) } : {},
|
|
220
|
-
...string(issue?.updated_at) ? { updatedAt: string(issue?.updated_at) } : {},
|
|
221
|
-
assignees: actorLogins(issue?.assignees),
|
|
222
|
-
labels: labelNames(issue?.labels),
|
|
223
|
-
...string(issue?.state) === "closed" || string(issue?.state) === "open" ? { state: string(issue?.state) } : {},
|
|
224
|
-
...string(issue?.state_reason) ? { stateReason: string(issue?.state_reason) } : {}
|
|
225
|
-
} } : {},
|
|
226
|
-
...event === "issueEdited" ? { issueChange: {
|
|
227
|
-
title: Boolean(object(changes?.title)),
|
|
228
|
-
body: Boolean(object(changes?.body))
|
|
229
|
-
} } : {},
|
|
230
|
-
...number(issueComment?.id) ? { issueComment: {
|
|
231
|
-
id: number(issueComment?.id),
|
|
232
|
-
...string(issueComment?.body) ? { body: string(issueComment?.body) } : {},
|
|
233
|
-
...string(issueComment?.html_url) ? { url: string(issueComment?.html_url) } : {},
|
|
234
|
-
...string(object(issueComment?.user)?.login) ? { author: string(object(issueComment?.user)?.login) } : {},
|
|
235
|
-
...string(object(issueComment?.user)?.type) ? { authorType: string(object(issueComment?.user)?.type) } : {},
|
|
236
|
-
...string(issueComment?.created_at) ? { createdAt: string(issueComment?.created_at) } : {},
|
|
237
|
-
...string(issueComment?.updated_at) ? { updatedAt: string(issueComment?.updated_at) } : {}
|
|
238
|
-
} } : {},
|
|
239
|
-
...pullRequestNumber && string(pullRequest?.title) && string(pullRequest?.html_url) ? { pullRequest: {
|
|
240
|
-
number: pullRequestNumber,
|
|
241
|
-
title: string(pullRequest?.title),
|
|
242
|
-
url: string(pullRequest?.html_url),
|
|
243
|
-
...string(pullRequest?.created_at) ? { createdAt: string(pullRequest?.created_at) } : {},
|
|
244
|
-
state: string(pullRequest?.state) === "closed" ? "closed" : "open",
|
|
245
|
-
draft: boolean(pullRequest?.draft) ?? false,
|
|
246
|
-
merged: boolean(pullRequest?.merged) ?? false,
|
|
247
|
-
assignees: actorLogins(pullRequest?.assignees),
|
|
248
|
-
requestedReviewers: actorLogins(pullRequest?.requested_reviewers),
|
|
249
|
-
labels: labelNames(pullRequest?.labels),
|
|
250
|
-
headBranch: string(object(pullRequest?.head)?.ref) ?? "",
|
|
251
|
-
baseBranch: string(object(pullRequest?.base)?.ref) ?? ""
|
|
252
|
-
} } : {},
|
|
253
|
-
...reviewRequested && requestedReviewer ? { reviewRequest: {
|
|
254
|
-
reviewer: requestedReviewer,
|
|
255
|
-
factoryReviewer: this.#isFactoryLogin(requestedReviewer)
|
|
256
|
-
} } : {},
|
|
257
|
-
...object(parsed.payload.review) ? { review: {
|
|
258
|
-
id: number(object(parsed.payload.review)?.id) ?? 0,
|
|
259
|
-
state: string(object(parsed.payload.review)?.state) ?? "unknown",
|
|
260
|
-
url: string(object(parsed.payload.review)?.html_url) ?? ""
|
|
261
|
-
} } : {}
|
|
294
|
+
ruleSetVersion: this.options.rules.version,
|
|
295
|
+
expectedRevision: item?.revision ?? null,
|
|
296
|
+
actor: { ...actor },
|
|
297
|
+
outcome,
|
|
298
|
+
decisions,
|
|
299
|
+
causalChain: [],
|
|
300
|
+
now: /* @__PURE__ */ new Date()
|
|
301
|
+
})).status };
|
|
262
302
|
};
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
factoryProjectId: project.factoryProjectId,
|
|
286
|
-
workItemId: relatedItem?.id ?? null,
|
|
287
|
-
ingress: {
|
|
288
|
-
identity: `${installationId}:${parsed.deliveryId}`,
|
|
289
|
-
triggerType: `github.${event}`
|
|
290
|
-
},
|
|
291
|
-
ruleSetVersion: this.options.rules.version,
|
|
292
|
-
expectedRevision: relatedItem?.revision ?? null,
|
|
293
|
-
actor: { ...actor },
|
|
294
|
-
outcome,
|
|
295
|
-
decisions,
|
|
296
|
-
causalChain: [],
|
|
297
|
-
now: /* @__PURE__ */ new Date()
|
|
298
|
-
})).status };
|
|
303
|
+
const deliveryIdentity = `${installationId}:${parsed.deliveryId}`;
|
|
304
|
+
const primary = await evaluate(relatedItem, deliveryIdentity);
|
|
305
|
+
const linked = event === "pullRequestMerged" && relatedItem && pullRequestNumber ? await this.#linkedClosureItem(project.orgId, project.factoryProjectId, repositoryId, repositoryName, pullRequestNumber, relatedItem) : void 0;
|
|
306
|
+
if (!linked) return primary;
|
|
307
|
+
const secondary = await evaluate(linked, `${deliveryIdentity}:${linked.id}`);
|
|
308
|
+
for (const status of ["committed", "replayed"]) if (primary.status === status || secondary.status === status) return { status };
|
|
309
|
+
return primary;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* The other card a closed pull request concerns, joined through the Review
|
|
313
|
+
* card's `parentWorkItemId` — the link `upsertLinkedWorkItem` records when the
|
|
314
|
+
* pull request is opened, and therefore an exact join that needs no branch
|
|
315
|
+
* heuristics. Returns nothing when the pull request has only one card, which
|
|
316
|
+
* is every pull request Factory did not open from a work item's session.
|
|
317
|
+
*/
|
|
318
|
+
async #linkedClosureItem(orgId, projectId, repositoryId, repositoryFullName, pullRequestNumber, resolved) {
|
|
319
|
+
const items = await this.options.storage.list({
|
|
320
|
+
orgId,
|
|
321
|
+
factoryProjectId: projectId
|
|
322
|
+
});
|
|
323
|
+
const linked = resolved.externalSource?.type === "pull-request" ? items.find((item) => item.id === resolved.parentWorkItemId) : items.find((item) => item.parentWorkItemId === resolved.id && (item.externalSource?.externalId === canonicalSourceKey("pull-request", pullRequestNumber) || item.externalSource?.externalId === legacySourceKey(repositoryId, "pull-request", pullRequestNumber)) && cardBelongsToRepository(item, repositoryId, repositoryFullName));
|
|
324
|
+
return linked?.id === resolved.id ? void 0 : linked;
|
|
299
325
|
}
|
|
300
326
|
async #relatedItem(orgId, projectId, repositoryId, repositoryFullName, issueNumber, pullRequestNumber, pullRequestHeadBranch, provenance, preferAuthoringItem = false) {
|
|
301
327
|
const items = await this.options.storage.list({
|
|
@@ -418,6 +444,22 @@ function reconciledClosedEvent(repository, pullRequestNumber, state) {
|
|
|
418
444
|
}
|
|
419
445
|
};
|
|
420
446
|
}
|
|
447
|
+
function reconciledPullRequestMetadata(state, reconciliation) {
|
|
448
|
+
return {
|
|
449
|
+
state: state.state,
|
|
450
|
+
draft: state.draft,
|
|
451
|
+
merged: state.merged,
|
|
452
|
+
...state.author ? { author: state.author } : {},
|
|
453
|
+
...state.assignees ? { assignees: state.assignees } : {},
|
|
454
|
+
...state.requestedReviewers ? { requestedReviewers: state.requestedReviewers } : {},
|
|
455
|
+
...state.labels ? { labels: state.labels } : {},
|
|
456
|
+
[FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: reconciliation === "settled" ? state.merged ? "merged" : "closed" : null
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function reconciledPullRequestOutcome(metadata) {
|
|
460
|
+
if (metadata.state !== "closed" || typeof metadata.merged !== "boolean") return void 0;
|
|
461
|
+
return metadata.merged ? "merged" : "closed";
|
|
462
|
+
}
|
|
421
463
|
/**
|
|
422
464
|
* The webhook handler retires subscriptions itself; the sweep replays only the
|
|
423
465
|
* rules ingress, so without this the thread's PR chip and the workspace row
|
|
@@ -479,8 +521,9 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
479
521
|
const pullRequestNumber = reconcilablePullRequestNumber(item, repository);
|
|
480
522
|
if (!pullRequestNumber) continue;
|
|
481
523
|
const metadata = item.metadata ?? {};
|
|
482
|
-
const
|
|
483
|
-
|
|
524
|
+
const reconciliation = metadata[FACTORY_PULL_REQUEST_RECONCILIATION_KEY];
|
|
525
|
+
const reconciledOutcome = reconciledPullRequestOutcome(metadata);
|
|
526
|
+
if ((stage === "done" || stage === "canceled") && reconciledOutcome !== void 0 && reconciliation === reconciledOutcome) continue;
|
|
484
527
|
const cards = cardsByNumber.get(pullRequestNumber) ?? [];
|
|
485
528
|
cards.push(item);
|
|
486
529
|
cardsByNumber.set(pullRequestNumber, cards);
|
|
@@ -499,35 +542,58 @@ function createGithubPullRequestReconciler(options, fetchPullRequest) {
|
|
|
499
542
|
summary.checked += 1;
|
|
500
543
|
if (!state) continue;
|
|
501
544
|
for (const card of cards) {
|
|
545
|
+
if (state.state === "closed") continue;
|
|
502
546
|
const metadata = card.metadata ?? {};
|
|
503
547
|
const statusChanged = metadata.state !== state.state || metadata.draft !== state.draft || metadata.merged !== state.merged;
|
|
504
548
|
const authorChanged = state.author !== void 0 && metadata.author !== state.author;
|
|
505
549
|
const assigneesChanged = !sameStrings(metadata.assignees, state.assignees);
|
|
506
550
|
const reviewersChanged = !sameStrings(metadata.requestedReviewers, state.requestedReviewers);
|
|
507
551
|
const labelsChanged = !sameStrings(metadata.labels, state.labels);
|
|
508
|
-
|
|
552
|
+
const metadataChanged = statusChanged || authorChanged || assigneesChanged || reviewersChanged || labelsChanged;
|
|
553
|
+
const reconciliation = metadata[FACTORY_PULL_REQUEST_RECONCILIATION_KEY];
|
|
554
|
+
if (!metadataChanged && reconciliation !== "merged" && reconciliation !== "closed") continue;
|
|
509
555
|
try {
|
|
510
556
|
await options.storage.update({
|
|
511
557
|
orgId: card.orgId,
|
|
512
558
|
id: card.id,
|
|
513
559
|
userId: "factory-rule-dispatcher",
|
|
514
|
-
patch: { metadata:
|
|
515
|
-
state: state.state,
|
|
516
|
-
draft: state.draft,
|
|
517
|
-
merged: state.merged,
|
|
518
|
-
...state.author ? { author: state.author } : {},
|
|
519
|
-
...state.assignees ? { assignees: state.assignees } : {},
|
|
520
|
-
...state.requestedReviewers ? { requestedReviewers: state.requestedReviewers } : {},
|
|
521
|
-
...state.labels ? { labels: state.labels } : {}
|
|
522
|
-
} }
|
|
560
|
+
patch: { metadata: reconciledPullRequestMetadata(state, "clear") }
|
|
523
561
|
});
|
|
524
562
|
} catch (error) {
|
|
525
563
|
recordFailure(repository, error, pullRequestNumber);
|
|
526
564
|
}
|
|
527
565
|
}
|
|
528
566
|
if (state.state !== "closed") continue;
|
|
567
|
+
const cleanupFailures = /* @__PURE__ */ new Set();
|
|
568
|
+
for (const card of cards) {
|
|
569
|
+
if (!isTerminalFactoryRuleStage(card.stages)) continue;
|
|
570
|
+
try {
|
|
571
|
+
await options.storage.supersedeDecisionsForWorkItem({
|
|
572
|
+
orgId: card.orgId,
|
|
573
|
+
factoryProjectId: card.factoryProjectId,
|
|
574
|
+
workItemId: card.id,
|
|
575
|
+
supersededAt: /* @__PURE__ */ new Date()
|
|
576
|
+
});
|
|
577
|
+
} catch (error) {
|
|
578
|
+
recordFailure(repository, error, pullRequestNumber);
|
|
579
|
+
cleanupFailures.add(card.id);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
529
582
|
await rules.ingest(reconciledClosedEvent(repository, pullRequestNumber, state));
|
|
530
583
|
await retireReconciledSubscriptions(options.integrationStorage, repository, pullRequestNumber, state.merged);
|
|
584
|
+
for (const card of cards) {
|
|
585
|
+
if (cleanupFailures.has(card.id)) continue;
|
|
586
|
+
try {
|
|
587
|
+
await options.storage.update({
|
|
588
|
+
orgId: card.orgId,
|
|
589
|
+
id: card.id,
|
|
590
|
+
userId: "factory-rule-dispatcher",
|
|
591
|
+
patch: { metadata: reconciledPullRequestMetadata(state, "settled") }
|
|
592
|
+
});
|
|
593
|
+
} catch (error) {
|
|
594
|
+
recordFailure(repository, error, pullRequestNumber);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
531
597
|
if (state.merged) summary.merged += 1;
|
|
532
598
|
else summary.closed += 1;
|
|
533
599
|
} catch (error) {
|