@mastra/factory 0.15.0 → 0.16.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.md +4 -3
  2. package/dist/boards/index.d.ts +1 -1
  3. package/dist/boards/index.d.ts.map +1 -1
  4. package/dist/boards/index.js +2 -2
  5. package/dist/boards/index.js.map +1 -1
  6. package/dist/boards/review.d.ts.map +1 -1
  7. package/dist/boards/review.js +4 -1
  8. package/dist/boards/review.js.map +1 -1
  9. package/dist/boards/semantics.d.ts +7 -0
  10. package/dist/boards/semantics.d.ts.map +1 -1
  11. package/dist/boards/semantics.js +15 -1
  12. package/dist/boards/semantics.js.map +1 -1
  13. package/dist/boards/transition-policy.d.ts +4 -0
  14. package/dist/boards/transition-policy.d.ts.map +1 -1
  15. package/dist/boards/transition-policy.js.map +1 -1
  16. package/dist/boards/work-transition-policy.d.ts.map +1 -1
  17. package/dist/boards/work-transition-policy.js +6 -1
  18. package/dist/boards/work-transition-policy.js.map +1 -1
  19. package/dist/boards/work.d.ts.map +1 -1
  20. package/dist/boards/work.js +37 -3
  21. package/dist/boards/work.js.map +1 -1
  22. package/dist/capabilities/intake.d.ts +4 -0
  23. package/dist/capabilities/intake.d.ts.map +1 -1
  24. package/dist/factory.d.ts +6 -3
  25. package/dist/factory.d.ts.map +1 -1
  26. package/dist/factory.js +66 -18
  27. package/dist/factory.js.map +1 -1
  28. package/dist/integrations/base.d.ts +6 -2
  29. package/dist/integrations/base.d.ts.map +1 -1
  30. package/dist/integrations/incidentio/agent-tools.d.ts +49 -0
  31. package/dist/integrations/incidentio/agent-tools.d.ts.map +1 -0
  32. package/dist/integrations/incidentio/agent-tools.js +90 -0
  33. package/dist/integrations/incidentio/agent-tools.js.map +1 -0
  34. package/dist/integrations/incidentio/claim.d.ts +8 -0
  35. package/dist/integrations/incidentio/claim.d.ts.map +1 -0
  36. package/dist/integrations/incidentio/claim.js +14 -0
  37. package/dist/integrations/incidentio/claim.js.map +1 -0
  38. package/dist/integrations/incidentio/default-rules.d.ts +46 -0
  39. package/dist/integrations/incidentio/default-rules.d.ts.map +1 -0
  40. package/dist/integrations/incidentio/default-rules.js +66 -0
  41. package/dist/integrations/incidentio/default-rules.js.map +1 -0
  42. package/dist/integrations/incidentio/integration.d.ts +48 -2
  43. package/dist/integrations/incidentio/integration.d.ts.map +1 -1
  44. package/dist/integrations/incidentio/integration.js +88 -2
  45. package/dist/integrations/incidentio/integration.js.map +1 -1
  46. package/dist/integrations/incidentio/issue-reconciler.d.ts +2 -0
  47. package/dist/integrations/incidentio/issue-reconciler.d.ts.map +1 -1
  48. package/dist/integrations/incidentio/issue-reconciler.js +38 -2
  49. package/dist/integrations/incidentio/issue-reconciler.js.map +1 -1
  50. package/dist/integrations/incidentio/routes.d.ts +71 -0
  51. package/dist/integrations/incidentio/routes.d.ts.map +1 -0
  52. package/dist/integrations/incidentio/routes.js +245 -0
  53. package/dist/integrations/incidentio/routes.js.map +1 -0
  54. package/dist/integrations/incidentio/rules.d.ts +55 -0
  55. package/dist/integrations/incidentio/rules.d.ts.map +1 -0
  56. package/dist/integrations/incidentio/rules.js +175 -0
  57. package/dist/integrations/incidentio/rules.js.map +1 -0
  58. package/dist/integrations/jira/adf.d.ts +26 -0
  59. package/dist/integrations/jira/adf.d.ts.map +1 -0
  60. package/dist/integrations/jira/adf.js +73 -0
  61. package/dist/integrations/jira/adf.js.map +1 -0
  62. package/dist/integrations/jira/agent-tools.d.ts +45 -0
  63. package/dist/integrations/jira/agent-tools.d.ts.map +1 -0
  64. package/dist/integrations/jira/agent-tools.js +87 -0
  65. package/dist/integrations/jira/agent-tools.js.map +1 -0
  66. package/dist/integrations/jira/api.d.ts +157 -0
  67. package/dist/integrations/jira/api.d.ts.map +1 -0
  68. package/dist/integrations/jira/api.js +141 -0
  69. package/dist/integrations/jira/api.js.map +1 -0
  70. package/dist/integrations/jira/claim.d.ts +9 -0
  71. package/dist/integrations/jira/claim.d.ts.map +1 -0
  72. package/dist/integrations/jira/claim.js +15 -0
  73. package/dist/integrations/jira/claim.js.map +1 -0
  74. package/dist/integrations/jira/default-rules.d.ts +47 -0
  75. package/dist/integrations/jira/default-rules.d.ts.map +1 -0
  76. package/dist/integrations/jira/default-rules.js +67 -0
  77. package/dist/integrations/jira/default-rules.js.map +1 -0
  78. package/dist/integrations/jira/integration.d.ts +114 -0
  79. package/dist/integrations/jira/integration.d.ts.map +1 -0
  80. package/dist/integrations/jira/integration.js +401 -0
  81. package/dist/integrations/jira/integration.js.map +1 -0
  82. package/dist/integrations/jira/issue-reconciler.d.ts +10 -0
  83. package/dist/integrations/jira/issue-reconciler.d.ts.map +1 -0
  84. package/dist/integrations/jira/issue-reconciler.js +70 -0
  85. package/dist/integrations/jira/issue-reconciler.js.map +1 -0
  86. package/dist/integrations/jira/reconciliation-config.d.ts +3 -0
  87. package/dist/integrations/jira/reconciliation-config.d.ts.map +1 -0
  88. package/dist/integrations/jira/reconciliation-config.js +17 -0
  89. package/dist/integrations/jira/reconciliation-config.js.map +1 -0
  90. package/dist/integrations/jira/routes.d.ts +54 -0
  91. package/dist/integrations/jira/routes.d.ts.map +1 -0
  92. package/dist/integrations/jira/routes.js +260 -0
  93. package/dist/integrations/jira/routes.js.map +1 -0
  94. package/dist/integrations/jira/rules.d.ts +57 -0
  95. package/dist/integrations/jira/rules.d.ts.map +1 -0
  96. package/dist/integrations/jira/rules.js +175 -0
  97. package/dist/integrations/jira/rules.js.map +1 -0
  98. package/dist/integrations/linear/claim.d.ts +8 -0
  99. package/dist/integrations/linear/claim.d.ts.map +1 -0
  100. package/dist/integrations/linear/claim.js +14 -0
  101. package/dist/integrations/linear/claim.js.map +1 -0
  102. package/dist/integrations/linear/default-rules.d.ts +1 -0
  103. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  104. package/dist/integrations/linear/default-rules.js +2 -0
  105. package/dist/integrations/linear/default-rules.js.map +1 -1
  106. package/dist/integrations/linear/integration.d.ts +24 -6
  107. package/dist/integrations/linear/integration.d.ts.map +1 -1
  108. package/dist/integrations/linear/integration.js +257 -37
  109. package/dist/integrations/linear/integration.js.map +1 -1
  110. package/dist/integrations/linear/routes.d.ts +10 -0
  111. package/dist/integrations/linear/routes.d.ts.map +1 -1
  112. package/dist/integrations/linear/routes.js +74 -12
  113. package/dist/integrations/linear/routes.js.map +1 -1
  114. package/dist/integrations/linear/rules.d.ts +1 -1
  115. package/dist/integrations/linear/rules.d.ts.map +1 -1
  116. package/dist/integrations/linear/rules.js +27 -2
  117. package/dist/integrations/linear/rules.js.map +1 -1
  118. package/dist/integrations/platform/connect/routes.d.ts +39 -0
  119. package/dist/integrations/platform/connect/routes.d.ts.map +1 -0
  120. package/dist/integrations/platform/connect/routes.js +129 -0
  121. package/dist/integrations/platform/connect/routes.js.map +1 -0
  122. package/dist/integrations/platform/github/event-worker.js +1 -1
  123. package/dist/integrations/platform/github/integration.js +1 -1
  124. package/dist/integrations/platform/incidentio/integration.d.ts +61 -4
  125. package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
  126. package/dist/integrations/platform/incidentio/integration.js +371 -22
  127. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  128. package/dist/integrations/platform/jira/agent-tools.d.ts +36 -0
  129. package/dist/integrations/platform/jira/agent-tools.d.ts.map +1 -0
  130. package/dist/integrations/platform/jira/agent-tools.js +85 -0
  131. package/dist/integrations/platform/jira/agent-tools.js.map +1 -0
  132. package/dist/integrations/platform/jira/integration.d.ts +65 -0
  133. package/dist/integrations/platform/jira/integration.d.ts.map +1 -0
  134. package/dist/integrations/platform/jira/integration.js +540 -0
  135. package/dist/integrations/platform/jira/integration.js.map +1 -0
  136. package/dist/integrations/platform/jira/routes.d.ts +55 -0
  137. package/dist/integrations/platform/jira/routes.d.ts.map +1 -0
  138. package/dist/integrations/platform/jira/routes.js +274 -0
  139. package/dist/integrations/platform/jira/routes.js.map +1 -0
  140. package/dist/integrations/platform/linear/integration.d.ts +34 -1
  141. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  142. package/dist/integrations/platform/linear/integration.js +271 -48
  143. package/dist/integrations/platform/linear/integration.js.map +1 -1
  144. package/dist/routes/intake.d.ts.map +1 -1
  145. package/dist/routes/intake.js +15 -5
  146. package/dist/routes/intake.js.map +1 -1
  147. package/dist/routes/oauth.js +2 -5
  148. package/dist/routes/oauth.js.map +1 -1
  149. package/dist/routes/surface.d.ts.map +1 -1
  150. package/dist/routes/surface.js +24 -4
  151. package/dist/routes/surface.js.map +1 -1
  152. package/dist/rules/dispatch-errors.d.ts.map +1 -1
  153. package/dist/rules/dispatch-errors.js +8 -0
  154. package/dist/rules/dispatch-errors.js.map +1 -1
  155. package/dist/rules/dispatcher.d.ts +1 -0
  156. package/dist/rules/dispatcher.d.ts.map +1 -1
  157. package/dist/rules/dispatcher.js +180 -106
  158. package/dist/rules/dispatcher.js.map +1 -1
  159. package/dist/rules/index.d.ts +2 -2
  160. package/dist/rules/index.d.ts.map +1 -1
  161. package/dist/rules/index.js +2 -2
  162. package/dist/rules/start-coordinator.d.ts.map +1 -1
  163. package/dist/rules/start-coordinator.js +4 -1
  164. package/dist/rules/start-coordinator.js.map +1 -1
  165. package/dist/rules/terminal-cleanup.d.ts +14 -1
  166. package/dist/rules/terminal-cleanup.d.ts.map +1 -1
  167. package/dist/rules/terminal-cleanup.js +20 -0
  168. package/dist/rules/terminal-cleanup.js.map +1 -1
  169. package/dist/rules/tools.d.ts +2 -0
  170. package/dist/rules/tools.d.ts.map +1 -1
  171. package/dist/rules/tools.js +30 -2
  172. package/dist/rules/tools.js.map +1 -1
  173. package/dist/rules/transition-service.d.ts +13 -0
  174. package/dist/rules/transition-service.d.ts.map +1 -1
  175. package/dist/rules/transition-service.js +11 -2
  176. package/dist/rules/transition-service.js.map +1 -1
  177. package/dist/rules/types.d.ts +72 -3
  178. package/dist/rules/types.d.ts.map +1 -1
  179. package/dist/rules/types.js +9 -1
  180. package/dist/rules/types.js.map +1 -1
  181. package/dist/rules/validation.d.ts.map +1 -1
  182. package/dist/rules/validation.js +13 -2
  183. package/dist/rules/validation.js.map +1 -1
  184. package/dist/session/run-audit.d.ts +9 -0
  185. package/dist/session/run-audit.d.ts.map +1 -1
  186. package/dist/session/run-audit.js +37 -6
  187. package/dist/session/run-audit.js.map +1 -1
  188. package/dist/skills/service.d.ts +12 -0
  189. package/dist/skills/service.d.ts.map +1 -1
  190. package/dist/skills/service.js +27 -1
  191. package/dist/skills/service.js.map +1 -1
  192. package/dist/storage/domains/intake/base.d.ts +11 -8
  193. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  194. package/dist/storage/domains/intake/base.js +69 -24
  195. package/dist/storage/domains/intake/base.js.map +1 -1
  196. package/dist/storage/domains/work-items/base.d.ts +41 -2
  197. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  198. package/dist/storage/domains/work-items/base.js +159 -12
  199. package/dist/storage/domains/work-items/base.js.map +1 -1
  200. package/dist/supervisor/session-messaging.d.ts +23 -0
  201. package/dist/supervisor/session-messaging.d.ts.map +1 -0
  202. package/dist/supervisor/session-messaging.js +15 -0
  203. package/dist/supervisor/session-messaging.js.map +1 -0
  204. package/dist/supervisor/write-tools.d.ts +2 -1
  205. package/dist/supervisor/write-tools.d.ts.map +1 -1
  206. package/dist/supervisor/write-tools.js +11 -7
  207. package/dist/supervisor/write-tools.js.map +1 -1
  208. package/dist/work-item-branch.d.ts +1 -1
  209. package/dist/work-item-branch.d.ts.map +1 -1
  210. package/dist/work-item-branch.js +4 -2
  211. package/dist/work-item-branch.js.map +1 -1
  212. package/package.json +11 -11
