@mastra/factory 0.2.2-alpha.0 → 0.2.2-alpha.2

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 (174) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/auth.d.ts +2 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +401 -314
  5. package/dist/auth.js.map +1 -1
  6. package/dist/capabilities/connection.js +1 -1
  7. package/dist/capabilities/intake.js +1 -1
  8. package/dist/capabilities/version-control.js +1 -1
  9. package/dist/factory.d.ts.map +1 -1
  10. package/dist/factory.js +441 -39455
  11. package/dist/factory.js.map +1 -1
  12. package/dist/index.js +2 -39466
  13. package/dist/integrations/base.js +1 -1
  14. package/dist/integrations/github/config.js +36 -22
  15. package/dist/integrations/github/config.js.map +1 -1
  16. package/dist/integrations/github/factory-session.js +33 -37
  17. package/dist/integrations/github/factory-session.js.map +1 -1
  18. package/dist/integrations/github/integration.js +1037 -3624
  19. package/dist/integrations/github/integration.js.map +1 -1
  20. package/dist/integrations/github/issue-triage.js +71 -71
  21. package/dist/integrations/github/issue-triage.js.map +1 -1
  22. package/dist/integrations/github/pat.js +47 -36
  23. package/dist/integrations/github/pat.js.map +1 -1
  24. package/dist/integrations/github/provenance.js +72 -101
  25. package/dist/integrations/github/provenance.js.map +1 -1
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +1272 -1833
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/rules.js +203 -471
  30. package/dist/integrations/github/rules.js.map +1 -1
  31. package/dist/integrations/github/sandbox.d.ts +2 -0
  32. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  33. package/dist/integrations/github/sandbox.js +401 -326
  34. package/dist/integrations/github/sandbox.js.map +1 -1
  35. package/dist/integrations/github/session-subscriptions.js +165 -223
  36. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  37. package/dist/integrations/github/subscriptions.js +43 -51
  38. package/dist/integrations/github/subscriptions.js.map +1 -1
  39. package/dist/integrations/github/token-refresh.js +15 -18
  40. package/dist/integrations/github/token-refresh.js.map +1 -1
  41. package/dist/integrations/github/webhook.js +359 -343
  42. package/dist/integrations/github/webhook.js.map +1 -1
  43. package/dist/integrations/linear/agent-tools.js +77 -100
  44. package/dist/integrations/linear/agent-tools.js.map +1 -1
  45. package/dist/integrations/linear/integration.js +611 -1309
  46. package/dist/integrations/linear/integration.js.map +1 -1
  47. package/dist/integrations/linear/routes.js +227 -230
  48. package/dist/integrations/linear/routes.js.map +1 -1
  49. package/dist/integrations/linear/rules.js +126 -346
  50. package/dist/integrations/linear/rules.js.map +1 -1
  51. package/dist/integrations/linear/storage.js +1 -1
  52. package/dist/integrations/platform/api-client.js +131 -135
  53. package/dist/integrations/platform/api-client.js.map +1 -1
  54. package/dist/integrations/platform/github/event-worker.js +275 -573
  55. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  56. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  57. package/dist/integrations/platform/github/integration.js +845 -3942
  58. package/dist/integrations/platform/github/integration.js.map +1 -1
  59. package/dist/integrations/platform/linear/integration.js +457 -1309
  60. package/dist/integrations/platform/linear/integration.js.map +1 -1
  61. package/dist/integrations/workos/integration.js +80 -85
  62. package/dist/integrations/workos/integration.js.map +1 -1
  63. package/dist/routes/config.d.ts.map +1 -1
  64. package/dist/routes/config.js +933 -915
  65. package/dist/routes/config.js.map +1 -1
  66. package/dist/routes/custom-provider-source.js +110 -93
  67. package/dist/routes/custom-provider-source.js.map +1 -1
  68. package/dist/routes/fs.js +401 -349
  69. package/dist/routes/fs.js.map +1 -1
  70. package/dist/routes/intake.js +165 -165
  71. package/dist/routes/intake.js.map +1 -1
  72. package/dist/routes/oauth.js +447 -25166
  73. package/dist/routes/oauth.js.map +1 -1
  74. package/dist/routes/projects.js +437 -376
  75. package/dist/routes/projects.js.map +1 -1
  76. package/dist/routes/provider-credentials.js +76 -58
  77. package/dist/routes/provider-credentials.js.map +1 -1
  78. package/dist/routes/route.js +12 -8
  79. package/dist/routes/route.js.map +1 -1
  80. package/dist/routes/skills.js +160 -173
  81. package/dist/routes/skills.js.map +1 -1
  82. package/dist/routes/surface.js +250 -28618
  83. package/dist/routes/surface.js.map +1 -1
  84. package/dist/routes/tenant-credentials.js +141 -166
  85. package/dist/routes/tenant-credentials.js.map +1 -1
  86. package/dist/routes/work-items.js +654 -862
  87. package/dist/routes/work-items.js.map +1 -1
  88. package/dist/rules/binding-context.js +23 -35
  89. package/dist/rules/binding-context.js.map +1 -1
  90. package/dist/rules/defaults.js +200 -309
  91. package/dist/rules/defaults.js.map +1 -1
  92. package/dist/rules/dispatcher.d.ts.map +1 -1
  93. package/dist/rules/dispatcher.js +438 -677
  94. package/dist/rules/dispatcher.js.map +1 -1
  95. package/dist/rules/index.js +5 -599
  96. package/dist/rules/processor.js +344 -596
  97. package/dist/rules/processor.js.map +1 -1
  98. package/dist/rules/resolve.js +21 -18
  99. package/dist/rules/resolve.js.map +1 -1
  100. package/dist/rules/start-coordinator.js +168 -157
  101. package/dist/rules/start-coordinator.js.map +1 -1
  102. package/dist/rules/tools.js +50 -85
  103. package/dist/rules/tools.js.map +1 -1
  104. package/dist/rules/transition-service.js +183 -454
  105. package/dist/rules/transition-service.js.map +1 -1
  106. package/dist/rules/types.js +33 -29
  107. package/dist/rules/types.js.map +1 -1
  108. package/dist/rules/validation.js +266 -281
  109. package/dist/rules/validation.js.map +1 -1
  110. package/dist/sandbox/fleet.js +272 -250
  111. package/dist/sandbox/fleet.js.map +1 -1
  112. package/dist/sandbox/reattach.js +13 -6
  113. package/dist/sandbox/reattach.js.map +1 -1
  114. package/dist/server-error.js +12 -16
  115. package/dist/server-error.js.map +1 -1
  116. package/dist/skills/service.js +30 -35
  117. package/dist/skills/service.js.map +1 -1
  118. package/dist/spa-static.d.ts.map +1 -1
  119. package/dist/spa-static.js +91 -64
  120. package/dist/spa-static.js.map +1 -1
  121. package/dist/state-signing.js +73 -44
  122. package/dist/state-signing.js.map +1 -1
  123. package/dist/storage/domains/audit/agent-audit.js +60 -59
  124. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  125. package/dist/storage/domains/audit/base.js +169 -112
  126. package/dist/storage/domains/audit/base.js.map +1 -1
  127. package/dist/storage/domains/audit/domain.js +155 -153
  128. package/dist/storage/domains/audit/domain.js.map +1 -1
  129. package/dist/storage/domains/credentials/base.js +259 -243
  130. package/dist/storage/domains/credentials/base.js.map +1 -1
  131. package/dist/storage/domains/custom-providers/base.js +138 -143
  132. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  133. package/dist/storage/domains/intake/base.js +75 -57
  134. package/dist/storage/domains/intake/base.js.map +1 -1
  135. package/dist/storage/domains/integrations/base.js +310 -245
  136. package/dist/storage/domains/integrations/base.js.map +1 -1
  137. package/dist/storage/domains/memory-settings/base.js +117 -102
  138. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  139. package/dist/storage/domains/model-packs/base.js +89 -88
  140. package/dist/storage/domains/model-packs/base.js.map +1 -1
  141. package/dist/storage/domains/projects/base.js +97 -92
  142. package/dist/storage/domains/projects/base.js.map +1 -1
  143. package/dist/storage/domains/queue-health/base.js +111 -76
  144. package/dist/storage/domains/queue-health/base.js.map +1 -1
  145. package/dist/storage/domains/source-control/base.d.ts +9 -0
  146. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  147. package/dist/storage/domains/source-control/base.js +777 -702
  148. package/dist/storage/domains/source-control/base.js.map +1 -1
  149. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  150. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  151. package/dist/storage/domains/source-control/inmemory.js +322 -344
  152. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  153. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  154. package/dist/storage/domains/work-items/base.js +1266 -1132
  155. package/dist/storage/domains/work-items/base.js.map +1 -1
  156. package/dist/storage/domains/work-items/metrics.js +202 -184
  157. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  158. package/dist/timing.d.ts +15 -0
  159. package/dist/timing.d.ts.map +1 -0
  160. package/dist/timing.js +34 -0
  161. package/dist/timing.js.map +1 -0
  162. package/dist/workspace.d.ts +2 -3
  163. package/dist/workspace.d.ts.map +1 -1
  164. package/dist/workspace.js +236 -438
  165. package/dist/workspace.js.map +1 -1
  166. package/factory-skills/factory-review/SKILL.md +9 -2
  167. package/package.json +9 -9
  168. package/dist/capabilities/connection.js.map +0 -1
  169. package/dist/capabilities/intake.js.map +0 -1
  170. package/dist/capabilities/version-control.js.map +0 -1
  171. package/dist/index.js.map +0 -1
  172. package/dist/integrations/base.js.map +0 -1
  173. package/dist/integrations/linear/storage.js.map +0 -1
  174. package/dist/rules/index.js.map +0 -1
