@mastra/factory 0.2.2-alpha.1 → 0.2.2-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/auth.js +401 -334
  3. package/dist/auth.js.map +1 -1
  4. package/dist/capabilities/connection.js +1 -1
  5. package/dist/capabilities/intake.js +1 -1
  6. package/dist/capabilities/version-control.js +1 -1
  7. package/dist/factory.js +441 -39622
  8. package/dist/factory.js.map +1 -1
  9. package/dist/index.js +2 -39633
  10. package/dist/integrations/base.js +1 -1
  11. package/dist/integrations/github/config.js +36 -22
  12. package/dist/integrations/github/config.js.map +1 -1
  13. package/dist/integrations/github/factory-session.js +33 -37
  14. package/dist/integrations/github/factory-session.js.map +1 -1
  15. package/dist/integrations/github/integration.js +1037 -3672
  16. package/dist/integrations/github/integration.js.map +1 -1
  17. package/dist/integrations/github/issue-triage.js +71 -71
  18. package/dist/integrations/github/issue-triage.js.map +1 -1
  19. package/dist/integrations/github/pat.js +47 -36
  20. package/dist/integrations/github/pat.js.map +1 -1
  21. package/dist/integrations/github/provenance.js +72 -101
  22. package/dist/integrations/github/provenance.js.map +1 -1
  23. package/dist/integrations/github/routes.js +1272 -1881
  24. package/dist/integrations/github/routes.js.map +1 -1
  25. package/dist/integrations/github/rules.js +203 -471
  26. package/dist/integrations/github/rules.js.map +1 -1
  27. package/dist/integrations/github/sandbox.js +400 -345
  28. package/dist/integrations/github/sandbox.js.map +1 -1
  29. package/dist/integrations/github/session-subscriptions.js +165 -223
  30. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  31. package/dist/integrations/github/subscriptions.js +43 -51
  32. package/dist/integrations/github/subscriptions.js.map +1 -1
  33. package/dist/integrations/github/token-refresh.js +15 -18
  34. package/dist/integrations/github/token-refresh.js.map +1 -1
  35. package/dist/integrations/github/webhook.js +359 -343
  36. package/dist/integrations/github/webhook.js.map +1 -1
  37. package/dist/integrations/linear/agent-tools.js +77 -100
  38. package/dist/integrations/linear/agent-tools.js.map +1 -1
  39. package/dist/integrations/linear/integration.js +611 -1309
  40. package/dist/integrations/linear/integration.js.map +1 -1
  41. package/dist/integrations/linear/routes.js +227 -230
  42. package/dist/integrations/linear/routes.js.map +1 -1
  43. package/dist/integrations/linear/rules.js +126 -346
  44. package/dist/integrations/linear/rules.js.map +1 -1
  45. package/dist/integrations/linear/storage.js +1 -1
  46. package/dist/integrations/platform/api-client.js +131 -135
  47. package/dist/integrations/platform/api-client.js.map +1 -1
  48. package/dist/integrations/platform/github/event-worker.js +275 -573
  49. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  50. package/dist/integrations/platform/github/integration.js +845 -4022
  51. package/dist/integrations/platform/github/integration.js.map +1 -1
  52. package/dist/integrations/platform/linear/integration.js +457 -1309
  53. package/dist/integrations/platform/linear/integration.js.map +1 -1
  54. package/dist/integrations/workos/integration.js +80 -85
  55. package/dist/integrations/workos/integration.js.map +1 -1
  56. package/dist/routes/config.d.ts.map +1 -1
  57. package/dist/routes/config.js +933 -912
  58. package/dist/routes/config.js.map +1 -1
  59. package/dist/routes/custom-provider-source.js +110 -93
  60. package/dist/routes/custom-provider-source.js.map +1 -1
  61. package/dist/routes/fs.js +401 -349
  62. package/dist/routes/fs.js.map +1 -1
  63. package/dist/routes/intake.js +165 -165
  64. package/dist/routes/intake.js.map +1 -1
  65. package/dist/routes/oauth.js +447 -25166
  66. package/dist/routes/oauth.js.map +1 -1
  67. package/dist/routes/projects.js +437 -376
  68. package/dist/routes/projects.js.map +1 -1
  69. package/dist/routes/provider-credentials.js +76 -58
  70. package/dist/routes/provider-credentials.js.map +1 -1
  71. package/dist/routes/route.js +12 -8
  72. package/dist/routes/route.js.map +1 -1
  73. package/dist/routes/skills.js +160 -173
  74. package/dist/routes/skills.js.map +1 -1
  75. package/dist/routes/surface.js +250 -28615
  76. package/dist/routes/surface.js.map +1 -1
  77. package/dist/routes/tenant-credentials.js +141 -166
  78. package/dist/routes/tenant-credentials.js.map +1 -1
  79. package/dist/routes/work-items.js +654 -862
  80. package/dist/routes/work-items.js.map +1 -1
  81. package/dist/rules/binding-context.js +23 -35
  82. package/dist/rules/binding-context.js.map +1 -1
  83. package/dist/rules/defaults.js +200 -309
  84. package/dist/rules/defaults.js.map +1 -1
  85. package/dist/rules/dispatcher.js +438 -683
  86. package/dist/rules/dispatcher.js.map +1 -1
  87. package/dist/rules/index.js +5 -599
  88. package/dist/rules/processor.js +344 -596
  89. package/dist/rules/processor.js.map +1 -1
  90. package/dist/rules/resolve.js +21 -18
  91. package/dist/rules/resolve.js.map +1 -1
  92. package/dist/rules/start-coordinator.js +168 -157
  93. package/dist/rules/start-coordinator.js.map +1 -1
  94. package/dist/rules/tools.js +50 -85
  95. package/dist/rules/tools.js.map +1 -1
  96. package/dist/rules/transition-service.js +183 -454
  97. package/dist/rules/transition-service.js.map +1 -1
  98. package/dist/rules/types.js +33 -29
  99. package/dist/rules/types.js.map +1 -1
  100. package/dist/rules/validation.js +266 -281
  101. package/dist/rules/validation.js.map +1 -1
  102. package/dist/sandbox/fleet.js +272 -250
  103. package/dist/sandbox/fleet.js.map +1 -1
  104. package/dist/sandbox/reattach.js +13 -6
  105. package/dist/sandbox/reattach.js.map +1 -1
  106. package/dist/server-error.js +12 -16
  107. package/dist/server-error.js.map +1 -1
  108. package/dist/skills/service.js +30 -35
  109. package/dist/skills/service.js.map +1 -1
  110. package/dist/spa-static.js +91 -65
  111. package/dist/spa-static.js.map +1 -1
  112. package/dist/state-signing.js +73 -44
  113. package/dist/state-signing.js.map +1 -1
  114. package/dist/storage/domains/audit/agent-audit.js +60 -59
  115. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  116. package/dist/storage/domains/audit/base.js +169 -112
  117. package/dist/storage/domains/audit/base.js.map +1 -1
  118. package/dist/storage/domains/audit/domain.js +155 -153
  119. package/dist/storage/domains/audit/domain.js.map +1 -1
  120. package/dist/storage/domains/credentials/base.js +259 -243
  121. package/dist/storage/domains/credentials/base.js.map +1 -1
  122. package/dist/storage/domains/custom-providers/base.js +138 -143
  123. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  124. package/dist/storage/domains/intake/base.js +75 -57
  125. package/dist/storage/domains/intake/base.js.map +1 -1
  126. package/dist/storage/domains/integrations/base.js +310 -245
  127. package/dist/storage/domains/integrations/base.js.map +1 -1
  128. package/dist/storage/domains/memory-settings/base.js +117 -102
  129. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  130. package/dist/storage/domains/model-packs/base.js +89 -88
  131. package/dist/storage/domains/model-packs/base.js.map +1 -1
  132. package/dist/storage/domains/projects/base.js +97 -92
  133. package/dist/storage/domains/projects/base.js.map +1 -1
  134. package/dist/storage/domains/queue-health/base.js +111 -76
  135. package/dist/storage/domains/queue-health/base.js.map +1 -1
  136. package/dist/storage/domains/source-control/base.js +777 -706
  137. package/dist/storage/domains/source-control/base.js.map +1 -1
  138. package/dist/storage/domains/source-control/inmemory.js +322 -348
  139. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  140. package/dist/storage/domains/work-items/base.js +1266 -1148
  141. package/dist/storage/domains/work-items/base.js.map +1 -1
  142. package/dist/storage/domains/work-items/metrics.js +202 -184
  143. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  144. package/dist/timing.js +29 -22
  145. package/dist/timing.js.map +1 -1
  146. package/dist/workspace.js +236 -477
  147. package/dist/workspace.js.map +1 -1
  148. package/package.json +7 -7
  149. package/dist/capabilities/connection.js.map +0 -1
  150. package/dist/capabilities/intake.js.map +0 -1
  151. package/dist/capabilities/version-control.js.map +0 -1
  152. package/dist/index.js.map +0 -1
  153. package/dist/integrations/base.js.map +0 -1
  154. package/dist/integrations/linear/storage.js.map +0 -1
  155. package/dist/rules/index.js.map +0 -1