@@ -0,0 +1,141 @@
1
+ import { textToAdf } from "./adf.js";
2
+ //#region src/integrations/jira/api.ts
3
+ const JIRA_TIMEOUT_MS = 15e3;
4
+ /** Hosts allowed to receive Basic credentials over plain http (local development mocks). */
5
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
6
+ "localhost",
7
+ "127.0.0.1",
8
+ "[::1]",
9
+ "::1"
10
+ ]);
11
+ /** Issue page size — Linear parity (`LINEAR_ISSUES_PAGE_SIZE`). */
12
+ const JIRA_ISSUES_PAGE_SIZE = 30;
13
+ /** Comment page size — Linear parity (`LINEAR_COMMENTS_PAGE_SIZE`). */
14
+ const JIRA_COMMENTS_PAGE_SIZE = 50;
15
+ const JIRA_PROJECTS_PAGE_SIZE = 50;
16
+ /** Fields requested from issue reads — `search/jql` returns ids only without an explicit list. */
17
+ const JIRA_ISSUE_FIELDS = [
18
+ "summary",
19
+ "description",
20
+ "status",
21
+ "assignee",
22
+ "reporter",
23
+ "labels",
24
+ "priority",
25
+ "issuetype",
26
+ "project",
27
+ "created",
28
+ "updated"
29
+ ];
30
+ /** Normalized Jira API failure carrying the HTTP status and Jira's error detail. */
31
+ var JiraApiError = class extends Error {
32
+ status;
33
+ code;
34
+ constructor(message, status) {
35
+ super(message);
36
+ this.name = "JiraApiError";
37
+ this.status = status;
38
+ this.code = status === 401 || status === 403 ? "jira_auth_failed" : "jira_request_failed";
39
+ }
40
+ };
41
+ var JiraApiClient = class {
42
+ #baseUrl;
43
+ #authHeader;
44
+ #fetch;
45
+ constructor(config) {
46
+ if (!config.baseUrl) throw new Error("JiraApiClient is missing required config: baseUrl.");
47
+ let parsed;
48
+ try {
49
+ parsed = new URL(config.baseUrl);
50
+ } catch {
51
+ throw new Error(`JiraApiClient baseUrl is not an absolute URL: "${config.baseUrl}".`);
52
+ }
53
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error(`JiraApiClient baseUrl must be an http(s) URL: "${config.baseUrl}".`);
54
+ if (config.accessToken !== void 0) {
55
+ if (!config.accessToken) throw new Error("JiraApiClient is missing required config: accessToken.");
56
+ this.#authHeader = `Bearer ${config.accessToken}`;
57
+ } else {
58
+ const missing = ["email", "apiToken"].filter((key) => !config[key]);
59
+ if (missing.length > 0) throw new Error(`JiraApiClient is missing required config: ${missing.join(", ")}.`);
60
+ if (parsed.protocol === "http:" && !LOOPBACK_HOSTS.has(parsed.hostname)) throw new Error("JiraApiClient baseUrl must use https when Basic credentials are configured.");
61
+ this.#authHeader = `Basic ${Buffer.from(`${config.email}:${config.apiToken}`).toString("base64")}`;
62
+ }
63
+ this.#baseUrl = config.baseUrl.replace(/\/+$/, "");
64
+ this.#fetch = config.fetchImpl ?? globalThis.fetch;
65
+ }
66
+ /** Normalized site base URL (no trailing slash) — used for `/browse/<key>` links. */
67
+ get baseUrl() {
68
+ return this.#baseUrl;
69
+ }
70
+ async #request(method, path, options = {}) {
71
+ const url = new URL(`${this.#baseUrl}${path}`);
72
+ for (const [key, value] of Object.entries(options.query ?? {})) if (value !== void 0) url.searchParams.set(key, String(value));
73
+ const hasBody = options.body !== void 0;
74
+ const res = await this.#fetch(url, {
75
+ method,
76
+ signal: AbortSignal.timeout(JIRA_TIMEOUT_MS),
77
+ headers: {
78
+ authorization: this.#authHeader,
79
+ accept: "application/json",
80
+ ...hasBody ? { "content-type": "application/json" } : {}
81
+ },
82
+ ...hasBody ? { body: JSON.stringify(options.body) } : {}
83
+ });
84
+ if (!res.ok) {
85
+ let detail = null;
86
+ try {
87
+ const errBody = await res.json();
88
+ detail = errBody.errorMessages?.[0] ?? Object.values(errBody.errors ?? {})[0] ?? errBody.detail ?? errBody.error ?? errBody.title ?? null;
89
+ } catch {}
90
+ throw new JiraApiError(`Jira API request failed (${res.status})${detail ? `: ${detail}` : ""}`, res.status);
91
+ }
92
+ if (res.status === 204) return void 0;
93
+ return await res.json();
94
+ }
95
+ async getServerInfo() {
96
+ return this.#request("GET", "/rest/api/3/serverInfo");
97
+ }
98
+ /** One page of the site's projects (Settings intake-source picker). */
99
+ async listProjects(options = {}) {
100
+ return this.#request("GET", "/rest/api/3/project/search", { query: {
101
+ startAt: options.startAt ?? 0,
102
+ maxResults: 50
103
+ } });
104
+ }
105
+ /** One page of a JQL search with explicitly requested fields. */
106
+ async searchIssues(options) {
107
+ return this.#request("POST", "/rest/api/3/search/jql", { body: {
108
+ jql: options.jql,
109
+ fields: [...options.fields ?? JIRA_ISSUE_FIELDS],
110
+ maxResults: options.maxResults ?? 30,
111
+ ...options.nextPageToken ? { nextPageToken: options.nextPageToken } : {}
112
+ } });
113
+ }
114
+ /** Full issue detail by key (`ENG-42`) or id. */
115
+ async getIssue(keyOrId) {
116
+ return this.#request("GET", `/rest/api/3/issue/${encodeURIComponent(keyOrId)}`, { query: { fields: JIRA_ISSUE_FIELDS.join(",") } });
117
+ }
118
+ /** One page of an issue's comments (oldest first, Jira default order). */
119
+ async listComments(keyOrId, options = {}) {
120
+ return this.#request("GET", `/rest/api/3/issue/${encodeURIComponent(keyOrId)}/comment`, { query: {
121
+ startAt: options.startAt ?? 0,
122
+ maxResults: 50
123
+ } });
124
+ }
125
+ /** Post a plain-text comment (wrapped into a minimal ADF document). */
126
+ async createComment(keyOrId, body) {
127
+ return this.#request("POST", `/rest/api/3/issue/${encodeURIComponent(keyOrId)}/comment`, { body: { body: textToAdf(body) } });
128
+ }
129
+ /** Transitions currently legal for the issue (workflow-dependent). */
130
+ async listTransitions(keyOrId) {
131
+ return (await this.#request("GET", `/rest/api/3/issue/${encodeURIComponent(keyOrId)}/transitions`)).transitions ?? [];
132
+ }
133
+ /** Apply a transition by id (Jira responds 204 on success). */
134
+ async applyTransition(keyOrId, transitionId) {
135
+ await this.#request("POST", `/rest/api/3/issue/${encodeURIComponent(keyOrId)}/transitions`, { body: { transition: { id: transitionId } } });
136
+ }
137
+ };
138
+ //#endregion
139
+ export { JIRA_COMMENTS_PAGE_SIZE, JIRA_ISSUES_PAGE_SIZE, JIRA_ISSUE_FIELDS, JIRA_PROJECTS_PAGE_SIZE, JiraApiClient, JiraApiError };
140
+
141
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","names":["#baseUrl","#authHeader","#fetch","#request"],"sources":["../../../src/integrations/jira/api.ts"],"sourcesContent":["/**\n * Typed Jira Cloud REST client (API v3).\n *\n * Shared by the direct and Platform-managed Jira integrations. Direct Jira\n * uses Basic auth against an Atlassian site; Platform Jira points the same\n * client at a connection proxy with a bearer token. Both modes use the same\n * request shapes, timeout, and error normalization.\n *\n * Endpoint notes (Jira Cloud, 2026):\n * - Issue search is `POST /rest/api/3/search/jql` — the legacy\n * `GET /rest/api/3/search` endpoint has been removed. The new endpoint\n * returns only ids unless `fields` is requested explicitly, and pages with\n * an opaque `nextPageToken` cursor (no `startAt`/`total`).\n * - Project listing is `GET /rest/api/3/project/search` with classic\n * `startAt`/`isLast` paging.\n * - Descriptions and comment bodies are ADF documents (see `adf.ts`).\n */\n\nimport type { AdfNode } from './adf.js';\nimport { textToAdf } from './adf.js';\n\nconst JIRA_TIMEOUT_MS = 15_000;\n/** Hosts allowed to receive Basic credentials over plain http (local development mocks). */\nconst LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);\n/** Issue page size — Linear parity (`LINEAR_ISSUES_PAGE_SIZE`). */\nexport const JIRA_ISSUES_PAGE_SIZE = 30;\n/** Comment page size — Linear parity (`LINEAR_COMMENTS_PAGE_SIZE`). */\nexport const JIRA_COMMENTS_PAGE_SIZE = 50;\nexport const JIRA_PROJECTS_PAGE_SIZE = 50;\n\n/** Fields requested from issue reads — `search/jql` returns ids only without an explicit list. */\nexport const JIRA_ISSUE_FIELDS = [\n 'summary',\n 'description',\n 'status',\n 'assignee',\n 'reporter',\n 'labels',\n 'priority',\n 'issuetype',\n 'project',\n 'created',\n 'updated',\n] as const;\n\ninterface JiraApiClientBaseConfig {\n /** Jira API base URL, either the Atlassian site or a Platform connection proxy. */\n baseUrl: string;\n fetchImpl?: typeof fetch;\n}\n\nexport type JiraApiClientConfig = JiraApiClientBaseConfig &\n (\n | {\n /** Atlassian account email the API token belongs to. */\n email: string;\n /** API token from id.atlassian.com → Security → API tokens. */\n apiToken: string;\n accessToken?: never;\n }\n | {\n /** Bearer token used when the base URL is a Mastra Platform connection proxy. */\n accessToken: string;\n email?: never;\n apiToken?: never;\n }\n );\n\n/** Stable error code the routes/tools surface to the SPA and agents. */\nexport type JiraApiErrorCode = 'jira_auth_failed' | 'jira_request_failed';\n\n/** Normalized Jira API failure carrying the HTTP status and Jira's error detail. */\nexport class JiraApiError extends Error {\n readonly status: number | null;\n readonly code: JiraApiErrorCode;\n\n constructor(message: string, status: number | null) {\n super(message);\n this.name = 'JiraApiError';\n this.status = status;\n this.code = status === 401 || status === 403 ? 'jira_auth_failed' : 'jira_request_failed';\n }\n}\n\nexport interface JiraStatusCategory {\n /** `new` | `indeterminate` | `done` — Jira's fixed status families. */\n key: string;\n name?: string;\n}\n\nexport interface JiraStatus {\n name: string;\n statusCategory?: JiraStatusCategory;\n}\n\nexport interface JiraProject {\n id: string;\n key: string;\n name: string;\n}\n\nexport interface JiraProjectSearchPage {\n values: JiraProject[];\n startAt: number;\n isLast: boolean;\n}\n\nexport interface JiraServerInfo {\n baseUrl: string;\n serverTitle?: string;\n}\n\nexport interface JiraUser {\n displayName: string;\n}\n\nexport interface JiraComment {\n id: string;\n author?: JiraUser | null;\n body?: AdfNode | null;\n created: string;\n}\n\nexport interface JiraCommentPage {\n comments: JiraComment[];\n startAt: number;\n maxResults: number;\n total: number;\n}\n\nexport interface JiraIssueFields {\n summary?: string;\n description?: AdfNode | null;\n status?: JiraStatus;\n assignee?: JiraUser | null;\n reporter?: JiraUser | null;\n labels?: string[];\n priority?: { name: string } | null;\n issuetype?: { name: string };\n project?: { id: string; key: string; name?: string };\n created?: string;\n updated?: string;\n}\n\nexport interface JiraIssue {\n id: string;\n key: string;\n fields: JiraIssueFields;\n}\n\nexport interface JiraSearchPage {\n issues: JiraIssue[];\n /** Opaque cursor for the next page; absent on the last page. */\n nextPageToken?: string | null;\n}\n\nexport interface JiraTransition {\n id: string;\n name: string;\n /** Target status the transition moves the issue to. */\n to?: JiraStatus;\n}\n\nexport class JiraApiClient {\n readonly #baseUrl: string;\n readonly #authHeader: string;\n readonly #fetch: typeof fetch;\n\n constructor(config: JiraApiClientConfig) {\n if (!config.baseUrl) {\n throw new Error('JiraApiClient is missing required config: baseUrl.');\n }\n // Fail fast on a malformed base URL: a bare host like `acme.atlassian.net`\n // would otherwise surface later as an opaque `TypeError: Invalid URL`.\n let parsed: URL;\n try {\n parsed = new URL(config.baseUrl);\n } catch {\n throw new Error(`JiraApiClient baseUrl is not an absolute URL: \"${config.baseUrl}\".`);\n }\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error(`JiraApiClient baseUrl must be an http(s) URL: \"${config.baseUrl}\".`);\n }\n if (config.accessToken !== undefined) {\n if (!config.accessToken) {\n throw new Error('JiraApiClient is missing required config: accessToken.');\n }\n this.#authHeader = `Bearer ${config.accessToken}`;\n } else {\n const missing = (['email', 'apiToken'] as const).filter(key => !config[key]);\n if (missing.length > 0) {\n throw new Error(`JiraApiClient is missing required config: ${missing.join(', ')}.`);\n }\n // Basic credentials ride on every request, so never send them over\n // plaintext to a remote host. Loopback stays allowed for local mocks.\n if (parsed.protocol === 'http:' && !LOOPBACK_HOSTS.has(parsed.hostname)) {\n throw new Error('JiraApiClient baseUrl must use https when Basic credentials are configured.');\n }\n this.#authHeader = `Basic ${Buffer.from(`${config.email}:${config.apiToken}`).toString('base64')}`;\n }\n this.#baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.#fetch = config.fetchImpl ?? globalThis.fetch;\n }\n\n /** Normalized site base URL (no trailing slash) — used for `/browse/<key>` links. */\n get baseUrl(): string {\n return this.#baseUrl;\n }\n\n async #request<T>(\n method: 'GET' | 'POST',\n path: string,\n options: { query?: Record<string, string | number | undefined>; body?: unknown } = {},\n ): Promise<T> {\n const url = new URL(`${this.#baseUrl}${path}`);\n for (const [key, value] of Object.entries(options.query ?? {})) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n const hasBody = options.body !== undefined;\n const res = await this.#fetch(url, {\n method,\n signal: AbortSignal.timeout(JIRA_TIMEOUT_MS),\n headers: {\n authorization: this.#authHeader,\n accept: 'application/json',\n ...(hasBody ? { 'content-type': 'application/json' } : {}),\n },\n ...(hasBody ? { body: JSON.stringify(options.body) } : {}),\n });\n if (!res.ok) {\n // Jira reports validation failures as `errorMessages` and `errors`;\n // the Platform proxy reports upstream failures through `detail`/`error`/`title`.\n let detail: string | null = null;\n try {\n const errBody = (await res.json()) as {\n errorMessages?: string[];\n errors?: Record<string, string>;\n detail?: string;\n error?: string;\n title?: string;\n };\n detail =\n errBody.errorMessages?.[0] ??\n Object.values(errBody.errors ?? {})[0] ??\n errBody.detail ??\n errBody.error ??\n errBody.title ??\n null;\n } catch {\n // Non-JSON error body; fall back to the status code alone.\n }\n throw new JiraApiError(`Jira API request failed (${res.status})${detail ? `: ${detail}` : ''}`, res.status);\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n\n async getServerInfo(): Promise<JiraServerInfo> {\n return this.#request<JiraServerInfo>('GET', '/rest/api/3/serverInfo');\n }\n\n /** One page of the site's projects (Settings intake-source picker). */\n async listProjects(options: { startAt?: number } = {}): Promise<JiraProjectSearchPage> {\n return this.#request<JiraProjectSearchPage>('GET', '/rest/api/3/project/search', {\n query: { startAt: options.startAt ?? 0, maxResults: JIRA_PROJECTS_PAGE_SIZE },\n });\n }\n\n /** One page of a JQL search with explicitly requested fields. */\n async searchIssues(options: {\n jql: string;\n fields?: readonly string[];\n nextPageToken?: string;\n maxResults?: number;\n }): Promise<JiraSearchPage> {\n return this.#request<JiraSearchPage>('POST', '/rest/api/3/search/jql', {\n body: {\n jql: options.jql,\n fields: [...(options.fields ?? JIRA_ISSUE_FIELDS)],\n maxResults: options.maxResults ?? JIRA_ISSUES_PAGE_SIZE,\n ...(options.nextPageToken ? { nextPageToken: options.nextPageToken } : {}),\n },\n });\n }\n\n /** Full issue detail by key (`ENG-42`) or id. */\n async getIssue(keyOrId: string): Promise<JiraIssue> {\n return this.#request<JiraIssue>('GET', `/rest/api/3/issue/${encodeURIComponent(keyOrId)}`, {\n query: { fields: JIRA_ISSUE_FIELDS.join(',') },\n });\n }\n\n /** One page of an issue's comments (oldest first, Jira default order). */\n async listComments(keyOrId: string, options: { startAt?: number } = {}): Promise<JiraCommentPage> {\n return this.#request<JiraCommentPage>('GET', `/rest/api/3/issue/${encodeURIComponent(keyOrId)}/comment`, {\n query: { startAt: options.startAt ?? 0, maxResults: JIRA_COMMENTS_PAGE_SIZE },\n });\n }\n\n /** Post a plain-text comment (wrapped into a minimal ADF document). */\n async createComment(keyOrId: string, body: string): Promise<JiraComment> {\n return this.#request<JiraComment>('POST', `/rest/api/3/issue/${encodeURIComponent(keyOrId)}/comment`, {\n body: { body: textToAdf(body) },\n });\n }\n\n /** Transitions currently legal for the issue (workflow-dependent). */\n async listTransitions(keyOrId: string): Promise<JiraTransition[]> {\n const data = await this.#request<{ transitions?: JiraTransition[] }>(\n 'GET',\n `/rest/api/3/issue/${encodeURIComponent(keyOrId)}/transitions`,\n );\n return data.transitions ?? [];\n }\n\n /** Apply a transition by id (Jira responds 204 on success). */\n async applyTransition(keyOrId: string, transitionId: string): Promise<void> {\n await this.#request<void>('POST', `/rest/api/3/issue/${encodeURIComponent(keyOrId)}/transitions`, {\n body: { transition: { id: transitionId } },\n });\n }\n}\n"],"mappings":";;AAqBA,MAAM,kBAAkB;;AAExB,MAAM,iCAAiB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAS;AAAK,CAAC;;AAEzE,MAAa,wBAAwB;;AAErC,MAAa,0BAA0B;AACvC,MAAa,0BAA0B;;AAGvC,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AA6BA,IAAa,eAAb,cAAkC,MAAM;CACtC;CACA;CAEA,YAAY,SAAiB,QAAuB;EAClD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,OAAO,WAAW,OAAO,WAAW,MAAM,qBAAqB;CACtE;AACF;AAiFA,IAAa,gBAAb,MAA2B;CACzB;CACA;CACA;CAEA,YAAY,QAA6B;EACvC,IAAI,CAAC,OAAO,SACV,MAAM,IAAI,MAAM,oDAAoD;EAItE,IAAI;EACJ,IAAI;GACF,SAAS,IAAI,IAAI,OAAO,OAAO;EACjC,QAAQ;GACN,MAAM,IAAI,MAAM,kDAAkD,OAAO,QAAQ,GAAG;EACtF;EACA,IAAI,OAAO,aAAa,YAAY,OAAO,aAAa,SACtD,MAAM,IAAI,MAAM,kDAAkD,OAAO,QAAQ,GAAG;EAEtF,IAAI,OAAO,gBAAgB,KAAA,GAAW;GACpC,IAAI,CAAC,OAAO,aACV,MAAM,IAAI,MAAM,wDAAwD;GAE1E,KAAKC,cAAc,UAAU,OAAO;EACtC,OAAO;GACL,MAAM,UAAW,CAAC,SAAS,UAAU,CAAC,CAAW,QAAO,QAAO,CAAC,OAAO,IAAI;GAC3E,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,MAAM,6CAA6C,QAAQ,KAAK,IAAI,EAAE,EAAE;GAIpF,IAAI,OAAO,aAAa,WAAW,CAAC,eAAe,IAAI,OAAO,QAAQ,GACpE,MAAM,IAAI,MAAM,6EAA6E;GAE/F,KAAKA,cAAc,SAAS,OAAO,KAAK,GAAG,OAAO,MAAM,GAAG,OAAO,UAAU,CAAC,CAAC,SAAS,QAAQ;EACjG;EACA,KAAKD,WAAW,OAAO,QAAQ,QAAQ,QAAQ,EAAE;EACjD,KAAKE,SAAS,OAAO,aAAa,WAAW;CAC/C;;CAGA,IAAI,UAAkB;EACpB,OAAO,KAAKF;CACd;CAEA,MAAMG,SACJ,QACA,MACA,UAAmF,CAAC,GACxE;EACZ,MAAM,MAAM,IAAI,IAAI,GAAG,KAAKH,WAAW,MAAM;EAC7C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,SAAS,CAAC,CAAC,GAC3D,IAAI,UAAU,KAAA,GAAW,IAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;EAElE,MAAM,UAAU,QAAQ,SAAS,KAAA;EACjC,MAAM,MAAM,MAAM,KAAKE,OAAO,KAAK;GACjC;GACA,QAAQ,YAAY,QAAQ,eAAe;GAC3C,SAAS;IACP,eAAe,KAAKD;IACpB,QAAQ;IACR,GAAI,UAAU,EAAE,gBAAgB,mBAAmB,IAAI,CAAC;GAC1D;GACA,GAAI,UAAU,EAAE,MAAM,KAAK,UAAU,QAAQ,IAAI,EAAE,IAAI,CAAC;EAC1D,CAAC;EACD,IAAI,CAAC,IAAI,IAAI;GAGX,IAAI,SAAwB;GAC5B,IAAI;IACF,MAAM,UAAW,MAAM,IAAI,KAAK;IAOhC,SACE,QAAQ,gBAAgB,MACxB,OAAO,OAAO,QAAQ,UAAU,CAAC,CAAC,CAAC,CAAC,MACpC,QAAQ,UACR,QAAQ,SACR,QAAQ,SACR;GACJ,QAAQ,CAER;GACA,MAAM,IAAI,aAAa,4BAA4B,IAAI,OAAO,GAAG,SAAS,KAAK,WAAW,MAAM,IAAI,MAAM;EAC5G;EACA,IAAI,IAAI,WAAW,KAAK,OAAO,KAAA;EAC/B,OAAQ,MAAM,IAAI,KAAK;CACzB;CAEA,MAAM,gBAAyC;EAC7C,OAAO,KAAKE,SAAyB,OAAO,wBAAwB;CACtE;;CAGA,MAAM,aAAa,UAAgC,CAAC,GAAmC;EACrF,OAAO,KAAKA,SAAgC,OAAO,8BAA8B,EAC/E,OAAO;GAAE,SAAS,QAAQ,WAAW;GAAG,YAAA;EAAoC,EAC9E,CAAC;CACH;;CAGA,MAAM,aAAa,SAKS;EAC1B,OAAO,KAAKA,SAAyB,QAAQ,0BAA0B,EACrE,MAAM;GACJ,KAAK,QAAQ;GACb,QAAQ,CAAC,GAAI,QAAQ,UAAU,iBAAkB;GACjD,YAAY,QAAQ,cAAA;GACpB,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;EAC1E,EACF,CAAC;CACH;;CAGA,MAAM,SAAS,SAAqC;EAClD,OAAO,KAAKA,SAAoB,OAAO,qBAAqB,mBAAmB,OAAO,KAAK,EACzF,OAAO,EAAE,QAAQ,kBAAkB,KAAK,GAAG,EAAE,EAC/C,CAAC;CACH;;CAGA,MAAM,aAAa,SAAiB,UAAgC,CAAC,GAA6B;EAChG,OAAO,KAAKA,SAA0B,OAAO,qBAAqB,mBAAmB,OAAO,EAAE,WAAW,EACvG,OAAO;GAAE,SAAS,QAAQ,WAAW;GAAG,YAAA;EAAoC,EAC9E,CAAC;CACH;;CAGA,MAAM,cAAc,SAAiB,MAAoC;EACvE,OAAO,KAAKA,SAAsB,QAAQ,qBAAqB,mBAAmB,OAAO,EAAE,WAAW,EACpG,MAAM,EAAE,MAAM,UAAU,IAAI,EAAE,EAChC,CAAC;CACH;;CAGA,MAAM,gBAAgB,SAA4C;EAKhE,QAAO,MAJY,KAAKA,SACtB,OACA,qBAAqB,mBAAmB,OAAO,EAAE,aACnD,EAAA,CACY,eAAe,CAAC;CAC9B;;CAGA,MAAM,gBAAgB,SAAiB,cAAqC;EAC1E,MAAM,KAAKA,SAAe,QAAQ,qBAAqB,mBAAmB,OAAO,EAAE,eAAe,EAChG,MAAM,EAAE,YAAY,EAAE,IAAI,aAAa,EAAE,EAC3C,CAAC;CACH;AACF"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Org-wide ownership key for a Jira issue's Factory card. Keyed by the issue
3
+ * reference the intake feed serves as `id` — the direct integration's stable
4
+ * Jira issue id, or the Platform-encoded issue reference (connection + key +
5
+ * project). Either way one key names one issue across every Factory project,
6
+ * so a single live card holds the claim org-wide.
7
+ */
8
+ export declare function jiraClaimKey(issueRef: string): string;
9
+ //# sourceMappingURL=claim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim.d.ts","sourceRoot":"","sources":["../../../src/integrations/jira/claim.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErD"}
@@ -0,0 +1,15 @@
1
+ //#region src/integrations/jira/claim.ts
2
+ /**
3
+ * Org-wide ownership key for a Jira issue's Factory card. Keyed by the issue
4
+ * reference the intake feed serves as `id` — the direct integration's stable
5
+ * Jira issue id, or the Platform-encoded issue reference (connection + key +
6
+ * project). Either way one key names one issue across every Factory project,
7
+ * so a single live card holds the claim org-wide.
8
+ */
9
+ function jiraClaimKey(issueRef) {
10
+ return `jira:issue:${issueRef}`;
11
+ }
12
+ //#endregion
13
+ export { jiraClaimKey };
14
+
15
+ //# sourceMappingURL=claim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim.js","names":[],"sources":["../../../src/integrations/jira/claim.ts"],"sourcesContent":["/**\n * Org-wide ownership key for a Jira issue's Factory card. Keyed by the issue\n * reference the intake feed serves as `id` — the direct integration's stable\n * Jira issue id, or the Platform-encoded issue reference (connection + key +\n * project). Either way one key names one issue across every Factory project,\n * so a single live card holds the claim org-wide.\n */\nexport function jiraClaimKey(issueRef: string): string {\n return `jira:issue:${issueRef}`;\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,aAAa,UAA0B;CACrD,OAAO,cAAc;AACvB"}
@@ -0,0 +1,47 @@
1
+ import type { FactoryJiraEventName, FactoryJiraRuleContext, FactoryRuleHandler } from '../../rules/types.js';
2
+ export type JiraRuleOverrides = Partial<Record<FactoryJiraEventName, FactoryRuleHandler<FactoryJiraRuleContext> | null | undefined>>;
3
+ export type JiraEventRules = Readonly<Record<FactoryJiraEventName, FactoryRuleHandler<FactoryJiraRuleContext> | null>>;
4
+ declare function jiraIssueObserved(context: FactoryJiraRuleContext): {
5
+ readonly type: "upsertLinkedWorkItem";
6
+ readonly idempotencyKey: `${string}:issue-triage`;
7
+ readonly board: string;
8
+ readonly source: "jira-issue";
9
+ readonly sourceKey: string;
10
+ readonly claimKey: string;
11
+ readonly title: string;
12
+ readonly url: string;
13
+ readonly stage: string;
14
+ readonly metadata: {
15
+ readonly identifier: string;
16
+ readonly issueRef: string;
17
+ readonly sourceCreatedAt: string;
18
+ readonly state: string;
19
+ readonly stateType: string;
20
+ readonly priority: string;
21
+ readonly project: string | null;
22
+ readonly site: string | null;
23
+ readonly assignee: string | null;
24
+ readonly assignees: string[];
25
+ readonly creator: string | null;
26
+ readonly author: string | null;
27
+ readonly labels: string[];
28
+ readonly createdAt: string;
29
+ readonly updatedAt: string;
30
+ };
31
+ } | undefined;
32
+ declare function jiraIssueClosed(context: FactoryJiraRuleContext): {
33
+ readonly type: "transition";
34
+ readonly idempotencyKey: `${string}:issue-closed`;
35
+ readonly board: "work";
36
+ readonly stage: "done" | "canceled";
37
+ readonly message: {
38
+ readonly text: `Jira issue ${string} was canceled; this Work card was moved to Done.` | `Jira issue ${string} was canceled; this Work card was moved to Canceled.` | `Jira issue ${string} was completed; this Work card was moved to Done.` | `Jira issue ${string} was completed; this Work card was moved to Canceled.`;
39
+ };
40
+ } | undefined;
41
+ export declare const defaultJiraRules: Readonly<{
42
+ issueObserved: typeof jiraIssueObserved;
43
+ issueClosed: typeof jiraIssueClosed;
44
+ }>;
45
+ export declare function resolveJiraRules(overrides?: JiraRuleOverrides): JiraEventRules;
46
+ export {};
47
+ //# sourceMappingURL=default-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/jira/default-rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG7G,MAAM,MAAM,iBAAiB,GAAG,OAAO,CACrC,MAAM,CAAC,oBAAoB,EAAE,kBAAkB,CAAC,sBAAsB,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAC5F,CAAC;AACF,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,oBAAoB,EAAE,kBAAkB,CAAC,sBAAsB,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AAEvH,iBAAS,iBAAiB,CAAC,OAAO,EAAE,sBAAsB;;;;;;;;;;;;;;;;;;;;4BAwBmB,MAAM,EAAE;;;yBAG1C,MAAM,EAAE;;;;cAKlD;AAED,iBAAS,eAAe,CAAC,OAAO,EAAE,sBAAsB;;;;;;;;cAkBvD;AAED,eAAO,MAAM,gBAAgB;;;EAGF,CAAC;AAE5B,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAsB9E"}
@@ -0,0 +1,67 @@
1
+ import { jiraClaimKey } from "./claim.js";
2
+ //#region src/integrations/jira/default-rules.ts
3
+ function jiraIssueObserved(context) {
4
+ if (context.item) return;
5
+ return {
6
+ type: "upsertLinkedWorkItem",
7
+ idempotencyKey: `${context.ingress.id}:issue-triage`,
8
+ board: context.intake?.board ?? "work",
9
+ source: "jira-issue",
10
+ sourceKey: context.issue.id,
11
+ claimKey: jiraClaimKey(context.issue.id),
12
+ title: context.issue.title,
13
+ url: context.issue.url,
14
+ stage: context.intake?.initialPhase ?? "triage",
15
+ metadata: {
16
+ identifier: context.issue.identifier,
17
+ issueRef: context.issue.id,
18
+ sourceCreatedAt: context.issue.createdAt,
19
+ state: context.issue.state,
20
+ stateType: context.issue.stateType,
21
+ priority: context.issue.priorityLabel,
22
+ project: context.issue.project,
23
+ site: context.issue.site,
24
+ assignee: context.issue.assignee,
25
+ assignees: context.issue.assignee ? [context.issue.assignee] : [],
26
+ creator: context.issue.author,
27
+ author: context.issue.author,
28
+ labels: [...context.issue.labels],
29
+ createdAt: context.issue.createdAt,
30
+ updatedAt: context.issue.updatedAt
31
+ }
32
+ };
33
+ }
34
+ function jiraIssueClosed(context) {
35
+ if (!context.item || context.item.source !== "jira-issue") return;
36
+ if (context.board !== "work") return;
37
+ if (context.item.stages.some((stage) => stage === "done" || stage === "canceled")) return;
38
+ const stateType = context.issue.stateType;
39
+ if (stateType !== "completed" && stateType !== "canceled") return;
40
+ const canceled = stateType === "canceled";
41
+ return {
42
+ type: "transition",
43
+ idempotencyKey: `${context.ingress.id}:issue-closed`,
44
+ board: "work",
45
+ stage: canceled ? "canceled" : "done",
46
+ message: { text: `Jira issue ${context.issue.identifier} was ${canceled ? "canceled" : "completed"}; this Work card was moved to ${canceled ? "Canceled" : "Done"}.` }
47
+ };
48
+ }
49
+ const defaultJiraRules = Object.freeze({
50
+ issueObserved: jiraIssueObserved,
51
+ issueClosed: jiraIssueClosed
52
+ });
53
+ function resolveJiraRules(overrides) {
54
+ if (overrides !== void 0 && (overrides === null || typeof overrides !== "object" || Array.isArray(overrides) || ![Object.prototype, null].includes(Object.getPrototypeOf(overrides)))) throw new Error("Jira rules must be a plain object.");
55
+ const rules = { ...defaultJiraRules };
56
+ for (const key of Reflect.ownKeys(overrides ?? {})) {
57
+ if (typeof key !== "string" || !Object.hasOwn(defaultJiraRules, key)) throw new Error(`Unknown Jira rule event: ${String(key)}.`);
58
+ const handler = overrides?.[key];
59
+ if (handler !== void 0 && handler !== null && typeof handler !== "function") throw new Error(`Jira rule ${key} must be a function, null, or undefined.`);
60
+ if (handler !== void 0) rules[key] = handler;
61
+ }
62
+ return Object.freeze(rules);
63
+ }
64
+ //#endregion
65
+ export { defaultJiraRules, resolveJiraRules };
66
+
67
+ //# sourceMappingURL=default-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-rules.js","names":[],"sources":["../../../src/integrations/jira/default-rules.ts"],"sourcesContent":["import type { FactoryJiraEventName, FactoryJiraRuleContext, FactoryRuleHandler } from '../../rules/types.js';\nimport { jiraClaimKey } from './claim.js';\n\nexport type JiraRuleOverrides = Partial<\n Record<FactoryJiraEventName, FactoryRuleHandler<FactoryJiraRuleContext> | null | undefined>\n>;\nexport type JiraEventRules = Readonly<Record<FactoryJiraEventName, FactoryRuleHandler<FactoryJiraRuleContext> | null>>;\n\nfunction jiraIssueObserved(context: FactoryJiraRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-triage`,\n // A source bound to a custom board lands on that board's initial phase;\n // otherwise Work auto-triages the new issue.\n board: context.intake?.board ?? 'work',\n source: 'jira-issue',\n sourceKey: context.issue.id,\n claimKey: jiraClaimKey(context.issue.id),\n title: context.issue.title,\n url: context.issue.url,\n stage: context.intake?.initialPhase ?? 'triage',\n metadata: {\n identifier: context.issue.identifier,\n issueRef: context.issue.id,\n sourceCreatedAt: context.issue.createdAt,\n state: context.issue.state,\n stateType: context.issue.stateType,\n priority: context.issue.priorityLabel,\n project: context.issue.project,\n site: context.issue.site,\n assignee: context.issue.assignee,\n assignees: (context.issue.assignee ? [context.issue.assignee] : []) as string[],\n creator: context.issue.author,\n author: context.issue.author,\n labels: [...context.issue.labels] as string[],\n createdAt: context.issue.createdAt,\n updatedAt: context.issue.updatedAt,\n },\n } as const;\n}\n\nfunction jiraIssueClosed(context: FactoryJiraRuleContext) {\n if (!context.item || context.item.source !== 'jira-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: `Jira issue ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nexport const defaultJiraRules = Object.freeze({\n issueObserved: jiraIssueObserved,\n issueClosed: jiraIssueClosed,\n} satisfies JiraEventRules);\n\nexport function resolveJiraRules(overrides?: JiraRuleOverrides): JiraEventRules {\n if (\n overrides !== undefined &&\n (overrides === null ||\n typeof overrides !== 'object' ||\n Array.isArray(overrides) ||\n ![Object.prototype, null].includes(Object.getPrototypeOf(overrides)))\n ) {\n throw new Error('Jira rules must be a plain object.');\n }\n const rules: Record<string, FactoryRuleHandler<FactoryJiraRuleContext> | null> = { ...defaultJiraRules };\n for (const key of Reflect.ownKeys(overrides ?? {})) {\n if (typeof key !== 'string' || !Object.hasOwn(defaultJiraRules, key)) {\n throw new Error(`Unknown Jira rule event: ${String(key)}.`);\n }\n const handler = overrides?.[key as FactoryJiraEventName];\n if (handler !== undefined && handler !== null && typeof handler !== 'function') {\n throw new Error(`Jira rule ${key} must be a function, null, or undefined.`);\n }\n if (handler !== undefined) rules[key] = handler;\n }\n return Object.freeze(rules) as JiraEventRules;\n}\n"],"mappings":";;AAQA,SAAS,kBAAkB,SAAiC;CAC1D,IAAI,QAAQ,MAAM;CAClB,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EAGtC,OAAO,QAAQ,QAAQ,SAAS;EAChC,QAAQ;EACR,WAAW,QAAQ,MAAM;EACzB,UAAU,aAAa,QAAQ,MAAM,EAAE;EACvC,OAAO,QAAQ,MAAM;EACrB,KAAK,QAAQ,MAAM;EACnB,OAAO,QAAQ,QAAQ,gBAAgB;EACvC,UAAU;GACR,YAAY,QAAQ,MAAM;GAC1B,UAAU,QAAQ,MAAM;GACxB,iBAAiB,QAAQ,MAAM;GAC/B,OAAO,QAAQ,MAAM;GACrB,WAAW,QAAQ,MAAM;GACzB,UAAU,QAAQ,MAAM;GACxB,SAAS,QAAQ,MAAM;GACvB,MAAM,QAAQ,MAAM;GACpB,UAAU,QAAQ,MAAM;GACxB,WAAY,QAAQ,MAAM,WAAW,CAAC,QAAQ,MAAM,QAAQ,IAAI,CAAC;GACjE,SAAS,QAAQ,MAAM;GACvB,QAAQ,QAAQ,MAAM;GACtB,QAAQ,CAAC,GAAG,QAAQ,MAAM,MAAM;GAChC,WAAW,QAAQ,MAAM;GACzB,WAAW,QAAQ,MAAM;EAC3B;CACF;AACF;AAEA,SAAS,gBAAgB,SAAiC;CACxD,IAAI,CAAC,QAAQ,QAAQ,QAAQ,KAAK,WAAW,cAAc;CAC3D,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,cAAc,QAAQ,MAAM,WAAW,OAAO,WAAW,aAAa,YAAY,gCAAgC,WAAW,aAAa,OAAO,GACzJ;CACF;AACF;AAEA,MAAa,mBAAmB,OAAO,OAAO;CAC5C,eAAe;CACf,aAAa;AACf,CAA0B;AAE1B,SAAgB,iBAAiB,WAA+C;CAC9E,IACE,cAAc,KAAA,MACb,cAAc,QACb,OAAO,cAAc,YACrB,MAAM,QAAQ,SAAS,KACvB,CAAC,CAAC,OAAO,WAAW,IAAI,CAAC,CAAC,SAAS,OAAO,eAAe,SAAS,CAAC,IAErE,MAAM,IAAI,MAAM,oCAAoC;CAEtD,MAAM,QAA2E,EAAE,GAAG,iBAAiB;CACvG,KAAK,MAAM,OAAO,QAAQ,QAAQ,aAAa,CAAC,CAAC,GAAG;EAClD,IAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,OAAO,kBAAkB,GAAG,GACjE,MAAM,IAAI,MAAM,4BAA4B,OAAO,GAAG,EAAE,EAAE;EAE5D,MAAM,UAAU,YAAY;EAC5B,IAAI,YAAY,KAAA,KAAa,YAAY,QAAQ,OAAO,YAAY,YAClE,MAAM,IAAI,MAAM,aAAa,IAAI,yCAAyC;EAE5E,IAAI,YAAY,KAAA,GAAW,MAAM,OAAO;CAC1C;CACA,OAAO,OAAO,OAAO,KAAK;AAC5B"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `JiraIntegration` — the self-contained Jira Cloud integration.
3
+ *
4
+ * Implements the system-wide `FactoryIntegration` contract
5
+ * (`../base.ts`): the deploy entry reads the `JIRA_*` env vars ONCE,
6
+ * constructs an instance with explicit credentials, and passes it to
7
+ * `MastraFactory`. Everything Jira-flavored the system does — project/issue
8
+ * reads for Intake and the agent's issue tools — flows through this
9
+ * instance. No other module reads `JIRA_*` env vars.
10
+ *
11
+ * Unlike Linear, credentials are **deployment-global** (Basic auth with
12
+ * `email:apiToken` from the environment): there is no OAuth flow, no state
13
+ * signer, and no per-org connection storage. The `IntegrationConnection`
14
+ * arguments required by the `Intake` contract are accepted but not consulted
15
+ * — the instance's own credentials authenticate every call.
16
+ */
17
+ import type { RequestContext } from '@mastra/core/request-context';
18
+ import type { ApiRoute } from '@mastra/core/server';
19
+ import type { MastraWorker } from '@mastra/core/worker';
20
+ import type { IntegrationConnection } from '../../capabilities/connection.js';
21
+ import type { Intake, IntakeIssue } from '../../capabilities/intake.js';
22
+ import type { RouteAuth } from '../../routes/route.js';
23
+ import type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';
24
+ import type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../base.js';
25
+ import { JiraApiClient } from './api.js';
26
+ import type { JiraEventRules, JiraRuleOverrides } from './default-rules.js';
27
+ /** Deployment-global Jira Cloud credentials. All credential fields are required. */
28
+ export interface JiraIntegrationConfig {
29
+ /** Site base URL, e.g. `https://acme.atlassian.net`. */
30
+ baseUrl: string;
31
+ /** Atlassian account email the API token belongs to. */
32
+ email: string;
33
+ /** API token from id.atlassian.com → Security → API tokens. */
34
+ apiToken: string;
35
+ /** Per-event replacements; omitted events retain defaults, null disables. */
36
+ rules?: JiraRuleOverrides;
37
+ }
38
+ /**
39
+ * The `Intake` contract requires a connection argument, but Jira credentials
40
+ * live on the integration instance — this inert placeholder is accepted and
41
+ * ignored everywhere, and keeps the real API token out of resolved dispatches
42
+ * and agent-tool calls.
43
+ */
44
+ export declare const DEPLOYMENT_CONNECTION: IntegrationConnection;
45
+ export declare class JiraIntegration implements FactoryIntegration {
46
+ #private;
47
+ /** Stable integration identifier (see `../base.ts`). */
48
+ readonly id = "jira";
49
+ /** Typed REST client bound to the deployment credentials. */
50
+ readonly api: JiraApiClient;
51
+ get rules(): JiraEventRules;
52
+ constructor(config: JiraIntegrationConfig);
53
+ /**
54
+ * Bind the projects domain and the host auth seam. Jira has no per-org
55
+ * connection rows, so the generic storage handle is unused — credentials
56
+ * are deployment-global constructor config.
57
+ */
58
+ initialize({ projects, auth }: {
59
+ projects: FactoryProjectsStorage;
60
+ auth: RouteAuth;
61
+ }): void;
62
+ /** Factory projects domain — maps a session's resourceId to its owning org. */
63
+ get projects(): FactoryProjectsStorage;
64
+ /**
65
+ * Whether the host runs with web auth enabled. Intake selections are
66
+ * org-owned, so every Jira surface is inert without a tenant auth seam.
67
+ */
68
+ get authEnabled(): boolean;
69
+ /**
70
+ * Map a session's resourceId (the factory project id) to its owning org.
71
+ * Same caching semantics as the Linear integration: definitive misses are
72
+ * cached, transient database failures are not.
73
+ */
74
+ resolveOrgId(resourceId: string): Promise<string | null>;
75
+ /** Test hook: clear the org cache between specs. */
76
+ clearCaches(): void;
77
+ /** Normalized site base URL (no trailing slash) — `/browse/<key>` links hang off this. */
78
+ get baseUrl(): string;
79
+ readonly intake: Intake;
80
+ /**
81
+ * One page of active issues (status category not Done), most recently
82
+ * updated first, optionally filtered to projects and labels. The
83
+ * capability cursor is Jira's opaque `nextPageToken` passed through.
84
+ */
85
+ listActiveIssues(cursor?: string, projectIds?: string[], labels?: string[]): Promise<{
86
+ issues: Array<IntakeIssue & {
87
+ sourceId: string;
88
+ }>;
89
+ nextCursor: string | null;
90
+ }>;
91
+ /**
92
+ * The integration's HTTP surface: `/web/jira/*` Mastra `apiRoutes` (status,
93
+ * projects + issues for Intake). No OAuth routes — credentials are
94
+ * deployment-global. Handlers operate on this instance.
95
+ */
96
+ routes(ctx: IntegrationContext): ApiRoute[];
97
+ /**
98
+ * Background reconciliation, matching the Platform integration: keeps filed
99
+ * Jira cards' metadata fresh and replays close events through the rules
100
+ * ingress. Registered only when reconciliation is enabled.
101
+ */
102
+ workers(ctx: IntegrationContext): MastraWorker[];
103
+ /**
104
+ * Org-scoped agent tools: the read-only issue-detail tool for sessions whose
105
+ * resource is a factory project (Jira credentials are deployment-global,
106
+ * so no per-org connection gates apply). v1 exposes no mutating Jira tools.
107
+ */
108
+ agentTools(args: {
109
+ requestContext: RequestContext;
110
+ }): Promise<IntegrationTools>;
111
+ /** Non-secret config snapshot for system diagnostics/startup logs. */
112
+ diagnostics(): Record<string, unknown>;
113
+ }
114
+ //# sourceMappingURL=integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/jira/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,KAAK,EAIV,MAAM,EACN,WAAW,EASZ,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAK3F,OAAO,EAAE,aAAa,EAAgB,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAO5E,oFAAoF;AACpF,MAAM,WAAW,qBAAqB;IACpC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAQD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,qBAA2E,CAAC;AAgChH,qBAAa,eAAgB,YAAW,kBAAkB;;IACxD,wDAAwD;IACxD,QAAQ,CAAC,EAAE,UAAU;IAGrB,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAS5B,IAAI,KAAK,IAAI,cAAc,CAE1B;gBAEW,MAAM,EAAE,qBAAqB;IAOzC;;;;OAIG;IACH,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;QAAE,QAAQ,EAAE,sBAAsB,CAAC;QAAC,IAAI,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI;IAK3F,+EAA+E;IAC/E,IAAI,QAAQ,IAAI,sBAAsB,CAKrC;IAED;;;OAGG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuB9D,oDAAoD;IACpD,WAAW,IAAI,IAAI;IAInB,0FAA0F;IAC1F,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,QAAQ,CAAC,MAAM,EAAE,MAAM,CAQrB;IA6EF;;;;OAIG;IACG,gBAAgB,CACpB,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,MAAM,CAAC,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC;QAAE,MAAM,EAAE,KAAK,CAAC,WAAW,GAAG;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAmH5F;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAS3C;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,YAAY,EAAE;IAchD;;;;OAIG;IACG,UAAU,CAAC,IAAI,EAAE;QAAE,cAAc,EAAE,cAAc,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIrF,sEAAsE;IACtE,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAOvC"}