@@ -1,474 +1,203 @@
1
- // src/rules/transition-service.ts
1
+ import { resolveFactoryStageRules } from "./resolve.js";
2
+ import { FACTORY_RULE_STAGES, factoryRuleSourceForWorkItem } from "./types.js";
3
+ import { validateFactoryRuleDecision, validateFactoryRuleDecisions } from "./validation.js";
2
4
  import { randomUUID } from "crypto";
3
-
4
- // src/rules/resolve.ts
5
- function resolveFactoryStageRules(rules, input) {
6
- if (input.fromStage === input.toStage && !input.initialEntry) return [];
7
- const boardRules = rules[input.board];
8
- const resolved = [];
9
- const onExit = input.initialEntry ? void 0 : boardRules[input.fromStage]?.[input.source]?.onExit;
10
- if (onExit) resolved.push({ phase: "exit", handler: onExit });
11
- const onEnter = boardRules[input.toStage]?.[input.source]?.onEnter;
12
- if (onEnter) resolved.push({ phase: "enter", handler: onEnter });
13
- return resolved;
14
- }
15
-
16
- // src/rules/types.ts
17
- var FACTORY_RULE_STAGES = ["intake", "triage", "planning", "execute", "review", "done", "canceled"];
18
- var FACTORY_RULE_BOARDS = ["work", "review"];
19
- function factoryRuleSourceForWorkItem(source) {
20
- switch (source) {
21
- case "github-issue":
22
- return "issue";
23
- case "github-pr":
24
- return "pullRequest";
25
- case "linear-issue":
26
- return "linearIssue";
27
- case "manual":
28
- return "manual";
29
- }
30
- }
31
-
32
- // src/rules/validation.ts
33
- var MAX_FACTORY_RULE_CAUSAL_DEPTH = 8;
34
- var MAX_IDEMPOTENCY_KEY_LENGTH = 256;
35
- var MAX_REASON_LENGTH = 512;
36
- var MAX_TITLE_LENGTH = 512;
37
- var MAX_MESSAGE_LENGTH = 8192;
38
- var MAX_ARGUMENTS_LENGTH = 4096;
39
- var MAX_ROLE_LENGTH = 32;
40
- var MAX_SKILL_NAME_LENGTH = 128;
41
- var MAX_SOURCE_KEY_LENGTH = 256;
42
- var MAX_URL_LENGTH = 2048;
43
- var MAX_METADATA_JSON_LENGTH = 16384;
44
- var MAX_JSON_DEPTH = 8;
45
- var MAX_JSON_COLLECTION_SIZE = 100;
46
- var IDENTIFIER_RE = /^[a-z0-9][a-z0-9_-]*$/i;
47
- var SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
48
- var SENSITIVE_KEY_RE = /(?:authorization|cookie|credential|password|secret|token)/i;
49
- var WORK_ITEM_SOURCES = ["github-issue", "github-pr", "linear-issue", "manual"];
50
- var REJECTION_CODES = [
51
- "forbidden",
52
- "invalid_transition",
53
- "missing_binding",
54
- "stale",
55
- "timeout",
56
- "rule_error",
57
- "causal_depth_exceeded",
58
- "repeated_transition"
59
- ];
60
- var FactoryRuleValidationError = class extends Error {
61
- code = "invalid_factory_rule";
62
- constructor(message) {
63
- super(message);
64
- this.name = "FactoryRuleValidationError";
65
- }
66
- };
67
- function isPlainObject(value) {
68
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
69
- const prototype = Object.getPrototypeOf(value);
70
- return prototype === Object.prototype || prototype === null;
71
- }
72
- function assertExactKeys(value, keys, label) {
73
- const allowed = new Set(keys);
74
- if (Object.keys(value).some((key) => !allowed.has(key))) {
75
- throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);
76
- }
77
- }
78
- function boundedString(value, label, max, pattern) {
79
- if (typeof value !== "string") throw new FactoryRuleValidationError(`${label} must be a string.`);
80
- const normalized = value.trim();
81
- if (normalized.length === 0 || normalized.length > max || pattern && !pattern.test(normalized)) {
82
- throw new FactoryRuleValidationError(`${label} is invalid.`);
83
- }
84
- return normalized;
85
- }
86
- function optionalBoundedString(value, label, max) {
87
- if (value === void 0) return void 0;
88
- return boundedString(value, label, max);
89
- }
90
- function enumValue(value, allowed, label) {
91
- if (typeof value !== "string" || !allowed.includes(value)) {
92
- throw new FactoryRuleValidationError(`${label} is invalid.`);
93
- }
94
- return value;
95
- }
96
- function normalizeFactoryRuleJsonValue(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
97
- if (value === null || typeof value === "boolean" || typeof value === "string") return value;
98
- if (typeof value === "number") {
99
- if (!Number.isFinite(value)) throw new FactoryRuleValidationError("Rule metadata must contain finite numbers.");
100
- return value;
101
- }
102
- if (depth >= MAX_JSON_DEPTH || typeof value !== "object" && !Array.isArray(value)) {
103
- throw new FactoryRuleValidationError("Rule metadata is not bounded JSON.");
104
- }
105
- if (seen.has(value)) throw new FactoryRuleValidationError("Rule metadata must not contain cycles.");
106
- seen.add(value);
107
- try {
108
- if (Array.isArray(value)) {
109
- if (value.length > MAX_JSON_COLLECTION_SIZE) {
110
- throw new FactoryRuleValidationError("Rule metadata contains too many entries.");
111
- }
112
- return value.map((entry) => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));
113
- }
114
- if (!isPlainObject(value)) throw new FactoryRuleValidationError("Rule metadata must use plain objects.");
115
- const entries = Object.entries(value);
116
- if (entries.length > MAX_JSON_COLLECTION_SIZE) {
117
- throw new FactoryRuleValidationError("Rule metadata contains too many fields.");
118
- }
119
- const sanitized = {};
120
- for (const [key, entry] of entries) {
121
- const normalizedKey = boundedString(key, "Rule metadata key", 128, IDENTIFIER_RE);
122
- sanitized[normalizedKey] = SENSITIVE_KEY_RE.test(normalizedKey) ? "[REDACTED]" : normalizeFactoryRuleJsonValue(entry, depth + 1, seen);
123
- }
124
- return sanitized;
125
- } finally {
126
- seen.delete(value);
127
- }
128
- }
129
- function sanitizeMetadata(value) {
130
- if (value === void 0) return void 0;
131
- const sanitized = normalizeFactoryRuleJsonValue(value);
132
- if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError("Rule metadata must be an object.");
133
- if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {
134
- throw new FactoryRuleValidationError("Rule metadata is too large.");
135
- }
136
- return sanitized;
137
- }
138
- function commonCommitFields(value) {
139
- return {
140
- idempotencyKey: boundedString(value.idempotencyKey, "Factory decision idempotencyKey", MAX_IDEMPOTENCY_KEY_LENGTH)
141
- };
142
- }
143
- function validateFactoryRuleDecision(value, causalDepth = 0) {
144
- if (causalDepth > MAX_FACTORY_RULE_CAUSAL_DEPTH) {
145
- throw new FactoryRuleValidationError("Factory rule causal depth exceeded.");
146
- }
147
- if (!isPlainObject(value)) throw new FactoryRuleValidationError("Factory rule decision must be an object.");
148
- const type = value.type;
149
- if (typeof type !== "string") throw new FactoryRuleValidationError("Factory rule decision type is required.");
150
- switch (type) {
151
- case "reject": {
152
- assertExactKeys(value, ["type", "code", "reason"], "Factory reject decision");
153
- return {
154
- type,
155
- code: enumValue(value.code, REJECTION_CODES, "Factory rejection code"),
156
- reason: boundedString(value.reason, "Factory rejection reason", MAX_REASON_LENGTH)
157
- };
158
- }
159
- case "transition": {
160
- assertExactKeys(value, ["type", "idempotencyKey", "board", "stage"], "Factory transition decision");
161
- return {
162
- type,
163
- ...commonCommitFields(value),
164
- board: enumValue(value.board, FACTORY_RULE_BOARDS, "Factory transition board"),
165
- stage: enumValue(value.stage, FACTORY_RULE_STAGES, "Factory transition stage")
166
- };
167
- }
168
- case "upsertLinkedWorkItem": {
169
- assertExactKeys(
170
- value,
171
- ["type", "idempotencyKey", "board", "source", "sourceKey", "title", "url", "stage", "metadata"],
172
- "Factory linked work item decision"
173
- );
174
- const url = value.url;
175
- if (url !== null && (typeof url !== "string" || url.length > MAX_URL_LENGTH || !/^https?:\/\//.test(url))) {
176
- throw new FactoryRuleValidationError("Factory linked work item URL is invalid.");
177
- }
178
- const metadata = sanitizeMetadata(value.metadata);
179
- return {
180
- type,
181
- ...commonCommitFields(value),
182
- board: enumValue(value.board, FACTORY_RULE_BOARDS, "Factory linked work item board"),
183
- source: enumValue(value.source, WORK_ITEM_SOURCES, "Factory linked work item source"),
184
- sourceKey: boundedString(value.sourceKey, "Factory linked work item sourceKey", MAX_SOURCE_KEY_LENGTH),
185
- title: boundedString(value.title, "Factory linked work item title", MAX_TITLE_LENGTH),
186
- url,
187
- stage: enumValue(value.stage, FACTORY_RULE_STAGES, "Factory linked work item stage"),
188
- ...metadata ? { metadata } : {}
189
- };
190
- }
191
- case "invokeSkill": {
192
- assertExactKeys(
193
- value,
194
- ["type", "idempotencyKey", "role", "skillName", "arguments", "precedingMessage"],
195
- "Factory invoke skill decision"
196
- );
197
- const args = optionalBoundedString(value.arguments, "Factory skill arguments", MAX_ARGUMENTS_LENGTH);
198
- const precedingMessage = optionalBoundedString(
199
- value.precedingMessage,
200
- "Factory skill preceding message",
201
- MAX_MESSAGE_LENGTH
202
- );
203
- return {
204
- type,
205
- ...commonCommitFields(value),
206
- role: boundedString(value.role, "Factory skill role", MAX_ROLE_LENGTH, IDENTIFIER_RE),
207
- skillName: boundedString(value.skillName, "Factory skill name", MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE),
208
- ...args ? { arguments: args } : {},
209
- ...precedingMessage ? { precedingMessage } : {}
210
- };
211
- }
212
- case "sendMessage": {
213
- assertExactKeys(
214
- value,
215
- ["type", "idempotencyKey", "role", "message", "priority", "idleBehavior", "prepareBinding"],
216
- "Factory send message decision"
217
- );
218
- const priority = value.priority === void 0 ? void 0 : enumValue(value.priority, ["medium", "high", "urgent"], "Factory message priority");
219
- const idleBehavior = value.idleBehavior === void 0 ? void 0 : enumValue(value.idleBehavior, ["persist", "wake"], "Factory message idle behavior");
220
- if (value.prepareBinding !== void 0 && typeof value.prepareBinding !== "boolean") {
221
- throw new FactoryRuleValidationError("Factory message prepareBinding must be a boolean.");
222
- }
223
- return {
224
- type,
225
- ...commonCommitFields(value),
226
- role: boundedString(value.role, "Factory message role", MAX_ROLE_LENGTH, IDENTIFIER_RE),
227
- message: boundedString(value.message, "Factory message", MAX_MESSAGE_LENGTH),
228
- ...priority ? { priority } : {},
229
- ...idleBehavior ? { idleBehavior } : {},
230
- ...value.prepareBinding === true ? { prepareBinding: true } : {}
231
- };
232
- }
233
- case "notify": {
234
- assertExactKeys(value, ["type", "idempotencyKey", "title", "body", "level"], "Factory notify decision");
235
- const body = optionalBoundedString(value.body, "Factory notification body", MAX_MESSAGE_LENGTH);
236
- const level = value.level === void 0 ? void 0 : enumValue(value.level, ["info", "warning", "error"], "Factory notification level");
237
- return {
238
- type,
239
- ...commonCommitFields(value),
240
- title: boundedString(value.title, "Factory notification title", MAX_TITLE_LENGTH),
241
- ...body ? { body } : {},
242
- ...level ? { level } : {}
243
- };
244
- }
245
- default:
246
- throw new FactoryRuleValidationError("Factory rule decision type is unsupported.");
247
- }
248
- }
249
- function validateFactoryRuleDecisions(values, causalDepth = 0) {
250
- if (values.length > MAX_JSON_COLLECTION_SIZE) {
251
- throw new FactoryRuleValidationError("Factory rule produced too many decisions.");
252
- }
253
- const decisions = [];
254
- for (const value of values) {
255
- const decision = validateFactoryRuleDecision(value, causalDepth);
256
- if (decision.type === "reject") {
257
- throw new FactoryRuleValidationError("A rejection cannot be persisted with commit decisions.");
258
- }
259
- decisions.push(decision);
260
- }
261
- const keys = decisions.map((decision) => decision.idempotencyKey);
262
- if (new Set(keys).size !== keys.length) {
263
- throw new FactoryRuleValidationError("Factory decisions require unique idempotency keys.");
264
- }
265
- return decisions;
266
- }
267
-
268
- // src/rules/transition-service.ts
269
- var RULE_TIMEOUT_MS = 5e3;
270
- var MAX_REJECTION_REASON = 512;
5
+ //#region src/rules/transition-service.ts
6
+ const RULE_TIMEOUT_MS = 5e3;
7
+ const MAX_REJECTION_REASON = 512;
271
8
  function rejection(transitionId, itemId, code, reason) {
272
- return { status: "rejected", transitionId, itemId, code, reason: reason.slice(0, MAX_REJECTION_REASON) };
9
+ return {
10
+ status: "rejected",
11
+ transitionId,
12
+ itemId,
13
+ code,
14
+ reason: reason.slice(0, MAX_REJECTION_REASON)
15
+ };
273
16
  }
274
17
  function actorId(actor) {
275
- switch (actor.type) {
276
- case "human":
277
- case "system":
278
- return actor.id;
279
- case "agent":
280
- return `agent:${actor.bindingId}`;
281
- case "github":
282
- return `github:${actor.login}`;
283
- }
18
+ switch (actor.type) {
19
+ case "human":
20
+ case "system": return actor.id;
21
+ case "agent": return `agent:${actor.bindingId}`;
22
+ case "github": return `github:${actor.login}`;
23
+ }
284
24
  }
285
25
  function currentStage(stages) {
286
- if (stages.length !== 1) return void 0;
287
- const stage = stages[0];
288
- return FACTORY_RULE_STAGES.includes(stage) ? stage : void 0;
26
+ if (stages.length !== 1) return void 0;
27
+ const stage = stages[0];
28
+ return FACTORY_RULE_STAGES.includes(stage) ? stage : void 0;
289
29
  }
290
30
  function workItemSource(source) {
291
- if (!source) return "manual";
292
- if (source.integrationId === "linear") return "linear-issue";
293
- return source.type === "pull-request" ? "github-pr" : "github-issue";
31
+ if (!source) return "manual";
32
+ if (source.integrationId === "linear") return "linear-issue";
33
+ return source.type === "pull-request" ? "github-pr" : "github-issue";
294
34
  }
295
35
  function roleForStage(board, stage) {
296
- if (board === "review") return "review";
297
- if (stage === "triage") return "triage";
298
- if (stage === "planning") return "plan";
299
- return "work";
36
+ if (board === "review") return "review";
37
+ if (stage === "triage") return "triage";
38
+ if (stage === "planning") return "plan";
39
+ return "work";
300
40
  }
301
41
  function stageTransitionMessage(fromStage, toStage) {
302
- return `This work was moved from the ${fromStage} stage to the ${toStage} stage.`;
42
+ return `This work was moved from the ${fromStage} stage to the ${toStage} stage.`;
303
43
  }
304
44
  function ruleFailure(error) {
305
- return {
306
- code: "rule_error",
307
- reason: error instanceof Error ? `Factory rule failed: ${error.message}` : "Factory rule failed."
308
- };
45
+ return {
46
+ code: "rule_error",
47
+ reason: error instanceof Error ? `Factory rule failed: ${error.message}` : "Factory rule failed."
48
+ };
309
49
  }
310
50
  async function withRuleTimeout(operation, timeoutMs) {
311
- let timer;
312
- const timeout = new Promise((_, reject) => {
313
- timer = setTimeout(() => reject(new Error("FACTORY_RULE_TIMEOUT")), timeoutMs);
314
- });
315
- try {
316
- return await Promise.race([operation, timeout]);
317
- } finally {
318
- if (timer) clearTimeout(timer);
319
- }
51
+ let timer;
52
+ const timeout = new Promise((_, reject) => {
53
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error("FACTORY_RULE_TIMEOUT")), timeoutMs);
54
+ });
55
+ try {
56
+ return await Promise.race([operation, timeout]);
57
+ } finally {
58
+ if (timer) clearTimeout(timer);
59
+ }
320
60
  }