@@ -1,599 +1,5 @@
1
- // src/rules/types.ts
2
- var FACTORY_RULE_STAGES = ["intake", "triage", "planning", "execute", "review", "done", "canceled"];
3
- var FACTORY_RULE_BOARDS = ["work", "review"];
4
- var FACTORY_RULE_SOURCES = ["issue", "pullRequest", "linearIssue", "manual"];
5
- var FACTORY_GITHUB_EVENTS = [
6
- "issueOpened",
7
- "pullRequestOpened",
8
- "pullRequestUpdated",
9
- "pullRequestReviewRequested",
10
- "pullRequestMerged"
11
- ];
12
- var FACTORY_LINEAR_EVENTS = ["issueObserved"];
13
- function factoryRuleSourceForWorkItem(source) {
14
- switch (source) {
15
- case "github-issue":
16
- return "issue";
17
- case "github-pr":
18
- return "pullRequest";
19
- case "linear-issue":
20
- return "linearIssue";
21
- case "manual":
22
- return "manual";
23
- }
24
- }
25
-
26
- // src/rules/validation.ts
27
- var MAX_FACTORY_RULE_CAUSAL_DEPTH = 8;
28
- var MAX_VERSION_LENGTH = 128;
29
- var MAX_IDEMPOTENCY_KEY_LENGTH = 256;
30
- var MAX_REASON_LENGTH = 512;
31
- var MAX_TITLE_LENGTH = 512;
32
- var MAX_MESSAGE_LENGTH = 8192;
33
- var MAX_ARGUMENTS_LENGTH = 4096;
34
- var MAX_ROLE_LENGTH = 32;
35
- var MAX_SKILL_NAME_LENGTH = 128;
36
- var MAX_SOURCE_KEY_LENGTH = 256;
37
- var MAX_URL_LENGTH = 2048;
38
- var MAX_METADATA_JSON_LENGTH = 16384;
39
- var MAX_JSON_DEPTH = 8;
40
- var MAX_JSON_COLLECTION_SIZE = 100;
41
- var IDENTIFIER_RE = /^[a-z0-9][a-z0-9_-]*$/i;
42
- var SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
43
- var SENSITIVE_KEY_RE = /(?:authorization|cookie|credential|password|secret|token)/i;
44
- var WORK_ITEM_SOURCES = ["github-issue", "github-pr", "linear-issue", "manual"];
45
- var REJECTION_CODES = [
46
- "forbidden",
47
- "invalid_transition",
48
- "missing_binding",
49
- "stale",
50
- "timeout",
51
- "rule_error",
52
- "causal_depth_exceeded",
53
- "repeated_transition"
54
- ];
55
- var FactoryRuleValidationError = class extends Error {
56
- code = "invalid_factory_rule";
57
- constructor(message) {
58
- super(message);
59
- this.name = "FactoryRuleValidationError";
60
- }
61
- };
62
- function isPlainObject(value) {
63
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
64
- const prototype = Object.getPrototypeOf(value);
65
- return prototype === Object.prototype || prototype === null;
66
- }
67
- function assertExactKeys(value, keys, label) {
68
- const allowed = new Set(keys);
69
- if (Object.keys(value).some((key) => !allowed.has(key))) {
70
- throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);
71
- }
72
- }
73
- function boundedString(value, label, max, pattern) {
74
- if (typeof value !== "string") throw new FactoryRuleValidationError(`${label} must be a string.`);
75
- const normalized = value.trim();
76
- if (normalized.length === 0 || normalized.length > max || pattern && !pattern.test(normalized)) {
77
- throw new FactoryRuleValidationError(`${label} is invalid.`);
78
- }
79
- return normalized;
80
- }
81
- function optionalBoundedString(value, label, max) {
82
- if (value === void 0) return void 0;
83
- return boundedString(value, label, max);
84
- }
85
- function enumValue(value, allowed, label) {
86
- if (typeof value !== "string" || !allowed.includes(value)) {
87
- throw new FactoryRuleValidationError(`${label} is invalid.`);
88
- }
89
- return value;
90
- }
91
- function normalizeFactoryRuleJsonValue(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
92
- if (value === null || typeof value === "boolean" || typeof value === "string") return value;
93
- if (typeof value === "number") {
94
- if (!Number.isFinite(value)) throw new FactoryRuleValidationError("Rule metadata must contain finite numbers.");
95
- return value;
96
- }
97
- if (depth >= MAX_JSON_DEPTH || typeof value !== "object" && !Array.isArray(value)) {
98
- throw new FactoryRuleValidationError("Rule metadata is not bounded JSON.");
99
- }
100
- if (seen.has(value)) throw new FactoryRuleValidationError("Rule metadata must not contain cycles.");
101
- seen.add(value);
102
- try {
103
- if (Array.isArray(value)) {
104
- if (value.length > MAX_JSON_COLLECTION_SIZE) {
105
- throw new FactoryRuleValidationError("Rule metadata contains too many entries.");
106
- }
107
- return value.map((entry) => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));
108
- }
109
- if (!isPlainObject(value)) throw new FactoryRuleValidationError("Rule metadata must use plain objects.");
110
- const entries = Object.entries(value);
111
- if (entries.length > MAX_JSON_COLLECTION_SIZE) {
112
- throw new FactoryRuleValidationError("Rule metadata contains too many fields.");
113
- }
114
- const sanitized = {};
115
- for (const [key, entry] of entries) {
116
- const normalizedKey = boundedString(key, "Rule metadata key", 128, IDENTIFIER_RE);
117
- sanitized[normalizedKey] = SENSITIVE_KEY_RE.test(normalizedKey) ? "[REDACTED]" : normalizeFactoryRuleJsonValue(entry, depth + 1, seen);
118
- }
119
- return sanitized;
120
- } finally {
121
- seen.delete(value);
122
- }
123
- }
124
- function sanitizeMetadata(value) {
125
- if (value === void 0) return void 0;
126
- const sanitized = normalizeFactoryRuleJsonValue(value);
127
- if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError("Rule metadata must be an object.");
128
- if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {
129
- throw new FactoryRuleValidationError("Rule metadata is too large.");
130
- }
131
- return sanitized;
132
- }
133
- function validateBoardRules(rules, label) {
134
- if (!isPlainObject(rules)) throw new FactoryRuleValidationError(`${label} must be an object.`);
135
- for (const [stage, sources] of Object.entries(rules)) {
136
- enumValue(stage, FACTORY_RULE_STAGES, `${label} stage`);
137
- if (!isPlainObject(sources)) throw new FactoryRuleValidationError(`${label}.${stage} must be an object.`);
138
- for (const [source, leaf] of Object.entries(sources)) {
139
- enumValue(source, FACTORY_RULE_SOURCES, `${label}.${stage} source`);
140
- if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`${label}.${stage}.${source} must be an object.`);
141
- assertExactKeys(leaf, ["onEnter", "onExit"], `${label}.${stage}.${source}`);
142
- for (const handler of Object.values(leaf)) {
143
- if (handler !== void 0 && typeof handler !== "function") {
144
- throw new FactoryRuleValidationError(`${label}.${stage}.${source} handlers must be functions.`);
145
- }
146
- }
147
- }
148
- }
149
- }
150
- function assertFactoryRules(rules) {
151
- if (!isPlainObject(rules)) throw new FactoryRuleValidationError("Factory rules must be an object.");
152
- assertExactKeys(rules, ["version", "work", "review", "tools", "github", "linear"], "Factory rules");
153
- boundedString(rules.version, "Factory rule version", MAX_VERSION_LENGTH);
154
- validateBoardRules(rules.work, "Factory rules.work");
155
- validateBoardRules(rules.review, "Factory rules.review");
156
- if (!isPlainObject(rules.tools)) throw new FactoryRuleValidationError("Factory rules.tools must be an object.");
157
- for (const [toolName, leaf] of Object.entries(rules.tools)) {
158
- boundedString(toolName, "Factory tool name", 128, IDENTIFIER_RE);
159
- if (!isPlainObject(leaf))
160
- throw new FactoryRuleValidationError(`Factory rules.tools.${toolName} must be an object.`);
161
- assertExactKeys(leaf, ["onResult"], `Factory rules.tools.${toolName}`);
162
- if (leaf.onResult !== void 0 && typeof leaf.onResult !== "function") {
163
- throw new FactoryRuleValidationError(`Factory rules.tools.${toolName}.onResult must be a function.`);
164
- }
165
- }
166
- if (!isPlainObject(rules.github)) throw new FactoryRuleValidationError("Factory rules.github must be an object.");
167
- for (const [event, leaf] of Object.entries(rules.github)) {
168
- enumValue(event, FACTORY_GITHUB_EVENTS, "Factory GitHub event");
169
- if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.github.${event} must be an object.`);
170
- assertExactKeys(leaf, ["onEvent"], `Factory rules.github.${event}`);
171
- if (leaf.onEvent !== void 0 && typeof leaf.onEvent !== "function") {
172
- throw new FactoryRuleValidationError(`Factory rules.github.${event}.onEvent must be a function.`);
173
- }
174
- }
175
- if (!isPlainObject(rules.linear)) throw new FactoryRuleValidationError("Factory rules.linear must be an object.");
176
- for (const [event, leaf] of Object.entries(rules.linear)) {
177
- enumValue(event, FACTORY_LINEAR_EVENTS, "Factory Linear event");
178
- if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.linear.${event} must be an object.`);
179
- assertExactKeys(leaf, ["onEvent"], `Factory rules.linear.${event}`);
180
- if (leaf.onEvent !== void 0 && typeof leaf.onEvent !== "function") {
181
- throw new FactoryRuleValidationError(`Factory rules.linear.${event}.onEvent must be a function.`);
182
- }
183
- }
184
- }
185
- function commonCommitFields(value) {
186
- return {
187
- idempotencyKey: boundedString(value.idempotencyKey, "Factory decision idempotencyKey", MAX_IDEMPOTENCY_KEY_LENGTH)
188
- };
189
- }
190
- function validateFactoryRuleDecision(value, causalDepth = 0) {
191
- if (causalDepth > MAX_FACTORY_RULE_CAUSAL_DEPTH) {
192
- throw new FactoryRuleValidationError("Factory rule causal depth exceeded.");
193
- }
194
- if (!isPlainObject(value)) throw new FactoryRuleValidationError("Factory rule decision must be an object.");
195
- const type = value.type;
196
- if (typeof type !== "string") throw new FactoryRuleValidationError("Factory rule decision type is required.");
197
- switch (type) {
198
- case "reject": {
199
- assertExactKeys(value, ["type", "code", "reason"], "Factory reject decision");
200
- return {
201
- type,
202
- code: enumValue(value.code, REJECTION_CODES, "Factory rejection code"),
203
- reason: boundedString(value.reason, "Factory rejection reason", MAX_REASON_LENGTH)
204
- };
205
- }
206
- case "transition": {
207
- assertExactKeys(value, ["type", "idempotencyKey", "board", "stage"], "Factory transition decision");
208
- return {
209
- type,
210
- ...commonCommitFields(value),
211
- board: enumValue(value.board, FACTORY_RULE_BOARDS, "Factory transition board"),
212
- stage: enumValue(value.stage, FACTORY_RULE_STAGES, "Factory transition stage")
213
- };
214
- }
215
- case "upsertLinkedWorkItem": {
216
- assertExactKeys(
217
- value,
218
- ["type", "idempotencyKey", "board", "source", "sourceKey", "title", "url", "stage", "metadata"],
219
- "Factory linked work item decision"
220
- );
221
- const url = value.url;
222
- if (url !== null && (typeof url !== "string" || url.length > MAX_URL_LENGTH || !/^https?:\/\//.test(url))) {
223
- throw new FactoryRuleValidationError("Factory linked work item URL is invalid.");
224
- }
225
- const metadata = sanitizeMetadata(value.metadata);
226
- return {
227
- type,
228
- ...commonCommitFields(value),
229
- board: enumValue(value.board, FACTORY_RULE_BOARDS, "Factory linked work item board"),
230
- source: enumValue(value.source, WORK_ITEM_SOURCES, "Factory linked work item source"),
231
- sourceKey: boundedString(value.sourceKey, "Factory linked work item sourceKey", MAX_SOURCE_KEY_LENGTH),
232
- title: boundedString(value.title, "Factory linked work item title", MAX_TITLE_LENGTH),
233
- url,
234
- stage: enumValue(value.stage, FACTORY_RULE_STAGES, "Factory linked work item stage"),
235
- ...metadata ? { metadata } : {}
236
- };
237
- }
238
- case "invokeSkill": {
239
- assertExactKeys(
240
- value,
241
- ["type", "idempotencyKey", "role", "skillName", "arguments", "precedingMessage"],
242
- "Factory invoke skill decision"
243
- );
244
- const args = optionalBoundedString(value.arguments, "Factory skill arguments", MAX_ARGUMENTS_LENGTH);
245
- const precedingMessage = optionalBoundedString(
246
- value.precedingMessage,
247
- "Factory skill preceding message",
248
- MAX_MESSAGE_LENGTH
249
- );
250
- return {
251
- type,
252
- ...commonCommitFields(value),
253
- role: boundedString(value.role, "Factory skill role", MAX_ROLE_LENGTH, IDENTIFIER_RE),
254
- skillName: boundedString(value.skillName, "Factory skill name", MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE),
255
- ...args ? { arguments: args } : {},
256
- ...precedingMessage ? { precedingMessage } : {}
257
- };
258
- }
259
- case "sendMessage": {
260
- assertExactKeys(
261
- value,
262
- ["type", "idempotencyKey", "role", "message", "priority", "idleBehavior", "prepareBinding"],
263
- "Factory send message decision"
264
- );
265
- const priority = value.priority === void 0 ? void 0 : enumValue(value.priority, ["medium", "high", "urgent"], "Factory message priority");
266
- const idleBehavior = value.idleBehavior === void 0 ? void 0 : enumValue(value.idleBehavior, ["persist", "wake"], "Factory message idle behavior");
267
- if (value.prepareBinding !== void 0 && typeof value.prepareBinding !== "boolean") {
268
- throw new FactoryRuleValidationError("Factory message prepareBinding must be a boolean.");
269
- }
270
- return {
271
- type,
272
- ...commonCommitFields(value),
273
- role: boundedString(value.role, "Factory message role", MAX_ROLE_LENGTH, IDENTIFIER_RE),
274
- message: boundedString(value.message, "Factory message", MAX_MESSAGE_LENGTH),
275
- ...priority ? { priority } : {},
276
- ...idleBehavior ? { idleBehavior } : {},
277
- ...value.prepareBinding === true ? { prepareBinding: true } : {}
278
- };
279
- }
280
- case "notify": {
281
- assertExactKeys(value, ["type", "idempotencyKey", "title", "body", "level"], "Factory notify decision");
282
- const body = optionalBoundedString(value.body, "Factory notification body", MAX_MESSAGE_LENGTH);
283
- const level = value.level === void 0 ? void 0 : enumValue(value.level, ["info", "warning", "error"], "Factory notification level");
284
- return {
285
- type,
286
- ...commonCommitFields(value),
287
- title: boundedString(value.title, "Factory notification title", MAX_TITLE_LENGTH),
288
- ...body ? { body } : {},
289
- ...level ? { level } : {}
290
- };
291
- }
292
- default:
293
- throw new FactoryRuleValidationError("Factory rule decision type is unsupported.");
294
- }
295
- }
296
- function validateFactoryRuleDecisions(values, causalDepth = 0) {
297
- if (values.length > MAX_JSON_COLLECTION_SIZE) {
298
- throw new FactoryRuleValidationError("Factory rule produced too many decisions.");
299
- }
300
- const decisions = [];
301
- for (const value of values) {
302
- const decision = validateFactoryRuleDecision(value, causalDepth);
303
- if (decision.type === "reject") {
304
- throw new FactoryRuleValidationError("A rejection cannot be persisted with commit decisions.");
305
- }
306
- decisions.push(decision);
307
- }
308
- const keys = decisions.map((decision) => decision.idempotencyKey);
309
- if (new Set(keys).size !== keys.length) {
310
- throw new FactoryRuleValidationError("Factory decisions require unique idempotency keys.");
311
- }
312
- return decisions;
313
- }
314
-
315
- // src/rules/defaults.ts
316
- var DEFAULT_FACTORY_RULE_VERSION = "factory-default-v1";
317
- function trustedGithubActor(context) {
318
- return context.actor.type === "github" && context.actor.trusted;
319
- }
320
- function invokeIssueInvestigation(context) {
321
- return {
322
- type: "invokeSkill",
323
- idempotencyKey: `${context.ingress.id}:factory-triage`,
324
- role: "triage",
325
- skillName: "factory-triage",
326
- arguments: context.item.url ? `GitHub issue (${context.item.url})` : context.item.title
327
- };
328
- }
329
- function investigateTriagedIssue(context) {
330
- return invokeIssueInvestigation(context);
331
- }
332
- function investigateTriagedLinearIssue(context) {
333
- const identifier = context.item.sourceKey?.startsWith("linear:") ? context.item.sourceKey.slice("linear:".length) : context.item.title;
334
- return {
335
- type: "invokeSkill",
336
- idempotencyKey: `${context.ingress.id}:factory-triage-linear`,
337
- role: "triage",
338
- skillName: "factory-triage",
339
- arguments: `Linear issue ${identifier}${context.item.url ? ` (${context.item.url})` : ""}`
340
- };
341
- }
342
- function planWorkItem(context) {
343
- return {
344
- type: "invokeSkill",
345
- idempotencyKey: `${context.ingress.id}:factory-plan`,
346
- role: "plan",
347
- skillName: "factory-plan",
348
- arguments: context.item.url ? `Work item (${context.item.url})` : context.item.title
349
- };
350
- }
351
- function reviewPullRequest(context) {
352
- return {
353
- type: "invokeSkill",
354
- idempotencyKey: `${context.ingress.id}:factory-review`,
355
- role: "review",
356
- skillName: "factory-review",
357
- arguments: context.item.url ? `GitHub pull request (${context.item.url})` : context.item.title
358
- };
359
- }
360
- function resultContent(value) {
361
- if (typeof value === "string") return value;
362
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
363
- const content = value.content;
364
- return typeof content === "string" ? content : void 0;
365
- }
366
- function advanceApprovedPlan(context) {
367
- if (context.result.status !== "success" || context.board !== "work" || context.item.stages.length !== 1 || context.item.stages[0] !== "planning" || context.actor.type !== "agent" || context.actor.role !== "plan" || !resultContent(context.result.value)?.startsWith("Plan approved.")) {
368
- return;
369
- }
370
- return {
371
- type: "transition",
372
- idempotencyKey: `${context.ingress.id}:approved-plan`,
373
- board: "work",
374
- stage: "execute"
375
- };
376
- }
377
- function createdAfterFactory(createdAt, factoryCreatedAt) {
378
- if (!createdAt) return false;
379
- const sourceCreatedAt = Date.parse(createdAt);
380
- const projectCreatedAt = Date.parse(factoryCreatedAt);
381
- return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;
382
- }
383
- function issueOpened(context) {
384
- if (!context.issue) return;
385
- return {
386
- type: "upsertLinkedWorkItem",
387
- idempotencyKey: `${context.ingress.id}:issue-intake`,
388
- board: "work",
389
- source: "github-issue",
390
- sourceKey: `github-issue:${context.issue.number}`,
391
- title: context.issue.title,
392
- url: context.issue.url,
393
- stage: trustedGithubActor(context) && createdAfterFactory(context.issue.createdAt, context.factory.createdAt) ? "triage" : "intake",
394
- metadata: {
395
- githubRepositoryId: context.repository.id,
396
- githubIssueNumber: context.issue.number
397
- }
398
- };
399
- }
400
- function pullRequestOpened(context) {
401
- if (!context.pullRequest) return;
402
- return {
403
- type: "upsertLinkedWorkItem",
404
- idempotencyKey: `${context.ingress.id}:pull-request-intake`,
405
- board: "review",
406
- source: "github-pr",
407
- sourceKey: `github-pr:${context.pullRequest.number}`,
408
- title: context.pullRequest.title,
409
- url: context.pullRequest.url,
410
- stage: trustedGithubActor(context) && createdAfterFactory(context.pullRequest.createdAt, context.factory.createdAt) ? "review" : "intake",
411
- metadata: {
412
- githubRepositoryId: context.repository.id,
413
- githubPullRequestNumber: context.pullRequest.number,
414
- factoryAuthored: context.actor.type === "github" && context.actor.factoryAuthored,
415
- headBranch: context.pullRequest.headBranch,
416
- baseBranch: context.pullRequest.baseBranch
417
- }
418
- };
419
- }
420
- function pullRequestMerged(context) {
421
- if (!context.item || !context.pullRequest?.merged) return;
422
- return {
423
- type: "sendMessage",
424
- idempotencyKey: `${context.ingress.id}:assess-work-completion`,
425
- role: "work",
426
- 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.`
427
- };
428
- }
429
- function linearIssueObserved(context) {
430
- if (context.item) return;
431
- return {
432
- type: "upsertLinkedWorkItem",
433
- idempotencyKey: `${context.ingress.id}:issue-triage`,
434
- board: "work",
435
- source: "linear-issue",
436
- sourceKey: `linear:${context.issue.identifier}`,
437
- title: `${context.issue.identifier}: ${context.issue.title}`,
438
- url: context.issue.url,
439
- stage: "triage",
440
- metadata: {
441
- linearIssueId: context.issue.id,
442
- linearIssueIdentifier: context.issue.identifier,
443
- linearState: context.issue.state,
444
- linearStateType: context.issue.stateType,
445
- linearPriority: context.issue.priorityLabel,
446
- linearAssignee: context.issue.assignee,
447
- linearTeam: context.issue.team
448
- }
449
- };
450
- }
451
- var BUILT_IN_DEFAULTS = {
452
- work: {
453
- triage: {
454
- issue: { onEnter: investigateTriagedIssue },
455
- linearIssue: { onEnter: investigateTriagedLinearIssue }
456
- },
457
- planning: {
458
- issue: { onEnter: planWorkItem },
459
- linearIssue: { onEnter: planWorkItem },
460
- manual: { onEnter: planWorkItem }
461
- }
462
- },
463
- review: { review: { pullRequest: { onEnter: reviewPullRequest } } },
464
- tools: { submit_plan: { onResult: advanceApprovedPlan } },
465
- github: {
466
- issueOpened: { onEvent: issueOpened },
467
- pullRequestOpened: { onEvent: pullRequestOpened },
468
- pullRequestMerged: { onEvent: pullRequestMerged }
469
- },
470
- linear: { issueObserved: { onEvent: linearIssueObserved } }
471
- };
472
- function mergeBoardRules(base, overrides) {
473
- const result = {};
474
- const stages = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]);
475
- for (const stage of stages) {
476
- const baseSources = base?.[stage];
477
- const overrideSources = overrides?.[stage];
478
- const sources = /* @__PURE__ */ new Set([
479
- ...Object.keys(baseSources ?? {}),
480
- ...Object.keys(overrideSources ?? {})
481
- ]);
482
- const mergedSources = {};
483
- for (const source of sources) {
484
- const baseLeaf = baseSources?.[source];
485
- const overrideLeaf = overrideSources?.[source];
486
- mergedSources[source] = {
487
- ...baseLeaf?.onEnter ? { onEnter: baseLeaf.onEnter } : {},
488
- ...baseLeaf?.onExit ? { onExit: baseLeaf.onExit } : {},
489
- ...overrideLeaf && "onEnter" in overrideLeaf ? { onEnter: overrideLeaf.onEnter } : {},
490
- ...overrideLeaf && "onExit" in overrideLeaf ? { onExit: overrideLeaf.onExit } : {}
491
- };
492
- }
493
- result[stage] = mergedSources;
494
- }
495
- return result;
496
- }
497
- function mergeToolRules(base, overrides) {
498
- const result = {};
499
- for (const name of /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {
500
- const baseLeaf = base?.[name];
501
- const overrideLeaf = overrides?.[name];
502
- result[name] = {
503
- ...baseLeaf?.onResult ? { onResult: baseLeaf.onResult } : {},
504
- ...overrideLeaf && "onResult" in overrideLeaf ? { onResult: overrideLeaf.onResult } : {}
505
- };
506
- }
507
- return result;
508
- }
509
- function mergeGithubRules(base, overrides) {
510
- const result = {};
511
- const events = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]);
512
- for (const event of events) {
513
- const baseLeaf = base?.[event];
514
- const overrideLeaf = overrides?.[event];
515
- result[event] = {
516
- ...baseLeaf?.onEvent ? { onEvent: baseLeaf.onEvent } : {},
517
- ...overrideLeaf && "onEvent" in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}
518
- };
519
- }
520
- return result;
521
- }
522
- function mergeLinearRules(base, overrides) {
523
- const result = {};
524
- const events = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]);
525
- for (const event of events) {
526
- const baseLeaf = base?.[event];
527
- const overrideLeaf = overrides?.[event];
528
- result[event] = {
529
- ...baseLeaf?.onEvent ? { onEvent: baseLeaf.onEvent } : {},
530
- ...overrideLeaf && "onEvent" in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}
531
- };
532
- }
533
- return result;
534
- }
535
- function mergeFactoryRuleOverrides(base, overrides = {}) {
536
- return {
537
- work: mergeBoardRules(base.work, overrides.work),
538
- review: mergeBoardRules(base.review, overrides.review),
539
- tools: mergeToolRules(base.tools, overrides.tools),
540
- github: mergeGithubRules(base.github, overrides.github),
541
- linear: mergeLinearRules(base.linear, overrides.linear)
542
- };
543
- }
544
- function defaultFactoryRules(input) {
545
- if (typeof input?.version !== "string" || input.version.trim().length === 0) {
546
- throw new FactoryRuleValidationError("Factory rule version is required.");
547
- }
548
- const rules = {
549
- version: input.version.trim(),
550
- ...mergeFactoryRuleOverrides(BUILT_IN_DEFAULTS, input.overrides)
551
- };
552
- assertFactoryRules(rules);
553
- return rules;
554
- }
555
- function builtInFactoryRules() {
556
- return defaultFactoryRules({ version: DEFAULT_FACTORY_RULE_VERSION });
557
- }
558
-
559
- // src/rules/resolve.ts
560
- function resolveFactoryStageRules(rules, input) {
561
- if (input.fromStage === input.toStage && !input.initialEntry) return [];
562
- const boardRules = rules[input.board];
563
- const resolved = [];
564
- const onExit = input.initialEntry ? void 0 : boardRules[input.fromStage]?.[input.source]?.onExit;
565
- if (onExit) resolved.push({ phase: "exit", handler: onExit });
566
- const onEnter = boardRules[input.toStage]?.[input.source]?.onEnter;
567
- if (onEnter) resolved.push({ phase: "enter", handler: onEnter });
568
- return resolved;
569
- }
570
- function resolveFactoryToolRule(rules, toolName) {
571
- return rules.tools[toolName]?.onResult;
572
- }
573
- function resolveFactoryGithubRule(rules, event) {
574
- return rules.github[event]?.onEvent;
575
- }
576
- function resolveFactoryLinearRule(rules, event) {
577
- return rules.linear[event]?.onEvent;
578
- }
579
- export {
580
- DEFAULT_FACTORY_RULE_VERSION,
581
- FACTORY_GITHUB_EVENTS,
582
- FACTORY_LINEAR_EVENTS,
583
- FACTORY_RULE_BOARDS,
584
- FACTORY_RULE_SOURCES,
585
- FACTORY_RULE_STAGES,
586
- FactoryRuleValidationError,
587
- MAX_FACTORY_RULE_CAUSAL_DEPTH,
588
- assertFactoryRules,
589
- builtInFactoryRules,
590
- defaultFactoryRules,
591
- factoryRuleSourceForWorkItem,
592
- resolveFactoryGithubRule,
593
- resolveFactoryLinearRule,
594
- resolveFactoryStageRules,
595
- resolveFactoryToolRule,
596
- validateFactoryRuleDecision,
597
- validateFactoryRuleDecisions
598
- };
599
- //# sourceMappingURL=index.js.map
1
+ import { resolveFactoryGithubRule, resolveFactoryLinearRule, resolveFactoryStageRules, resolveFactoryToolRule } from "./resolve.js";
2
+ import { FACTORY_GITHUB_EVENTS, FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES, factoryRuleSourceForWorkItem } from "./types.js";
3
+ import { FactoryRuleValidationError, MAX_FACTORY_RULE_CAUSAL_DEPTH, assertFactoryRules, validateFactoryRuleDecision, validateFactoryRuleDecisions } from "./validation.js";
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, resolveFactoryGithubRule, resolveFactoryLinearRule, resolveFactoryStageRules, resolveFactoryToolRule, validateFactoryRuleDecision, validateFactoryRuleDecisions };