@mastra/factory 0.13.0-alpha.6 → 0.13.0-alpha.8
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/README.md +153 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +15 -1
- package/dist/auth.js.map +1 -1
- package/dist/boards/define-board.d.ts +3 -0
- package/dist/boards/define-board.d.ts.map +1 -1
- package/dist/boards/define-board.js +2 -0
- package/dist/boards/define-board.js.map +1 -1
- package/dist/boards/index.d.ts +5 -3
- package/dist/boards/index.d.ts.map +1 -1
- package/dist/boards/index.js +2 -1
- package/dist/boards/index.js.map +1 -1
- package/dist/boards/registry.d.ts +16 -0
- package/dist/boards/registry.d.ts.map +1 -0
- package/dist/boards/registry.js +27 -0
- package/dist/boards/registry.js.map +1 -0
- package/dist/boards/review.js +1 -1
- package/dist/boards/transition-policy.d.ts +63 -0
- package/dist/boards/transition-policy.d.ts.map +1 -0
- package/dist/boards/transition-policy.js +36 -0
- package/dist/boards/transition-policy.js.map +1 -0
- package/dist/boards/work-transition-policy.d.ts +3 -0
- package/dist/boards/work-transition-policy.d.ts.map +1 -0
- package/dist/boards/work-transition-policy.js +31 -0
- package/dist/boards/work-transition-policy.js.map +1 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +4 -2
- package/dist/boards/work.js.map +1 -1
- package/dist/factory.d.ts +6 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +10 -0
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/integrations/github/default-rules.d.ts +194 -0
- package/dist/integrations/github/default-rules.d.ts.map +1 -0
- package/dist/integrations/github/default-rules.js +230 -0
- package/dist/integrations/github/default-rules.js.map +1 -0
- package/dist/integrations/github/integration.d.ts +4 -0
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +6 -0
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/routes.js +1 -1
- package/dist/integrations/github/rules.d.ts +3 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +1 -2
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/webhook.js +1 -1
- package/dist/integrations/linear/default-rules.d.ts +44 -0
- package/dist/integrations/linear/default-rules.d.ts.map +1 -0
- package/dist/integrations/linear/default-rules.js +65 -0
- package/dist/integrations/linear/default-rules.js.map +1 -0
- package/dist/integrations/linear/integration.d.ts +5 -1
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +7 -1
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/issue-reconciler.d.ts +1 -1
- package/dist/integrations/linear/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/linear/issue-reconciler.js +2 -1
- package/dist/integrations/linear/issue-reconciler.js.map +1 -1
- package/dist/integrations/linear/rules.d.ts +5 -1
- package/dist/integrations/linear/rules.d.ts.map +1 -1
- package/dist/integrations/linear/rules.js +4 -4
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +4 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +6 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/integration.d.ts +5 -1
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +9 -3
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +3 -1
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +20 -2
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/routes/surface.d.ts +3 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +2 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts +3 -0
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +48 -15
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +2 -325
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.js +1 -1
- package/dist/rules/index.d.ts +2 -2
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +2 -2
- package/dist/rules/processor.d.ts +2 -0
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +9 -7
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/resolve.d.ts +4 -5
- package/dist/rules/resolve.d.ts.map +1 -1
- package/dist/rules/resolve.js +4 -9
- package/dist/rules/resolve.js.map +1 -1
- package/dist/rules/transition-service.d.ts +2 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +46 -28
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +2 -17
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +2 -38
- package/dist/rules/validation.js.map +1 -1
- package/dist/storage/domains/comments/domain.js +1 -1
- package/dist/storage/domains/comments/routes.js +1 -1
- package/dist/storage/domains/work-items/base.d.ts +11 -1
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +22 -2
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/factory-skills/configure-factory-rules/SKILL.md +42 -11
- package/package.json +13 -7
package/dist/rules/defaults.js
CHANGED
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
import { FactoryRuleValidationError, assertFactoryRules } from "./validation.js";
|
|
2
|
-
import { reviewBoard } from "../boards/review.js";
|
|
3
|
-
import { workBoard } from "../boards/work.js";
|
|
4
2
|
//#region src/rules/defaults.ts
|
|
5
3
|
const DEFAULT_FACTORY_RULE_VERSION = "factory-default-v1";
|
|
6
|
-
function trustedGithubActor(context) {
|
|
7
|
-
return context.actor.type === "github" && context.actor.trusted;
|
|
8
|
-
}
|
|
9
|
-
function githubActorLogin(context) {
|
|
10
|
-
return context.actor.type === "github" ? context.actor.login : void 0;
|
|
11
|
-
}
|
|
12
|
-
function retriageGithubIssue(context) {
|
|
13
|
-
if (!context.item || context.item.source !== "github-issue" || !context.item.url) return;
|
|
14
|
-
if (context.actor.type === "github" && context.actor.factoryAuthored) return;
|
|
15
|
-
const reason = context.event === "issueEdited" ? context.issueChange?.title && context.issueChange.body ? "issue title and body edited" : context.issueChange?.title ? "issue title edited" : "issue body edited" : context.event === "issueCommentDeleted" ? "comment deleted" : context.event === "issueCommentEdited" ? "comment edited" : "comment created";
|
|
16
|
-
return {
|
|
17
|
-
type: "invokeSkill",
|
|
18
|
-
idempotencyKey: `${context.ingress.id}:factory-triage`,
|
|
19
|
-
role: "triage",
|
|
20
|
-
skillName: "factory-triage",
|
|
21
|
-
arguments: `Re-triage GitHub issue (${context.item.url}) after ${reason}.`
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
4
|
function resultContent(value) {
|
|
25
5
|
if (typeof value === "string") return value;
|
|
26
6
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
@@ -36,278 +16,7 @@ function advanceApprovedPlan(context) {
|
|
|
36
16
|
stage: "execute"
|
|
37
17
|
};
|
|
38
18
|
}
|
|
39
|
-
|
|
40
|
-
if (!createdAt) return false;
|
|
41
|
-
const sourceCreatedAt = Date.parse(createdAt);
|
|
42
|
-
const projectCreatedAt = Date.parse(factoryCreatedAt);
|
|
43
|
-
return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;
|
|
44
|
-
}
|
|
45
|
-
function issueOpened(context) {
|
|
46
|
-
if (!context.issue) return;
|
|
47
|
-
return {
|
|
48
|
-
type: "upsertLinkedWorkItem",
|
|
49
|
-
idempotencyKey: `${context.ingress.id}:issue-intake`,
|
|
50
|
-
board: "work",
|
|
51
|
-
source: "github-issue",
|
|
52
|
-
sourceKey: `github-issue:${context.issue.number}`,
|
|
53
|
-
title: context.issue.title,
|
|
54
|
-
url: context.issue.url,
|
|
55
|
-
stage: "intake",
|
|
56
|
-
metadata: {
|
|
57
|
-
githubRepositoryId: context.repository.id,
|
|
58
|
-
githubIssueNumber: context.issue.number,
|
|
59
|
-
...context.issue.createdAt ? { sourceCreatedAt: context.issue.createdAt } : {},
|
|
60
|
-
...githubActorLogin(context) ? { author: githubActorLogin(context) } : {},
|
|
61
|
-
authorTrusted: trustedGithubActor(context),
|
|
62
|
-
autoStartCandidate: trustedGithubActor(context) && createdAfterFactory(context.issue.createdAt, context.factory.createdAt),
|
|
63
|
-
assignees: context.issue.assignees ?? [],
|
|
64
|
-
labels: context.issue.labels ?? []
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function issueClosed(context) {
|
|
69
|
-
if (!context.item || context.item.source !== "github-issue" || !context.issue) return;
|
|
70
|
-
if (context.board !== "work") return;
|
|
71
|
-
if (context.item.stages.some((stage) => stage === "done" || stage === "canceled")) return;
|
|
72
|
-
const canceled = context.issue.stateReason === "not_planned" || context.issue.stateReason === "duplicate";
|
|
73
|
-
return {
|
|
74
|
-
type: "transition",
|
|
75
|
-
idempotencyKey: `${context.ingress.id}:issue-closed`,
|
|
76
|
-
board: "work",
|
|
77
|
-
stage: canceled ? "canceled" : "done",
|
|
78
|
-
message: { text: `GitHub issue #${context.issue.number} was closed${context.issue.stateReason ? ` (${context.issue.stateReason})` : ""}; this Work card was moved to ${canceled ? "Canceled" : "Done"}.` }
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function materializePullRequestIntake(context, { idempotencyKey, autoStartCandidate }) {
|
|
82
|
-
if (!context.pullRequest) return;
|
|
83
|
-
return {
|
|
84
|
-
type: "upsertLinkedWorkItem",
|
|
85
|
-
idempotencyKey,
|
|
86
|
-
board: "review",
|
|
87
|
-
source: "github-pr",
|
|
88
|
-
sourceKey: `github-pr:${context.pullRequest.number}`,
|
|
89
|
-
title: context.pullRequest.title,
|
|
90
|
-
url: context.pullRequest.url,
|
|
91
|
-
stage: "intake",
|
|
92
|
-
metadata: {
|
|
93
|
-
githubRepositoryId: context.repository.id,
|
|
94
|
-
githubPullRequestNumber: context.pullRequest.number,
|
|
95
|
-
...context.pullRequest.createdAt ? { sourceCreatedAt: context.pullRequest.createdAt } : {},
|
|
96
|
-
factoryAuthored: context.pullRequest.factoryAuthored,
|
|
97
|
-
authorTrusted: trustedGithubActor(context),
|
|
98
|
-
autoStartCandidate,
|
|
99
|
-
state: context.pullRequest.state,
|
|
100
|
-
draft: context.pullRequest.draft,
|
|
101
|
-
merged: context.pullRequest.merged,
|
|
102
|
-
assignees: context.pullRequest.assignees ?? [],
|
|
103
|
-
requestedReviewers: context.pullRequest.requestedReviewers ?? [],
|
|
104
|
-
labels: context.pullRequest.labels ?? [],
|
|
105
|
-
headBranch: context.pullRequest.headBranch,
|
|
106
|
-
baseBranch: context.pullRequest.baseBranch,
|
|
107
|
-
...context.pullRequest.author ? { author: context.pullRequest.author } : {}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
function pullRequestOpened(context) {
|
|
112
|
-
if (!context.pullRequest) return;
|
|
113
|
-
const autoStartCandidate = (trustedGithubActor(context) || context.pullRequest.factoryAuthored) && createdAfterFactory(context.pullRequest.createdAt, context.factory.createdAt);
|
|
114
|
-
return materializePullRequestIntake(context, {
|
|
115
|
-
idempotencyKey: `${context.ingress.id}:pull-request-intake`,
|
|
116
|
-
autoStartCandidate
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
function pullRequestMerged(context) {
|
|
120
|
-
if (!context.item || !context.pullRequest?.merged) return;
|
|
121
|
-
if (context.board === "review") return {
|
|
122
|
-
type: "transition",
|
|
123
|
-
idempotencyKey: `${context.ingress.id}:pull-request-merged`,
|
|
124
|
-
board: "review",
|
|
125
|
-
stage: "done",
|
|
126
|
-
message: { text: `Pull request #${context.pullRequest.number} merged; this Review card was moved to Done. No further review is needed unless follow-up work was requested.` }
|
|
127
|
-
};
|
|
128
|
-
return {
|
|
129
|
-
type: "sendMessage",
|
|
130
|
-
idempotencyKey: `${context.ingress.id}:assess-work-completion`,
|
|
131
|
-
role: "work",
|
|
132
|
-
message: `Pull request #${context.pullRequest.number} merged. Assess whether the linked Work item is complete. Do not mark it Done solely because this PR merged; use factory_transition_work_item only after verifying the work.`
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
function addressReviewFeedback(context) {
|
|
136
|
-
if (!context.item || !context.pullRequest || !context.review) return;
|
|
137
|
-
if (context.board !== "work") return;
|
|
138
|
-
if (context.pullRequest.state !== "open" || context.pullRequest.merged) return;
|
|
139
|
-
if (context.review.state.toLowerCase() !== "changes_requested") return;
|
|
140
|
-
return {
|
|
141
|
-
type: "sendMessage",
|
|
142
|
-
idempotencyKey: `${context.ingress.id}:address-review-feedback`,
|
|
143
|
-
role: "work",
|
|
144
|
-
priority: "high",
|
|
145
|
-
message: `Changes were requested on pull request #${context.pullRequest.number} (${context.review.url}). Read the review comments on this PR, address the ones you agree with, and push the fixes to the PR branch. Reply on GitHub to anything you are deliberately not changing, explaining why.`
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Detects the `factory-review` handoff verdict in a comment body.
|
|
150
|
-
*
|
|
151
|
-
* GitHub forbids an app from reviewing a pull request it authored, so on
|
|
152
|
-
* Factory-authored PRs the review skill falls back to posting its verdict as a
|
|
153
|
-
* plain comment. That comment is the only signal the authoring agent gets, so
|
|
154
|
-
* it has to be readable back out. The skill's handoff contract puts the verdict
|
|
155
|
-
* on the first line (`Verdict: request changes`), so only that line is
|
|
156
|
-
* inspected — a verdict quoted later in the findings must not count.
|
|
157
|
-
*/
|
|
158
|
-
function requestsChangesVerdict(body) {
|
|
159
|
-
const firstLine = body?.split("\n").map((line) => line.trim()).find((line) => line.length > 0);
|
|
160
|
-
if (!firstLine) return false;
|
|
161
|
-
const normalized = firstLine.replaceAll(/[*_`#>\s]+/g, " ").trim().toLowerCase();
|
|
162
|
-
return /^verdict: ?(request changes|changes requested)$/.test(normalized);
|
|
163
|
-
}
|
|
164
|
-
function addressPullRequestComment(context) {
|
|
165
|
-
if (context.reviewCommand) return reReviewRequestedPullRequest(context);
|
|
166
|
-
if (!context.item || !context.pullRequest || !context.issueComment) return;
|
|
167
|
-
if (context.board !== "work") return;
|
|
168
|
-
if (context.pullRequest.state !== "open" || context.pullRequest.merged) return;
|
|
169
|
-
if (context.actor.type === "github" && context.actor.factoryAuthored && !requestsChangesVerdict(context.issueComment.body)) return;
|
|
170
|
-
return {
|
|
171
|
-
type: "sendMessage",
|
|
172
|
-
idempotencyKey: `${context.ingress.id}:address-pull-request-comment`,
|
|
173
|
-
role: "work",
|
|
174
|
-
priority: "high",
|
|
175
|
-
message: `${context.issueComment.author ?? "Someone"} commented on pull request #${context.pullRequest.number} (${context.issueComment.url ?? context.pullRequest.url}). Read the comment, address it if you agree, and push the fixes to the PR branch. Reply on GitHub to anything you are deliberately not changing, explaining why.`
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function pullRequestClosed(context) {
|
|
179
|
-
if (!context.item || !context.pullRequest || context.pullRequest.merged) return;
|
|
180
|
-
if (context.board !== "review") return;
|
|
181
|
-
return {
|
|
182
|
-
type: "transition",
|
|
183
|
-
idempotencyKey: `${context.ingress.id}:pull-request-closed`,
|
|
184
|
-
board: "review",
|
|
185
|
-
stage: "canceled",
|
|
186
|
-
message: { text: `Pull request #${context.pullRequest.number} was closed without merging; this Review card was moved to Canceled.` }
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
function reReviewRequestedPullRequest(context) {
|
|
190
|
-
const factoryReviewEntry = context.reviewRequest?.factoryReviewer || context.reviewCommand !== void 0;
|
|
191
|
-
if (context.item && context.board !== "review" || !factoryReviewEntry) return;
|
|
192
|
-
if (!context.pullRequest || context.pullRequest.state !== "open" || context.pullRequest.merged) return;
|
|
193
|
-
if (!trustedGithubActor(context)) return;
|
|
194
|
-
if (context.actor.type === "github" && context.actor.factoryAuthored) return;
|
|
195
|
-
if (!context.item) return materializePullRequestIntake(context, {
|
|
196
|
-
idempotencyKey: `${context.ingress.id}:pull-request-review-requested-intake`,
|
|
197
|
-
autoStartCandidate: true
|
|
198
|
-
});
|
|
199
|
-
if (context.item.stages.length === 1 && context.item.stages[0] === "review") return;
|
|
200
|
-
return {
|
|
201
|
-
type: "transition",
|
|
202
|
-
idempotencyKey: `${context.ingress.id}:re-review-requested`,
|
|
203
|
-
board: "review",
|
|
204
|
-
stage: "review"
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
function reReviewUpdatedPullRequest(context) {
|
|
208
|
-
if (!context.item || context.board !== "review") return;
|
|
209
|
-
if (!context.pullRequest || context.pullRequest.state !== "open" || context.pullRequest.merged) return;
|
|
210
|
-
if (context.item.stages.some((stage) => stage === "intake")) return;
|
|
211
|
-
const alreadyReviewing = context.item.stages.some((stage) => stage === "review");
|
|
212
|
-
return {
|
|
213
|
-
type: "transition",
|
|
214
|
-
idempotencyKey: `${context.ingress.id}:re-review-updated`,
|
|
215
|
-
board: "review",
|
|
216
|
-
stage: "review",
|
|
217
|
-
...alreadyReviewing ? { reenter: true } : {}
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
function linearIssueObserved(context) {
|
|
221
|
-
if (context.item) return;
|
|
222
|
-
return {
|
|
223
|
-
type: "upsertLinkedWorkItem",
|
|
224
|
-
idempotencyKey: `${context.ingress.id}:issue-triage`,
|
|
225
|
-
board: "work",
|
|
226
|
-
source: "linear-issue",
|
|
227
|
-
sourceKey: `linear:${context.issue.identifier}`,
|
|
228
|
-
title: `${context.issue.identifier}: ${context.issue.title}`,
|
|
229
|
-
url: context.issue.url,
|
|
230
|
-
stage: "triage",
|
|
231
|
-
metadata: {
|
|
232
|
-
linearIssueId: context.issue.id,
|
|
233
|
-
identifier: context.issue.identifier,
|
|
234
|
-
sourceCreatedAt: context.issue.createdAt,
|
|
235
|
-
linearState: context.issue.state,
|
|
236
|
-
linearStateType: context.issue.stateType,
|
|
237
|
-
linearPriority: context.issue.priorityLabel,
|
|
238
|
-
linearAssignee: context.issue.assignee,
|
|
239
|
-
linearCreator: context.issue.creator,
|
|
240
|
-
linearTeam: context.issue.team,
|
|
241
|
-
labels: [...context.issue.labels],
|
|
242
|
-
...context.issue.assignee ? { assignee: context.issue.assignee } : {},
|
|
243
|
-
...context.issue.creator ? {
|
|
244
|
-
creator: context.issue.creator,
|
|
245
|
-
author: context.issue.creator
|
|
246
|
-
} : {}
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
function linearIssueClosed(context) {
|
|
251
|
-
if (!context.item || context.item.source !== "linear-issue") return;
|
|
252
|
-
if (context.board !== "work") return;
|
|
253
|
-
if (context.item.stages.some((stage) => stage === "done" || stage === "canceled")) return;
|
|
254
|
-
const stateType = context.issue.stateType;
|
|
255
|
-
if (stateType !== "completed" && stateType !== "canceled") return;
|
|
256
|
-
const canceled = stateType === "canceled";
|
|
257
|
-
return {
|
|
258
|
-
type: "transition",
|
|
259
|
-
idempotencyKey: `${context.ingress.id}:issue-closed`,
|
|
260
|
-
board: "work",
|
|
261
|
-
stage: canceled ? "canceled" : "done",
|
|
262
|
-
message: { text: `Linear issue ${context.issue.identifier} was ${canceled ? "canceled" : "completed"}; this Work card was moved to ${canceled ? "Canceled" : "Done"}.` }
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
const BUILT_IN_DEFAULTS = {
|
|
266
|
-
work: workBoard.rules,
|
|
267
|
-
review: reviewBoard.rules,
|
|
268
|
-
tools: { submit_plan: { onResult: advanceApprovedPlan } },
|
|
269
|
-
github: {
|
|
270
|
-
issueOpened: { onEvent: issueOpened },
|
|
271
|
-
issueEdited: { onEvent: retriageGithubIssue },
|
|
272
|
-
issueClosed: { onEvent: issueClosed },
|
|
273
|
-
issueCommentCreated: { onEvent: retriageGithubIssue },
|
|
274
|
-
issueCommentEdited: { onEvent: retriageGithubIssue },
|
|
275
|
-
issueCommentDeleted: { onEvent: retriageGithubIssue },
|
|
276
|
-
pullRequestOpened: { onEvent: pullRequestOpened },
|
|
277
|
-
pullRequestUpdated: { onEvent: reReviewUpdatedPullRequest },
|
|
278
|
-
pullRequestCommentCreated: { onEvent: addressPullRequestComment },
|
|
279
|
-
pullRequestReviewRequested: { onEvent: reReviewRequestedPullRequest },
|
|
280
|
-
pullRequestReviewSubmitted: { onEvent: addressReviewFeedback },
|
|
281
|
-
pullRequestMerged: { onEvent: pullRequestMerged },
|
|
282
|
-
pullRequestClosed: { onEvent: pullRequestClosed }
|
|
283
|
-
},
|
|
284
|
-
linear: {
|
|
285
|
-
issueObserved: { onEvent: linearIssueObserved },
|
|
286
|
-
issueClosed: { onEvent: linearIssueClosed }
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
function mergeBoardRules(base, overrides) {
|
|
290
|
-
const result = {};
|
|
291
|
-
const stages = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]);
|
|
292
|
-
for (const stage of stages) {
|
|
293
|
-
const baseSources = base?.[stage];
|
|
294
|
-
const overrideSources = overrides?.[stage];
|
|
295
|
-
const sources = /* @__PURE__ */ new Set([...Object.keys(baseSources ?? {}), ...Object.keys(overrideSources ?? {})]);
|
|
296
|
-
const mergedSources = {};
|
|
297
|
-
for (const source of sources) {
|
|
298
|
-
const baseLeaf = baseSources?.[source];
|
|
299
|
-
const overrideLeaf = overrideSources?.[source];
|
|
300
|
-
mergedSources[source] = {
|
|
301
|
-
...baseLeaf && "onEnter" in baseLeaf ? { onEnter: baseLeaf.onEnter } : {},
|
|
302
|
-
...baseLeaf && "onExit" in baseLeaf ? { onExit: baseLeaf.onExit } : {},
|
|
303
|
-
...overrideLeaf && "onEnter" in overrideLeaf ? { onEnter: overrideLeaf.onEnter } : {},
|
|
304
|
-
...overrideLeaf && "onExit" in overrideLeaf ? { onExit: overrideLeaf.onExit } : {}
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
result[stage] = mergedSources;
|
|
308
|
-
}
|
|
309
|
-
return result;
|
|
310
|
-
}
|
|
19
|
+
const BUILT_IN_DEFAULTS = { tools: { submit_plan: { onResult: advanceApprovedPlan } } };
|
|
311
20
|
function mergeToolRules(base, overrides) {
|
|
312
21
|
const result = {};
|
|
313
22
|
for (const name of /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {
|
|
@@ -320,40 +29,8 @@ function mergeToolRules(base, overrides) {
|
|
|
320
29
|
}
|
|
321
30
|
return result;
|
|
322
31
|
}
|
|
323
|
-
function mergeGithubRules(base, overrides) {
|
|
324
|
-
const result = {};
|
|
325
|
-
const events = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]);
|
|
326
|
-
for (const event of events) {
|
|
327
|
-
const baseLeaf = base?.[event];
|
|
328
|
-
const overrideLeaf = overrides?.[event];
|
|
329
|
-
result[event] = {
|
|
330
|
-
...baseLeaf && "onEvent" in baseLeaf ? { onEvent: baseLeaf.onEvent } : {},
|
|
331
|
-
...overrideLeaf && "onEvent" in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
return result;
|
|
335
|
-
}
|
|
336
|
-
function mergeLinearRules(base, overrides) {
|
|
337
|
-
const result = {};
|
|
338
|
-
const events = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]);
|
|
339
|
-
for (const event of events) {
|
|
340
|
-
const baseLeaf = base?.[event];
|
|
341
|
-
const overrideLeaf = overrides?.[event];
|
|
342
|
-
result[event] = {
|
|
343
|
-
...baseLeaf && "onEvent" in baseLeaf ? { onEvent: baseLeaf.onEvent } : {},
|
|
344
|
-
...overrideLeaf && "onEvent" in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
return result;
|
|
348
|
-
}
|
|
349
32
|
function mergeFactoryRuleOverrides(base, overrides = {}) {
|
|
350
|
-
return {
|
|
351
|
-
work: mergeBoardRules(base.work, overrides.work),
|
|
352
|
-
review: mergeBoardRules(base.review, overrides.review),
|
|
353
|
-
tools: mergeToolRules(base.tools, overrides.tools),
|
|
354
|
-
github: mergeGithubRules(base.github, overrides.github),
|
|
355
|
-
linear: mergeLinearRules(base.linear, overrides.linear)
|
|
356
|
-
};
|
|
33
|
+
return { tools: mergeToolRules(base.tools, overrides.tools) };
|
|
357
34
|
}
|
|
358
35
|
function defaultFactoryRules(input) {
|
|
359
36
|
if (typeof input?.version !== "string" || input.version.trim().length === 0) throw new FactoryRuleValidationError("Factory rule version is required.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","names":[],"sources":["../../src/rules/defaults.ts"],"sourcesContent":["import { reviewBoard } from '../boards/review.js';\nimport { workBoard } from '../boards/work.js';\nimport type {\n FactoryBoardRuleLeaf,\n FactoryBoardRules,\n FactoryGithubRuleLeaf,\n FactoryGithubEventName,\n FactoryGithubRuleContext,\n FactoryLinearEventName,\n FactoryLinearRuleContext,\n FactoryLinearRuleLeaf,\n FactoryRules,\n FactoryRulesOverrides,\n FactoryRuleSource,\n FactoryRuleStage,\n FactoryToolResultRuleContext,\n FactoryToolRuleLeaf,\n} from './types.js';\nimport { assertFactoryRules, FactoryRuleValidationError } from './validation.js';\n\nexport const DEFAULT_FACTORY_RULE_VERSION = 'factory-default-v1';\n\nfunction trustedGithubActor(context: Pick<FactoryGithubRuleContext, 'actor'>): boolean {\n return context.actor.type === 'github' && context.actor.trusted;\n}\n\nfunction githubActorLogin(context: Pick<FactoryGithubRuleContext, 'actor'>): string | undefined {\n return context.actor.type === 'github' ? context.actor.login : undefined;\n}\n\nfunction retriageGithubIssue(context: FactoryGithubRuleContext) {\n if (!context.item || context.item.source !== 'github-issue' || !context.item.url) return;\n if (context.actor.type === 'github' && context.actor.factoryAuthored) return;\n\n const reason =\n context.event === 'issueEdited'\n ? context.issueChange?.title && context.issueChange.body\n ? 'issue title and body edited'\n : context.issueChange?.title\n ? 'issue title edited'\n : 'issue body edited'\n : context.event === 'issueCommentDeleted'\n ? 'comment deleted'\n : context.event === 'issueCommentEdited'\n ? 'comment edited'\n : 'comment created';\n return {\n type: 'invokeSkill',\n idempotencyKey: `${context.ingress.id}:factory-triage`,\n role: 'triage',\n skillName: 'factory-triage',\n arguments: `Re-triage GitHub issue (${context.item.url}) after ${reason}.`,\n } as const;\n}\n\nfunction resultContent(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const content = (value as { content?: unknown }).content;\n return typeof content === 'string' ? content : undefined;\n}\n\n// Interactive-session path only: factory-plan never calls submit_plan — it\n// advances planning → execute via factory_transition_work_item directly.\nfunction advanceApprovedPlan(context: FactoryToolResultRuleContext) {\n if (\n context.result.status !== 'success' ||\n context.board !== 'work' ||\n context.item.stages.length !== 1 ||\n context.item.stages[0] !== 'planning' ||\n context.actor.type !== 'agent' ||\n context.actor.role !== 'plan' ||\n !resultContent(context.result.value)?.startsWith('Plan approved.')\n ) {\n return;\n }\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:approved-plan`,\n board: 'work',\n stage: 'execute',\n } as const;\n}\n\nfunction createdAfterFactory(createdAt: string | undefined, factoryCreatedAt: string): boolean {\n if (!createdAt) return false;\n const sourceCreatedAt = Date.parse(createdAt);\n const projectCreatedAt = Date.parse(factoryCreatedAt);\n return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;\n}\n\nfunction issueOpened(context: FactoryGithubRuleContext) {\n if (!context.issue) return;\n // Everything arrives in Intake; arrival only stamps whether `onArrival` may\n // suggest this card's run without a person.\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-intake`,\n board: 'work',\n source: 'github-issue',\n sourceKey: `github-issue:${context.issue.number}`,\n title: context.issue.title,\n url: context.issue.url,\n stage: 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubIssueNumber: context.issue.number,\n ...(context.issue.createdAt ? { sourceCreatedAt: context.issue.createdAt } : {}),\n ...(githubActorLogin(context) ? { author: githubActorLogin(context) } : {}),\n authorTrusted: trustedGithubActor(context),\n autoStartCandidate:\n trustedGithubActor(context) && createdAfterFactory(context.issue.createdAt, context.factory.createdAt),\n assignees: context.issue.assignees ?? [],\n labels: context.issue.labels ?? [],\n },\n } as const;\n}\n\nfunction issueClosed(context: FactoryGithubRuleContext) {\n if (!context.item || context.item.source !== 'github-issue' || !context.issue) return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Issue closure is a repository fact, not third-party input — no actor trust\n // gate. `not_planned` (and `duplicate`) means abandoned, everything else is\n // completed work.\n const canceled = context.issue.stateReason === 'not_planned' || context.issue.stateReason === 'duplicate';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text:\n `GitHub issue #${context.issue.number} was closed` +\n `${context.issue.stateReason ? ` (${context.issue.stateReason})` : ''}; ` +\n `this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nfunction materializePullRequestIntake(\n context: FactoryGithubRuleContext,\n { idempotencyKey, autoStartCandidate }: { idempotencyKey: string; autoStartCandidate: boolean },\n) {\n if (!context.pullRequest) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey,\n board: 'review',\n source: 'github-pr',\n sourceKey: `github-pr:${context.pullRequest.number}`,\n title: context.pullRequest.title,\n url: context.pullRequest.url,\n stage: 'intake',\n metadata: {\n githubRepositoryId: context.repository.id,\n githubPullRequestNumber: context.pullRequest.number,\n ...(context.pullRequest.createdAt ? { sourceCreatedAt: context.pullRequest.createdAt } : {}),\n factoryAuthored: context.pullRequest.factoryAuthored,\n authorTrusted: trustedGithubActor(context),\n autoStartCandidate,\n state: context.pullRequest.state,\n draft: context.pullRequest.draft,\n merged: context.pullRequest.merged,\n assignees: context.pullRequest.assignees ?? [],\n requestedReviewers: context.pullRequest.requestedReviewers ?? [],\n labels: context.pullRequest.labels ?? [],\n headBranch: context.pullRequest.headBranch,\n baseBranch: context.pullRequest.baseBranch,\n ...(context.pullRequest.author ? { author: context.pullRequest.author } : {}),\n },\n } as const;\n}\n\nfunction pullRequestOpened(context: FactoryGithubRuleContext) {\n if (!context.pullRequest) return;\n // A GitHub App bot is never a collaborator, so Factory's own PRs score\n // untrusted; their authorship is the trust signal.\n const autoStartCandidate =\n (trustedGithubActor(context) || context.pullRequest.factoryAuthored) &&\n createdAfterFactory(context.pullRequest.createdAt, context.factory.createdAt);\n return materializePullRequestIntake(context, {\n idempotencyKey: `${context.ingress.id}:pull-request-intake`,\n autoStartCandidate,\n });\n}\n\nfunction pullRequestMerged(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest?.merged) return;\n if (context.board === 'review') {\n // The event is bound to the PR's own Review card: a merged PR is finished\n // review work, so always move the card to Done. The message only reaches\n // an active session (if any) — cards without one just move, instead of\n // failing retries against a binding that never existed.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-merged`,\n board: 'review',\n stage: 'done',\n message: {\n text:\n `Pull request #${context.pullRequest.number} merged; this Review card was moved to Done. ` +\n 'No further review is needed unless follow-up work was requested.',\n },\n } as const;\n }\n // Provenance bound the event to the originating Work item instead: remind\n // its agent to assess completion — never auto-complete the Work item.\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:assess-work-completion`,\n role: 'work',\n message:\n `Pull request #${context.pullRequest.number} merged. Assess whether the linked Work item is complete. ` +\n 'Do not mark it Done solely because this PR merged; use factory_transition_work_item only after verifying the work.',\n } as const;\n}\n\nfunction addressReviewFeedback(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || !context.review) return;\n // Only the Work item that authored the PR can act on the feedback. Provenance\n // binds the event there; a Review card seeing its own posted review must not\n // react to it (that would loop the reviewer against itself).\n if (context.board !== 'work') return;\n // A closed or merged pull request has no branch left to push fixes to.\n if (context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // `approved` needs no work, and `commented` (a review body with no verdict)\n // is how a reviewer leaves notes without blocking — only a verdict that asks\n // for changes should pull the author back in.\n if (context.review.state.toLowerCase() !== 'changes_requested') return;\n // The authoring thread is already subscribed to this PR (`gh pr create`\n // subscribes automatically), so it can read the individual line comments\n // from its own notification inbox — the message only has to wake it and\n // point at the review.\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:address-review-feedback`,\n role: 'work',\n priority: 'high',\n message:\n `Changes were requested on pull request #${context.pullRequest.number} (${context.review.url}). ` +\n 'Read the review comments on this PR, address the ones you agree with, and push the fixes to the PR branch. ' +\n 'Reply on GitHub to anything you are deliberately not changing, explaining why.',\n } as const;\n}\n\n/**\n * Detects the `factory-review` handoff verdict in a comment body.\n *\n * GitHub forbids an app from reviewing a pull request it authored, so on\n * Factory-authored PRs the review skill falls back to posting its verdict as a\n * plain comment. That comment is the only signal the authoring agent gets, so\n * it has to be readable back out. The skill's handoff contract puts the verdict\n * on the first line (`Verdict: request changes`), so only that line is\n * inspected — a verdict quoted later in the findings must not count.\n */\nfunction requestsChangesVerdict(body: string | undefined): boolean {\n const firstLine = body\n ?.split('\\n')\n .map(line => line.trim())\n .find(line => line.length > 0);\n if (!firstLine) return false;\n // Tolerate the markdown the skill wraps the line in (`**Verdict: ...**`).\n const normalized = firstLine\n .replaceAll(/[*_`#>\\s]+/g, ' ')\n .trim()\n .toLowerCase();\n // Match the verdict exactly so negated phrasings (\"Verdict: do not request\n // changes\") cannot wake the author.\n return /^verdict: ?(request changes|changes requested)$/.test(normalized);\n}\n\nfunction addressPullRequestComment(context: FactoryGithubRuleContext) {\n // A validated Factory mention is a review-entry request, not feedback for the\n // authoring Work session. Invalid or unrecognized comments retain the normal\n // feedback route below.\n if (context.reviewCommand) return reReviewRequestedPullRequest(context);\n if (!context.item || !context.pullRequest || !context.issueComment) return;\n // Provenance binds the comment to the Work item that authored the PR — the\n // only session that can act on it. A Review card must not react to comments\n // on the PR it is reviewing.\n if (context.board !== 'work') return;\n if (context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // `factoryAuthored` is one bit for the whole Factory, so Factory's own\n // comments are indistinguishable between roles — waking on all of them would\n // let the Work agent's own progress comments wake itself in a loop. The one\n // exception is the review verdict the Review run had to post as a comment\n // because GitHub refused a self-review: that is the handoff, and it only ever\n // asks for changes once per review, so it cannot sustain a loop.\n if (\n context.actor.type === 'github' &&\n context.actor.factoryAuthored &&\n !requestsChangesVerdict(context.issueComment.body)\n ) {\n return;\n }\n return {\n type: 'sendMessage',\n idempotencyKey: `${context.ingress.id}:address-pull-request-comment`,\n role: 'work',\n priority: 'high',\n message:\n `${context.issueComment.author ?? 'Someone'} commented on pull request #${context.pullRequest.number} ` +\n `(${context.issueComment.url ?? context.pullRequest.url}). Read the comment, address it if you agree, and push ` +\n 'the fixes to the PR branch. Reply on GitHub to anything you are deliberately not changing, explaining why.',\n } as const;\n}\n\nfunction pullRequestClosed(context: FactoryGithubRuleContext) {\n if (!context.item || !context.pullRequest || context.pullRequest.merged) return;\n if (context.board !== 'review') return;\n // A PR closed without merging is abandoned review work: clear the card off\n // the board instead of leaving it in Reviewing forever.\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:pull-request-closed`,\n board: 'review',\n stage: 'canceled',\n message: {\n text:\n `Pull request #${context.pullRequest.number} was closed without merging; ` +\n 'this Review card was moved to Canceled.',\n },\n } as const;\n}\n\nfunction reReviewRequestedPullRequest(context: FactoryGithubRuleContext) {\n // GitHub reviewer requests and Factory's exact mention command are both\n // explicit requests to enter the same Review lifecycle.\n const factoryReviewEntry = context.reviewRequest?.factoryReviewer || context.reviewCommand !== undefined;\n if ((context.item && context.board !== 'review') || !factoryReviewEntry) return;\n if (!context.pullRequest || context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // Trusted (write/admin) requesters only: creating or re-entering review checks\n // out and executes PR code, the same bar pullRequestOpened applies to auto-review.\n if (!trustedGithubActor(context)) return;\n if (context.actor.type === 'github' && context.actor.factoryAuthored) return;\n if (!context.item) {\n // On this path the actor is the *requester*, so the materialized\n // `authorTrusted` stamp records their trust (always true past the gate\n // above), not the PR author's: a trusted maintainer requesting a Factory\n // review vouches for the PR.\n return materializePullRequestIntake(context, {\n idempotencyKey: `${context.ingress.id}:pull-request-review-requested-intake`,\n autoStartCandidate: true,\n });\n }\n // Already in Reviewing: a review pass is pending or running; re-entering\n // would be a same-stage no-op anyway (stage rules only fire on change).\n if (context.item.stages.length === 1 && context.item.stages[0] === 'review') return;\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:re-review-requested`,\n board: 'review',\n stage: 'review',\n } as const;\n}\n\nfunction reReviewUpdatedPullRequest(context: FactoryGithubRuleContext) {\n if (!context.item || context.board !== 'review') return;\n if (!context.pullRequest || context.pullRequest.state !== 'open' || context.pullRequest.merged) return;\n // Intake has not started a review pass yet, so a push there is just more of\n // the code the first pass will read. A push to a card sitting in Reviewing is\n // different: it invalidates whatever that pass is reading, so re-enter the\n // stage to supersede it. `reviewPullRequest` cancels the stale run and picks\n // the right skill for the entry it sees.\n if (context.item.stages.some(stage => stage === 'intake')) return;\n const alreadyReviewing = context.item.stages.some(stage => stage === 'review');\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:re-review-updated`,\n board: 'review',\n stage: 'review',\n // Re-entry is the point when the card is already Reviewing: the stage's\n // entry rule is what cancels the superseded pass and starts one on the code\n // that just landed.\n ...(alreadyReviewing ? { reenter: true } : {}),\n } as const;\n}\n\nfunction linearIssueObserved(context: FactoryLinearRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-triage`,\n board: 'work',\n source: 'linear-issue',\n sourceKey: `linear:${context.issue.identifier}`,\n title: `${context.issue.identifier}: ${context.issue.title}`,\n url: context.issue.url,\n stage: 'triage',\n metadata: {\n linearIssueId: context.issue.id,\n identifier: context.issue.identifier,\n sourceCreatedAt: context.issue.createdAt,\n linearState: context.issue.state,\n linearStateType: context.issue.stateType,\n linearPriority: context.issue.priorityLabel,\n linearAssignee: context.issue.assignee,\n linearCreator: context.issue.creator,\n linearTeam: context.issue.team,\n labels: [...context.issue.labels] as string[],\n ...(context.issue.assignee ? { assignee: context.issue.assignee } : {}),\n ...(context.issue.creator ? { creator: context.issue.creator, author: context.issue.creator } : {}),\n },\n } as const;\n}\n\nfunction linearIssueClosed(context: FactoryLinearRuleContext) {\n if (!context.item || context.item.source !== 'linear-issue') return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Only terminal state types trigger close.\n const stateType = context.issue.stateType;\n if (stateType !== 'completed' && stateType !== 'canceled') return;\n const canceled = stateType === 'canceled';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text: `Linear issue ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nconst BUILT_IN_DEFAULTS: FactoryRulesOverrides = {\n work: workBoard.rules,\n review: reviewBoard.rules,\n tools: { submit_plan: { onResult: advanceApprovedPlan } },\n github: {\n issueOpened: { onEvent: issueOpened },\n issueEdited: { onEvent: retriageGithubIssue },\n issueClosed: { onEvent: issueClosed },\n issueCommentCreated: { onEvent: retriageGithubIssue },\n issueCommentEdited: { onEvent: retriageGithubIssue },\n issueCommentDeleted: { onEvent: retriageGithubIssue },\n pullRequestOpened: { onEvent: pullRequestOpened },\n pullRequestUpdated: { onEvent: reReviewUpdatedPullRequest },\n pullRequestCommentCreated: { onEvent: addressPullRequestComment },\n pullRequestReviewRequested: { onEvent: reReviewRequestedPullRequest },\n pullRequestReviewSubmitted: { onEvent: addressReviewFeedback },\n pullRequestMerged: { onEvent: pullRequestMerged },\n pullRequestClosed: { onEvent: pullRequestClosed },\n },\n linear: { issueObserved: { onEvent: linearIssueObserved }, issueClosed: { onEvent: linearIssueClosed } },\n};\n\nfunction mergeBoardRules(\n base: FactoryBoardRules | undefined,\n overrides: FactoryBoardRules | undefined,\n): FactoryBoardRules {\n const result: FactoryBoardRules = {};\n const stages = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryRuleStage>;\n for (const stage of stages) {\n const baseSources = base?.[stage];\n const overrideSources = overrides?.[stage];\n const sources = new Set([\n ...Object.keys(baseSources ?? {}),\n ...Object.keys(overrideSources ?? {}),\n ]) as Set<FactoryRuleSource>;\n const mergedSources: Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>> = {};\n for (const source of sources) {\n const baseLeaf = baseSources?.[source];\n const overrideLeaf = overrideSources?.[source];\n mergedSources[source] = {\n ...(baseLeaf && 'onEnter' in baseLeaf ? { onEnter: baseLeaf.onEnter } : {}),\n ...(baseLeaf && 'onExit' in baseLeaf ? { onExit: baseLeaf.onExit } : {}),\n ...(overrideLeaf && 'onEnter' in overrideLeaf ? { onEnter: overrideLeaf.onEnter } : {}),\n ...(overrideLeaf && 'onExit' in overrideLeaf ? { onExit: overrideLeaf.onExit } : {}),\n };\n }\n result[stage] = mergedSources;\n }\n return result;\n}\n\nfunction mergeToolRules(\n base: Record<string, FactoryToolRuleLeaf> | undefined,\n overrides: Record<string, FactoryToolRuleLeaf> | undefined,\n): Record<string, FactoryToolRuleLeaf> {\n const result: Record<string, FactoryToolRuleLeaf> = {};\n for (const name of new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {\n const baseLeaf = base?.[name];\n const overrideLeaf = overrides?.[name];\n result[name] = {\n ...(baseLeaf && 'onResult' in baseLeaf ? { onResult: baseLeaf.onResult } : {}),\n ...(overrideLeaf && 'onResult' in overrideLeaf ? { onResult: overrideLeaf.onResult } : {}),\n };\n }\n return result;\n}\n\nfunction mergeGithubRules(\n base: FactoryRulesOverrides['github'],\n overrides: FactoryRulesOverrides['github'],\n): NonNullable<FactoryRulesOverrides['github']> {\n const result: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryGithubEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf && 'onEvent' in baseLeaf ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nfunction mergeLinearRules(\n base: FactoryRulesOverrides['linear'],\n overrides: FactoryRulesOverrides['linear'],\n): NonNullable<FactoryRulesOverrides['linear']> {\n const result: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryLinearEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf && 'onEvent' in baseLeaf ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nexport function mergeFactoryRuleOverrides(\n base: FactoryRulesOverrides,\n overrides: FactoryRulesOverrides = {},\n): Omit<FactoryRules, 'version'> {\n return {\n work: mergeBoardRules(base.work, overrides.work),\n review: mergeBoardRules(base.review, overrides.review),\n tools: mergeToolRules(base.tools, overrides.tools),\n github: mergeGithubRules(base.github, overrides.github),\n linear: mergeLinearRules(base.linear, overrides.linear),\n };\n}\n\nexport function defaultFactoryRules(input: { version: string; overrides?: FactoryRulesOverrides }): FactoryRules {\n if (typeof input?.version !== 'string' || input.version.trim().length === 0) {\n throw new FactoryRuleValidationError('Factory rule version is required.');\n }\n\n const rules: FactoryRules = {\n version: input.version.trim(),\n ...mergeFactoryRuleOverrides(BUILT_IN_DEFAULTS, input.overrides),\n };\n assertFactoryRules(rules);\n return rules;\n}\n\nexport function builtInFactoryRules(): FactoryRules {\n return defaultFactoryRules({ version: DEFAULT_FACTORY_RULE_VERSION });\n}\n"],"mappings":";;;;AAoBA,MAAa,+BAA+B;AAE5C,SAAS,mBAAmB,SAA2D;CACrF,OAAO,QAAQ,MAAM,SAAS,YAAY,QAAQ,MAAM;AAC1D;AAEA,SAAS,iBAAiB,SAAsE;CAC9F,OAAO,QAAQ,MAAM,SAAS,WAAW,QAAQ,MAAM,QAAQ,KAAA;AACjE;AAEA,SAAS,oBAAoB,SAAmC;CAC9D,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,kBAAkB,CAAC,QAAQ,KAAK,KAAK;CAClF,IAAI,QAAQ,MAAM,SAAS,YAAY,QAAQ,MAAM,iBAAiB;CAEtE,MAAM,SACJ,QAAQ,UAAU,gBACd,QAAQ,aAAa,SAAS,QAAQ,YAAY,OAChD,gCACA,QAAQ,aAAa,QACnB,uBACA,sBACJ,QAAQ,UAAU,wBAChB,oBACA,QAAQ,UAAU,uBAChB,mBACA;CACV,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,WAAW;EACX,WAAW,2BAA2B,QAAQ,KAAK,IAAI,UAAU,OAAO;CAC1E;AACF;AAEA,SAAS,cAAc,OAAoC;CACzD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;CACjE,MAAM,UAAW,MAAgC;CACjD,OAAO,OAAO,YAAY,WAAW,UAAU,KAAA;AACjD;AAIA,SAAS,oBAAoB,SAAuC;CAClE,IACE,QAAQ,OAAO,WAAW,aAC1B,QAAQ,UAAU,UAClB,QAAQ,KAAK,OAAO,WAAW,KAC/B,QAAQ,KAAK,OAAO,OAAO,cAC3B,QAAQ,MAAM,SAAS,WACvB,QAAQ,MAAM,SAAS,UACvB,CAAC,cAAc,QAAQ,OAAO,KAAK,CAAC,EAAE,WAAW,gBAAgB,GAEjE;CAEF,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;CACT;AACF;AAEA,SAAS,oBAAoB,WAA+B,kBAAmC;CAC7F,IAAI,CAAC,WAAW,OAAO;CACvB,MAAM,kBAAkB,KAAK,MAAM,SAAS;CAC5C,MAAM,mBAAmB,KAAK,MAAM,gBAAgB;CACpD,OAAO,OAAO,SAAS,eAAe,KAAK,OAAO,SAAS,gBAAgB,KAAK,kBAAkB;AACpG;AAEA,SAAS,YAAY,SAAmC;CACtD,IAAI,CAAC,QAAQ,OAAO;CAGpB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,QAAQ;EACR,WAAW,gBAAgB,QAAQ,MAAM;EACzC,OAAO,QAAQ,MAAM;EACrB,KAAK,QAAQ,MAAM;EACnB,OAAO;EACP,UAAU;GACR,oBAAoB,QAAQ,WAAW;GACvC,mBAAmB,QAAQ,MAAM;GACjC,GAAI,QAAQ,MAAM,YAAY,EAAE,iBAAiB,QAAQ,MAAM,UAAU,IAAI,CAAC;GAC9E,GAAI,iBAAiB,OAAO,IAAI,EAAE,QAAQ,iBAAiB,OAAO,EAAE,IAAI,CAAC;GACzE,eAAe,mBAAmB,OAAO;GACzC,oBACE,mBAAmB,OAAO,KAAK,oBAAoB,QAAQ,MAAM,WAAW,QAAQ,QAAQ,SAAS;GACvG,WAAW,QAAQ,MAAM,aAAa,CAAC;GACvC,QAAQ,QAAQ,MAAM,UAAU,CAAC;EACnC;CACF;AACF;AAEA,SAAS,YAAY,SAAmC;CACtD,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,kBAAkB,CAAC,QAAQ,OAAO;CAC/E,IAAI,QAAQ,UAAU,QAAQ;CAE9B,IAAI,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,UAAU,UAAU,UAAU,GAAG;CAIjF,MAAM,WAAW,QAAQ,MAAM,gBAAgB,iBAAiB,QAAQ,MAAM,gBAAgB;CAC9F,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO,WAAW,aAAa;EAC/B,SAAS,EACP,MACE,iBAAiB,QAAQ,MAAM,OAAO,aACnC,QAAQ,MAAM,cAAc,KAAK,QAAQ,MAAM,YAAY,KAAK,GAAG,gCACvC,WAAW,aAAa,OAAO,GAClE;CACF;AACF;AAEA,SAAS,6BACP,SACA,EAAE,gBAAgB,sBAClB;CACA,IAAI,CAAC,QAAQ,aAAa;CAC1B,OAAO;EACL,MAAM;EACN;EACA,OAAO;EACP,QAAQ;EACR,WAAW,aAAa,QAAQ,YAAY;EAC5C,OAAO,QAAQ,YAAY;EAC3B,KAAK,QAAQ,YAAY;EACzB,OAAO;EACP,UAAU;GACR,oBAAoB,QAAQ,WAAW;GACvC,yBAAyB,QAAQ,YAAY;GAC7C,GAAI,QAAQ,YAAY,YAAY,EAAE,iBAAiB,QAAQ,YAAY,UAAU,IAAI,CAAC;GAC1F,iBAAiB,QAAQ,YAAY;GACrC,eAAe,mBAAmB,OAAO;GACzC;GACA,OAAO,QAAQ,YAAY;GAC3B,OAAO,QAAQ,YAAY;GAC3B,QAAQ,QAAQ,YAAY;GAC5B,WAAW,QAAQ,YAAY,aAAa,CAAC;GAC7C,oBAAoB,QAAQ,YAAY,sBAAsB,CAAC;GAC/D,QAAQ,QAAQ,YAAY,UAAU,CAAC;GACvC,YAAY,QAAQ,YAAY;GAChC,YAAY,QAAQ,YAAY;GAChC,GAAI,QAAQ,YAAY,SAAS,EAAE,QAAQ,QAAQ,YAAY,OAAO,IAAI,CAAC;EAC7E;CACF;AACF;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,aAAa;CAG1B,MAAM,sBACH,mBAAmB,OAAO,KAAK,QAAQ,YAAY,oBACpD,oBAAoB,QAAQ,YAAY,WAAW,QAAQ,QAAQ,SAAS;CAC9E,OAAO,6BAA6B,SAAS;EAC3C,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC;CACF,CAAC;AACH;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,aAAa,QAAQ;CACnD,IAAI,QAAQ,UAAU,UAKpB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;EACP,SAAS,EACP,MACE,iBAAiB,QAAQ,YAAY,OAAO,+GAEhD;CACF;CAIF,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,SACE,iBAAiB,QAAQ,YAAY,OAAO;CAEhD;AACF;AAEA,SAAS,sBAAsB,SAAmC;CAChE,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,eAAe,CAAC,QAAQ,QAAQ;CAI9D,IAAI,QAAQ,UAAU,QAAQ;CAE9B,IAAI,QAAQ,YAAY,UAAU,UAAU,QAAQ,YAAY,QAAQ;CAIxE,IAAI,QAAQ,OAAO,MAAM,YAAY,MAAM,qBAAqB;CAKhE,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,UAAU;EACV,SACE,2CAA2C,QAAQ,YAAY,OAAO,IAAI,QAAQ,OAAO,IAAI;CAGjG;AACF;;;;;;;;;;;AAYA,SAAS,uBAAuB,MAAmC;CACjE,MAAM,YAAY,MACd,MAAM,IAAI,CAAC,CACZ,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,MAAK,SAAQ,KAAK,SAAS,CAAC;CAC/B,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,aAAa,UAChB,WAAW,eAAe,GAAG,CAAC,CAC9B,KAAK,CAAC,CACN,YAAY;CAGf,OAAO,kDAAkD,KAAK,UAAU;AAC1E;AAEA,SAAS,0BAA0B,SAAmC;CAIpE,IAAI,QAAQ,eAAe,OAAO,6BAA6B,OAAO;CACtE,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,eAAe,CAAC,QAAQ,cAAc;CAIpE,IAAI,QAAQ,UAAU,QAAQ;CAC9B,IAAI,QAAQ,YAAY,UAAU,UAAU,QAAQ,YAAY,QAAQ;CAOxE,IACE,QAAQ,MAAM,SAAS,YACvB,QAAQ,MAAM,mBACd,CAAC,uBAAuB,QAAQ,aAAa,IAAI,GAEjD;CAEF,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,MAAM;EACN,UAAU;EACV,SACE,GAAG,QAAQ,aAAa,UAAU,UAAU,8BAA8B,QAAQ,YAAY,OAAO,IACjG,QAAQ,aAAa,OAAO,QAAQ,YAAY,IAAI;CAE5D;AACF;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,eAAe,QAAQ,YAAY,QAAQ;CACzE,IAAI,QAAQ,UAAU,UAAU;CAGhC,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;EACP,SAAS,EACP,MACE,iBAAiB,QAAQ,YAAY,OAAO,sEAEhD;CACF;AACF;AAEA,SAAS,6BAA6B,SAAmC;CAGvE,MAAM,qBAAqB,QAAQ,eAAe,mBAAmB,QAAQ,kBAAkB,KAAA;CAC/F,IAAK,QAAQ,QAAQ,QAAQ,UAAU,YAAa,CAAC,oBAAoB;CACzE,IAAI,CAAC,QAAQ,eAAe,QAAQ,YAAY,UAAU,UAAU,QAAQ,YAAY,QAAQ;CAGhG,IAAI,CAAC,mBAAmB,OAAO,GAAG;CAClC,IAAI,QAAQ,MAAM,SAAS,YAAY,QAAQ,MAAM,iBAAiB;CACtE,IAAI,CAAC,QAAQ,MAKX,OAAO,6BAA6B,SAAS;EAC3C,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,oBAAoB;CACtB,CAAC;CAIH,IAAI,QAAQ,KAAK,OAAO,WAAW,KAAK,QAAQ,KAAK,OAAO,OAAO,UAAU;CAC7E,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;CACT;AACF;AAEA,SAAS,2BAA2B,SAAmC;CACrE,IAAI,CAAC,QAAQ,QAAQ,QAAQ,UAAU,UAAU;CACjD,IAAI,CAAC,QAAQ,eAAe,QAAQ,YAAY,UAAU,UAAU,QAAQ,YAAY,QAAQ;CAMhG,IAAI,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,QAAQ,GAAG;CAC3D,MAAM,mBAAmB,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,QAAQ;CAC7E,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;EAIP,GAAI,mBAAmB,EAAE,SAAS,KAAK,IAAI,CAAC;CAC9C;AACF;AAEA,SAAS,oBAAoB,SAAmC;CAC9D,IAAI,QAAQ,MAAM;CAClB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,QAAQ;EACR,WAAW,UAAU,QAAQ,MAAM;EACnC,OAAO,GAAG,QAAQ,MAAM,WAAW,IAAI,QAAQ,MAAM;EACrD,KAAK,QAAQ,MAAM;EACnB,OAAO;EACP,UAAU;GACR,eAAe,QAAQ,MAAM;GAC7B,YAAY,QAAQ,MAAM;GAC1B,iBAAiB,QAAQ,MAAM;GAC/B,aAAa,QAAQ,MAAM;GAC3B,iBAAiB,QAAQ,MAAM;GAC/B,gBAAgB,QAAQ,MAAM;GAC9B,gBAAgB,QAAQ,MAAM;GAC9B,eAAe,QAAQ,MAAM;GAC7B,YAAY,QAAQ,MAAM;GAC1B,QAAQ,CAAC,GAAG,QAAQ,MAAM,MAAM;GAChC,GAAI,QAAQ,MAAM,WAAW,EAAE,UAAU,QAAQ,MAAM,SAAS,IAAI,CAAC;GACrE,GAAI,QAAQ,MAAM,UAAU;IAAE,SAAS,QAAQ,MAAM;IAAS,QAAQ,QAAQ,MAAM;GAAQ,IAAI,CAAC;EACnG;CACF;AACF;AAEA,SAAS,kBAAkB,SAAmC;CAC5D,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,gBAAgB;CAC7D,IAAI,QAAQ,UAAU,QAAQ;CAE9B,IAAI,QAAQ,KAAK,OAAO,MAAK,UAAS,UAAU,UAAU,UAAU,UAAU,GAAG;CAEjF,MAAM,YAAY,QAAQ,MAAM;CAChC,IAAI,cAAc,eAAe,cAAc,YAAY;CAC3D,MAAM,WAAW,cAAc;CAC/B,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO,WAAW,aAAa;EAC/B,SAAS,EACP,MAAM,gBAAgB,QAAQ,MAAM,WAAW,OAAO,WAAW,aAAa,YAAY,gCAAgC,WAAW,aAAa,OAAO,GAC3J;CACF;AACF;AAEA,MAAM,oBAA2C;CAC/C,MAAM,UAAU;CAChB,QAAQ,YAAY;CACpB,OAAO,EAAE,aAAa,EAAE,UAAU,oBAAoB,EAAE;CACxD,QAAQ;EACN,aAAa,EAAE,SAAS,YAAY;EACpC,aAAa,EAAE,SAAS,oBAAoB;EAC5C,aAAa,EAAE,SAAS,YAAY;EACpC,qBAAqB,EAAE,SAAS,oBAAoB;EACpD,oBAAoB,EAAE,SAAS,oBAAoB;EACnD,qBAAqB,EAAE,SAAS,oBAAoB;EACpD,mBAAmB,EAAE,SAAS,kBAAkB;EAChD,oBAAoB,EAAE,SAAS,2BAA2B;EAC1D,2BAA2B,EAAE,SAAS,0BAA0B;EAChE,4BAA4B,EAAE,SAAS,6BAA6B;EACpE,4BAA4B,EAAE,SAAS,sBAAsB;EAC7D,mBAAmB,EAAE,SAAS,kBAAkB;EAChD,mBAAmB,EAAE,SAAS,kBAAkB;CAClD;CACA,QAAQ;EAAE,eAAe,EAAE,SAAS,oBAAoB;EAAG,aAAa,EAAE,SAAS,kBAAkB;CAAE;AACzG;AAEA,SAAS,gBACP,MACA,WACmB;CACnB,MAAM,SAA4B,CAAC;CACnC,MAAM,yBAAS,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC;CACpF,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,cAAc,OAAO;EAC3B,MAAM,kBAAkB,YAAY;EACpC,MAAM,0BAAU,IAAI,IAAI,CACtB,GAAG,OAAO,KAAK,eAAe,CAAC,CAAC,GAChC,GAAG,OAAO,KAAK,mBAAmB,CAAC,CAAC,CACtC,CAAC;EACD,MAAM,gBAA0E,CAAC;EACjF,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,WAAW,cAAc;GAC/B,MAAM,eAAe,kBAAkB;GACvC,cAAc,UAAU;IACtB,GAAI,YAAY,aAAa,WAAW,EAAE,SAAS,SAAS,QAAQ,IAAI,CAAC;IACzE,GAAI,YAAY,YAAY,WAAW,EAAE,QAAQ,SAAS,OAAO,IAAI,CAAC;IACtE,GAAI,gBAAgB,aAAa,eAAe,EAAE,SAAS,aAAa,QAAQ,IAAI,CAAC;IACrF,GAAI,gBAAgB,YAAY,eAAe,EAAE,QAAQ,aAAa,OAAO,IAAI,CAAC;GACpF;EACF;EACA,OAAO,SAAS;CAClB;CACA,OAAO;AACT;AAEA,SAAS,eACP,MACA,WACqC;CACrC,MAAM,SAA8C,CAAC;CACrD,KAAK,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG;EACzF,MAAM,WAAW,OAAO;EACxB,MAAM,eAAe,YAAY;EACjC,OAAO,QAAQ;GACb,GAAI,YAAY,cAAc,WAAW,EAAE,UAAU,SAAS,SAAS,IAAI,CAAC;GAC5E,GAAI,gBAAgB,cAAc,eAAe,EAAE,UAAU,aAAa,SAAS,IAAI,CAAC;EAC1F;CACF;CACA,OAAO;AACT;AAEA,SAAS,iBACP,MACA,WAC8C;CAC9C,MAAM,SAAyE,CAAC;CAChF,MAAM,yBAAS,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC;CACpF,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,WAAW,OAAO;EACxB,MAAM,eAAe,YAAY;EACjC,OAAO,SAAS;GACd,GAAI,YAAY,aAAa,WAAW,EAAE,SAAS,SAAS,QAAQ,IAAI,CAAC;GACzE,GAAI,gBAAgB,aAAa,eAAe,EAAE,SAAS,aAAa,QAAQ,IAAI,CAAC;EACvF;CACF;CACA,OAAO;AACT;AAEA,SAAS,iBACP,MACA,WAC8C;CAC9C,MAAM,SAAyE,CAAC;CAChF,MAAM,yBAAS,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC;CACpF,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,WAAW,OAAO;EACxB,MAAM,eAAe,YAAY;EACjC,OAAO,SAAS;GACd,GAAI,YAAY,aAAa,WAAW,EAAE,SAAS,SAAS,QAAQ,IAAI,CAAC;GACzE,GAAI,gBAAgB,aAAa,eAAe,EAAE,SAAS,aAAa,QAAQ,IAAI,CAAC;EACvF;CACF;CACA,OAAO;AACT;AAEA,SAAgB,0BACd,MACA,YAAmC,CAAC,GACL;CAC/B,OAAO;EACL,MAAM,gBAAgB,KAAK,MAAM,UAAU,IAAI;EAC/C,QAAQ,gBAAgB,KAAK,QAAQ,UAAU,MAAM;EACrD,OAAO,eAAe,KAAK,OAAO,UAAU,KAAK;EACjD,QAAQ,iBAAiB,KAAK,QAAQ,UAAU,MAAM;EACtD,QAAQ,iBAAiB,KAAK,QAAQ,UAAU,MAAM;CACxD;AACF;AAEA,SAAgB,oBAAoB,OAA6E;CAC/G,IAAI,OAAO,OAAO,YAAY,YAAY,MAAM,QAAQ,KAAK,CAAC,CAAC,WAAW,GACxE,MAAM,IAAI,2BAA2B,mCAAmC;CAG1E,MAAM,QAAsB;EAC1B,SAAS,MAAM,QAAQ,KAAK;EAC5B,GAAG,0BAA0B,mBAAmB,MAAM,SAAS;CACjE;CACA,mBAAmB,KAAK;CACxB,OAAO;AACT;AAEA,SAAgB,sBAAoC;CAClD,OAAO,oBAAoB,EAAE,SAAS,6BAA6B,CAAC;AACtE"}
|
|
1
|
+
{"version":3,"file":"defaults.js","names":[],"sources":["../../src/rules/defaults.ts"],"sourcesContent":["import type {\n FactoryRules,\n FactoryRulesOverrides,\n FactoryToolResultRuleContext,\n FactoryToolRuleLeaf,\n} from './types.js';\nimport { assertFactoryRules, FactoryRuleValidationError } from './validation.js';\n\nexport const DEFAULT_FACTORY_RULE_VERSION = 'factory-default-v1';\n\nfunction resultContent(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const content = (value as { content?: unknown }).content;\n return typeof content === 'string' ? content : undefined;\n}\n\n// Interactive-session path only: factory-plan never calls submit_plan — it\n// advances planning → execute via factory_transition_work_item directly.\nfunction advanceApprovedPlan(context: FactoryToolResultRuleContext) {\n if (\n context.result.status !== 'success' ||\n context.board !== 'work' ||\n context.item.stages.length !== 1 ||\n context.item.stages[0] !== 'planning' ||\n context.actor.type !== 'agent' ||\n context.actor.role !== 'plan' ||\n !resultContent(context.result.value)?.startsWith('Plan approved.')\n ) {\n return;\n }\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:approved-plan`,\n board: 'work',\n stage: 'execute',\n } as const;\n}\n\nconst BUILT_IN_DEFAULTS: FactoryRulesOverrides = {\n tools: { submit_plan: { onResult: advanceApprovedPlan } },\n};\n\nfunction mergeToolRules(\n base: Record<string, FactoryToolRuleLeaf> | undefined,\n overrides: Record<string, FactoryToolRuleLeaf> | undefined,\n): Record<string, FactoryToolRuleLeaf> {\n const result: Record<string, FactoryToolRuleLeaf> = {};\n for (const name of new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {\n const baseLeaf = base?.[name];\n const overrideLeaf = overrides?.[name];\n result[name] = {\n ...(baseLeaf && 'onResult' in baseLeaf ? { onResult: baseLeaf.onResult } : {}),\n ...(overrideLeaf && 'onResult' in overrideLeaf ? { onResult: overrideLeaf.onResult } : {}),\n };\n }\n return result;\n}\n\nexport function mergeFactoryRuleOverrides(\n base: FactoryRulesOverrides,\n overrides: FactoryRulesOverrides = {},\n): Omit<FactoryRules, 'version'> {\n return {\n tools: mergeToolRules(base.tools, overrides.tools),\n };\n}\n\nexport function defaultFactoryRules(input: { version: string; overrides?: FactoryRulesOverrides }): FactoryRules {\n if (typeof input?.version !== 'string' || input.version.trim().length === 0) {\n throw new FactoryRuleValidationError('Factory rule version is required.');\n }\n\n const rules: FactoryRules = {\n version: input.version.trim(),\n ...mergeFactoryRuleOverrides(BUILT_IN_DEFAULTS, input.overrides),\n };\n assertFactoryRules(rules);\n return rules;\n}\n\nexport function builtInFactoryRules(): FactoryRules {\n return defaultFactoryRules({ version: DEFAULT_FACTORY_RULE_VERSION });\n}\n"],"mappings":";;AAQA,MAAa,+BAA+B;AAE5C,SAAS,cAAc,OAAoC;CACzD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;CACjE,MAAM,UAAW,MAAgC;CACjD,OAAO,OAAO,YAAY,WAAW,UAAU,KAAA;AACjD;AAIA,SAAS,oBAAoB,SAAuC;CAClE,IACE,QAAQ,OAAO,WAAW,aAC1B,QAAQ,UAAU,UAClB,QAAQ,KAAK,OAAO,WAAW,KAC/B,QAAQ,KAAK,OAAO,OAAO,cAC3B,QAAQ,MAAM,SAAS,WACvB,QAAQ,MAAM,SAAS,UACvB,CAAC,cAAc,QAAQ,OAAO,KAAK,CAAC,EAAE,WAAW,gBAAgB,GAEjE;CAEF,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;CACT;AACF;AAEA,MAAM,oBAA2C,EAC/C,OAAO,EAAE,aAAa,EAAE,UAAU,oBAAoB,EAAE,EAC1D;AAEA,SAAS,eACP,MACA,WACqC;CACrC,MAAM,SAA8C,CAAC;CACrD,KAAK,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG;EACzF,MAAM,WAAW,OAAO;EACxB,MAAM,eAAe,YAAY;EACjC,OAAO,QAAQ;GACb,GAAI,YAAY,cAAc,WAAW,EAAE,UAAU,SAAS,SAAS,IAAI,CAAC;GAC5E,GAAI,gBAAgB,cAAc,eAAe,EAAE,UAAU,aAAa,SAAS,IAAI,CAAC;EAC1F;CACF;CACA,OAAO;AACT;AAEA,SAAgB,0BACd,MACA,YAAmC,CAAC,GACL;CAC/B,OAAO,EACL,OAAO,eAAe,KAAK,OAAO,UAAU,KAAK,EACnD;AACF;AAEA,SAAgB,oBAAoB,OAA6E;CAC/G,IAAI,OAAO,OAAO,YAAY,YAAY,MAAM,QAAQ,KAAK,CAAC,CAAC,WAAW,GACxE,MAAM,IAAI,2BAA2B,mCAAmC;CAG1E,MAAM,QAAsB;EAC1B,SAAS,MAAM,QAAQ,KAAK;EAC5B,GAAG,0BAA0B,mBAAmB,MAAM,SAAS;CACjE;CACA,mBAAmB,KAAK;CACxB,OAAO;AACT;AAEA,SAAgB,sBAAoC;CAClD,OAAO,oBAAoB,EAAE,SAAS,6BAA6B,CAAC;AACtE"}
|
package/dist/rules/dispatcher.js
CHANGED
|
@@ -4,8 +4,8 @@ import { FACTORY_RULE_MATERIALIZATION_KEY } from "../storage/domains/work-items/
|
|
|
4
4
|
import { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata } from "./dispatch-errors.js";
|
|
5
5
|
import { resolvePromptInvocation, resolveSkillInvocation } from "../skills/service.js";
|
|
6
6
|
import { withWorkItemFeed } from "../storage/domains/comments/feed-context.js";
|
|
7
|
-
import { randomUUID } from "crypto";
|
|
8
7
|
import { RequestContext } from "@mastra/core/request-context";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
9
|
//#region src/rules/dispatcher.ts
|
|
10
10
|
const LEASE_MS = 3e4;
|
|
11
11
|
const POLL_MS = 1e3;
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { builtInFactoryRules, defaultFactoryRules, DEFAULT_FACTORY_RULE_VERSION } from './defaults.js';
|
|
2
|
-
export {
|
|
2
|
+
export { resolveFactoryStageRules, resolveFactoryToolRule } from './resolve.js';
|
|
3
3
|
export type { ResolvedFactoryStageRule, ResolvedFactoryToolRule } from './resolve.js';
|
|
4
4
|
export { FACTORY_GITHUB_EVENTS, FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES, factoryRuleSourceForWorkItem, isFactoryRuleStage, } from './types.js';
|
|
5
|
-
export type { FactoryBoardRuleLeaf,
|
|
5
|
+
export type { FactoryBoardRuleLeaf, FactoryBoundRuleContext, FactoryCommitDecision, FactoryGithubEventName, FactoryGithubRuleContext, FactoryLinearEventName, FactoryLinearRuleContext, FactoryInvokeSkillDecision, FactoryNotifyDecision, FactoryRuleActor, FactoryRuleBoard, FactoryRuleCausalEntry, FactoryRuleContextBase, FactoryRuleDecision, FactoryRuleHandler, FactoryRuleIngressIdentity, FactoryRuleItemContext, FactoryRuleJsonValue, FactoryRuleRejectionCode, FactoryRuleRejectDecision, FactoryRuleSource, FactoryRules, FactoryRulesOverrides, FactoryRuleStage, FactorySendMessageDecision, FactoryStageRuleContext, FactoryToolResultRuleContext, FactoryToolRuleLeaf, FactoryTransitionDecision, FactoryTransitionResult, FactoryTransitionResultAccepted, FactoryTransitionResultRejected, FactoryUpsertLinkedWorkItemDecision, } from './types.js';
|
|
6
6
|
export { assertFactoryRules, FactoryRuleValidationError, MAX_FACTORY_RULE_CAUSAL_DEPTH, validateFactoryRuleDecision, validateFactoryRuleDecisions, } from './validation.js';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAChF,YAAY,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,4BAA4B,EAC5B,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,oBAAoB,EACpB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,YAAY,EACZ,qBAAqB,EACrB,gBAAgB,EAChB,0BAA0B,EAC1B,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,+BAA+B,EAC/B,+BAA+B,EAC/B,mCAAmC,GACpC,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,6BAA6B,EAC7B,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,iBAAiB,CAAC"}
|
package/dist/rules/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FACTORY_GITHUB_EVENTS, FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES, factoryRuleSourceForWorkItem, isFactoryRuleStage } from "./types.js";
|
|
2
|
-
import { resolveFactoryGithubRule, resolveFactoryLinearRule, resolveFactoryStageRules, resolveFactoryToolRule } from "./resolve.js";
|
|
3
2
|
import { FactoryRuleValidationError, MAX_FACTORY_RULE_CAUSAL_DEPTH, assertFactoryRules, validateFactoryRuleDecision, validateFactoryRuleDecisions } from "./validation.js";
|
|
3
|
+
import { resolveFactoryStageRules, resolveFactoryToolRule } from "./resolve.js";
|
|
4
4
|
import { DEFAULT_FACTORY_RULE_VERSION, builtInFactoryRules, defaultFactoryRules } from "./defaults.js";
|
|
5
|
-
export { DEFAULT_FACTORY_RULE_VERSION, FACTORY_GITHUB_EVENTS, FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES, FactoryRuleValidationError, MAX_FACTORY_RULE_CAUSAL_DEPTH, assertFactoryRules, builtInFactoryRules, defaultFactoryRules, factoryRuleSourceForWorkItem, isFactoryRuleStage,
|
|
5
|
+
export { DEFAULT_FACTORY_RULE_VERSION, FACTORY_GITHUB_EVENTS, FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES, FactoryRuleValidationError, MAX_FACTORY_RULE_CAUSAL_DEPTH, assertFactoryRules, builtInFactoryRules, defaultFactoryRules, factoryRuleSourceForWorkItem, isFactoryRuleStage, resolveFactoryStageRules, resolveFactoryToolRule, validateFactoryRuleDecision, validateFactoryRuleDecisions };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MastraDBMessage, MessageList } from '@mastra/core/agent/message-list';
|
|
2
2
|
import type { ComputeStateSignalArgs, ComputeStateSignalResult, ProcessInputStepArgs, Processor } from '@mastra/core/processors';
|
|
3
|
+
import type { BoardRegistry } from '../boards/index.js';
|
|
3
4
|
import type { FactoryRunBindingRecord, WorkItemsStorage, WorkItemRow } from '../storage/domains/work-items/base.js';
|
|
4
5
|
import type { FactoryTransitionService } from './transition-service.js';
|
|
5
6
|
import type { FactoryRuleJsonValue, FactoryRules } from './types.js';
|
|
@@ -30,6 +31,7 @@ export declare class FactoryPhaseStateProcessor implements Processor<'factory-ph
|
|
|
30
31
|
constructor(options: {
|
|
31
32
|
rules: FactoryRules;
|
|
32
33
|
storage: WorkItemsStorage;
|
|
34
|
+
boardRegistry?: BoardRegistry;
|
|
33
35
|
transitionService?: Pick<FactoryTransitionService, 'transition'>;
|
|
34
36
|
messageReader?: PersistedMessageReader;
|
|
35
37
|
recordPullRequestProvenance?: (input: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../src/rules/processor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEpF,OAAO,KAAK,EACV,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAGpH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAExE,OAAO,KAAK,EAIV,oBAAoB,EAEpB,YAAY,EAEb,MAAM,YAAY,CAAC;AAyBpB,KAAK,sBAAsB,GAAG;IAC5B,YAAY,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE;YAAE,SAAS,CAAC,EAAE;gBAAE,KAAK,CAAC,EAAE,IAAI,CAAA;aAAE,CAAA;SAAE,CAAC;QAC1C,OAAO,EAAE;YAAE,KAAK,EAAE,WAAW,CAAC;YAAC,SAAS,EAAE,KAAK,CAAA;SAAE,CAAC;KACnD,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAChE,CAAC;
|
|
1
|
+
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../src/rules/processor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEpF,OAAO,KAAK,EACV,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAGpH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAExE,OAAO,KAAK,EAIV,oBAAoB,EAEpB,YAAY,EAEb,MAAM,YAAY,CAAC;AAyBpB,KAAK,sBAAsB,GAAG;IAC5B,YAAY,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE;YAAE,SAAS,CAAC,EAAE;gBAAE,KAAK,CAAC,EAAE,IAAI,CAAA;aAAE,CAAA;SAAE,CAAC;QAC1C,OAAO,EAAE;YAAE,KAAK,EAAE,WAAW,CAAC;YAAC,SAAS,EAAE,KAAK,CAAA;SAAE,CAAC;KACnD,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAChE,CAAC;AA4KF,qBAAa,0BAA2B,YAAW,SAAS,CAAC,eAAe,CAAC;IAKzE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ1B,QAAQ,CAAC,EAAE,mBAAY;IACvB,QAAQ,CAAC,OAAO,mBAAY;gBAGT,OAAO,EAAE;QACxB,KAAK,EAAE,YAAY,CAAC;QACpB,OAAO,EAAE,gBAAgB,CAAC;QAC1B,aAAa,CAAC,EAAE,aAAa,CAAC;QAC9B,iBAAiB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;QACjE,aAAa,CAAC,EAAE,sBAAsB,CAAC;QACvC,2BAA2B,CAAC,EAAE,CAAC,KAAK,EAAE;YACpC,OAAO,EAAE,uBAAuB,CAAC;YACjC,IAAI,EAAE,WAAW,CAAC;YAClB,kBAAkB,EAAE,MAAM,CAAC;YAC3B,UAAU,EAAE,MAAM,CAAC;YACnB,QAAQ,EAAE,MAAM,CAAC;YACjB,SAAS,EAAE,oBAAoB,CAAC;YAChC,UAAU,EAAE,oBAAoB,CAAC;YACjC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;SAC7B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrB;IAGG,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAY9E,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAwFnF,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAMzC,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;YAoCzD,cAAc;YA8Bd,gBAAgB;CAmG/B"}
|
package/dist/rules/processor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { factoryRuleStage, workItemSource } from "./types.js";
|
|
2
|
-
import { resolveFactoryToolRule } from "./resolve.js";
|
|
3
2
|
import { normalizeFactoryRuleJsonValue, validateFactoryRuleDecisions } from "./validation.js";
|
|
3
|
+
import { resolveFactoryToolRule } from "./resolve.js";
|
|
4
4
|
import { getFactorySessionCoordinates } from "./binding-context.js";
|
|
5
5
|
import { createHash } from "crypto";
|
|
6
6
|
import { resolveRequestThinkingLevel } from "@mastra/code-sdk/agents/model";
|
|
@@ -39,7 +39,7 @@ function workItemSourceKey(item) {
|
|
|
39
39
|
return source ? `${source.integrationId}:${source.type}:${source.externalId}` : null;
|
|
40
40
|
}
|
|
41
41
|
function boardForItem(item) {
|
|
42
|
-
return item.externalSource?.type === "pull-request" ? "review" : "work";
|
|
42
|
+
return item.board ?? (item.externalSource?.type === "pull-request" ? "review" : "work");
|
|
43
43
|
}
|
|
44
44
|
function boundedError(value) {
|
|
45
45
|
return { message: (value instanceof Error ? value.message : typeof value === "string" ? value : "Tool execution failed.").slice(0, 2e3) };
|
|
@@ -202,7 +202,12 @@ var FactoryPhaseStateProcessor = class {
|
|
|
202
202
|
const cacheKey = phaseCacheKey(value, linked);
|
|
203
203
|
if (hasBase && (args.tracking?.currentCacheKey ?? args.lastSnapshot?.metadata?.state?.cacheKey) === cacheKey) return;
|
|
204
204
|
const linkedText = linked.length ? `\nLinked items: ${linked.map((candidate) => `${workItemSource(candidate.externalSource)} ${candidate.title}`).join("; ")}` : "";
|
|
205
|
-
const
|
|
205
|
+
const phaseLabel = this.options.boardRegistry?.get(board)?.phases[stage]?.title ?? PHASE_LABELS[stage] ?? stage;
|
|
206
|
+
const runtime = value.modelId && value.thinkingLevel ? {
|
|
207
|
+
modelId: value.modelId,
|
|
208
|
+
thinkingLevel: value.thinkingLevel
|
|
209
|
+
} : null;
|
|
210
|
+
const snapshotContents = `Factory ${board} phase: ${escapeText(phaseLabel)} (${escapeText(stage)})\nWork item: ${escapeText(item.title)} (${item.id})\nRole: ${escapeText(binding.role)}\nRevision: ${item.revision}\nRules: ${escapeText(this.options.rules.version)}\n` + (runtime ? `Runtime: model=${escapeText(runtime.modelId)}, reasoning-setting=${escapeText(runtime.thinkingLevel)}\n` : "") + (stage === "intake" ? "This card rests in Intake: its work is paused. Answer questions without moving it; when the user asks to resume, request the transition into the working stage first, then continue the work in this session.\n" : "") + `Use factory_transition_work_item with expectedRevision ${item.revision} to request a phase change.${escapeText(linkedText)}`;
|
|
206
211
|
const isDelta = hasBase && prior?.status === "active";
|
|
207
212
|
return {
|
|
208
213
|
id: STATE_ID,
|
|
@@ -218,10 +223,7 @@ var FactoryPhaseStateProcessor = class {
|
|
|
218
223
|
stage,
|
|
219
224
|
role: binding.role,
|
|
220
225
|
revision: item.revision,
|
|
221
|
-
...
|
|
222
|
-
modelId: value.modelId,
|
|
223
|
-
thinkingLevel: value.thinkingLevel
|
|
224
|
-
} : {}
|
|
226
|
+
...runtime ?? {}
|
|
225
227
|
},
|
|
226
228
|
metadata: { value: { phase: value } }
|
|
227
229
|
};
|