321
61
  var FactoryTransitionService = class {
322
- #rules;
323
- #storage;
324
- #timeoutMs;
325
- constructor(options) {
326
- this.#rules = options.rules;
327
- this.#storage = options.storage;
328
- this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;
329
- }
330
- get ruleSetVersion() {
331
- return this.#rules.version;
332
- }
333
- async transition(request) {
334
- const replay = await this.#storage.getTransitionResultByIngress(
335
- request.orgId,
336
- request.factoryProjectId,
337
- request.ingress.identity
338
- );
339
- if (replay) return replay;
340
- const transitionId = request.ingress.transitionId ?? randomUUID();
341
- const item = await this.#storage.get({ orgId: request.orgId, id: request.workItemId });
342
- if (!item) {
343
- return this.#commitRejection(request, transitionId, "invalid_transition", "Work item not found.");
344
- }
345
- if (request.causalChain && request.causalChain.length > MAX_FACTORY_RULE_CAUSAL_DEPTH) {
346
- return this.#commitRejection(
347
- request,
348
- transitionId,
349
- "causal_depth_exceeded",
350
- "Factory rule causal depth exceeded."
351
- );
352
- }
353
- const itemSource = workItemSource(item.externalSource);
354
- const source = factoryRuleSourceForWorkItem(itemSource);
355
- if (request.board === "review" !== (source === "pullRequest")) {
356
- return this.#commitRejection(
357
- request,
358
- transitionId,
359
- "invalid_transition",
360
- "The work item does not belong to the requested board."
361
- );
362
- }
363
- const fromStage = currentStage(item.stages);
364
- if (!fromStage) {
365
- return this.#commitRejection(
366
- request,
367
- transitionId,
368
- "invalid_transition",
369
- "The work item does not have one canonical Factory stage."
370
- );
371
- }
372
- const contextBase = {
373
- tenant: { orgId: request.orgId, projectId: request.factoryProjectId },
374
- actor: request.actor,
375
- ingress: { type: request.ingress.type, id: request.ingress.identity },
376
- cause: request.cause,
377
- causalChain: request.causalChain ?? [],
378
- ruleSetVersion: this.#rules.version,
379
- item: {
380
- id: item.id,
381
- source: itemSource,
382
- sourceKey: item.externalSource ? `${item.externalSource.integrationId}:${item.externalSource.type}:${item.externalSource.externalId}` : null,
383
- parentWorkItemId: item.parentWorkItemId,
384
- title: item.title,
385
- url: item.externalSource?.url ?? null,
386
- stages: [...item.stages]
387
- },
388
- board: request.board,
389
- itemRevision: item.revision,
390
- source,
391
- fromStage,
392
- toStage: request.stage
393
- };
394
- let evaluation;
395
- try {
396
- evaluation = await withRuleTimeout(
397
- (async () => {
398
- const decisions = [];
399
- for (const rule of resolveFactoryStageRules(this.#rules, {
400
- board: request.board,
401
- source,
402
- fromStage,
403
- toStage: request.stage,
404
- initialEntry: request.initialEntry
405
- })) {
406
- const context = Object.freeze({
407
- ...contextBase,
408
- stage: rule.phase === "exit" ? fromStage : request.stage
409
- });
410
- const raw = await rule.handler(context);
411
- if (raw === void 0) continue;
412
- const decision = validateFactoryRuleDecision(raw, context.causalChain.length);
413
- if (decision.type === "reject") {
414
- return { outcome: "rejected", code: decision.code, reason: decision.reason };
415
- }
416
- decisions.push(decision);
417
- }
418
- const validated = validateFactoryRuleDecisions(decisions);
419
- if (request.actor.type === "human" && request.cause === "board_drag" && fromStage !== request.stage) {
420
- const message = stageTransitionMessage(fromStage, request.stage);
421
- const skill = validated.find((decision) => decision.type === "invokeSkill");
422
- if (skill) {
423
- skill.precedingMessage = message;
424
- } else {
425
- validated.unshift({
426
- type: "sendMessage",
427
- idempotencyKey: `factory-stage:${transitionId}`,
428
- role: roleForStage(request.board, request.stage),
429
- message,
430
- priority: "urgent",
431
- idleBehavior: "wake",
432
- prepareBinding: true
433
- });
434
- }
435
- }
436
- return {
437
- outcome: "accepted",
438
- decisions: validateFactoryRuleDecisions(validated)
439
- };
440
- })(),
441
- this.#timeoutMs
442
- );
443
- } catch (error) {
444
- const failed = error instanceof Error && error.message === "FACTORY_RULE_TIMEOUT" ? { code: "timeout", reason: "Factory rule evaluation timed out." } : ruleFailure(error);
445
- evaluation = { outcome: "rejected", ...failed };
446
- }
447
- return this.#commit(request, transitionId, evaluation);
448
- }
449
- async #commitRejection(request, transitionId, code, reason) {
450
- return this.#commit(request, transitionId, { outcome: "rejected", code, reason });
451
- }
452
- async #commit(request, transitionId, evaluation) {
453
- const committed = await this.#storage.commitTransition({
454
- orgId: request.orgId,
455
- factoryProjectId: request.factoryProjectId,
456
- workItemId: request.workItemId,
457
- expectedRevision: request.expectedRevision,
458
- destinationStage: request.stage,
459
- actorId: actorId(request.actor),
460
- ingress: { identity: request.ingress.identity, triggerType: request.ingress.type, transitionId },
461
- ruleSetVersion: this.#rules.version,
462
- causalChain: [...request.causalChain ?? []],
463
- evaluation
464
- });
465
- if (committed.status === "missing") {
466
- return rejection(transitionId, request.workItemId, "invalid_transition", "Work item not found.");
467
- }
468
- return committed.result;
469
- }
470
- };
471
- export {
472
- FactoryTransitionService
62
+ #rules;
63
+ #storage;
64
+ #timeoutMs;
65
+ constructor(options) {
66
+ this.#rules = options.rules;
67
+ this.#storage = options.storage;
68
+ this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;
69
+ }
70
+ get ruleSetVersion() {
71
+ return this.#rules.version;
72
+ }
73
+ async transition(request) {
74
+ const replay = await this.#storage.getTransitionResultByIngress(request.orgId, request.factoryProjectId, request.ingress.identity);
75
+ if (replay) return replay;
76
+ const transitionId = request.ingress.transitionId ?? randomUUID();
77
+ const item = await this.#storage.get({
78
+ orgId: request.orgId,
79
+ id: request.workItemId
80
+ });
81
+ if (!item) return this.#commitRejection(request, transitionId, "invalid_transition", "Work item not found.");
82
+ if (request.causalChain && request.causalChain.length > 8) return this.#commitRejection(request, transitionId, "causal_depth_exceeded", "Factory rule causal depth exceeded.");
83
+ const itemSource = workItemSource(item.externalSource);
84
+ const source = factoryRuleSourceForWorkItem(itemSource);
85
+ if (request.board === "review" !== (source === "pullRequest")) return this.#commitRejection(request, transitionId, "invalid_transition", "The work item does not belong to the requested board.");
86
+ const fromStage = currentStage(item.stages);
87
+ if (!fromStage) return this.#commitRejection(request, transitionId, "invalid_transition", "The work item does not have one canonical Factory stage.");
88
+ const contextBase = {
89
+ tenant: {
90
+ orgId: request.orgId,
91
+ projectId: request.factoryProjectId
92
+ },
93
+ actor: request.actor,
94
+ ingress: {
95
+ type: request.ingress.type,
96
+ id: request.ingress.identity
97
+ },
98
+ cause: request.cause,
99
+ causalChain: request.causalChain ?? [],
100
+ ruleSetVersion: this.#rules.version,
101
+ item: {
102
+ id: item.id,
103
+ source: itemSource,
104
+ sourceKey: item.externalSource ? `${item.externalSource.integrationId}:${item.externalSource.type}:${item.externalSource.externalId}` : null,
105
+ parentWorkItemId: item.parentWorkItemId,
106
+ title: item.title,
107
+ url: item.externalSource?.url ?? null,
108
+ stages: [...item.stages]
109
+ },
110
+ board: request.board,
111
+ itemRevision: item.revision,
112
+ source,
113
+ fromStage,
114
+ toStage: request.stage
115
+ };
116
+ let evaluation;
117
+ try {
118
+ evaluation = await withRuleTimeout((async () => {
119
+ const decisions = [];
120
+ for (const rule of resolveFactoryStageRules(this.#rules, {
121
+ board: request.board,
122
+ source,
123
+ fromStage,
124
+ toStage: request.stage,
125
+ initialEntry: request.initialEntry
126
+ })) {
127
+ const context = Object.freeze({
128
+ ...contextBase,
129
+ stage: rule.phase === "exit" ? fromStage : request.stage
130
+ });
131
+ const raw = await rule.handler(context);
132
+ if (raw === void 0) continue;
133
+ const decision = validateFactoryRuleDecision(raw, context.causalChain.length);
134
+ if (decision.type === "reject") return {
135
+ outcome: "rejected",
136
+ code: decision.code,
137
+ reason: decision.reason
138
+ };
139
+ decisions.push(decision);
140
+ }
141
+ const validated = validateFactoryRuleDecisions(decisions);
142
+ if (request.actor.type === "human" && request.cause === "board_drag" && fromStage !== request.stage) {
143
+ const message = stageTransitionMessage(fromStage, request.stage);
144
+ const skill = validated.find((decision) => decision.type === "invokeSkill");
145
+ if (skill) skill.precedingMessage = message;
146
+ else validated.unshift({
147
+ type: "sendMessage",
148
+ idempotencyKey: `factory-stage:${transitionId}`,
149
+ role: roleForStage(request.board, request.stage),
150
+ message,
151
+ priority: "urgent",
152
+ idleBehavior: "wake",
153
+ prepareBinding: true
154
+ });
155
+ }
156
+ return {
157
+ outcome: "accepted",
158
+ decisions: validateFactoryRuleDecisions(validated)
159
+ };
160
+ })(), this.#timeoutMs);
161
+ } catch (error) {
162
+ evaluation = {
163
+ outcome: "rejected",
164
+ ...error instanceof Error && error.message === "FACTORY_RULE_TIMEOUT" ? {
165
+ code: "timeout",
166
+ reason: "Factory rule evaluation timed out."
167
+ } : ruleFailure(error)
168
+ };
169
+ }
170
+ return this.#commit(request, transitionId, evaluation);
171
+ }
172
+ async #commitRejection(request, transitionId, code, reason) {
173
+ return this.#commit(request, transitionId, {
174
+ outcome: "rejected",
175
+ code,
176
+ reason
177
+ });
178
+ }
179
+ async #commit(request, transitionId, evaluation) {
180
+ const committed = await this.#storage.commitTransition({
181
+ orgId: request.orgId,
182
+ factoryProjectId: request.factoryProjectId,
183
+ workItemId: request.workItemId,
184
+ expectedRevision: request.expectedRevision,
185
+ destinationStage: request.stage,
186
+ actorId: actorId(request.actor),
187
+ ingress: {
188
+ identity: request.ingress.identity,
189
+ triggerType: request.ingress.type,
190
+ transitionId
191
+ },
192
+ ruleSetVersion: this.#rules.version,
193
+ causalChain: [...request.causalChain ?? []],
194
+ evaluation
195
+ });
196
+ if (committed.status === "missing") return rejection(transitionId, request.workItemId, "invalid_transition", "Work item not found.");
197
+ return committed.result;
198
+ }
473
199
  };
200
+ //#endregion
201
+ export { FactoryTransitionService };
202
+
474
203
  //# sourceMappingURL=transition-service.js.map