@mastra/factory 0.13.0-alpha.7 → 0.13.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +15 -1
- package/dist/auth.js.map +1 -1
- package/dist/boards/define-board.d.ts +3 -0
- package/dist/boards/define-board.d.ts.map +1 -1
- package/dist/boards/define-board.js +2 -0
- package/dist/boards/define-board.js.map +1 -1
- package/dist/boards/index.d.ts +1 -0
- package/dist/boards/index.d.ts.map +1 -1
- package/dist/boards/index.js.map +1 -1
- package/dist/boards/transition-policy.d.ts +63 -0
- package/dist/boards/transition-policy.d.ts.map +1 -0
- package/dist/boards/transition-policy.js +36 -0
- package/dist/boards/transition-policy.js.map +1 -0
- package/dist/boards/work-transition-policy.d.ts +3 -0
- package/dist/boards/work-transition-policy.d.ts.map +1 -0
- package/dist/boards/work-transition-policy.js +31 -0
- package/dist/boards/work-transition-policy.js.map +1 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +2 -0
- package/dist/boards/work.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +1 -1
- package/dist/integrations/github/webhook.js +1 -1
- package/dist/integrations/linear/default-rules.d.ts +44 -0
- package/dist/integrations/linear/default-rules.d.ts.map +1 -0
- package/dist/integrations/linear/default-rules.js +65 -0
- package/dist/integrations/linear/default-rules.js.map +1 -0
- package/dist/integrations/linear/integration.d.ts +5 -1
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +7 -1
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/issue-reconciler.d.ts +1 -1
- package/dist/integrations/linear/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/linear/issue-reconciler.js +2 -1
- package/dist/integrations/linear/issue-reconciler.js.map +1 -1
- package/dist/integrations/linear/rules.d.ts +5 -1
- package/dist/integrations/linear/rules.d.ts.map +1 -1
- package/dist/integrations/linear/rules.js +4 -4
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/linear/integration.d.ts +5 -1
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +9 -3
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/slack/slack.js +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +2 -97
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.js +1 -1
- package/dist/rules/index.d.ts +2 -2
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +2 -2
- package/dist/rules/resolve.d.ts +3 -4
- package/dist/rules/resolve.d.ts.map +1 -1
- package/dist/rules/resolve.js +3 -6
- package/dist/rules/resolve.js.map +1 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +32 -20
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +0 -10
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +2 -30
- package/dist/rules/validation.js.map +1 -1
- package/dist/storage/domains/comments/domain.js +1 -1
- package/dist/storage/domains/comments/routes.js +1 -1
- package/factory-skills/configure-factory-rules/SKILL.md +36 -13
- package/package.json +12 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","names":["#client","#endpointHost","#workflowStatesByTeam","#resolveIntakeDispatch","#listProjectSources","#listIssues","#findIssue","#loadComments","#resolveWorkspaceForIssue","#listWorkflowStates","#projects","#auth","#listWorkspaces","#connectRoute","#candidateWorkspaceIds"],"sources":["../../../../src/integrations/platform/linear/integration.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { MastraWorker } from '@mastra/core/worker';\nimport type { Context } from 'hono';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport type { Intake, IntakeIssue, IntakeIssueDetail, UpdateIntakeIssueInput } from '../../../capabilities/intake.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { buildLinearAgentTools } from '../../linear/agent-tools.js';\nimport type { LinearConnectionCheck, LinearIntegration } from '../../linear/integration.js';\nimport { attachLinearIssueReconciler } from '../../linear/issue-reconciler.js';\nimport { linearIssueReconciliationEnabled, linearIssueReconciliationInterval } from '../../linear/reconciliation-config.js';\nimport { buildLinearRoutes } from '../../linear/routes.js';\nimport { attachLinearRules } from '../../linear/rules.js';\nimport type { LinearConnectionData, LinearConnectionRow, LinearStorageHandle } from '../../linear/storage.js';\nimport {\n logPlatformInfo,\n logPlatformWarn,\n PlatformApiClient,\n PlatformApiError,\n platformApiClientConfigFromEnv,\n} from '../api-client.js';\nimport { PlatformLinearEventWorker } from './event-worker.js';\nimport type { PlatformLinearEventStorage } from './event-worker.js';\n\ntype PageInfo = { hasNextPage: boolean; endCursor: string | null };\ntype LinearUser = {\n id: string;\n name: string;\n displayName: string;\n email: string | null;\n avatarUrl: string | null;\n};\ntype LinearIssue = {\n id: string;\n identifier: string;\n number: number;\n title: string;\n description: string | null;\n url: string;\n priority: number;\n priorityLabel: string;\n labels: Array<{ id: string; name: string }>;\n state: { id: string; name: string; type: string };\n team: { id: string; key: string; name: string };\n assignee: LinearUser | null;\n creator: LinearUser | null;\n createdAt: string;\n updatedAt: string;\n archivedAt: string | null;\n};\ntype LinearComment = {\n id: string;\n body: string;\n url: string;\n issue: { id: string; identifier: string };\n user: LinearUser | null;\n parent: { id: string } | null;\n createdAt: string;\n updatedAt: string;\n};\ntype LinearWorkspace = {\n linearWorkspaceId: string;\n linearWorkspaceName: string;\n urlKey: string | null;\n connected: boolean;\n};\ntype LinearProject = {\n id: string;\n name: string;\n state: string;\n teams: Array<{ id: string; key: string; name: string }>;\n};\ntype ProjectSource = { workspace: LinearWorkspace; project: LinearProject };\ntype LinearWorkflowState = {\n id: string;\n name: string;\n type: string;\n position: number;\n teamId: string;\n};\n\nconst API_PREFIX = '/v1/server/linear';\nconst PAGE_SIZE = 30;\nconst MAX_REFERENCE_PAGES = 20;\nconst MAX_COMMENT_PAGES = 20;\nconst PLATFORM_MANAGED_CONNECTION_TOKEN = 'platform-managed';\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction routeBaseUrl(ctx: IntegrationContext, requestUrl: string): string {\n return (ctx.baseUrl || new URL(requestUrl).origin).replace(/\\/+$/, '');\n}\n\nexport class PlatformLinearIntegration implements FactoryIntegration {\n readonly id = 'linear';\n readonly #client: PlatformApiClient;\n readonly #endpointHost: string;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n /**\n * Per-instance workflow-state cache keyed by `${workspaceId}:${teamId}`. Scoped to the process\n * lifetime; not shared across requests intentionally to keep failure modes simple (stale states\n * clear on process restart). Populated lazily on first `updateIssue` per team.\n */\n readonly #workflowStatesByTeam = new Map<string, LinearWorkflowState[]>();\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: async () => {\n const sources = await this.#listProjectSources();\n return sources.map(({ workspace, project }) => ({\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n name: project.name,\n type: 'project',\n metadata: {\n workspaceId: workspace.linearWorkspaceId,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n state: project.state,\n teams: project.teams,\n },\n }));\n },\n listItems: async ({ sourceIds, cursor }) => {\n const result = await this.#listIssues(sourceIds, cursor);\n return {\n items: result.issues.map(({ issue, source }) => ({\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId: encodeSourceId(source.workspace.linearWorkspaceId, source.project.id),\n title: issue.title,\n status: issue.state.name,\n labels: issue.labels.map(label => label.name),\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n workspaceId: source.workspace.linearWorkspaceId,\n workspaceName: source.workspace.linearWorkspaceName,\n projectId: source.project.id,\n projectName: source.project.name,\n team: issue.team.key,\n priority: issue.priorityLabel,\n },\n })),\n nextCursor: result.nextCursor,\n };\n },\n listIssues: async ({ connection, sourceIds, labels, cursor }) => {\n requireLinearConnection(connection);\n const result = await this.#listIssues(sourceIds, cursor, labels);\n return {\n issues: result.issues.map(({ issue }) => parseIssue(issue)),\n nextCursor: result.nextCursor,\n };\n },\n getIssue: async ({ connection, sourceId, issueId }) => {\n requireLinearConnection(connection);\n const located = await this.#findIssue(sourceId, issueId);\n if (!located) return null;\n const comments = await this.#loadComments(located.workspaceId, issueId, located.issue.comments);\n return parseIssueDetail(located.issue, comments);\n },\n createComment: async ({ connection, sourceId, issueId, body }) => {\n requireLinearConnection(connection);\n const workspaceId = await this.#resolveWorkspaceForIssue(sourceId, issueId);\n if (!workspaceId) return null;\n try {\n const comment = await this.#client.request<LinearComment>(\n 'POST',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments`,\n { body },\n );\n return { id: comment.id, url: comment.url };\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n },\n updateIssue: async (input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> => {\n requireLinearConnection(input.connection);\n const located = await this.#findIssue(input.sourceId, input.issueId);\n if (!located) return null;\n const { workspaceId, issue } = located;\n\n const target = input.state;\n // Idempotency: skip the write entirely if the issue is already at the target state.\n if (target.kind === 'byType' && issue.state.type === target.stateType) {\n return parseIssue(issue);\n }\n if (target.kind === 'byName' && issue.state.name.toLowerCase() === target.name.toLowerCase()) {\n return parseIssue(issue);\n }\n\n let states: LinearWorkflowState[];\n try {\n states = await this.#listWorkflowStates(workspaceId, issue.team.id);\n } catch (error) {\n if (isNotFound(error)) {\n // Platform companion endpoint not deployed yet — degrade to policy skip so PR 1\n // is standalone-mergeable ahead of the platform-side workflow-states route landing.\n logPlatformWarn('Platform Linear: workflow-states endpoint not available; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n });\n return null;\n }\n throw error;\n }\n let matched: LinearWorkflowState | undefined;\n if (target.kind === 'byType') {\n matched = states\n .slice()\n .sort((a, b) => a.position - b.position)\n .find(s => s.type === target.stateType);\n } else {\n const wanted = target.name.toLowerCase();\n matched = states.find(s => s.name.toLowerCase() === wanted);\n }\n if (!matched) {\n logPlatformWarn('Platform Linear: no workflow state matched target; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n target,\n });\n return null;\n }\n\n try {\n await this.#client.request<LinearIssue>(\n 'PATCH',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(input.issueId)}`,\n { stateId: matched.id },\n );\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n\n const refreshed = await this.#findIssue(input.sourceId, input.issueId);\n return refreshed ? parseIssue(refreshed.issue) : null;\n },\n };\n\n constructor() {\n const config = platformApiClientConfigFromEnv();\n this.#client = new PlatformApiClient(config);\n this.#endpointHost = new URL(config.baseUrl).host;\n }\n\n get storage(): LinearStorageHandle {\n const now = new Date();\n return {\n integrationId: this.id,\n connections: {\n get: async (orgId: string) => ({\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n data: {\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n refreshToken: null,\n expiresAtMs: null,\n scope: 'read,comments:create',\n workspaceName: null,\n workspaceUrlKey: null,\n } satisfies LinearConnectionData,\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }),\n },\n } as unknown as LinearStorageHandle;\n }\n\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) throw new Error('PlatformLinearIntegration projects storage has not been initialized.');\n return this.#projects;\n }\n\n initialize({ projects, auth }: { projects: FactoryProjectsStorage; auth?: RouteAuth }): void {\n this.#projects = projects;\n this.#auth = auth;\n logPlatformInfo('Platform Linear integration initialized', { endpointHost: this.#endpointHost });\n }\n\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n async resolveOrgId(resourceId: string): Promise<string | null> {\n try {\n const project = await this.projects.getById({ id: resourceId });\n return project?.orgId ?? null;\n } catch {\n return null;\n }\n }\n\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const workspace = (await this.#listWorkspaces())[0];\n if (!workspace) return null;\n const now = new Date();\n return {\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n scope: 'read,comments:create',\n refreshToken: null,\n expiresAt: null,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n async getFreshAccessToken(_connection: LinearConnectionRow): Promise<string> {\n return PLATFORM_MANAGED_CONNECTION_TOKEN;\n }\n\n /**\n * Background-dispatch context: platform-managed connection token when the\n * org has a connected workspace. Linear work items store the issue UUID\n * directly as `externalId`.\n */\n async #resolveIntakeDispatch({\n orgId,\n externalSource,\n }: {\n orgId: string;\n externalSource: { type: string; externalId: string };\n }): Promise<{ connection: IntegrationConnection; issueId: string } | null> {\n if (externalSource.type !== 'issue') return null;\n const connection = await this.loadConnection(orgId);\n if (!connection) return null;\n return {\n connection: { type: 'oauth', accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN },\n issueId: externalSource.externalId,\n };\n }\n\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const connection = await this.loadConnection(orgId);\n return {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n const pollingEnabled = process.env.MASTRACODE_PLATFORM_LINEAR_POLLING_ENABLED?.trim().toLowerCase() !== 'false';\n const reconcileEnabled = linearIssueReconciliationEnabled();\n if (!pollingEnabled && !reconcileEnabled) return [];\n\n const ingest = attachLinearRules(ctx);\n const reconcile = reconcileEnabled ? attachLinearIssueReconciler(this as unknown as LinearIntegration, ctx) : undefined;\n const workItems = ctx.rules?.workItems;\n if (!workItems) return [];\n if (!ingest && !reconcile) return [];\n\n const pollIntervalMs = optionalPositiveIntegerEnv('MASTRACODE_PLATFORM_LINEAR_POLLING_INTERVAL_MS');\n const reconcileIntervalMs = linearIssueReconciliationInterval();\n\n return [\n new PlatformLinearEventWorker({\n client: this.#client,\n linear: { listWorkspaces: () => this.listWorkspaces() },\n storage: ctx.storage.generic as unknown as PlatformLinearEventStorage,\n projects: ctx.storage.projects,\n workItems,\n ...(ingest ? { ingestFactoryIssue: ingest } : {}),\n ...(reconcile ? { reconcileFactoryState: reconcile } : {}),\n pollEventsEnabled: pollingEnabled,\n ...(pollIntervalMs !== undefined ? { intervalMs: pollIntervalMs } : {}),\n ...(reconcileIntervalMs !== undefined ? { reconcileIntervalMs } : {}),\n }),\n ];\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n return [\n this.#connectRoute(ctx),\n ...buildLinearRoutes({\n auth: ctx.auth,\n linear: this as unknown as LinearIntegration,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n projects: ctx.storage.projects,\n ingestFactoryIssues: attachLinearRules(ctx),\n }).filter(route => !route.path.startsWith('/auth/linear/')),\n ];\n }\n\n #connectRoute(ctx: IntegrationContext): ApiRoute {\n return registerApiRoute('/auth/linear/connect', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n await ctx.auth.ensureUser(loose(c));\n const tenant = ctx.auth.tenant(loose(c));\n if (!tenant?.orgId) return c.json({ error: 'unauthorized' }, 401);\n\n const returnTo = c.req.query('return_to') || '/';\n const originator = routeBaseUrl(ctx, c.req.url);\n logPlatformInfo('Starting Platform Linear connect flow', {\n orgId: tenant.orgId,\n returnTo,\n originator,\n });\n const query = new URLSearchParams({ return_to: returnTo, originator });\n const location = await this.#client.requestRedirect('GET', `${API_PREFIX}/authorize?${query}`);\n return c.redirect(location);\n },\n });\n }\n\n async agentTools({ requestContext }: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext, linear: this as unknown as LinearIntegration });\n }\n\n diagnostics(): Record<string, unknown> {\n return { mode: 'platform', endpointHost: this.#endpointHost };\n }\n\n async listProjects(): Promise<Array<LinearProject & { workspaceId: string }>> {\n return (await this.#listProjectSources()).map(({ workspace, project }) => ({\n ...project,\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n workspaceId: workspace.linearWorkspaceId,\n }));\n }\n\n async #listProjectSources(): Promise<ProjectSource[]> {\n const workspaces = await this.#listWorkspaces();\n const projectGroups = await Promise.all(\n workspaces.map(async workspace => {\n const projects: LinearProject[] = [];\n let after: string | undefined;\n for (let page = 0; page < MAX_REFERENCE_PAGES; page += 1) {\n const query = new URLSearchParams({ first: '200' });\n if (after) query.set('after', after);\n const result = await this.#client.request<{ projects: LinearProject[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/projects?${query}`,\n );\n projects.push(...result.projects);\n if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor) break;\n after = result.pageInfo.endCursor;\n }\n return projects.map(project => ({ workspace, project }));\n }),\n );\n return projectGroups.flat();\n }\n\n async listWorkspaces(): Promise<LinearWorkspace[]> {\n return this.#listWorkspaces();\n }\n\n async #listWorkspaces(): Promise<LinearWorkspace[]> {\n const result = await this.#client.request<{ workspaces: LinearWorkspace[] }>('GET', `${API_PREFIX}/workspaces`);\n return result.workspaces.filter(workspace => workspace.connected);\n }\n\n async #listIssues(sourceIds: string[], cursor?: string, labels?: string[]) {\n if (sourceIds.length === 0)\n return { issues: [] as Array<{ issue: LinearIssue; source: ProjectSource }>, nextCursor: null };\n const sources = await this.#listProjectSources();\n const sourceMap = new Map(\n sources.map(source => [encodeSourceId(source.workspace.linearWorkspaceId, source.project.id), source]),\n );\n const selected = sourceIds\n .map(sourceId => sourceMap.get(sourceId))\n .filter((source): source is ProjectSource => !!source);\n const cursors = decodeCursor(cursor, sourceIds);\n const normalizedLabels = normalizeLabels(labels);\n const nextState: Record<string, string | null> = {};\n let hasNextPage = false;\n const pages = await Promise.all(\n selected.map(async source => {\n const sourceId = encodeSourceId(source.workspace.linearWorkspaceId, source.project.id);\n if (cursors[sourceId] === null) {\n nextState[sourceId] = null;\n return [] as Array<{ issue: LinearIssue; source: ProjectSource }>;\n }\n const query = new URLSearchParams({\n first: String(PAGE_SIZE),\n projectIds: source.project.id,\n stateType: 'triage,backlog,unstarted,started',\n orderBy: 'updatedAt',\n });\n const after = cursors[sourceId];\n if (after) query.set('after', after);\n const result = await this.#client.request<{ issues: LinearIssue[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(source.workspace.linearWorkspaceId)}/issues?${query}`,\n );\n const next = result.pageInfo.hasNextPage ? result.pageInfo.endCursor : null;\n nextState[sourceId] = next;\n hasNextPage ||= next !== null;\n return result.issues\n .filter(\n issue => normalizedLabels.length === 0 || issue.labels.some(label => normalizedLabels.includes(label.name)),\n )\n .map(issue => ({ issue, source }));\n }),\n );\n return {\n issues: pages.flat(),\n nextCursor: hasNextPage ? encodeCursor(nextState, sourceIds) : null,\n };\n }\n\n async #findIssue(\n sourceId: string | undefined,\n issueId: string,\n ): Promise<{\n workspaceId: string;\n issue: LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } };\n } | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n for (const workspaceId of workspaceIds) {\n try {\n const issue = await this.#client.request<\n LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } }\n >(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}?include=comments`,\n );\n return { workspaceId, issue };\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #resolveWorkspaceForIssue(sourceId: string | undefined, issueId: string): Promise<string | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n if (workspaceIds.length === 1) return workspaceIds[0]!;\n for (const workspaceId of workspaceIds) {\n try {\n await this.#client.request<LinearIssue>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}`,\n );\n return workspaceId;\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #listWorkflowStates(workspaceId: string, teamId: string): Promise<LinearWorkflowState[]> {\n const cacheKey = `${workspaceId}:${teamId}`;\n const cached = this.#workflowStatesByTeam.get(cacheKey);\n if (cached) return cached;\n const result = await this.#client.request<{ workflowStates: LinearWorkflowState[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/workflow-states?teamId=${encodeURIComponent(teamId)}&first=100`,\n );\n this.#workflowStatesByTeam.set(cacheKey, result.workflowStates);\n return result.workflowStates;\n }\n\n async #candidateWorkspaceIds(sourceId: string | undefined): Promise<string[]> {\n if (sourceId) return [decodeSourceId(sourceId).workspaceId];\n return (await this.#listWorkspaces()).map(workspace => workspace.linearWorkspaceId);\n }\n\n async #loadComments(\n workspaceId: string,\n issueId: string,\n embedded: { nodes: LinearComment[]; pageInfo: PageInfo } | undefined,\n ): Promise<LinearComment[]> {\n const comments = [...(embedded?.nodes ?? [])];\n let pageInfo = embedded?.pageInfo;\n let page = 0;\n while (pageInfo?.hasNextPage && pageInfo.endCursor && page < MAX_COMMENT_PAGES) {\n const result = await this.#client.request<{ comments: LinearComment[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments?first=200&after=${encodeURIComponent(pageInfo.endCursor)}`,\n );\n comments.push(...result.comments);\n pageInfo = result.pageInfo;\n page += 1;\n }\n return comments;\n }\n}\n\nfunction parseIssue(issue: LinearIssue): IntakeIssue {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.creator?.displayName ?? issue.creator?.name ?? null,\n state: issue.state.name,\n stateType: issue.state.type,\n priority: issue.priorityLabel,\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n source: issue.team.key,\n labels: issue.labels.map(label => label.name),\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n\nfunction parseIssueDetail(issue: LinearIssue, comments: LinearComment[]): IntakeIssueDetail {\n return {\n ...parseIssue(issue),\n commentCount: comments.length,\n description: issue.description?.trim() ? issue.description : null,\n comments: comments.map(comment => ({\n author: comment.user?.displayName ?? comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n}\n\nfunction encodeSourceId(workspaceId: string, projectId: string): string {\n return `linear-project:${Buffer.from(JSON.stringify({ workspaceId, projectId })).toString('base64url')}`;\n}\n\nfunction decodeSourceId(sourceId: string): { workspaceId: string; projectId: string } {\n if (!sourceId.startsWith('linear-project:')) throw new Error('Linear project source id is invalid.');\n try {\n const parsed = JSON.parse(Buffer.from(sourceId.slice('linear-project:'.length), 'base64url').toString('utf8')) as {\n workspaceId?: unknown;\n projectId?: unknown;\n };\n if (typeof parsed.workspaceId !== 'string' || !parsed.workspaceId) throw new Error();\n if (typeof parsed.projectId !== 'string' || !parsed.projectId) throw new Error();\n return { workspaceId: parsed.workspaceId, projectId: parsed.projectId };\n } catch {\n throw new Error('Linear project source id is invalid.');\n }\n}\n\nfunction normalizeLabels(labels: string[] | undefined): string[] {\n return [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n}\n\nfunction decodeCursor(cursor: string | undefined, sourceIds: string[]): Record<string, string | null | undefined> {\n if (!cursor) return {};\n if (sourceIds.length === 1) return { [sourceIds[0]!]: cursor };\n try {\n const parsed = JSON.parse(cursor) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error();\n return parsed as Record<string, string | null>;\n } catch {\n throw new Error('Linear cursor is invalid.');\n }\n}\n\nfunction encodeCursor(state: Record<string, string | null>, sourceIds: string[]): string {\n if (sourceIds.length === 1) return state[sourceIds[0]!]!;\n return JSON.stringify(state);\n}\n\nfunction requireLinearConnection(connection: IntegrationConnection): void {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n}\n\nfunction isNotFound(error: unknown): boolean {\n return error instanceof PlatformApiError && error.status === 404;\n}\n\nfunction optionalPositiveIntegerEnv(name: string): number | undefined {\n const value = process.env[name]?.trim();\n if (!value) return undefined;\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed <= 0) {\n throw new Error(`${name} must be a positive integer.`);\n }\n return parsed;\n}\n"],"mappings":";;;;;;;;;AAqFA,MAAM,aAAa;AACnB,MAAM,YAAY;AAClB,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAC1B,MAAM,oCAAoC;AAE1C,SAAS,MAAM,GAAqB;CAClC,OAAO;AACT;AAEA,SAAS,aAAa,KAAyB,YAA4B;CACzE,QAAQ,IAAI,WAAW,IAAI,IAAI,UAAU,CAAC,CAAC,OAAA,CAAQ,QAAQ,QAAQ,EAAE;AACvE;AAEA,IAAa,4BAAb,MAAqE;CACnE,KAAc;CACd;CACA;CACA;CACA;;;;;;CAMA,wCAAiC,IAAI,IAAmC;CAExE,SAA0B;EACxB,wBAAuB,UAAS,KAAKG,uBAAuB,KAAK;EACjE,aAAa,YAAY;GAEvB,QAAO,MADe,KAAKC,oBAAoB,EAAA,CAChC,KAAK,EAAE,WAAW,eAAe;IAC9C,IAAI,eAAe,UAAU,mBAAmB,QAAQ,EAAE;IAC1D,MAAM,QAAQ;IACd,MAAM;IACN,UAAU;KACR,aAAa,UAAU;KACvB,eAAe,UAAU;KACzB,iBAAiB,UAAU;KAC3B,OAAO,QAAQ;KACf,OAAO,QAAQ;IACjB;GACF,EAAE;EACJ;EACA,WAAW,OAAO,EAAE,WAAW,aAAa;GAC1C,MAAM,SAAS,MAAM,KAAKC,YAAY,WAAW,MAAM;GACvD,OAAO;IACL,OAAO,OAAO,OAAO,KAAK,EAAE,OAAO,cAAc;KAC/C,QAAQ;MAAE,MAAM;MAAS,YAAY,MAAM;MAAI,KAAK,MAAM;KAAI;KAC9D,UAAU,eAAe,OAAO,UAAU,mBAAmB,OAAO,QAAQ,EAAE;KAC9E,OAAO,MAAM;KACb,QAAQ,MAAM,MAAM;KACpB,QAAQ,MAAM,OAAO,KAAI,UAAS,MAAM,IAAI;KAC5C,UAAU,MAAM,UAAU,eAAe,MAAM,UAAU,QAAQ;KACjE,WAAW,MAAM;KACjB,WAAW,MAAM;KACjB,UAAU;MACR,YAAY,MAAM;MAClB,aAAa,OAAO,UAAU;MAC9B,eAAe,OAAO,UAAU;MAChC,WAAW,OAAO,QAAQ;MAC1B,aAAa,OAAO,QAAQ;MAC5B,MAAM,MAAM,KAAK;MACjB,UAAU,MAAM;KAClB;IACF,EAAE;IACF,YAAY,OAAO;GACrB;EACF;EACA,YAAY,OAAO,EAAE,YAAY,WAAW,QAAQ,aAAa;GAC/D,wBAAwB,UAAU;GAClC,MAAM,SAAS,MAAM,KAAKA,YAAY,WAAW,QAAQ,MAAM;GAC/D,OAAO;IACL,QAAQ,OAAO,OAAO,KAAK,EAAE,YAAY,WAAW,KAAK,CAAC;IAC1D,YAAY,OAAO;GACrB;EACF;EACA,UAAU,OAAO,EAAE,YAAY,UAAU,cAAc;GACrD,wBAAwB,UAAU;GAClC,MAAM,UAAU,MAAM,KAAKC,WAAW,UAAU,OAAO;GACvD,IAAI,CAAC,SAAS,OAAO;GACrB,MAAM,WAAW,MAAM,KAAKC,cAAc,QAAQ,aAAa,SAAS,QAAQ,MAAM,QAAQ;GAC9F,OAAO,iBAAiB,QAAQ,OAAO,QAAQ;EACjD;EACA,eAAe,OAAO,EAAE,YAAY,UAAU,SAAS,WAAW;GAChE,wBAAwB,UAAU;GAClC,MAAM,cAAc,MAAM,KAAKC,0BAA0B,UAAU,OAAO;GAC1E,IAAI,CAAC,aAAa,OAAO;GACzB,IAAI;IACF,MAAM,UAAU,MAAM,KAAKR,QAAQ,QACjC,QACA,GAAG,WAAW,cAAc,mBAAmB,WAAW,EAAE,UAAU,mBAAmB,OAAO,EAAE,YAClG,EAAE,KAAK,CACT;IACA,OAAO;KAAE,IAAI,QAAQ;KAAI,KAAK,QAAQ;IAAI;GAC5C,SAAS,OAAO;IACd,IAAI,WAAW,KAAK,GAAG,OAAO;IAC9B,MAAM;GACR;EACF;EACA,aAAa,OAAO,UAA+D;GACjF,wBAAwB,MAAM,UAAU;GACxC,MAAM,UAAU,MAAM,KAAKM,WAAW,MAAM,UAAU,MAAM,OAAO;GACnE,IAAI,CAAC,SAAS,OAAO;GACrB,MAAM,EAAE,aAAa,UAAU;GAE/B,MAAM,SAAS,MAAM;GAErB,IAAI,OAAO,SAAS,YAAY,MAAM,MAAM,SAAS,OAAO,WAC1D,OAAO,WAAW,KAAK;GAEzB,IAAI,OAAO,SAAS,YAAY,MAAM,MAAM,KAAK,YAAY,MAAM,OAAO,KAAK,YAAY,GACzF,OAAO,WAAW,KAAK;GAGzB,IAAI;GACJ,IAAI;IACF,SAAS,MAAM,KAAKG,oBAAoB,aAAa,MAAM,KAAK,EAAE;GACpE,SAAS,OAAO;IACd,IAAI,WAAW,KAAK,GAAG;KAGrB,gBAAgB,kFAAkF;MAChG;MACA,QAAQ,MAAM,KAAK;KACrB,CAAC;KACD,OAAO;IACT;IACA,MAAM;GACR;GACA,IAAI;GACJ,IAAI,OAAO,SAAS,UAClB,UAAU,OACP,MAAM,CAAC,CACP,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,CACvC,MAAK,MAAK,EAAE,SAAS,OAAO,SAAS;QACnC;IACL,MAAM,SAAS,OAAO,KAAK,YAAY;IACvC,UAAU,OAAO,MAAK,MAAK,EAAE,KAAK,YAAY,MAAM,MAAM;GAC5D;GACA,IAAI,CAAC,SAAS;IACZ,gBAAgB,4EAA4E;KAC1F;KACA,QAAQ,MAAM,KAAK;KACnB;IACF,CAAC;IACD,OAAO;GACT;GAEA,IAAI;IACF,MAAM,KAAKT,QAAQ,QACjB,SACA,GAAG,WAAW,cAAc,mBAAmB,WAAW,EAAE,UAAU,mBAAmB,MAAM,OAAO,KACtG,EAAE,SAAS,QAAQ,GAAG,CACxB;GACF,SAAS,OAAO;IACd,IAAI,WAAW,KAAK,GAAG,OAAO;IAC9B,MAAM;GACR;GAEA,MAAM,YAAY,MAAM,KAAKM,WAAW,MAAM,UAAU,MAAM,OAAO;GACrE,OAAO,YAAY,WAAW,UAAU,KAAK,IAAI;EACnD;CACF;CAEA,cAAc;EACZ,MAAM,SAAS,+BAA+B;EAC9C,KAAKN,UAAU,IAAI,kBAAkB,MAAM;EAC3C,KAAKC,gBAAgB,IAAI,IAAI,OAAO,OAAO,CAAC,CAAC;CAC/C;CAEA,IAAI,UAA+B;EACjC,MAAM,sBAAM,IAAI,KAAK;EACrB,OAAO;GACL,eAAe,KAAK;GACpB,aAAa,EACX,KAAK,OAAO,WAAmB;IAC7B,IAAI,mBAAmB;IACvB;IACA,QAAQ;IACR,MAAM;KACJ,aAAa;KACb,cAAc;KACd,aAAa;KACb,OAAO;KACP,eAAe;KACf,iBAAiB;IACnB;IACA,UAAU,CAAC;IACX,WAAW;IACX,WAAW;GACb,GACF;EACF;CACF;CAEA,IAAI,WAAmC;EACrC,IAAI,CAAC,KAAKS,WAAW,MAAM,IAAI,MAAM,sEAAsE;EAC3G,OAAO,KAAKA;CACd;CAEA,WAAW,EAAE,UAAU,QAAsE;EAC3F,KAAKA,YAAY;EACjB,KAAKC,QAAQ;EACb,gBAAgB,2CAA2C,EAAE,cAAc,KAAKV,cAAc,CAAC;CACjG;CAEA,IAAI,cAAuB;EACzB,OAAO,KAAKU,OAAO,QAAQ,KAAK;CAClC;CAEA,MAAM,aAAa,YAA4C;EAC7D,IAAI;GAEF,QAAO,MADe,KAAK,SAAS,QAAQ,EAAE,IAAI,WAAW,CAAC,EAAA,EAC9C,SAAS;EAC3B,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,eAAe,OAAoD;EACvE,MAAM,aAAa,MAAM,KAAKC,gBAAgB,EAAA,CAAG;EACjD,IAAI,CAAC,WAAW,OAAO;EACvB,MAAM,sBAAM,IAAI,KAAK;EACrB,OAAO;GACL,IAAI,mBAAmB;GACvB;GACA,QAAQ;GACR,aAAa;GACb,OAAO;GACP,cAAc;GACd,WAAW;GACX,eAAe,UAAU;GACzB,iBAAiB,UAAU;GAC3B,WAAW;GACX,WAAW;EACb;CACF;CAEA,MAAM,oBAAoB,aAAmD;EAC3E,OAAO;CACT;;;;;;CAOA,MAAMT,uBAAuB,EAC3B,OACA,kBAIyE;EACzE,IAAI,eAAe,SAAS,SAAS,OAAO;EAE5C,IAAI,CAAC,MADoB,KAAK,eAAe,KAAK,GACjC,OAAO;EACxB,OAAO;GACL,YAAY;IAAE,MAAM;IAAS,aAAa;GAAkC;GAC5E,SAAS,eAAe;EAC1B;CACF;CAEA,gBAAgB,YAA0C;EAExD,QADgB,WAAW,SAAS,GAAA,CAAI,MAAM,QAAQ,CAAC,CAAC,OAAO,OACnD,CAAC,CAAC,MAAK,UAAS,UAAU,qBAAqB,UAAU,WAAW,UAAU,OAAO;CACnG;CAEA,MAAM,gBAAgB,OAA+C;EACnE,MAAM,aAAa,MAAM,KAAK,eAAe,KAAK;EAClD,OAAO;GACL,WAAW,eAAe;GAC1B,YAAY,eAAe,QAAQ,KAAK,gBAAgB,UAAU;GAClE,WAAW,KAAK,IAAI;EACtB;CACF;CAEA,QAAQ,KAAyC;EAC/C,MAAM,iBAAiB,QAAQ,IAAI,4CAA4C,KAAK,CAAC,CAAC,YAAY,MAAM;EACxG,MAAM,mBAAmB,iCAAiC;EAC1D,IAAI,CAAC,kBAAkB,CAAC,kBAAkB,OAAO,CAAC;EAElD,MAAM,SAAS,kBAAkB,GAAG;EACpC,MAAM,YAAY,mBAAmB,4BAA4B,MAAsC,GAAG,IAAI,KAAA;EAC9G,MAAM,YAAY,IAAI,OAAO;EAC7B,IAAI,CAAC,WAAW,OAAO,CAAC;EACxB,IAAI,CAAC,UAAU,CAAC,WAAW,OAAO,CAAC;EAEnC,MAAM,iBAAiB,2BAA2B,gDAAgD;EAClG,MAAM,sBAAsB,kCAAkC;EAE9D,OAAO,CACL,IAAI,0BAA0B;GAC5B,QAAQ,KAAKH;GACb,QAAQ,EAAE,sBAAsB,KAAK,eAAe,EAAE;GACtD,SAAS,IAAI,QAAQ;GACrB,UAAU,IAAI,QAAQ;GACtB;GACA,GAAI,SAAS,EAAE,oBAAoB,OAAO,IAAI,CAAC;GAC/C,GAAI,YAAY,EAAE,uBAAuB,UAAU,IAAI,CAAC;GACxD,mBAAmB;GACnB,GAAI,mBAAmB,KAAA,IAAY,EAAE,YAAY,eAAe,IAAI,CAAC;GACrE,GAAI,wBAAwB,KAAA,IAAY,EAAE,oBAAoB,IAAI,CAAC;EACrE,CAAC,CACH;CACF;CAEA,OAAO,KAAqC;EAC1C,OAAO,CACL,KAAKa,cAAc,GAAG,GACtB,GAAG,kBAAkB;GACnB,MAAM,IAAI;GACV,QAAQ;GACR,aAAa,IAAI;GACjB,SAAS,IAAI;GACb,QAAQ,IAAI,QAAQ;GACpB,UAAU,IAAI,QAAQ;GACtB,qBAAqB,kBAAkB,GAAG;EAC5C,CAAC,CAAC,CAAC,QAAO,UAAS,CAAC,MAAM,KAAK,WAAW,eAAe,CAAC,CAC5D;CACF;CAEA,cAAc,KAAmC;EAC/C,OAAO,iBAAiB,wBAAwB;GAC9C,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,IAAI,KAAK,WAAW,MAAM,CAAC,CAAC;IAClC,MAAM,SAAS,IAAI,KAAK,OAAO,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,QAAQ,OAAO,OAAO,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG;IAEhE,MAAM,WAAW,EAAE,IAAI,MAAM,WAAW,KAAK;IAC7C,MAAM,aAAa,aAAa,KAAK,EAAE,IAAI,GAAG;IAC9C,gBAAgB,yCAAyC;KACvD,OAAO,OAAO;KACd;KACA;IACF,CAAC;IACD,MAAM,QAAQ,IAAI,gBAAgB;KAAE,WAAW;KAAU;IAAW,CAAC;IACrE,MAAM,WAAW,MAAM,KAAKb,QAAQ,gBAAgB,OAAO,GAAG,WAAW,aAAa,OAAO;IAC7F,OAAO,EAAE,SAAS,QAAQ;GAC5B;EACF,CAAC;CACH;CAEA,MAAM,WAAW,EAAE,kBAAiF;EAClG,OAAO,sBAAsB;GAAE;GAAgB,QAAQ;EAAqC,CAAC;CAC/F;CAEA,cAAuC;EACrC,OAAO;GAAE,MAAM;GAAY,cAAc,KAAKC;EAAc;CAC9D;CAEA,MAAM,eAAwE;EAC5E,QAAQ,MAAM,KAAKG,oBAAoB,EAAA,CAAG,KAAK,EAAE,WAAW,eAAe;GACzE,GAAG;GACH,IAAI,eAAe,UAAU,mBAAmB,QAAQ,EAAE;GAC1D,aAAa,UAAU;EACzB,EAAE;CACJ;CAEA,MAAMA,sBAAgD;EACpD,MAAM,aAAa,MAAM,KAAKQ,gBAAgB;EAmB9C,QAAO,MAlBqB,QAAQ,IAClC,WAAW,IAAI,OAAM,cAAa;GAChC,MAAM,WAA4B,CAAC;GACnC,IAAI;GACJ,KAAK,IAAI,OAAO,GAAG,OAAO,qBAAqB,QAAQ,GAAG;IACxD,MAAM,QAAQ,IAAI,gBAAgB,EAAE,OAAO,MAAM,CAAC;IAClD,IAAI,OAAO,MAAM,IAAI,SAAS,KAAK;IACnC,MAAM,SAAS,MAAM,KAAKZ,QAAQ,QAChC,OACA,GAAG,WAAW,cAAc,mBAAmB,UAAU,iBAAiB,EAAE,YAAY,OAC1F;IACA,SAAS,KAAK,GAAG,OAAO,QAAQ;IAChC,IAAI,CAAC,OAAO,SAAS,eAAe,CAAC,OAAO,SAAS,WAAW;IAChE,QAAQ,OAAO,SAAS;GAC1B;GACA,OAAO,SAAS,KAAI,aAAY;IAAE;IAAW;GAAQ,EAAE;EACzD,CAAC,CACH,EAAA,CACqB,KAAK;CAC5B;CAEA,MAAM,iBAA6C;EACjD,OAAO,KAAKY,gBAAgB;CAC9B;CAEA,MAAMA,kBAA8C;EAElD,QAAO,MADc,KAAKZ,QAAQ,QAA2C,OAAO,GAAG,WAAW,YAAY,EAAA,CAChG,WAAW,QAAO,cAAa,UAAU,SAAS;CAClE;CAEA,MAAMK,YAAY,WAAqB,QAAiB,QAAmB;EACzE,IAAI,UAAU,WAAW,GACvB,OAAO;GAAE,QAAQ,CAAC;GAA2D,YAAY;EAAK;EAChG,MAAM,UAAU,MAAM,KAAKD,oBAAoB;EAC/C,MAAM,YAAY,IAAI,IACpB,QAAQ,KAAI,WAAU,CAAC,eAAe,OAAO,UAAU,mBAAmB,OAAO,QAAQ,EAAE,GAAG,MAAM,CAAC,CACvG;EACA,MAAM,WAAW,UACd,KAAI,aAAY,UAAU,IAAI,QAAQ,CAAC,CAAC,CACxC,QAAQ,WAAoC,CAAC,CAAC,MAAM;EACvD,MAAM,UAAU,aAAa,QAAQ,SAAS;EAC9C,MAAM,mBAAmB,gBAAgB,MAAM;EAC/C,MAAM,YAA2C,CAAC;EAClD,IAAI,cAAc;EA8BlB,OAAO;GACL,SAAQ,MA9BU,QAAQ,IAC1B,SAAS,IAAI,OAAM,WAAU;IAC3B,MAAM,WAAW,eAAe,OAAO,UAAU,mBAAmB,OAAO,QAAQ,EAAE;IACrF,IAAI,QAAQ,cAAc,MAAM;KAC9B,UAAU,YAAY;KACtB,OAAO,CAAC;IACV;IACA,MAAM,QAAQ,IAAI,gBAAgB;KAChC,OAAO,OAAO,SAAS;KACvB,YAAY,OAAO,QAAQ;KAC3B,WAAW;KACX,SAAS;IACX,CAAC;IACD,MAAM,QAAQ,QAAQ;IACtB,IAAI,OAAO,MAAM,IAAI,SAAS,KAAK;IACnC,MAAM,SAAS,MAAM,KAAKJ,QAAQ,QAChC,OACA,GAAG,WAAW,cAAc,mBAAmB,OAAO,UAAU,iBAAiB,EAAE,UAAU,OAC/F;IACA,MAAM,OAAO,OAAO,SAAS,cAAc,OAAO,SAAS,YAAY;IACvE,UAAU,YAAY;IACtB,gBAAgB,SAAS;IACzB,OAAO,OAAO,OACX,QACC,UAAS,iBAAiB,WAAW,KAAK,MAAM,OAAO,MAAK,UAAS,iBAAiB,SAAS,MAAM,IAAI,CAAC,CAC5G,CAAC,CACA,KAAI,WAAU;KAAE;KAAO;IAAO,EAAE;GACrC,CAAC,CACH,EAAA,CAEgB,KAAK;GACnB,YAAY,cAAc,aAAa,WAAW,SAAS,IAAI;EACjE;CACF;CAEA,MAAMM,WACJ,UACA,SAIQ;EACR,MAAM,eAAe,MAAM,KAAKQ,uBAAuB,QAAQ;EAC/D,KAAK,MAAM,eAAe,cACxB,IAAI;GAOF,OAAO;IAAE;IAAa,OAAA,MANF,KAAKd,QAAQ,QAG/B,OACA,GAAG,WAAW,cAAc,mBAAmB,WAAW,EAAE,UAAU,mBAAmB,OAAO,EAAE,kBACpG;GAC4B;EAC9B,SAAS,OAAO;GACd,IAAI,CAAC,WAAW,KAAK,GAAG,MAAM;EAChC;EAEF,OAAO;CACT;CAEA,MAAMQ,0BAA0B,UAA8B,SAAyC;EACrG,MAAM,eAAe,MAAM,KAAKM,uBAAuB,QAAQ;EAC/D,IAAI,aAAa,WAAW,GAAG,OAAO,aAAa;EACnD,KAAK,MAAM,eAAe,cACxB,IAAI;GACF,MAAM,KAAKd,QAAQ,QACjB,OACA,GAAG,WAAW,cAAc,mBAAmB,WAAW,EAAE,UAAU,mBAAmB,OAAO,GAClG;GACA,OAAO;EACT,SAAS,OAAO;GACd,IAAI,CAAC,WAAW,KAAK,GAAG,MAAM;EAChC;EAEF,OAAO;CACT;CAEA,MAAMS,oBAAoB,aAAqB,QAAgD;EAC7F,MAAM,WAAW,GAAG,YAAY,GAAG;EACnC,MAAM,SAAS,KAAKP,sBAAsB,IAAI,QAAQ;EACtD,IAAI,QAAQ,OAAO;EACnB,MAAM,SAAS,MAAM,KAAKF,QAAQ,QAChC,OACA,GAAG,WAAW,cAAc,mBAAmB,WAAW,EAAE,0BAA0B,mBAAmB,MAAM,EAAE,WACnH;EACA,KAAKE,sBAAsB,IAAI,UAAU,OAAO,cAAc;EAC9D,OAAO,OAAO;CAChB;CAEA,MAAMY,uBAAuB,UAAiD;EAC5E,IAAI,UAAU,OAAO,CAAC,eAAe,QAAQ,CAAC,CAAC,WAAW;EAC1D,QAAQ,MAAM,KAAKF,gBAAgB,EAAA,CAAG,KAAI,cAAa,UAAU,iBAAiB;CACpF;CAEA,MAAML,cACJ,aACA,SACA,UAC0B;EAC1B,MAAM,WAAW,CAAC,GAAI,UAAU,SAAS,CAAC,CAAE;EAC5C,IAAI,WAAW,UAAU;EACzB,IAAI,OAAO;EACX,OAAO,UAAU,eAAe,SAAS,aAAa,OAAO,mBAAmB;GAC9E,MAAM,SAAS,MAAM,KAAKP,QAAQ,QAChC,OACA,GAAG,WAAW,cAAc,mBAAmB,WAAW,EAAE,UAAU,mBAAmB,OAAO,EAAE,4BAA4B,mBAAmB,SAAS,SAAS,GACrK;GACA,SAAS,KAAK,GAAG,OAAO,QAAQ;GAChC,WAAW,OAAO;GAClB,QAAQ;EACV;EACA,OAAO;CACT;AACF;AAEA,SAAS,WAAW,OAAiC;CACnD,OAAO;EACL,IAAI,MAAM;EACV,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,KAAK,MAAM;EACX,QAAQ,MAAM,SAAS,eAAe,MAAM,SAAS,QAAQ;EAC7D,OAAO,MAAM,MAAM;EACnB,WAAW,MAAM,MAAM;EACvB,UAAU,MAAM;EAChB,UAAU,MAAM,UAAU,eAAe,MAAM,UAAU,QAAQ;EACjE,QAAQ,MAAM,KAAK;EACnB,QAAQ,MAAM,OAAO,KAAI,UAAS,MAAM,IAAI;EAC5C,cAAc;EACd,WAAW,MAAM;EACjB,WAAW,MAAM;CACnB;AACF;AAEA,SAAS,iBAAiB,OAAoB,UAA8C;CAC1F,OAAO;EACL,GAAG,WAAW,KAAK;EACnB,cAAc,SAAS;EACvB,aAAa,MAAM,aAAa,KAAK,IAAI,MAAM,cAAc;EAC7D,UAAU,SAAS,KAAI,aAAY;GACjC,QAAQ,QAAQ,MAAM,eAAe,QAAQ,MAAM,QAAQ;GAC3D,MAAM,QAAQ;GACd,WAAW,QAAQ;EACrB,EAAE;CACJ;AACF;AAEA,SAAS,eAAe,aAAqB,WAA2B;CACtE,OAAO,kBAAkB,OAAO,KAAK,KAAK,UAAU;EAAE;EAAa;CAAU,CAAC,CAAC,CAAC,CAAC,SAAS,WAAW;AACvG;AAEA,SAAS,eAAe,UAA8D;CACpF,IAAI,CAAC,SAAS,WAAW,iBAAiB,GAAG,MAAM,IAAI,MAAM,sCAAsC;CACnG,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,SAAS,MAAM,EAAwB,GAAG,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;EAI7G,IAAI,OAAO,OAAO,gBAAgB,YAAY,CAAC,OAAO,aAAa,MAAM,IAAI,MAAM;EACnF,IAAI,OAAO,OAAO,cAAc,YAAY,CAAC,OAAO,WAAW,MAAM,IAAI,MAAM;EAC/E,OAAO;GAAE,aAAa,OAAO;GAAa,WAAW,OAAO;EAAU;CACxE,QAAQ;EACN,MAAM,IAAI,MAAM,sCAAsC;CACxD;AACF;AAEA,SAAS,gBAAgB,QAAwC;CAC/D,OAAO,CAAC,GAAG,IAAI,KAAK,UAAU,CAAC,EAAA,CAAG,KAAI,UAAS,MAAM,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC;AAC/E;AAEA,SAAS,aAAa,QAA4B,WAAgE;CAChH,IAAI,CAAC,QAAQ,OAAO,CAAC;CACrB,IAAI,UAAU,WAAW,GAAG,OAAO,GAAG,UAAU,KAAM,OAAO;CAC7D,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,MAAM;EAChC,IAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG,MAAM,IAAI,MAAM;EACpF,OAAO;CACT,QAAQ;EACN,MAAM,IAAI,MAAM,2BAA2B;CAC7C;AACF;AAEA,SAAS,aAAa,OAAsC,WAA6B;CACvF,IAAI,UAAU,WAAW,GAAG,OAAO,MAAM,UAAU;CACnD,OAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,SAAS,wBAAwB,YAAyC;CACxE,IAAI,WAAW,SAAS,SACtB,MAAM,IAAI,MAAM,kDAAkD;AAEtE;AAEA,SAAS,WAAW,OAAyB;CAC3C,OAAO,iBAAiB,oBAAoB,MAAM,WAAW;AAC/D;AAEA,SAAS,2BAA2B,MAAkC;CACpE,MAAM,QAAQ,QAAQ,IAAI,KAAK,EAAE,KAAK;CACtC,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,MAAM,SAAS,OAAO,KAAK;CAC3B,IAAI,CAAC,OAAO,cAAc,MAAM,KAAK,UAAU,GAC7C,MAAM,IAAI,MAAM,GAAG,KAAK,6BAA6B;CAEvD,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"integration.js","names":["#client","#endpointHost","#workflowStatesByTeam","#resolveIntakeDispatch","#listProjectSources","#listIssues","#findIssue","#loadComments","#resolveWorkspaceForIssue","#listWorkflowStates","#rules","#projects","#auth","#listWorkspaces","#connectRoute","#candidateWorkspaceIds"],"sources":["../../../../src/integrations/platform/linear/integration.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { MastraWorker } from '@mastra/core/worker';\nimport type { Context } from 'hono';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport type { Intake, IntakeIssue, IntakeIssueDetail, UpdateIntakeIssueInput } from '../../../capabilities/intake.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { buildLinearAgentTools } from '../../linear/agent-tools.js';\nimport type { LinearEventRules, LinearRuleOverrides } from '../../linear/default-rules.js';\nimport { resolveLinearRules } from '../../linear/default-rules.js';\nimport type { LinearConnectionCheck, LinearIntegration } from '../../linear/integration.js';\nimport { attachLinearIssueReconciler } from '../../linear/issue-reconciler.js';\nimport {\n linearIssueReconciliationEnabled,\n linearIssueReconciliationInterval,\n} from '../../linear/reconciliation-config.js';\nimport { buildLinearRoutes } from '../../linear/routes.js';\nimport { attachLinearRules } from '../../linear/rules.js';\nimport type { LinearConnectionData, LinearConnectionRow, LinearStorageHandle } from '../../linear/storage.js';\nimport {\n logPlatformInfo,\n logPlatformWarn,\n PlatformApiClient,\n PlatformApiError,\n platformApiClientConfigFromEnv,\n} from '../api-client.js';\nimport { PlatformLinearEventWorker } from './event-worker.js';\nimport type { PlatformLinearEventStorage } from './event-worker.js';\n\ntype PageInfo = { hasNextPage: boolean; endCursor: string | null };\ntype LinearUser = {\n id: string;\n name: string;\n displayName: string;\n email: string | null;\n avatarUrl: string | null;\n};\ntype LinearIssue = {\n id: string;\n identifier: string;\n number: number;\n title: string;\n description: string | null;\n url: string;\n priority: number;\n priorityLabel: string;\n labels: Array<{ id: string; name: string }>;\n state: { id: string; name: string; type: string };\n team: { id: string; key: string; name: string };\n assignee: LinearUser | null;\n creator: LinearUser | null;\n createdAt: string;\n updatedAt: string;\n archivedAt: string | null;\n};\ntype LinearComment = {\n id: string;\n body: string;\n url: string;\n issue: { id: string; identifier: string };\n user: LinearUser | null;\n parent: { id: string } | null;\n createdAt: string;\n updatedAt: string;\n};\ntype LinearWorkspace = {\n linearWorkspaceId: string;\n linearWorkspaceName: string;\n urlKey: string | null;\n connected: boolean;\n};\ntype LinearProject = {\n id: string;\n name: string;\n state: string;\n teams: Array<{ id: string; key: string; name: string }>;\n};\ntype ProjectSource = { workspace: LinearWorkspace; project: LinearProject };\ntype LinearWorkflowState = {\n id: string;\n name: string;\n type: string;\n position: number;\n teamId: string;\n};\n\nconst API_PREFIX = '/v1/server/linear';\nconst PAGE_SIZE = 30;\nconst MAX_REFERENCE_PAGES = 20;\nconst MAX_COMMENT_PAGES = 20;\nconst PLATFORM_MANAGED_CONNECTION_TOKEN = 'platform-managed';\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction routeBaseUrl(ctx: IntegrationContext, requestUrl: string): string {\n return (ctx.baseUrl || new URL(requestUrl).origin).replace(/\\/+$/, '');\n}\n\nexport class PlatformLinearIntegration implements FactoryIntegration {\n readonly id = 'linear';\n readonly #client: PlatformApiClient;\n readonly #endpointHost: string;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n /**\n * Per-instance workflow-state cache keyed by `${workspaceId}:${teamId}`. Scoped to the process\n * lifetime; not shared across requests intentionally to keep failure modes simple (stale states\n * clear on process restart). Populated lazily on first `updateIssue` per team.\n */\n readonly #workflowStatesByTeam = new Map<string, LinearWorkflowState[]>();\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: async () => {\n const sources = await this.#listProjectSources();\n return sources.map(({ workspace, project }) => ({\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n name: project.name,\n type: 'project',\n metadata: {\n workspaceId: workspace.linearWorkspaceId,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n state: project.state,\n teams: project.teams,\n },\n }));\n },\n listItems: async ({ sourceIds, cursor }) => {\n const result = await this.#listIssues(sourceIds, cursor);\n return {\n items: result.issues.map(({ issue, source }) => ({\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId: encodeSourceId(source.workspace.linearWorkspaceId, source.project.id),\n title: issue.title,\n status: issue.state.name,\n labels: issue.labels.map(label => label.name),\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n workspaceId: source.workspace.linearWorkspaceId,\n workspaceName: source.workspace.linearWorkspaceName,\n projectId: source.project.id,\n projectName: source.project.name,\n team: issue.team.key,\n priority: issue.priorityLabel,\n },\n })),\n nextCursor: result.nextCursor,\n };\n },\n listIssues: async ({ connection, sourceIds, labels, cursor }) => {\n requireLinearConnection(connection);\n const result = await this.#listIssues(sourceIds, cursor, labels);\n return {\n issues: result.issues.map(({ issue }) => parseIssue(issue)),\n nextCursor: result.nextCursor,\n };\n },\n getIssue: async ({ connection, sourceId, issueId }) => {\n requireLinearConnection(connection);\n const located = await this.#findIssue(sourceId, issueId);\n if (!located) return null;\n const comments = await this.#loadComments(located.workspaceId, issueId, located.issue.comments);\n return parseIssueDetail(located.issue, comments);\n },\n createComment: async ({ connection, sourceId, issueId, body }) => {\n requireLinearConnection(connection);\n const workspaceId = await this.#resolveWorkspaceForIssue(sourceId, issueId);\n if (!workspaceId) return null;\n try {\n const comment = await this.#client.request<LinearComment>(\n 'POST',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments`,\n { body },\n );\n return { id: comment.id, url: comment.url };\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n },\n updateIssue: async (input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> => {\n requireLinearConnection(input.connection);\n const located = await this.#findIssue(input.sourceId, input.issueId);\n if (!located) return null;\n const { workspaceId, issue } = located;\n\n const target = input.state;\n // Idempotency: skip the write entirely if the issue is already at the target state.\n if (target.kind === 'byType' && issue.state.type === target.stateType) {\n return parseIssue(issue);\n }\n if (target.kind === 'byName' && issue.state.name.toLowerCase() === target.name.toLowerCase()) {\n return parseIssue(issue);\n }\n\n let states: LinearWorkflowState[];\n try {\n states = await this.#listWorkflowStates(workspaceId, issue.team.id);\n } catch (error) {\n if (isNotFound(error)) {\n // Platform companion endpoint not deployed yet — degrade to policy skip so PR 1\n // is standalone-mergeable ahead of the platform-side workflow-states route landing.\n logPlatformWarn('Platform Linear: workflow-states endpoint not available; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n });\n return null;\n }\n throw error;\n }\n let matched: LinearWorkflowState | undefined;\n if (target.kind === 'byType') {\n matched = states\n .slice()\n .sort((a, b) => a.position - b.position)\n .find(s => s.type === target.stateType);\n } else {\n const wanted = target.name.toLowerCase();\n matched = states.find(s => s.name.toLowerCase() === wanted);\n }\n if (!matched) {\n logPlatformWarn('Platform Linear: no workflow state matched target; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n target,\n });\n return null;\n }\n\n try {\n await this.#client.request<LinearIssue>(\n 'PATCH',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(input.issueId)}`,\n { stateId: matched.id },\n );\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n\n const refreshed = await this.#findIssue(input.sourceId, input.issueId);\n return refreshed ? parseIssue(refreshed.issue) : null;\n },\n };\n\n readonly #rules: LinearEventRules;\n\n get rules(): LinearEventRules {\n return this.#rules;\n }\n\n constructor({ rules }: { rules?: LinearRuleOverrides } = {}) {\n this.#rules = resolveLinearRules(rules);\n const config = platformApiClientConfigFromEnv();\n this.#client = new PlatformApiClient(config);\n this.#endpointHost = new URL(config.baseUrl).host;\n }\n\n get storage(): LinearStorageHandle {\n const now = new Date();\n return {\n integrationId: this.id,\n connections: {\n get: async (orgId: string) => ({\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n data: {\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n refreshToken: null,\n expiresAtMs: null,\n scope: 'read,comments:create',\n workspaceName: null,\n workspaceUrlKey: null,\n } satisfies LinearConnectionData,\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }),\n },\n } as unknown as LinearStorageHandle;\n }\n\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) throw new Error('PlatformLinearIntegration projects storage has not been initialized.');\n return this.#projects;\n }\n\n initialize({ projects, auth }: { projects: FactoryProjectsStorage; auth?: RouteAuth }): void {\n this.#projects = projects;\n this.#auth = auth;\n logPlatformInfo('Platform Linear integration initialized', { endpointHost: this.#endpointHost });\n }\n\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n async resolveOrgId(resourceId: string): Promise<string | null> {\n try {\n const project = await this.projects.getById({ id: resourceId });\n return project?.orgId ?? null;\n } catch {\n return null;\n }\n }\n\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const workspace = (await this.#listWorkspaces())[0];\n if (!workspace) return null;\n const now = new Date();\n return {\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n scope: 'read,comments:create',\n refreshToken: null,\n expiresAt: null,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n async getFreshAccessToken(_connection: LinearConnectionRow): Promise<string> {\n return PLATFORM_MANAGED_CONNECTION_TOKEN;\n }\n\n /**\n * Background-dispatch context: platform-managed connection token when the\n * org has a connected workspace. Linear work items store the issue UUID\n * directly as `externalId`.\n */\n async #resolveIntakeDispatch({\n orgId,\n externalSource,\n }: {\n orgId: string;\n externalSource: { type: string; externalId: string };\n }): Promise<{ connection: IntegrationConnection; issueId: string } | null> {\n if (externalSource.type !== 'issue') return null;\n const connection = await this.loadConnection(orgId);\n if (!connection) return null;\n return {\n connection: { type: 'oauth', accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN },\n issueId: externalSource.externalId,\n };\n }\n\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const connection = await this.loadConnection(orgId);\n return {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n const pollingEnabled = process.env.MASTRACODE_PLATFORM_LINEAR_POLLING_ENABLED?.trim().toLowerCase() !== 'false';\n const reconcileEnabled = linearIssueReconciliationEnabled();\n if (!pollingEnabled && !reconcileEnabled) return [];\n\n const ingest = attachLinearRules(this, ctx);\n const reconcile = reconcileEnabled ? attachLinearIssueReconciler(this, ctx) : undefined;\n const workItems = ctx.rules?.workItems;\n if (!workItems) return [];\n if (!ingest && !reconcile) return [];\n\n const pollIntervalMs = optionalPositiveIntegerEnv('MASTRACODE_PLATFORM_LINEAR_POLLING_INTERVAL_MS');\n const reconcileIntervalMs = linearIssueReconciliationInterval();\n\n return [\n new PlatformLinearEventWorker({\n client: this.#client,\n linear: { listWorkspaces: () => this.listWorkspaces() },\n storage: ctx.storage.generic as unknown as PlatformLinearEventStorage,\n projects: ctx.storage.projects,\n workItems,\n ...(ingest ? { ingestFactoryIssue: ingest } : {}),\n ...(reconcile ? { reconcileFactoryState: reconcile } : {}),\n pollEventsEnabled: pollingEnabled,\n ...(pollIntervalMs !== undefined ? { intervalMs: pollIntervalMs } : {}),\n ...(reconcileIntervalMs !== undefined ? { reconcileIntervalMs } : {}),\n }),\n ];\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n return [\n this.#connectRoute(ctx),\n ...buildLinearRoutes({\n auth: ctx.auth,\n linear: this as unknown as LinearIntegration,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n projects: ctx.storage.projects,\n ingestFactoryIssues: attachLinearRules(this, ctx),\n }).filter(route => !route.path.startsWith('/auth/linear/')),\n ];\n }\n\n #connectRoute(ctx: IntegrationContext): ApiRoute {\n return registerApiRoute('/auth/linear/connect', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n await ctx.auth.ensureUser(loose(c));\n const tenant = ctx.auth.tenant(loose(c));\n if (!tenant?.orgId) return c.json({ error: 'unauthorized' }, 401);\n\n const returnTo = c.req.query('return_to') || '/';\n const originator = routeBaseUrl(ctx, c.req.url);\n logPlatformInfo('Starting Platform Linear connect flow', {\n orgId: tenant.orgId,\n returnTo,\n originator,\n });\n const query = new URLSearchParams({ return_to: returnTo, originator });\n const location = await this.#client.requestRedirect('GET', `${API_PREFIX}/authorize?${query}`);\n return c.redirect(location);\n },\n });\n }\n\n async agentTools({ requestContext }: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext, linear: this as unknown as LinearIntegration });\n }\n\n diagnostics(): Record<string, unknown> {\n return { mode: 'platform', endpointHost: this.#endpointHost };\n }\n\n async listProjects(): Promise<Array<LinearProject & { workspaceId: string }>> {\n return (await this.#listProjectSources()).map(({ workspace, project }) => ({\n ...project,\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n workspaceId: workspace.linearWorkspaceId,\n }));\n }\n\n async #listProjectSources(): Promise<ProjectSource[]> {\n const workspaces = await this.#listWorkspaces();\n const projectGroups = await Promise.all(\n workspaces.map(async workspace => {\n const projects: LinearProject[] = [];\n let after: string | undefined;\n for (let page = 0; page < MAX_REFERENCE_PAGES; page += 1) {\n const query = new URLSearchParams({ first: '200' });\n if (after) query.set('after', after);\n const result = await this.#client.request<{ projects: LinearProject[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/projects?${query}`,\n );\n projects.push(...result.projects);\n if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor) break;\n after = result.pageInfo.endCursor;\n }\n return projects.map(project => ({ workspace, project }));\n }),\n );\n return projectGroups.flat();\n }\n\n async listWorkspaces(): Promise<LinearWorkspace[]> {\n return this.#listWorkspaces();\n }\n\n async #listWorkspaces(): Promise<LinearWorkspace[]> {\n const result = await this.#client.request<{ workspaces: LinearWorkspace[] }>('GET', `${API_PREFIX}/workspaces`);\n return result.workspaces.filter(workspace => workspace.connected);\n }\n\n async #listIssues(sourceIds: string[], cursor?: string, labels?: string[]) {\n if (sourceIds.length === 0)\n return { issues: [] as Array<{ issue: LinearIssue; source: ProjectSource }>, nextCursor: null };\n const sources = await this.#listProjectSources();\n const sourceMap = new Map(\n sources.map(source => [encodeSourceId(source.workspace.linearWorkspaceId, source.project.id), source]),\n );\n const selected = sourceIds\n .map(sourceId => sourceMap.get(sourceId))\n .filter((source): source is ProjectSource => !!source);\n const cursors = decodeCursor(cursor, sourceIds);\n const normalizedLabels = normalizeLabels(labels);\n const nextState: Record<string, string | null> = {};\n let hasNextPage = false;\n const pages = await Promise.all(\n selected.map(async source => {\n const sourceId = encodeSourceId(source.workspace.linearWorkspaceId, source.project.id);\n if (cursors[sourceId] === null) {\n nextState[sourceId] = null;\n return [] as Array<{ issue: LinearIssue; source: ProjectSource }>;\n }\n const query = new URLSearchParams({\n first: String(PAGE_SIZE),\n projectIds: source.project.id,\n stateType: 'triage,backlog,unstarted,started',\n orderBy: 'updatedAt',\n });\n const after = cursors[sourceId];\n if (after) query.set('after', after);\n const result = await this.#client.request<{ issues: LinearIssue[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(source.workspace.linearWorkspaceId)}/issues?${query}`,\n );\n const next = result.pageInfo.hasNextPage ? result.pageInfo.endCursor : null;\n nextState[sourceId] = next;\n hasNextPage ||= next !== null;\n return result.issues\n .filter(\n issue => normalizedLabels.length === 0 || issue.labels.some(label => normalizedLabels.includes(label.name)),\n )\n .map(issue => ({ issue, source }));\n }),\n );\n return {\n issues: pages.flat(),\n nextCursor: hasNextPage ? encodeCursor(nextState, sourceIds) : null,\n };\n }\n\n async #findIssue(\n sourceId: string | undefined,\n issueId: string,\n ): Promise<{\n workspaceId: string;\n issue: LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } };\n } | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n for (const workspaceId of workspaceIds) {\n try {\n const issue = await this.#client.request<\n LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } }\n >(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}?include=comments`,\n );\n return { workspaceId, issue };\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #resolveWorkspaceForIssue(sourceId: string | undefined, issueId: string): Promise<string | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n if (workspaceIds.length === 1) return workspaceIds[0]!;\n for (const workspaceId of workspaceIds) {\n try {\n await this.#client.request<LinearIssue>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}`,\n );\n return workspaceId;\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #listWorkflowStates(workspaceId: string, teamId: string): Promise<LinearWorkflowState[]> {\n const cacheKey = `${workspaceId}:${teamId}`;\n const cached = this.#workflowStatesByTeam.get(cacheKey);\n if (cached) return cached;\n const result = await this.#client.request<{ workflowStates: LinearWorkflowState[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/workflow-states?teamId=${encodeURIComponent(teamId)}&first=100`,\n );\n this.#workflowStatesByTeam.set(cacheKey, result.workflowStates);\n return result.workflowStates;\n }\n\n async #candidateWorkspaceIds(sourceId: string | undefined): Promise<string[]> {\n if (sourceId) return [decodeSourceId(sourceId).workspaceId];\n return (await this.#listWorkspaces()).map(workspace => workspace.linearWorkspaceId);\n }\n\n async #loadComments(\n workspaceId: string,\n issueId: string,\n embedded: { nodes: LinearComment[]; pageInfo: PageInfo } | undefined,\n ): Promise<LinearComment[]> {\n const comments = [...(embedded?.nodes ?? [])];\n let pageInfo = embedded?.pageInfo;\n let page = 0;\n while (pageInfo?.hasNextPage && pageInfo.endCursor && page < MAX_COMMENT_PAGES) {\n const result = await this.#client.request<{ comments: LinearComment[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments?first=200&after=${encodeURIComponent(pageInfo.endCursor)}`,\n );\n comments.push(...result.comments);\n pageInfo = result.pageInfo;\n page += 1;\n }\n return comments;\n }\n}\n\nfunction parseIssue(issue: LinearIssue): IntakeIssue {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.creator?.displayName ?? issue.creator?.name ?? null,\n state: issue.state.name,\n stateType: issue.state.type,\n priority: issue.priorityLabel,\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n source: issue.team.key,\n labels: issue.labels.map(label => label.name),\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n\nfunction parseIssueDetail(issue: LinearIssue, comments: LinearComment[]): IntakeIssueDetail {\n return {\n ...parseIssue(issue),\n commentCount: comments.length,\n description: issue.description?.trim() ? issue.description : null,\n comments: comments.map(comment => ({\n author: comment.user?.displayName ?? comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n}\n\nfunction encodeSourceId(workspaceId: string, projectId: string): string {\n return `linear-project:${Buffer.from(JSON.stringify({ workspaceId, projectId })).toString('base64url')}`;\n}\n\nfunction decodeSourceId(sourceId: string): { workspaceId: string; projectId: string } {\n if (!sourceId.startsWith('linear-project:')) throw new Error('Linear project source id is invalid.');\n try {\n const parsed = JSON.parse(Buffer.from(sourceId.slice('linear-project:'.length), 'base64url').toString('utf8')) as {\n workspaceId?: unknown;\n projectId?: unknown;\n };\n if (typeof parsed.workspaceId !== 'string' || !parsed.workspaceId) throw new Error();\n if (typeof parsed.projectId !== 'string' || !parsed.projectId) throw new Error();\n return { workspaceId: parsed.workspaceId, projectId: parsed.projectId };\n } catch {\n throw new Error('Linear project source id is invalid.');\n }\n}\n\nfunction normalizeLabels(labels: string[] | undefined): string[] {\n return [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n}\n\nfunction decodeCursor(cursor: string | undefined, sourceIds: string[]): Record<string, string | null | undefined> {\n if (!cursor) return {};\n if (sourceIds.length === 1) return { [sourceIds[0]!]: cursor };\n try {\n const parsed = JSON.parse(cursor) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error();\n return parsed as Record<string, string | null>;\n } catch {\n throw new Error('Linear cursor is invalid.');\n }\n}\n\nfunction encodeCursor(state: Record<string, string | null>, sourceIds: string[]): string {\n if (sourceIds.length === 1) return state[sourceIds[0]!]!;\n return JSON.stringify(state);\n}\n\nfunction requireLinearConnection(connection: IntegrationConnection): void {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n}\n\nfunction isNotFound(error: unknown): boolean {\n return error instanceof PlatformApiError && error.status === 404;\n}\n\nfunction optionalPositiveIntegerEnv(name: string): number | undefined {\n const value = process.env[name]?.trim();\n if (!value) return undefined;\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed <= 0) {\n throw new Error(`${name} must be a positive integer.`);\n }\n return parsed;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { actorFromChannelAuthor } from "../../storage/domains/comments/actor.js";
|
|
1
2
|
import { readRequestContextOrgId, seedSessionOrg } from "../../session/org-seed.js";
|
|
2
3
|
import { hydrateFactorySession, resolveFactoryDefaultModelId, resolveFactoryProjectForSession, resolveFactorySourceRepository } from "../../session/factory-session.js";
|
|
3
|
-
import { actorFromChannelAuthor } from "../../storage/domains/comments/actor.js";
|
|
4
4
|
import { slackCommentSource } from "./feed-publisher.js";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
6
|
import { createSlackAdapter } from "@mastra/slack";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/rules/defaults.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/rules/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,qBAAqB,EAGtB,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,4BAA4B,uBAAuB,CAAC;AAmDjE,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,qBAAqB,EAC3B,SAAS,GAAE,qBAA0B,GACpC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAI/B;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,qBAAqB,CAAA;CAAE,GAAG,YAAY,CAW/G;AAED,wBAAgB,mBAAmB,IAAI,YAAY,CAElD"}
|
package/dist/rules/defaults.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { reviewBoard } from "../boards/review.js";
|
|
2
|
-
import { workBoard } from "../boards/work.js";
|
|
3
1
|
import { FactoryRuleValidationError, assertFactoryRules } from "./validation.js";
|
|
4
2
|
//#region src/rules/defaults.ts
|
|
5
3
|
const DEFAULT_FACTORY_RULE_VERSION = "factory-default-v1";
|
|
@@ -18,82 +16,7 @@ function advanceApprovedPlan(context) {
|
|
|
18
16
|
stage: "execute"
|
|
19
17
|
};
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
if (context.item) return;
|
|
23
|
-
return {
|
|
24
|
-
type: "upsertLinkedWorkItem",
|
|
25
|
-
idempotencyKey: `${context.ingress.id}:issue-triage`,
|
|
26
|
-
board: "work",
|
|
27
|
-
source: "linear-issue",
|
|
28
|
-
sourceKey: `linear:${context.issue.identifier}`,
|
|
29
|
-
title: `${context.issue.identifier}: ${context.issue.title}`,
|
|
30
|
-
url: context.issue.url,
|
|
31
|
-
stage: "triage",
|
|
32
|
-
metadata: {
|
|
33
|
-
linearIssueId: context.issue.id,
|
|
34
|
-
identifier: context.issue.identifier,
|
|
35
|
-
sourceCreatedAt: context.issue.createdAt,
|
|
36
|
-
linearState: context.issue.state,
|
|
37
|
-
linearStateType: context.issue.stateType,
|
|
38
|
-
linearPriority: context.issue.priorityLabel,
|
|
39
|
-
linearAssignee: context.issue.assignee,
|
|
40
|
-
linearCreator: context.issue.creator,
|
|
41
|
-
linearTeam: context.issue.team,
|
|
42
|
-
labels: [...context.issue.labels],
|
|
43
|
-
...context.issue.assignee ? { assignee: context.issue.assignee } : {},
|
|
44
|
-
...context.issue.creator ? {
|
|
45
|
-
creator: context.issue.creator,
|
|
46
|
-
author: context.issue.creator
|
|
47
|
-
} : {}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function linearIssueClosed(context) {
|
|
52
|
-
if (!context.item || context.item.source !== "linear-issue") return;
|
|
53
|
-
if (context.board !== "work") return;
|
|
54
|
-
if (context.item.stages.some((stage) => stage === "done" || stage === "canceled")) return;
|
|
55
|
-
const stateType = context.issue.stateType;
|
|
56
|
-
if (stateType !== "completed" && stateType !== "canceled") return;
|
|
57
|
-
const canceled = stateType === "canceled";
|
|
58
|
-
return {
|
|
59
|
-
type: "transition",
|
|
60
|
-
idempotencyKey: `${context.ingress.id}:issue-closed`,
|
|
61
|
-
board: "work",
|
|
62
|
-
stage: canceled ? "canceled" : "done",
|
|
63
|
-
message: { text: `Linear issue ${context.issue.identifier} was ${canceled ? "canceled" : "completed"}; this Work card was moved to ${canceled ? "Canceled" : "Done"}.` }
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
const BUILT_IN_DEFAULTS = {
|
|
67
|
-
work: workBoard.rules,
|
|
68
|
-
review: reviewBoard.rules,
|
|
69
|
-
tools: { submit_plan: { onResult: advanceApprovedPlan } },
|
|
70
|
-
linear: {
|
|
71
|
-
issueObserved: { onEvent: linearIssueObserved },
|
|
72
|
-
issueClosed: { onEvent: linearIssueClosed }
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
function mergeBoardRules(base, overrides) {
|
|
76
|
-
const result = {};
|
|
77
|
-
const stages = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]);
|
|
78
|
-
for (const stage of stages) {
|
|
79
|
-
const baseSources = base?.[stage];
|
|
80
|
-
const overrideSources = overrides?.[stage];
|
|
81
|
-
const sources = /* @__PURE__ */ new Set([...Object.keys(baseSources ?? {}), ...Object.keys(overrideSources ?? {})]);
|
|
82
|
-
const mergedSources = {};
|
|
83
|
-
for (const source of sources) {
|
|
84
|
-
const baseLeaf = baseSources?.[source];
|
|
85
|
-
const overrideLeaf = overrideSources?.[source];
|
|
86
|
-
mergedSources[source] = {
|
|
87
|
-
...baseLeaf && "onEnter" in baseLeaf ? { onEnter: baseLeaf.onEnter } : {},
|
|
88
|
-
...baseLeaf && "onExit" in baseLeaf ? { onExit: baseLeaf.onExit } : {},
|
|
89
|
-
...overrideLeaf && "onEnter" in overrideLeaf ? { onEnter: overrideLeaf.onEnter } : {},
|
|
90
|
-
...overrideLeaf && "onExit" in overrideLeaf ? { onExit: overrideLeaf.onExit } : {}
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
result[stage] = mergedSources;
|
|
94
|
-
}
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
19
|
+
const BUILT_IN_DEFAULTS = { tools: { submit_plan: { onResult: advanceApprovedPlan } } };
|
|
97
20
|
function mergeToolRules(base, overrides) {
|
|
98
21
|
const result = {};
|
|
99
22
|
for (const name of /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {
|
|
@@ -106,26 +29,8 @@ function mergeToolRules(base, overrides) {
|
|
|
106
29
|
}
|
|
107
30
|
return result;
|
|
108
31
|
}
|
|
109
|
-
function mergeLinearRules(base, overrides) {
|
|
110
|
-
const result = {};
|
|
111
|
-
const events = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]);
|
|
112
|
-
for (const event of events) {
|
|
113
|
-
const baseLeaf = base?.[event];
|
|
114
|
-
const overrideLeaf = overrides?.[event];
|
|
115
|
-
result[event] = {
|
|
116
|
-
...baseLeaf && "onEvent" in baseLeaf ? { onEvent: baseLeaf.onEvent } : {},
|
|
117
|
-
...overrideLeaf && "onEvent" in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
32
|
function mergeFactoryRuleOverrides(base, overrides = {}) {
|
|
123
|
-
return {
|
|
124
|
-
work: mergeBoardRules(base.work, overrides.work),
|
|
125
|
-
review: mergeBoardRules(base.review, overrides.review),
|
|
126
|
-
tools: mergeToolRules(base.tools, overrides.tools),
|
|
127
|
-
linear: mergeLinearRules(base.linear, overrides.linear)
|
|
128
|
-
};
|
|
33
|
+
return { tools: mergeToolRules(base.tools, overrides.tools) };
|
|
129
34
|
}
|
|
130
35
|
function defaultFactoryRules(input) {
|
|
131
36
|
if (typeof input?.version !== "string" || input.version.trim().length === 0) throw new FactoryRuleValidationError("Factory rule version is required.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","names":[],"sources":["../../src/rules/defaults.ts"],"sourcesContent":["import { reviewBoard } from '../boards/review.js';\nimport { workBoard } from '../boards/work.js';\nimport type {\n FactoryBoardRuleLeaf,\n FactoryBoardRules,\n FactoryLinearEventName,\n FactoryLinearRuleContext,\n FactoryLinearRuleLeaf,\n FactoryRules,\n FactoryRulesOverrides,\n FactoryRuleSource,\n FactoryRuleStage,\n FactoryToolResultRuleContext,\n FactoryToolRuleLeaf,\n} from './types.js';\nimport { assertFactoryRules, FactoryRuleValidationError } from './validation.js';\n\nexport const DEFAULT_FACTORY_RULE_VERSION = 'factory-default-v1';\n\nfunction resultContent(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const content = (value as { content?: unknown }).content;\n return typeof content === 'string' ? content : undefined;\n}\n\n// Interactive-session path only: factory-plan never calls submit_plan — it\n// advances planning → execute via factory_transition_work_item directly.\nfunction advanceApprovedPlan(context: FactoryToolResultRuleContext) {\n if (\n context.result.status !== 'success' ||\n context.board !== 'work' ||\n context.item.stages.length !== 1 ||\n context.item.stages[0] !== 'planning' ||\n context.actor.type !== 'agent' ||\n context.actor.role !== 'plan' ||\n !resultContent(context.result.value)?.startsWith('Plan approved.')\n ) {\n return;\n }\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:approved-plan`,\n board: 'work',\n stage: 'execute',\n } as const;\n}\n\nfunction linearIssueObserved(context: FactoryLinearRuleContext) {\n if (context.item) return;\n return {\n type: 'upsertLinkedWorkItem',\n idempotencyKey: `${context.ingress.id}:issue-triage`,\n board: 'work',\n source: 'linear-issue',\n sourceKey: `linear:${context.issue.identifier}`,\n title: `${context.issue.identifier}: ${context.issue.title}`,\n url: context.issue.url,\n stage: 'triage',\n metadata: {\n linearIssueId: context.issue.id,\n identifier: context.issue.identifier,\n sourceCreatedAt: context.issue.createdAt,\n linearState: context.issue.state,\n linearStateType: context.issue.stateType,\n linearPriority: context.issue.priorityLabel,\n linearAssignee: context.issue.assignee,\n linearCreator: context.issue.creator,\n linearTeam: context.issue.team,\n labels: [...context.issue.labels] as string[],\n ...(context.issue.assignee ? { assignee: context.issue.assignee } : {}),\n ...(context.issue.creator ? { creator: context.issue.creator, author: context.issue.creator } : {}),\n },\n } as const;\n}\n\nfunction linearIssueClosed(context: FactoryLinearRuleContext) {\n if (!context.item || context.item.source !== 'linear-issue') return;\n if (context.board !== 'work') return;\n // Already off the board: nothing to reconcile.\n if (context.item.stages.some(stage => stage === 'done' || stage === 'canceled')) return;\n // Only terminal state types trigger close.\n const stateType = context.issue.stateType;\n if (stateType !== 'completed' && stateType !== 'canceled') return;\n const canceled = stateType === 'canceled';\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:issue-closed`,\n board: 'work',\n stage: canceled ? 'canceled' : 'done',\n message: {\n text: `Linear issue ${context.issue.identifier} was ${canceled ? 'canceled' : 'completed'}; this Work card was moved to ${canceled ? 'Canceled' : 'Done'}.`,\n },\n } as const;\n}\n\nconst BUILT_IN_DEFAULTS: FactoryRulesOverrides = {\n work: workBoard.rules,\n review: reviewBoard.rules,\n tools: { submit_plan: { onResult: advanceApprovedPlan } },\n linear: { issueObserved: { onEvent: linearIssueObserved }, issueClosed: { onEvent: linearIssueClosed } },\n};\n\nfunction mergeBoardRules(\n base: FactoryBoardRules | undefined,\n overrides: FactoryBoardRules | undefined,\n): FactoryBoardRules {\n const result: FactoryBoardRules = {};\n const stages = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryRuleStage>;\n for (const stage of stages) {\n const baseSources = base?.[stage];\n const overrideSources = overrides?.[stage];\n const sources = new Set([\n ...Object.keys(baseSources ?? {}),\n ...Object.keys(overrideSources ?? {}),\n ]) as Set<FactoryRuleSource>;\n const mergedSources: Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>> = {};\n for (const source of sources) {\n const baseLeaf = baseSources?.[source];\n const overrideLeaf = overrideSources?.[source];\n mergedSources[source] = {\n ...(baseLeaf && 'onEnter' in baseLeaf ? { onEnter: baseLeaf.onEnter } : {}),\n ...(baseLeaf && 'onExit' in baseLeaf ? { onExit: baseLeaf.onExit } : {}),\n ...(overrideLeaf && 'onEnter' in overrideLeaf ? { onEnter: overrideLeaf.onEnter } : {}),\n ...(overrideLeaf && 'onExit' in overrideLeaf ? { onExit: overrideLeaf.onExit } : {}),\n };\n }\n result[stage] = mergedSources;\n }\n return result;\n}\n\nfunction mergeToolRules(\n base: Record<string, FactoryToolRuleLeaf> | undefined,\n overrides: Record<string, FactoryToolRuleLeaf> | undefined,\n): Record<string, FactoryToolRuleLeaf> {\n const result: Record<string, FactoryToolRuleLeaf> = {};\n for (const name of new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {\n const baseLeaf = base?.[name];\n const overrideLeaf = overrides?.[name];\n result[name] = {\n ...(baseLeaf && 'onResult' in baseLeaf ? { onResult: baseLeaf.onResult } : {}),\n ...(overrideLeaf && 'onResult' in overrideLeaf ? { onResult: overrideLeaf.onResult } : {}),\n };\n }\n return result;\n}\n\nfunction mergeLinearRules(\n base: FactoryRulesOverrides['linear'],\n overrides: FactoryRulesOverrides['linear'],\n): NonNullable<FactoryRulesOverrides['linear']> {\n const result: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>> = {};\n const events = new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})]) as Set<FactoryLinearEventName>;\n for (const event of events) {\n const baseLeaf = base?.[event];\n const overrideLeaf = overrides?.[event];\n result[event] = {\n ...(baseLeaf && 'onEvent' in baseLeaf ? { onEvent: baseLeaf.onEvent } : {}),\n ...(overrideLeaf && 'onEvent' in overrideLeaf ? { onEvent: overrideLeaf.onEvent } : {}),\n };\n }\n return result;\n}\n\nexport function mergeFactoryRuleOverrides(\n base: FactoryRulesOverrides,\n overrides: FactoryRulesOverrides = {},\n): Omit<FactoryRules, 'version'> {\n return {\n work: mergeBoardRules(base.work, overrides.work),\n review: mergeBoardRules(base.review, overrides.review),\n tools: mergeToolRules(base.tools, overrides.tools),\n linear: mergeLinearRules(base.linear, overrides.linear),\n };\n}\n\nexport function defaultFactoryRules(input: { version: string; overrides?: FactoryRulesOverrides }): FactoryRules {\n if (typeof input?.version !== 'string' || input.version.trim().length === 0) {\n throw new FactoryRuleValidationError('Factory rule version is required.');\n }\n\n const rules: FactoryRules = {\n version: input.version.trim(),\n ...mergeFactoryRuleOverrides(BUILT_IN_DEFAULTS, input.overrides),\n };\n assertFactoryRules(rules);\n return rules;\n}\n\nexport function builtInFactoryRules(): FactoryRules {\n return defaultFactoryRules({ version: DEFAULT_FACTORY_RULE_VERSION });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"defaults.js","names":[],"sources":["../../src/rules/defaults.ts"],"sourcesContent":["import type {\n FactoryRules,\n FactoryRulesOverrides,\n FactoryToolResultRuleContext,\n FactoryToolRuleLeaf,\n} from './types.js';\nimport { assertFactoryRules, FactoryRuleValidationError } from './validation.js';\n\nexport const DEFAULT_FACTORY_RULE_VERSION = 'factory-default-v1';\n\nfunction resultContent(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const content = (value as { content?: unknown }).content;\n return typeof content === 'string' ? content : undefined;\n}\n\n// Interactive-session path only: factory-plan never calls submit_plan — it\n// advances planning → execute via factory_transition_work_item directly.\nfunction advanceApprovedPlan(context: FactoryToolResultRuleContext) {\n if (\n context.result.status !== 'success' ||\n context.board !== 'work' ||\n context.item.stages.length !== 1 ||\n context.item.stages[0] !== 'planning' ||\n context.actor.type !== 'agent' ||\n context.actor.role !== 'plan' ||\n !resultContent(context.result.value)?.startsWith('Plan approved.')\n ) {\n return;\n }\n return {\n type: 'transition',\n idempotencyKey: `${context.ingress.id}:approved-plan`,\n board: 'work',\n stage: 'execute',\n } as const;\n}\n\nconst BUILT_IN_DEFAULTS: FactoryRulesOverrides = {\n tools: { submit_plan: { onResult: advanceApprovedPlan } },\n};\n\nfunction mergeToolRules(\n base: Record<string, FactoryToolRuleLeaf> | undefined,\n overrides: Record<string, FactoryToolRuleLeaf> | undefined,\n): Record<string, FactoryToolRuleLeaf> {\n const result: Record<string, FactoryToolRuleLeaf> = {};\n for (const name of new Set([...Object.keys(base ?? {}), ...Object.keys(overrides ?? {})])) {\n const baseLeaf = base?.[name];\n const overrideLeaf = overrides?.[name];\n result[name] = {\n ...(baseLeaf && 'onResult' in baseLeaf ? { onResult: baseLeaf.onResult } : {}),\n ...(overrideLeaf && 'onResult' in overrideLeaf ? { onResult: overrideLeaf.onResult } : {}),\n };\n }\n return result;\n}\n\nexport function mergeFactoryRuleOverrides(\n base: FactoryRulesOverrides,\n overrides: FactoryRulesOverrides = {},\n): Omit<FactoryRules, 'version'> {\n return {\n tools: mergeToolRules(base.tools, overrides.tools),\n };\n}\n\nexport function defaultFactoryRules(input: { version: string; overrides?: FactoryRulesOverrides }): FactoryRules {\n if (typeof input?.version !== 'string' || input.version.trim().length === 0) {\n throw new FactoryRuleValidationError('Factory rule version is required.');\n }\n\n const rules: FactoryRules = {\n version: input.version.trim(),\n ...mergeFactoryRuleOverrides(BUILT_IN_DEFAULTS, input.overrides),\n };\n assertFactoryRules(rules);\n return rules;\n}\n\nexport function builtInFactoryRules(): FactoryRules {\n return defaultFactoryRules({ version: DEFAULT_FACTORY_RULE_VERSION });\n}\n"],"mappings":";;AAQA,MAAa,+BAA+B;AAE5C,SAAS,cAAc,OAAoC;CACzD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;CACjE,MAAM,UAAW,MAAgC;CACjD,OAAO,OAAO,YAAY,WAAW,UAAU,KAAA;AACjD;AAIA,SAAS,oBAAoB,SAAuC;CAClE,IACE,QAAQ,OAAO,WAAW,aAC1B,QAAQ,UAAU,UAClB,QAAQ,KAAK,OAAO,WAAW,KAC/B,QAAQ,KAAK,OAAO,OAAO,cAC3B,QAAQ,MAAM,SAAS,WACvB,QAAQ,MAAM,SAAS,UACvB,CAAC,cAAc,QAAQ,OAAO,KAAK,CAAC,EAAE,WAAW,gBAAgB,GAEjE;CAEF,OAAO;EACL,MAAM;EACN,gBAAgB,GAAG,QAAQ,QAAQ,GAAG;EACtC,OAAO;EACP,OAAO;CACT;AACF;AAEA,MAAM,oBAA2C,EAC/C,OAAO,EAAE,aAAa,EAAE,UAAU,oBAAoB,EAAE,EAC1D;AAEA,SAAS,eACP,MACA,WACqC;CACrC,MAAM,SAA8C,CAAC;CACrD,KAAK,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG,GAAG,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG;EACzF,MAAM,WAAW,OAAO;EACxB,MAAM,eAAe,YAAY;EACjC,OAAO,QAAQ;GACb,GAAI,YAAY,cAAc,WAAW,EAAE,UAAU,SAAS,SAAS,IAAI,CAAC;GAC5E,GAAI,gBAAgB,cAAc,eAAe,EAAE,UAAU,aAAa,SAAS,IAAI,CAAC;EAC1F;CACF;CACA,OAAO;AACT;AAEA,SAAgB,0BACd,MACA,YAAmC,CAAC,GACL;CAC/B,OAAO,EACL,OAAO,eAAe,KAAK,OAAO,UAAU,KAAK,EACnD;AACF;AAEA,SAAgB,oBAAoB,OAA6E;CAC/G,IAAI,OAAO,OAAO,YAAY,YAAY,MAAM,QAAQ,KAAK,CAAC,CAAC,WAAW,GACxE,MAAM,IAAI,2BAA2B,mCAAmC;CAG1E,MAAM,QAAsB;EAC1B,SAAS,MAAM,QAAQ,KAAK;EAC5B,GAAG,0BAA0B,mBAAmB,MAAM,SAAS;CACjE;CACA,mBAAmB,KAAK;CACxB,OAAO;AACT;AAEA,SAAgB,sBAAoC;CAClD,OAAO,oBAAoB,EAAE,SAAS,6BAA6B,CAAC;AACtE"}
|
package/dist/rules/dispatcher.js
CHANGED
|
@@ -4,8 +4,8 @@ import { FACTORY_RULE_MATERIALIZATION_KEY } from "../storage/domains/work-items/
|
|
|
4
4
|
import { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata } from "./dispatch-errors.js";
|
|
5
5
|
import { resolvePromptInvocation, resolveSkillInvocation } from "../skills/service.js";
|
|
6
6
|
import { withWorkItemFeed } from "../storage/domains/comments/feed-context.js";
|
|
7
|
-
import { randomUUID } from "crypto";
|
|
8
7
|
import { RequestContext } from "@mastra/core/request-context";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
9
|
//#region src/rules/dispatcher.ts
|
|
10
10
|
const LEASE_MS = 3e4;
|
|
11
11
|
const POLL_MS = 1e3;
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { builtInFactoryRules, defaultFactoryRules, DEFAULT_FACTORY_RULE_VERSION } from './defaults.js';
|
|
2
|
-
export {
|
|
2
|
+
export { resolveFactoryStageRules, resolveFactoryToolRule } from './resolve.js';
|
|
3
3
|
export type { ResolvedFactoryStageRule, ResolvedFactoryToolRule } from './resolve.js';
|
|
4
4
|
export { FACTORY_GITHUB_EVENTS, FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES, factoryRuleSourceForWorkItem, isFactoryRuleStage, } from './types.js';
|
|
5
|
-
export type { FactoryBoardRuleLeaf,
|
|
5
|
+
export type { FactoryBoardRuleLeaf, FactoryBoundRuleContext, FactoryCommitDecision, FactoryGithubEventName, FactoryGithubRuleContext, FactoryLinearEventName, FactoryLinearRuleContext, FactoryInvokeSkillDecision, FactoryNotifyDecision, FactoryRuleActor, FactoryRuleBoard, FactoryRuleCausalEntry, FactoryRuleContextBase, FactoryRuleDecision, FactoryRuleHandler, FactoryRuleIngressIdentity, FactoryRuleItemContext, FactoryRuleJsonValue, FactoryRuleRejectionCode, FactoryRuleRejectDecision, FactoryRuleSource, FactoryRules, FactoryRulesOverrides, FactoryRuleStage, FactorySendMessageDecision, FactoryStageRuleContext, FactoryToolResultRuleContext, FactoryToolRuleLeaf, FactoryTransitionDecision, FactoryTransitionResult, FactoryTransitionResultAccepted, FactoryTransitionResultRejected, FactoryUpsertLinkedWorkItemDecision, } from './types.js';
|
|
6
6
|
export { assertFactoryRules, FactoryRuleValidationError, MAX_FACTORY_RULE_CAUSAL_DEPTH, validateFactoryRuleDecision, validateFactoryRuleDecisions, } from './validation.js';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,wBAAwB,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAChF,YAAY,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,4BAA4B,EAC5B,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,oBAAoB,EACpB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,EACjB,YAAY,EACZ,qBAAqB,EACrB,gBAAgB,EAChB,0BAA0B,EAC1B,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,+BAA+B,EAC/B,+BAA+B,EAC/B,mCAAmC,GACpC,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,6BAA6B,EAC7B,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,iBAAiB,CAAC"}
|
package/dist/rules/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FACTORY_GITHUB_EVENTS, FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES, factoryRuleSourceForWorkItem, isFactoryRuleStage } from "./types.js";
|
|
2
2
|
import { FactoryRuleValidationError, MAX_FACTORY_RULE_CAUSAL_DEPTH, assertFactoryRules, validateFactoryRuleDecision, validateFactoryRuleDecisions } from "./validation.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveFactoryStageRules, resolveFactoryToolRule } from "./resolve.js";
|
|
4
4
|
import { DEFAULT_FACTORY_RULE_VERSION, builtInFactoryRules, defaultFactoryRules } from "./defaults.js";
|
|
5
|
-
export { DEFAULT_FACTORY_RULE_VERSION, FACTORY_GITHUB_EVENTS, FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES, FactoryRuleValidationError, MAX_FACTORY_RULE_CAUSAL_DEPTH, assertFactoryRules, builtInFactoryRules, defaultFactoryRules, factoryRuleSourceForWorkItem, isFactoryRuleStage,
|
|
5
|
+
export { DEFAULT_FACTORY_RULE_VERSION, FACTORY_GITHUB_EVENTS, FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES, FactoryRuleValidationError, MAX_FACTORY_RULE_CAUSAL_DEPTH, assertFactoryRules, builtInFactoryRules, defaultFactoryRules, factoryRuleSourceForWorkItem, isFactoryRuleStage, resolveFactoryStageRules, resolveFactoryToolRule, validateFactoryRuleDecision, validateFactoryRuleDecisions };
|
package/dist/rules/resolve.d.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import type { BoardRegistry } from '../boards/index.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FactoryRuleHandler, FactoryRuleSource, FactoryRuleStage, FactoryRules, FactoryStageRuleContext, FactoryToolResultRuleContext, FactoryToolRuleLeaf } from './types.js';
|
|
3
3
|
export interface ResolvedFactoryStageRule {
|
|
4
4
|
phase: 'exit' | 'enter';
|
|
5
5
|
handler: FactoryRuleHandler<FactoryStageRuleContext>;
|
|
6
6
|
}
|
|
7
|
-
export declare function resolveFactoryStageRules(
|
|
7
|
+
export declare function resolveFactoryStageRules(boardRegistry: BoardRegistry, input: {
|
|
8
8
|
board: string;
|
|
9
9
|
source: FactoryRuleSource;
|
|
10
10
|
fromStage: FactoryRuleStage;
|
|
11
11
|
toStage: FactoryRuleStage;
|
|
12
12
|
initialEntry?: boolean;
|
|
13
13
|
reenter?: boolean;
|
|
14
|
-
}
|
|
14
|
+
}): ResolvedFactoryStageRule[];
|
|
15
15
|
export declare function resolveFactoryToolRule(rules: FactoryRules, toolName: string): FactoryToolRuleLeaf['onResult'];
|
|
16
|
-
export declare function resolveFactoryLinearRule(rules: FactoryRules, event: FactoryLinearEventName): FactoryLinearRuleLeaf['onEvent'];
|
|
17
16
|
export type ResolvedFactoryToolRule = FactoryRuleHandler<FactoryToolResultRuleContext>;
|
|
18
17
|
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/rules/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/rules/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,OAAO,EAAE,kBAAkB,CAAC,uBAAuB,CAAC,CAAC;CACtD;AAED,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE;IACL,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GACA,wBAAwB,EAAE,CAc5B;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAE7G;AAED,MAAM,MAAM,uBAAuB,GAAG,kBAAkB,CAAC,4BAA4B,CAAC,CAAC"}
|
package/dist/rules/resolve.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/rules/resolve.ts
|
|
2
|
-
function resolveFactoryStageRules(
|
|
2
|
+
function resolveFactoryStageRules(boardRegistry, input) {
|
|
3
3
|
if (input.fromStage === input.toStage && !input.initialEntry && !input.reenter) return [];
|
|
4
|
-
const boardRules =
|
|
4
|
+
const boardRules = boardRegistry.get(input.board)?.rules;
|
|
5
5
|
if (!boardRules) return [];
|
|
6
6
|
const resolved = [];
|
|
7
7
|
const sameStageReentry = input.fromStage === input.toStage && input.reenter === true;
|
|
@@ -20,10 +20,7 @@ function resolveFactoryStageRules(rules, input, boardRegistry) {
|
|
|
20
20
|
function resolveFactoryToolRule(rules, toolName) {
|
|
21
21
|
return rules.tools[toolName]?.onResult;
|
|
22
22
|
}
|
|
23
|
-
function resolveFactoryLinearRule(rules, event) {
|
|
24
|
-
return rules.linear[event]?.onEvent;
|
|
25
|
-
}
|
|
26
23
|
//#endregion
|
|
27
|
-
export {
|
|
24
|
+
export { resolveFactoryStageRules, resolveFactoryToolRule };
|
|
28
25
|
|
|
29
26
|
//# sourceMappingURL=resolve.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.js","names":[],"sources":["../../src/rules/resolve.ts"],"sourcesContent":["import type { BoardRegistry } from '../boards/index.js';\nimport type {\n
|
|
1
|
+
{"version":3,"file":"resolve.js","names":[],"sources":["../../src/rules/resolve.ts"],"sourcesContent":["import type { BoardRegistry } from '../boards/index.js';\nimport type {\n FactoryRuleHandler,\n FactoryRuleSource,\n FactoryRuleStage,\n FactoryRules,\n FactoryStageRuleContext,\n FactoryToolResultRuleContext,\n FactoryToolRuleLeaf,\n} from './types.js';\n\nexport interface ResolvedFactoryStageRule {\n phase: 'exit' | 'enter';\n handler: FactoryRuleHandler<FactoryStageRuleContext>;\n}\n\nexport function resolveFactoryStageRules(\n boardRegistry: BoardRegistry,\n input: {\n board: string;\n source: FactoryRuleSource;\n fromStage: FactoryRuleStage;\n toStage: FactoryRuleStage;\n initialEntry?: boolean;\n reenter?: boolean;\n },\n): ResolvedFactoryStageRule[] {\n if (input.fromStage === input.toStage && !input.initialEntry && !input.reenter) return [];\n const boardRules = boardRegistry.get(input.board)?.rules;\n if (!boardRules) return [];\n const resolved: ResolvedFactoryStageRule[] = [];\n // Same-stage reentry re-runs the stage's entry work; the item never left the\n // stage, so its exit rules must not fire.\n const sameStageReentry = input.fromStage === input.toStage && input.reenter === true;\n const onExit =\n input.initialEntry || sameStageReentry ? undefined : boardRules[input.fromStage]?.[input.source]?.onExit;\n if (onExit) resolved.push({ phase: 'exit', handler: onExit });\n const onEnter = boardRules[input.toStage]?.[input.source]?.onEnter;\n if (onEnter) resolved.push({ phase: 'enter', handler: onEnter });\n return resolved;\n}\n\nexport function resolveFactoryToolRule(rules: FactoryRules, toolName: string): FactoryToolRuleLeaf['onResult'] {\n return rules.tools[toolName]?.onResult;\n}\n\nexport type ResolvedFactoryToolRule = FactoryRuleHandler<FactoryToolResultRuleContext>;\n"],"mappings":";AAgBA,SAAgB,yBACd,eACA,OAQ4B;CAC5B,IAAI,MAAM,cAAc,MAAM,WAAW,CAAC,MAAM,gBAAgB,CAAC,MAAM,SAAS,OAAO,CAAC;CACxF,MAAM,aAAa,cAAc,IAAI,MAAM,KAAK,CAAC,EAAE;CACnD,IAAI,CAAC,YAAY,OAAO,CAAC;CACzB,MAAM,WAAuC,CAAC;CAG9C,MAAM,mBAAmB,MAAM,cAAc,MAAM,WAAW,MAAM,YAAY;CAChF,MAAM,SACJ,MAAM,gBAAgB,mBAAmB,KAAA,IAAY,WAAW,MAAM,UAAU,GAAG,MAAM,OAAO,EAAE;CACpG,IAAI,QAAQ,SAAS,KAAK;EAAE,OAAO;EAAQ,SAAS;CAAO,CAAC;CAC5D,MAAM,UAAU,WAAW,MAAM,QAAQ,GAAG,MAAM,OAAO,EAAE;CAC3D,IAAI,SAAS,SAAS,KAAK;EAAE,OAAO;EAAS,SAAS;CAAQ,CAAC;CAC/D,OAAO;AACT;AAEA,SAAgB,uBAAuB,OAAqB,UAAmD;CAC7G,OAAO,MAAM,MAAM,SAAS,EAAE;AAChC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"transition-service.d.ts","sourceRoot":"","sources":["../../src/rules/transition-service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F,OAAO,KAAK,EAEV,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EAEtB,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EAEZ,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAuBpB,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjH,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAChD,iHAAiH;IACjH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qHAAqH;IACrH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QACvB,KAAK,EAAE,MAAM,CAAC;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,gBAAgB,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3B;;2CAEuC;IACvC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,WAAW,CAAC;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5B;AAuBD,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAIpF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAKrF;AAqED,qBAAa,wBAAwB;;gBASvB,OAAO,EAAE,+BAA+B;IAUpD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAEK,UAAU,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,uBAAuB,CAAC;CAmTtF"}
|
|
@@ -2,6 +2,7 @@ import { externallyAuthoredWorkItem, factoryRuleSourceForWorkItem, isFactoryRule
|
|
|
2
2
|
import { createBoardRegistry } from "../boards/registry.js";
|
|
3
3
|
import "../boards/index.js";
|
|
4
4
|
import { validateFactoryRuleDecision, validateFactoryRuleDecisions } from "./validation.js";
|
|
5
|
+
import { boardTransitionPolicyResultSchema, immutablePolicySnapshot } from "../boards/transition-policy.js";
|
|
5
6
|
import { resolveFactoryStageRules } from "./resolve.js";
|
|
6
7
|
import { randomUUID } from "crypto";
|
|
7
8
|
//#region src/rules/transition-service.ts
|
|
@@ -71,18 +72,6 @@ function isTriageAgent(actor) {
|
|
|
71
72
|
function isHumanTransition(request) {
|
|
72
73
|
return request.actor.type === "human" && request.ingress.type === "human";
|
|
73
74
|
}
|
|
74
|
-
function requiresHumanApproval(triageType) {
|
|
75
|
-
return triageType !== void 0 && triageType !== null && triageType !== "bug";
|
|
76
|
-
}
|
|
77
|
-
function isAtRest(stage) {
|
|
78
|
-
return stage === "intake" || stage === "triage";
|
|
79
|
-
}
|
|
80
|
-
function entersWork(stage) {
|
|
81
|
-
return stage === "planning" || stage === "execute";
|
|
82
|
-
}
|
|
83
|
-
function acceptsItem(request) {
|
|
84
|
-
return isHumanTransition(request) && entersWork(request.stage);
|
|
85
|
-
}
|
|
86
75
|
function ruleFailure(error) {
|
|
87
76
|
return {
|
|
88
77
|
code: "rule_error",
|
|
@@ -142,10 +131,6 @@ var FactoryTransitionService = class {
|
|
|
142
131
|
const fromStage = item.stages.length === 1 ? item.stages[0] : void 0;
|
|
143
132
|
if (!fromStage || !Object.prototype.hasOwnProperty.call(board.phases, fromStage)) return this.#commitRejection(request, transitionId, "invalid_transition", "The work item does not have one canonical phase on the requested board.");
|
|
144
133
|
if (!Object.prototype.hasOwnProperty.call(board.phases, request.stage) || !board.allowsTransition(fromStage, request.stage)) return this.#commitRejection(request, transitionId, "invalid_transition", `The ${board.title} board does not allow moving from ${fromStage} to ${request.stage}.`);
|
|
145
|
-
if (isTriageAgent(request.actor) && request.triageType === void 0) return this.#commitRejection(request, transitionId, "invalid_transition", "Triage transitions must report a structured triage classification.");
|
|
146
|
-
if (item.triageType && request.triageType && item.triageType !== request.triageType) return this.#commitRejection(request, transitionId, "forbidden", "The persisted triage classification cannot be changed by a later transition.");
|
|
147
|
-
if (requiresHumanApproval(item.triageType ?? request.triageType) && entersWork(request.stage) && isAtRest(fromStage) && !isHumanTransition(request) && !item.acceptedAt) return this.#commitRejection(request, transitionId, "approval_required", "A maintainer must move this non-bug work item into Planning or Execute from the Factory UI.");
|
|
148
|
-
if (request.actor.type === "agent" && !isWorkingFactoryRuleStage(fromStage) && isWorkingFactoryRuleStage(request.stage) && externallyAuthoredWorkItem(item)) return this.#commitRejection(request, transitionId, "approval_required", "This card comes from outside the write-access circle; a person must resume it from the Factory board.");
|
|
149
134
|
const humanMove = request.actor.type === "human" && fromStage !== request.stage && request.cause !== "run_start";
|
|
150
135
|
const contextBase = {
|
|
151
136
|
tenant: {
|
|
@@ -180,15 +165,38 @@ var FactoryTransitionService = class {
|
|
|
180
165
|
let evaluation;
|
|
181
166
|
try {
|
|
182
167
|
evaluation = await withRuleTimeout((async () => {
|
|
168
|
+
const policy = boardTransitionPolicyResultSchema.parse(await board.transitionPolicy?.(immutablePolicySnapshot({
|
|
169
|
+
...contextBase,
|
|
170
|
+
item: {
|
|
171
|
+
...contextBase.item,
|
|
172
|
+
triageType: item.triageType
|
|
173
|
+
},
|
|
174
|
+
initialEntry: request.initialEntry ?? false,
|
|
175
|
+
reenter: request.reenter ?? false,
|
|
176
|
+
isHumanTransition: isHumanTransition(request),
|
|
177
|
+
requestedTriageType: request.triageType
|
|
178
|
+
})));
|
|
179
|
+
if (policy?.type === "reject") return {
|
|
180
|
+
outcome: "rejected",
|
|
181
|
+
code: policy.code,
|
|
182
|
+
reason: policy.reason
|
|
183
|
+
};
|
|
184
|
+
if (policy?.triageType !== void 0 && (!isTriageAgent(request.actor) || policy.triageType !== request.triageType || item.triageType !== null && item.triageType !== policy.triageType)) throw new Error("Board policy requested an unauthorized classification.");
|
|
185
|
+
if (policy?.accept && !isHumanTransition(request)) throw new Error("Board policy requested unauthorized acceptance.");
|
|
186
|
+
if (request.actor.type === "agent" && !isWorkingFactoryRuleStage(fromStage) && isWorkingFactoryRuleStage(request.stage) && externallyAuthoredWorkItem(item)) return {
|
|
187
|
+
outcome: "rejected",
|
|
188
|
+
code: "approval_required",
|
|
189
|
+
reason: "This card comes from outside the write-access circle; a person must resume it from the Factory board."
|
|
190
|
+
};
|
|
183
191
|
const decisions = [];
|
|
184
|
-
for (const rule of resolveFactoryStageRules(this.#
|
|
192
|
+
for (const rule of resolveFactoryStageRules(this.#boards, {
|
|
185
193
|
board: request.board,
|
|
186
194
|
source,
|
|
187
195
|
fromStage,
|
|
188
196
|
toStage: request.stage,
|
|
189
197
|
initialEntry: request.initialEntry,
|
|
190
198
|
reenter: request.reenter
|
|
191
|
-
}
|
|
199
|
+
})) {
|
|
192
200
|
const context = Object.freeze({
|
|
193
201
|
...contextBase,
|
|
194
202
|
stage: rule.phase === "exit" ? fromStage : request.stage
|
|
@@ -223,6 +231,10 @@ var FactoryTransitionService = class {
|
|
|
223
231
|
}
|
|
224
232
|
return {
|
|
225
233
|
outcome: "accepted",
|
|
234
|
+
intents: {
|
|
235
|
+
triageType: policy?.triageType,
|
|
236
|
+
accept: policy?.accept === true && !item.acceptedAt
|
|
237
|
+
},
|
|
226
238
|
decisions: validateFactoryRuleDecisions(validated)
|
|
227
239
|
};
|
|
228
240
|
})(), this.#timeoutMs);
|
|
@@ -237,7 +249,7 @@ var FactoryTransitionService = class {
|
|
|
237
249
|
}
|
|
238
250
|
return this.#commit(request, transitionId, evaluation, evaluation.outcome === "accepted" ? {
|
|
239
251
|
...consentEffect(request, humanMove),
|
|
240
|
-
|
|
252
|
+
...evaluation.intents
|
|
241
253
|
} : {});
|
|
242
254
|
}
|
|
243
255
|
async #commitRejection(request, transitionId, code, reason) {
|
|
@@ -266,7 +278,7 @@ var FactoryTransitionService = class {
|
|
|
266
278
|
ruleSetVersion: this.#rules.version,
|
|
267
279
|
causalChain: [...request.causalChain ?? []],
|
|
268
280
|
evaluation,
|
|
269
|
-
...
|
|
281
|
+
...options.triageType ? { triageType: options.triageType } : {}
|
|
270
282
|
});
|
|
271
283
|
if (committed.status === "missing") return rejection(transitionId, request.workItemId, "invalid_transition", "Work item not found.");
|
|
272
284
|
const result = committed.result;
|