@mastra/factory 0.18.0 → 0.18.1-alpha.0
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/dist/integrations/github/default-rules.d.ts.map +1 -1
- package/dist/integrations/github/default-rules.js +3 -3
- package/dist/integrations/github/default-rules.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +1 -2
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +1 -14
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/gitlab/version-control.d.ts.map +1 -1
- package/dist/integrations/gitlab/version-control.js +4 -5
- package/dist/integrations/gitlab/version-control.js.map +1 -1
- package/dist/integrations/gitlab/webhook.d.ts.map +1 -1
- package/dist/integrations/gitlab/webhook.js +2 -2
- package/dist/integrations/gitlab/webhook.js.map +1 -1
- package/dist/integrations/issue-reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/issue-reconcile-worker.js +1 -2
- package/dist/integrations/issue-reconcile-worker.js.map +1 -1
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +10 -30
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +1 -2
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/gitlab/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/gitlab/event-worker.js +1 -2
- package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
- package/dist/integrations/platform/linear/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/linear/event-worker.js +1 -2
- package/dist/integrations/platform/linear/event-worker.js.map +1 -1
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +9 -9
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +1 -2
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/review-verdict.d.ts +6 -0
- package/dist/review-verdict.d.ts.map +1 -0
- package/dist/review-verdict.js +14 -0
- package/dist/review-verdict.js.map +1 -0
- package/dist/routes/oauth.d.ts.map +1 -1
- package/dist/routes/oauth.js +3 -3
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts.map +1 -1
- package/dist/routes/source-control-sessions.js +1 -2
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +1 -2
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +5 -5
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +1 -2
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/secret-encryption.js +2 -2
- package/dist/secret-encryption.js.map +1 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +1 -2
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/source-control-tools.d.ts +9 -0
- package/dist/session/source-control-tools.d.ts.map +1 -1
- package/dist/session/source-control-tools.js +51 -2
- package/dist/session/source-control-tools.js.map +1 -1
- package/dist/state-signing.js +3 -3
- package/dist/state-signing.js.map +1 -1
- package/dist/storage/domains/audit/base.d.ts.map +1 -1
- package/dist/storage/domains/audit/base.js +2 -3
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +5 -6
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/factory-skills/factory-gitlab-rereview/SKILL.md +1 -1
- package/factory-skills/factory-gitlab-review/SKILL.md +1 -1
- package/factory-skills/factory-rereview/SKILL.md +12 -3
- package/factory-skills/factory-review/SKILL.md +12 -3
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inmemory.js","names":[],"sources":["../../../../src/storage/domains/source-control/inmemory.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { UniqueViolationError } from '@mastra/core/storage';\n\nimport type {\n ConfiguredExternalRepositoryKey,\n CreateProjectSourceControlConnectionInput,\n CreateSourceControlSessionInput,\n ExternalRepositoryProjectTarget,\n LinkProjectRepositoryInput,\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n SourceControlSession,\n SourceControlStorageHandle,\n UpdateProjectRepositoryInput,\n UpsertSourceControlInstallationInput,\n UpsertSourceControlRepositoryInput,\n} from './base.js';\n\n/** In-memory provider-scoped source-control handle for route tests. */\nexport class SourceControlStorageInMemory implements SourceControlStorageHandle {\n readonly integrationId: string;\n installationsRows: SourceControlInstallation[] = [];\n repositoriesRows: SourceControlRepository[] = [];\n connectionsRows: ProjectSourceControlConnection[] = [];\n projectRepositoriesRows: ProjectRepository[] = [];\n sessionsRows: SourceControlSession[] = [];\n\n constructor(integrationId = 'github') {\n this.integrationId = integrationId;\n }\n\n readonly installations = {\n list: async ({ orgId }: { orgId: string }): Promise<SourceControlInstallation[]> =>\n this.installationsRows.filter(row => row.orgId === orgId),\n get: async ({ orgId, id }: { orgId: string; id: string }): Promise<SourceControlInstallation | null> =>\n this.installationsRows.find(row => row.orgId === orgId && row.id === id) ?? null,\n findByExternalId: async ({\n orgId,\n externalId,\n }: {\n orgId: string;\n externalId: string;\n }): Promise<SourceControlInstallation | null> =>\n this.installationsRows.find(row => row.orgId === orgId && row.externalId === externalId) ?? null,\n upsert: async (input: UpsertSourceControlInstallationInput): Promise<SourceControlInstallation> => {\n const existing = this.installationsRows.find(\n row => row.orgId === input.orgId && row.externalId === input.externalId,\n );\n if (existing) {\n Object.assign(existing, {\n connectedByUserId: input.connectedByUserId,\n accountName: input.accountName ?? null,\n accountType: input.accountType ?? null,\n providerMetadata: input.providerMetadata ?? {},\n });\n return existing;\n }\n const created: SourceControlInstallation = {\n id: randomUUID(),\n integrationId: this.integrationId,\n orgId: input.orgId,\n connectedByUserId: input.connectedByUserId,\n externalId: input.externalId,\n accountName: input.accountName ?? null,\n accountType: input.accountType ?? null,\n providerMetadata: input.providerMetadata ?? {},\n createdAt: new Date(),\n };\n this.installationsRows.push(created);\n return created;\n },\n delete: async ({ orgId, id }: { orgId: string; id: string }): Promise<boolean> => {\n const index = this.installationsRows.findIndex(row => row.orgId === orgId && row.id === id);\n if (index < 0) return false;\n this.installationsRows.splice(index, 1);\n return true;\n },\n };\n\n readonly repositories = {\n list: async ({ orgId, installationId }: { orgId: string; installationId: string }) => {\n const installation = await this.installations.get({ orgId, id: installationId });\n return installation ? this.repositoriesRows.filter(row => row.installationId === installationId) : [];\n },\n get: async ({ orgId, id }: { orgId: string; id: string }): Promise<SourceControlRepository | null> => {\n const row = this.repositoriesRows.find(candidate => candidate.id === id);\n if (!row) return null;\n return (await this.installations.get({ orgId, id: row.installationId })) ? row : null;\n },\n findByExternalId: async ({\n orgId,\n installationId,\n externalId,\n }: {\n orgId: string;\n installationId: string;\n externalId: string;\n }) => {\n const rows = await this.repositories.list({ orgId, installationId });\n return rows.find(row => row.externalId === externalId) ?? null;\n },\n findBySlug: async ({ orgId, installationId, slug }: { orgId: string; installationId: string; slug: string }) => {\n const rows = await this.repositories.list({ orgId, installationId });\n return rows.find(row => row.slug === slug) ?? null;\n },\n upsert: async ({\n orgId,\n input,\n }: {\n orgId: string;\n input: UpsertSourceControlRepositoryInput;\n }): Promise<SourceControlRepository> => {\n if (!(await this.installations.get({ orgId, id: input.installationId }))) {\n throw new Error('Source-control installation not found');\n }\n const existing = this.repositoriesRows.find(\n row => row.installationId === input.installationId && row.externalId === input.externalId,\n );\n const now = new Date();\n if (existing) {\n Object.assign(existing, {\n slug: input.slug,\n defaultBranch: input.defaultBranch,\n providerMetadata: input.providerMetadata ?? {},\n updatedAt: now,\n });\n return existing;\n }\n const created: SourceControlRepository = {\n id: randomUUID(),\n installationId: input.installationId,\n externalId: input.externalId,\n slug: input.slug,\n defaultBranch: input.defaultBranch,\n providerMetadata: input.providerMetadata ?? {},\n createdAt: now,\n updatedAt: now,\n };\n this.repositoriesRows.push(created);\n return created;\n },\n migrateInstallation: async ({\n orgId,\n id,\n newInstallationId,\n }: {\n orgId: string;\n id: string;\n newInstallationId: string;\n }) => {\n const existing = await this.repositories.get({ orgId, id });\n if (!existing) {\n throw new Error(`Repository ${id} not found in organization ${orgId}`);\n }\n if (!(await this.installations.get({ orgId, id: newInstallationId }))) {\n throw new Error('Source-control installation not found');\n }\n // Check if a repository with the same external_id exists under the new installation\n const conflict = this.repositoriesRows.find(\n row => row.installationId === newInstallationId && row.externalId === existing.externalId,\n );\n if (conflict) {\n // Return the existing repository under the new installation\n return conflict;\n }\n // Update the repository's installation\n existing.installationId = newInstallationId;\n existing.updatedAt = new Date();\n // Migrate dependent connections to the new installation\n for (const conn of this.connectionsRows) {\n if (conn.installationId === existing.installationId) {\n conn.installationId = newInstallationId;\n }\n }\n return existing;\n },\n };\n\n readonly connections = {\n list: async ({ factoryProjectId }: { orgId: string; factoryProjectId: string }) =>\n this.connectionsRows.filter(row => row.factoryProjectId === factoryProjectId),\n get: async ({ orgId, id }: { orgId: string; id: string }): Promise<ProjectSourceControlConnection | null> => {\n const row = this.connectionsRows.find(candidate => candidate.id === id);\n if (!row) return null;\n return (await this.installations.get({ orgId, id: row.installationId })) ? row : null;\n },\n create: async (input: CreateProjectSourceControlConnectionInput): Promise<ProjectSourceControlConnection> => {\n if (!(await this.installations.get({ orgId: input.orgId, id: input.installationId }))) {\n throw new Error('Source-control installation not found');\n }\n const existing = this.connectionsRows.find(\n row =>\n row.factoryProjectId === input.factoryProjectId &&\n row.integrationId === this.integrationId &&\n row.installationId === input.installationId,\n );\n if (existing) return existing;\n const created: ProjectSourceControlConnection = {\n id: randomUUID(),\n factoryProjectId: input.factoryProjectId,\n integrationId: this.integrationId,\n installationId: input.installationId,\n createdByUserId: input.createdByUserId,\n createdAt: new Date(),\n };\n this.connectionsRows.push(created);\n return created;\n },\n delete: async ({ orgId, id }: { orgId: string; id: string }): Promise<boolean> => {\n if (!(await this.connections.get({ orgId, id }))) return false;\n this.connectionsRows.splice(0, this.connectionsRows.length, ...this.connectionsRows.filter(row => row.id !== id));\n return true;\n },\n };\n\n readonly projectRepositories = {\n list: async ({ orgId, connectionId }: { orgId: string; connectionId: string }) =>\n (await this.connections.get({ orgId, id: connectionId }))\n ? this.projectRepositoriesRows.filter(row => row.connectionId === connectionId)\n : [],\n listConfiguredExternalKeys: async (): Promise<ConfiguredExternalRepositoryKey[]> => {\n const keys = new Map<string, ConfiguredExternalRepositoryKey>();\n for (const connection of this.connectionsRows.filter(row => row.integrationId === this.integrationId)) {\n const installation = this.installationsRows.find(row => row.id === connection.installationId);\n if (!installation) continue;\n for (const link of this.projectRepositoriesRows.filter(row => row.connectionId === connection.id)) {\n const repository = this.repositoriesRows.find(row => row.id === link.repositoryId);\n if (!repository) continue;\n keys.set(`${installation.externalId}\\u0000${repository.externalId}`, {\n installationExternalId: installation.externalId,\n repositoryExternalId: repository.externalId,\n });\n }\n }\n return [...keys.values()];\n },\n listByExternalRepository: async ({\n installationExternalId,\n repositoryExternalId,\n }: {\n installationExternalId: string;\n repositoryExternalId: string;\n }): Promise<ExternalRepositoryProjectTarget[]> => {\n const targets: ExternalRepositoryProjectTarget[] = [];\n for (const installation of this.installationsRows.filter(row => row.externalId === installationExternalId)) {\n const repository = this.repositoriesRows.find(\n row => row.installationId === installation.id && row.externalId === repositoryExternalId,\n );\n if (!repository) continue;\n for (const projectRepository of this.projectRepositoriesRows.filter(\n row => row.repositoryId === repository.id,\n )) {\n const connection = this.connectionsRows.find(\n row =>\n row.id === projectRepository.connectionId &&\n row.installationId === installation.id &&\n row.integrationId === this.integrationId,\n );\n if (!connection) continue;\n targets.push({\n orgId: installation.orgId,\n factoryProjectId: connection.factoryProjectId,\n projectRepository,\n });\n }\n }\n return targets;\n },\n get: async ({ orgId, id }: { orgId: string; id: string }): Promise<ProjectRepository | null> => {\n const row = this.projectRepositoriesRows.find(candidate => candidate.id === id);\n if (!row) return null;\n return (await this.connections.get({ orgId, id: row.connectionId })) ? row : null;\n },\n link: async (input: LinkProjectRepositoryInput): Promise<ProjectRepository> => {\n const connection = await this.connections.get({ orgId: input.orgId, id: input.connectionId });\n const repository = await this.repositories.get({ orgId: input.orgId, id: input.repositoryId });\n if (!connection || !repository || repository.installationId !== connection.installationId) {\n throw new Error('Source-control connection or repository not found');\n }\n const existing = this.projectRepositoriesRows.find(\n row => row.connectionId === input.connectionId && row.repositoryId === input.repositoryId,\n );\n if (existing) return existing;\n const now = new Date();\n const created: ProjectRepository = {\n id: randomUUID(),\n connectionId: input.connectionId,\n repositoryId: input.repositoryId,\n createdByUserId: input.createdByUserId,\n branch: input.branch ?? null,\n sandboxProvider: input.sandboxProvider,\n sandboxWorkdir: input.sandboxWorkdir,\n setupCommand: input.setupCommand ?? null,\n teardownCommand: input.teardownCommand ?? null,\n createdAt: now,\n updatedAt: now,\n };\n this.projectRepositoriesRows.push(created);\n return created;\n },\n update: async ({\n orgId,\n id,\n input,\n }: {\n orgId: string;\n id: string;\n input: UpdateProjectRepositoryInput;\n }): Promise<ProjectRepository | null> => {\n const row = await this.projectRepositories.get({ orgId, id });\n if (!row) return null;\n Object.assign(row, input, { updatedAt: new Date() });\n return row;\n },\n unlink: async ({ orgId, id }: { orgId: string; id: string }): Promise<boolean> => {\n if (!(await this.projectRepositories.get({ orgId, id }))) return false;\n this.projectRepositoriesRows.splice(\n 0,\n this.projectRepositoriesRows.length,\n ...this.projectRepositoriesRows.filter(row => row.id !== id),\n );\n return true;\n },\n };\n\n readonly sessions = {\n list: async ({ projectRepositoryId, viewerUserId }: { projectRepositoryId: string; viewerUserId: string }) =>\n this.sessionsRows.filter(\n row =>\n row.projectRepositoryId === projectRepositoryId &&\n (row.visibility !== 'private' || row.userId === viewerUserId),\n ),\n listByProjectRepository: async ({ projectRepositoryId }: { projectRepositoryId: string }) =>\n this.sessionsRows.filter(row => row.projectRepositoryId === projectRepositoryId),\n getBySessionId: async (sessionId: string): Promise<SourceControlSession | null> => {\n const row = this.sessionsRows.find(candidate => candidate.sessionId === sessionId);\n if (!row) return null;\n // Mirrors base.ts: a session whose project-repository link is gone does not resolve.\n return this.projectRepositoriesRows.some(link => link.id === row.projectRepositoryId) ? row : null;\n },\n rename: async ({ sessionId, title }: { sessionId: string; title: string }): Promise<void> => {\n const row = this.sessionsRows.find(candidate => candidate.sessionId === sessionId);\n if (row) {\n row.title = title;\n row.updatedAt = new Date();\n }\n },\n getForBranch: async ({\n projectRepositoryId,\n userId,\n branch,\n }: {\n projectRepositoryId: string;\n userId: string;\n branch: string;\n }): Promise<SourceControlSession | null> =>\n this.sessionsRows.find(\n row => row.projectRepositoryId === projectRepositoryId && row.userId === userId && row.branch === branch,\n ) ?? null,\n create: async (input: CreateSourceControlSessionInput): Promise<SourceControlSession> => {\n const existing = await this.sessions.getForBranch(input);\n if (existing) return existing;\n if (this.sessionsRows.some(row => row.sessionId === input.sessionId)) {\n throw new UniqueViolationError('Source-control session ID already exists');\n }\n const now = new Date();\n const session: SourceControlSession = {\n id: randomUUID(),\n ...input,\n title: input.title ?? null,\n visibility: input.visibility ?? 'org',\n sandboxId: null,\n sandboxWorkdir: null,\n materializedAt: null,\n firstMessageAt: null,\n firstMeaningfulExecAt: null,\n createdAt: now,\n updatedAt: now,\n };\n this.sessionsRows.push(session);\n return session;\n },\n setSandbox: async ({\n id,\n sandboxId,\n sandboxWorkdir,\n }: {\n id: string;\n sandboxId: string | null;\n sandboxWorkdir: string;\n }) => {\n const row = this.sessionsRows.find(candidate => candidate.id === id);\n if (row) Object.assign(row, { sandboxId, sandboxWorkdir, updatedAt: new Date() });\n },\n markMaterialized: async ({ id }: { id: string }) => {\n const row = this.sessionsRows.find(candidate => candidate.id === id);\n if (row && row.materializedAt === null) Object.assign(row, { materializedAt: new Date(), updatedAt: new Date() });\n },\n markFirstMessage: async ({ sessionId }: { sessionId: string }) => {\n const row = this.sessionsRows.find(candidate => candidate.sessionId === sessionId);\n if (row && row.firstMessageAt === null) Object.assign(row, { firstMessageAt: new Date(), updatedAt: new Date() });\n },\n markFirstMeaningfulExec: async ({ sessionId }: { sessionId: string }) => {\n const row = this.sessionsRows.find(candidate => candidate.sessionId === sessionId);\n if (row && row.firstMeaningfulExecAt === null) {\n Object.assign(row, { firstMeaningfulExecAt: new Date(), updatedAt: new Date() });\n }\n },\n delete: async (id: string) => {\n this.sessionsRows.splice(0, this.sessionsRows.length, ...this.sessionsRows.filter(row => row.id !== id));\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"inmemory.js","names":[],"sources":["../../../../src/storage/domains/source-control/inmemory.ts"],"sourcesContent":["import { UniqueViolationError } from '@mastra/core/storage';\n\nimport type {\n ConfiguredExternalRepositoryKey,\n CreateProjectSourceControlConnectionInput,\n CreateSourceControlSessionInput,\n ExternalRepositoryProjectTarget,\n LinkProjectRepositoryInput,\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n SourceControlSession,\n SourceControlStorageHandle,\n UpdateProjectRepositoryInput,\n UpsertSourceControlInstallationInput,\n UpsertSourceControlRepositoryInput,\n} from './base.js';\n\n/** In-memory provider-scoped source-control handle for route tests. */\nexport class SourceControlStorageInMemory implements SourceControlStorageHandle {\n readonly integrationId: string;\n installationsRows: SourceControlInstallation[] = [];\n repositoriesRows: SourceControlRepository[] = [];\n connectionsRows: ProjectSourceControlConnection[] = [];\n projectRepositoriesRows: ProjectRepository[] = [];\n sessionsRows: SourceControlSession[] = [];\n\n constructor(integrationId = 'github') {\n this.integrationId = integrationId;\n }\n\n readonly installations = {\n list: async ({ orgId }: { orgId: string }): Promise<SourceControlInstallation[]> =>\n this.installationsRows.filter(row => row.orgId === orgId),\n get: async ({ orgId, id }: { orgId: string; id: string }): Promise<SourceControlInstallation | null> =>\n this.installationsRows.find(row => row.orgId === orgId && row.id === id) ?? null,\n findByExternalId: async ({\n orgId,\n externalId,\n }: {\n orgId: string;\n externalId: string;\n }): Promise<SourceControlInstallation | null> =>\n this.installationsRows.find(row => row.orgId === orgId && row.externalId === externalId) ?? null,\n upsert: async (input: UpsertSourceControlInstallationInput): Promise<SourceControlInstallation> => {\n const existing = this.installationsRows.find(\n row => row.orgId === input.orgId && row.externalId === input.externalId,\n );\n if (existing) {\n Object.assign(existing, {\n connectedByUserId: input.connectedByUserId,\n accountName: input.accountName ?? null,\n accountType: input.accountType ?? null,\n providerMetadata: input.providerMetadata ?? {},\n });\n return existing;\n }\n const created: SourceControlInstallation = {\n id: globalThis.crypto.randomUUID(),\n integrationId: this.integrationId,\n orgId: input.orgId,\n connectedByUserId: input.connectedByUserId,\n externalId: input.externalId,\n accountName: input.accountName ?? null,\n accountType: input.accountType ?? null,\n providerMetadata: input.providerMetadata ?? {},\n createdAt: new Date(),\n };\n this.installationsRows.push(created);\n return created;\n },\n delete: async ({ orgId, id }: { orgId: string; id: string }): Promise<boolean> => {\n const index = this.installationsRows.findIndex(row => row.orgId === orgId && row.id === id);\n if (index < 0) return false;\n this.installationsRows.splice(index, 1);\n return true;\n },\n };\n\n readonly repositories = {\n list: async ({ orgId, installationId }: { orgId: string; installationId: string }) => {\n const installation = await this.installations.get({ orgId, id: installationId });\n return installation ? this.repositoriesRows.filter(row => row.installationId === installationId) : [];\n },\n get: async ({ orgId, id }: { orgId: string; id: string }): Promise<SourceControlRepository | null> => {\n const row = this.repositoriesRows.find(candidate => candidate.id === id);\n if (!row) return null;\n return (await this.installations.get({ orgId, id: row.installationId })) ? row : null;\n },\n findByExternalId: async ({\n orgId,\n installationId,\n externalId,\n }: {\n orgId: string;\n installationId: string;\n externalId: string;\n }) => {\n const rows = await this.repositories.list({ orgId, installationId });\n return rows.find(row => row.externalId === externalId) ?? null;\n },\n findBySlug: async ({ orgId, installationId, slug }: { orgId: string; installationId: string; slug: string }) => {\n const rows = await this.repositories.list({ orgId, installationId });\n return rows.find(row => row.slug === slug) ?? null;\n },\n upsert: async ({\n orgId,\n input,\n }: {\n orgId: string;\n input: UpsertSourceControlRepositoryInput;\n }): Promise<SourceControlRepository> => {\n if (!(await this.installations.get({ orgId, id: input.installationId }))) {\n throw new Error('Source-control installation not found');\n }\n const existing = this.repositoriesRows.find(\n row => row.installationId === input.installationId && row.externalId === input.externalId,\n );\n const now = new Date();\n if (existing) {\n Object.assign(existing, {\n slug: input.slug,\n defaultBranch: input.defaultBranch,\n providerMetadata: input.providerMetadata ?? {},\n updatedAt: now,\n });\n return existing;\n }\n const created: SourceControlRepository = {\n id: globalThis.crypto.randomUUID(),\n installationId: input.installationId,\n externalId: input.externalId,\n slug: input.slug,\n defaultBranch: input.defaultBranch,\n providerMetadata: input.providerMetadata ?? {},\n createdAt: now,\n updatedAt: now,\n };\n this.repositoriesRows.push(created);\n return created;\n },\n migrateInstallation: async ({\n orgId,\n id,\n newInstallationId,\n }: {\n orgId: string;\n id: string;\n newInstallationId: string;\n }) => {\n const existing = await this.repositories.get({ orgId, id });\n if (!existing) {\n throw new Error(`Repository ${id} not found in organization ${orgId}`);\n }\n if (!(await this.installations.get({ orgId, id: newInstallationId }))) {\n throw new Error('Source-control installation not found');\n }\n // Check if a repository with the same external_id exists under the new installation\n const conflict = this.repositoriesRows.find(\n row => row.installationId === newInstallationId && row.externalId === existing.externalId,\n );\n if (conflict) {\n // Return the existing repository under the new installation\n return conflict;\n }\n // Update the repository's installation\n existing.installationId = newInstallationId;\n existing.updatedAt = new Date();\n // Migrate dependent connections to the new installation\n for (const conn of this.connectionsRows) {\n if (conn.installationId === existing.installationId) {\n conn.installationId = newInstallationId;\n }\n }\n return existing;\n },\n };\n\n readonly connections = {\n list: async ({ factoryProjectId }: { orgId: string; factoryProjectId: string }) =>\n this.connectionsRows.filter(row => row.factoryProjectId === factoryProjectId),\n get: async ({ orgId, id }: { orgId: string; id: string }): Promise<ProjectSourceControlConnection | null> => {\n const row = this.connectionsRows.find(candidate => candidate.id === id);\n if (!row) return null;\n return (await this.installations.get({ orgId, id: row.installationId })) ? row : null;\n },\n create: async (input: CreateProjectSourceControlConnectionInput): Promise<ProjectSourceControlConnection> => {\n if (!(await this.installations.get({ orgId: input.orgId, id: input.installationId }))) {\n throw new Error('Source-control installation not found');\n }\n const existing = this.connectionsRows.find(\n row =>\n row.factoryProjectId === input.factoryProjectId &&\n row.integrationId === this.integrationId &&\n row.installationId === input.installationId,\n );\n if (existing) return existing;\n const created: ProjectSourceControlConnection = {\n id: globalThis.crypto.randomUUID(),\n factoryProjectId: input.factoryProjectId,\n integrationId: this.integrationId,\n installationId: input.installationId,\n createdByUserId: input.createdByUserId,\n createdAt: new Date(),\n };\n this.connectionsRows.push(created);\n return created;\n },\n delete: async ({ orgId, id }: { orgId: string; id: string }): Promise<boolean> => {\n if (!(await this.connections.get({ orgId, id }))) return false;\n this.connectionsRows.splice(0, this.connectionsRows.length, ...this.connectionsRows.filter(row => row.id !== id));\n return true;\n },\n };\n\n readonly projectRepositories = {\n list: async ({ orgId, connectionId }: { orgId: string; connectionId: string }) =>\n (await this.connections.get({ orgId, id: connectionId }))\n ? this.projectRepositoriesRows.filter(row => row.connectionId === connectionId)\n : [],\n listConfiguredExternalKeys: async (): Promise<ConfiguredExternalRepositoryKey[]> => {\n const keys = new Map<string, ConfiguredExternalRepositoryKey>();\n for (const connection of this.connectionsRows.filter(row => row.integrationId === this.integrationId)) {\n const installation = this.installationsRows.find(row => row.id === connection.installationId);\n if (!installation) continue;\n for (const link of this.projectRepositoriesRows.filter(row => row.connectionId === connection.id)) {\n const repository = this.repositoriesRows.find(row => row.id === link.repositoryId);\n if (!repository) continue;\n keys.set(`${installation.externalId}\\u0000${repository.externalId}`, {\n installationExternalId: installation.externalId,\n repositoryExternalId: repository.externalId,\n });\n }\n }\n return [...keys.values()];\n },\n listByExternalRepository: async ({\n installationExternalId,\n repositoryExternalId,\n }: {\n installationExternalId: string;\n repositoryExternalId: string;\n }): Promise<ExternalRepositoryProjectTarget[]> => {\n const targets: ExternalRepositoryProjectTarget[] = [];\n for (const installation of this.installationsRows.filter(row => row.externalId === installationExternalId)) {\n const repository = this.repositoriesRows.find(\n row => row.installationId === installation.id && row.externalId === repositoryExternalId,\n );\n if (!repository) continue;\n for (const projectRepository of this.projectRepositoriesRows.filter(\n row => row.repositoryId === repository.id,\n )) {\n const connection = this.connectionsRows.find(\n row =>\n row.id === projectRepository.connectionId &&\n row.installationId === installation.id &&\n row.integrationId === this.integrationId,\n );\n if (!connection) continue;\n targets.push({\n orgId: installation.orgId,\n factoryProjectId: connection.factoryProjectId,\n projectRepository,\n });\n }\n }\n return targets;\n },\n get: async ({ orgId, id }: { orgId: string; id: string }): Promise<ProjectRepository | null> => {\n const row = this.projectRepositoriesRows.find(candidate => candidate.id === id);\n if (!row) return null;\n return (await this.connections.get({ orgId, id: row.connectionId })) ? row : null;\n },\n link: async (input: LinkProjectRepositoryInput): Promise<ProjectRepository> => {\n const connection = await this.connections.get({ orgId: input.orgId, id: input.connectionId });\n const repository = await this.repositories.get({ orgId: input.orgId, id: input.repositoryId });\n if (!connection || !repository || repository.installationId !== connection.installationId) {\n throw new Error('Source-control connection or repository not found');\n }\n const existing = this.projectRepositoriesRows.find(\n row => row.connectionId === input.connectionId && row.repositoryId === input.repositoryId,\n );\n if (existing) return existing;\n const now = new Date();\n const created: ProjectRepository = {\n id: globalThis.crypto.randomUUID(),\n connectionId: input.connectionId,\n repositoryId: input.repositoryId,\n createdByUserId: input.createdByUserId,\n branch: input.branch ?? null,\n sandboxProvider: input.sandboxProvider,\n sandboxWorkdir: input.sandboxWorkdir,\n setupCommand: input.setupCommand ?? null,\n teardownCommand: input.teardownCommand ?? null,\n createdAt: now,\n updatedAt: now,\n };\n this.projectRepositoriesRows.push(created);\n return created;\n },\n update: async ({\n orgId,\n id,\n input,\n }: {\n orgId: string;\n id: string;\n input: UpdateProjectRepositoryInput;\n }): Promise<ProjectRepository | null> => {\n const row = await this.projectRepositories.get({ orgId, id });\n if (!row) return null;\n Object.assign(row, input, { updatedAt: new Date() });\n return row;\n },\n unlink: async ({ orgId, id }: { orgId: string; id: string }): Promise<boolean> => {\n if (!(await this.projectRepositories.get({ orgId, id }))) return false;\n this.projectRepositoriesRows.splice(\n 0,\n this.projectRepositoriesRows.length,\n ...this.projectRepositoriesRows.filter(row => row.id !== id),\n );\n return true;\n },\n };\n\n readonly sessions = {\n list: async ({ projectRepositoryId, viewerUserId }: { projectRepositoryId: string; viewerUserId: string }) =>\n this.sessionsRows.filter(\n row =>\n row.projectRepositoryId === projectRepositoryId &&\n (row.visibility !== 'private' || row.userId === viewerUserId),\n ),\n listByProjectRepository: async ({ projectRepositoryId }: { projectRepositoryId: string }) =>\n this.sessionsRows.filter(row => row.projectRepositoryId === projectRepositoryId),\n getBySessionId: async (sessionId: string): Promise<SourceControlSession | null> => {\n const row = this.sessionsRows.find(candidate => candidate.sessionId === sessionId);\n if (!row) return null;\n // Mirrors base.ts: a session whose project-repository link is gone does not resolve.\n return this.projectRepositoriesRows.some(link => link.id === row.projectRepositoryId) ? row : null;\n },\n rename: async ({ sessionId, title }: { sessionId: string; title: string }): Promise<void> => {\n const row = this.sessionsRows.find(candidate => candidate.sessionId === sessionId);\n if (row) {\n row.title = title;\n row.updatedAt = new Date();\n }\n },\n getForBranch: async ({\n projectRepositoryId,\n userId,\n branch,\n }: {\n projectRepositoryId: string;\n userId: string;\n branch: string;\n }): Promise<SourceControlSession | null> =>\n this.sessionsRows.find(\n row => row.projectRepositoryId === projectRepositoryId && row.userId === userId && row.branch === branch,\n ) ?? null,\n create: async (input: CreateSourceControlSessionInput): Promise<SourceControlSession> => {\n const existing = await this.sessions.getForBranch(input);\n if (existing) return existing;\n if (this.sessionsRows.some(row => row.sessionId === input.sessionId)) {\n throw new UniqueViolationError('Source-control session ID already exists');\n }\n const now = new Date();\n const session: SourceControlSession = {\n id: globalThis.crypto.randomUUID(),\n ...input,\n title: input.title ?? null,\n visibility: input.visibility ?? 'org',\n sandboxId: null,\n sandboxWorkdir: null,\n materializedAt: null,\n firstMessageAt: null,\n firstMeaningfulExecAt: null,\n createdAt: now,\n updatedAt: now,\n };\n this.sessionsRows.push(session);\n return session;\n },\n setSandbox: async ({\n id,\n sandboxId,\n sandboxWorkdir,\n }: {\n id: string;\n sandboxId: string | null;\n sandboxWorkdir: string;\n }) => {\n const row = this.sessionsRows.find(candidate => candidate.id === id);\n if (row) Object.assign(row, { sandboxId, sandboxWorkdir, updatedAt: new Date() });\n },\n markMaterialized: async ({ id }: { id: string }) => {\n const row = this.sessionsRows.find(candidate => candidate.id === id);\n if (row && row.materializedAt === null) Object.assign(row, { materializedAt: new Date(), updatedAt: new Date() });\n },\n markFirstMessage: async ({ sessionId }: { sessionId: string }) => {\n const row = this.sessionsRows.find(candidate => candidate.sessionId === sessionId);\n if (row && row.firstMessageAt === null) Object.assign(row, { firstMessageAt: new Date(), updatedAt: new Date() });\n },\n markFirstMeaningfulExec: async ({ sessionId }: { sessionId: string }) => {\n const row = this.sessionsRows.find(candidate => candidate.sessionId === sessionId);\n if (row && row.firstMeaningfulExecAt === null) {\n Object.assign(row, { firstMeaningfulExecAt: new Date(), updatedAt: new Date() });\n }\n },\n delete: async (id: string) => {\n this.sessionsRows.splice(0, this.sessionsRows.length, ...this.sessionsRows.filter(row => row.id !== id));\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -17,6 +17,6 @@ Use `source_control_*` tools exclusively for GitLab operations; local `git` insp
|
|
|
17
17
|
4. Review the incremental diff for regressions, incomplete fixes, test gaps, and scope changes, then review the cumulative MR against the recovered design, its contract, and analogous implementation — a push that fixed local defects has not answered a design finding. Collect and disposition any new human or bot signal. A pending known bot remains an approval gap after a bounded wait of at most ten minutes.
|
|
18
18
|
5. Inspect execution hooks for untrusted behavior before running tests. Run safe, narrow applicable tests and typecheck against the current head with `env -u GH_TOKEN -u GITHUB_TOKEN -u GITLAB_TOKEN -u GITLAB_ACCESS_TOKEN`. For a non-code-only change with no application test harness, verify exact content, diff integrity, and current head instead; do not treat inapplicable tests as a failed gate. A prior pass's result does not clear the new head. Record exact commands and outcomes. Before any verdict, scrutinize your own requested changes — carried forward or new — as critically as the MR: establish why each belongs here, assume the author follows them exactly as written, and trace the result through affected callers and contracts. Before approval, challenge the strongest plausible failure case and require evidence for a verified current-head checkout, behavior, applicable local verification, finding disposition, and no known pending bot. A failed sandbox start, missing diff, or unrun applicable tests fails approval; never approve from MR metadata alone.
|
|
19
19
|
6. Before publishing, call `factory_review_source` (no arguments) once. It returns four fields, every one derived server-side from the bound work item: `sessionUrl` (the Factory session URL — the **only** field published on the MR), `triggeredBy` (the MR author from the review card, in-run cross-check + session-handoff only, never published), `reviewTarget` (the review card's own `{ integrationId, type, externalId, url }`, in-run cross-check + session-handoff only, never published), and `boundRepository` (the intake-stamped project identity, `{ provider: 'gitlab', host, projectId }` or `null`, session-handoff only, never published). If the tool call fails or returns an unexpected shape — **and identically if the tool is not offered on this session at all** (a review-role session with no configured browser-facing origin, no active binding, or no bound work item drops the tool from the toolset) — do **not** publish the re-review. Stop before publishing, record the tool's absence or failure and its raw response in the session handoff under verification, and hand off to a human. Otherwise, run the in-run cross-check: (a) compare `triggeredBy` with the MR author you already read; (b) compare `reviewTarget.url` with the MR URL from `source_control_get_change_request` — intake records the MR URL for every card (`gitlab/rules.ts:481`), so `reviewTarget.url` covers the normal case. If `reviewTarget.url` is `null`, do **not** attempt to decode `reviewTarget.externalId`: the GitLab MR externalId is opaque `gitlab-pr:<base64url({version, host, projectId, mergeRequestIid})>` (`gitlab/routes.ts:94`, `gitlab/rules.ts:109`), with no documented way to check it against an MR IID. The bound identity itself is not the gap — the tool returns the intake-stamped `boundRepository`, and the MR fetch is binding-scoped (`session/source-control-tools.ts` resolves the repository from the session record, so it cannot return another project's MR) — but the normalized change-request result carries no numeric project id or intake identifier to compare it against, and a matching `triggeredBy` author cannot distinguish two MRs by the same author within the project. With no URL, nothing confirms the fetched MR **is the card's MR**. Do not publish: record the `reviewTarget.url` gap and both raw values in the session handoff, skip the verdict call, and hand off to a human. On any mismatch, stop and record a blocking security finding with both values verbatim; do **not** publish anything on the MR — posting any verdict (a request-changes comment included) puts a review on an MR that may be the wrong one. Re-read the MR with `source_control_get_change_request` once and re-run the cross-check; publish only if the fresh check matches, otherwise record the mismatch in the **Factory routing** block, skip the verdict call, and hand off to a human. When publication does proceed, include only `sessionUrl` as a `Factory Session` section at the end of every published body (approve, request-changes comment, and the approval-fallback comment).
|
|
20
|
-
7. Publish a complete current-head verdict with `source_control_review_change_request`. Use `approve` only when all applicable gates pass; otherwise use `comment` with `Verdict: request changes` and an actionable defect or verification gap because GitLab has no request-changes review state. If approval is rejected for a confirmed authorization reason (self-review or missing permission), keep the substantive approve verdict: make a separate `source_control_comment_change_request` call with `Verdict: approve (approval not recorded)` and
|
|
20
|
+
7. Publish a complete current-head verdict with `source_control_review_change_request`. Compose the body fresh in this pass — never reuse an earlier pass's body. It opens with the verdict line (`Verdict: approve` or `Verdict: request changes`), then `Reviewed head: <full SHA>` equal to the current MR head, the `commitId`, and the `reviewedHeadSha` you record. The tool rejects a body whose verdict line contradicts `event` or whose reviewed head is stale; regenerate the body in that case, never flip the event to match it. Use `approve` only when all applicable gates pass; otherwise use `comment` with `Verdict: request changes` and an actionable defect or verification gap because GitLab has no request-changes review state. If approval is rejected for a confirmed authorization reason (self-review or missing permission), keep the substantive approve verdict: first re-read the MR with `source_control_get_change_request` and confirm its head still equals the head you reviewed — if it moved, do not post the fallback; refresh and re-run the review gates against the new head. Then make a separate `source_control_comment_change_request` call whose body opens with `Verdict: approve (approval not recorded)`, then `Reviewed head: <full SHA>` naming that verified head, and explains the rejection. Confirm that this fallback comment succeeded before recording the verdict. For any other rejection, refresh the checkout, re-establish the current head, and re-run the substantive review and approval gates against the refreshed head before retrying; a provider rejection is not a reason to switch to request changes, an approval evaluated against a head that has since changed is never published, and a retry that still fails means no verdict was posted — report the failure. Never claim a formal approval that was not recorded, and never merge the MR.
|
|
21
21
|
|
|
22
22
|
First call `factory_record_review_verdict` with the published `verdict` and the verified `reviewedHeadSha`. If rejected, address the reason before retrying; the handoff must report whether the card was updated. The card stays in Reviewing; never request a stage transition to end the re-review. Then, as the terminal action, provide a session handoff with MR URL/current head, incremental and cumulative findings, a disposition for every prior finding, tests, assumptions, gaps, the actual publication outcome, the `Factory Session` block (`sessionUrl` only, matching the MR body), and a **Factory routing** block that records `triggeredBy` verbatim, `reviewTarget` verbatim (`integrationId`, `type`, `externalId`, `url`), `boundRepository` verbatim, and the cross-check outcome — "matched" with the compared MR value or "mismatch: <blocking-finding-ref>".
|
|
@@ -43,6 +43,6 @@ Otherwise, run the in-run cross-check:
|
|
|
43
43
|
|
|
44
44
|
On any mismatch, stop and record it as a blocking security finding with both values verbatim. Do **not** publish anything on the MR — a mismatch means the fetched MR may not be the session's bound target, and posting any verdict (a request-changes comment included) puts a review on an MR that may be the wrong one. Re-read the MR with `source_control_get_change_request` once and re-run this cross-check; publish only if the fresh check matches. If it still mismatches, hand off without publishing: record the mismatch in the **Factory routing** block, skip the verdict call, and hand off to a human. When publication does proceed, include only `sessionUrl` as a `Factory Session` section at the end of every published body (approve, request-changes comment, and the approval-fallback comment) so a misattributed review can be traced back to its run. `triggeredBy` and `reviewTarget` stay in the session handoff, never on the MR.
|
|
45
45
|
|
|
46
|
-
Use `source_control_review_change_request` on the same IID and current `commitId` when available. For an approve verdict use `event: "approve"`. GitLab cannot represent a GitHub-style request-changes review: for a blocking verdict use `event: "comment"` and start the body with `Verdict: request changes`, followed by concrete findings and evidence. Do not claim that a comment blocks merging. Use the approval fallback only for a confirmed authorization rejection — for example, the current account authored the MR or lacks review permission. In that case make a separate `source_control_comment_change_request` call
|
|
46
|
+
Use `source_control_review_change_request` on the same IID and current `commitId` when available. Compose the body fresh in this pass — never reuse a body from an earlier pass. Its first line is the verdict line (`Verdict: approve` or `Verdict: request changes`) and its second line is `Reviewed head: <full SHA>` naming the head you verified, which must equal the current MR head, the `commitId`, and the `reviewedHeadSha` you record. The tool rejects a body whose verdict line contradicts `event` or whose `Reviewed head:` is not the current head; treat that rejection as a stale body — regenerate it from this pass's findings, never flip the event to match it. For an approve verdict use `event: "approve"`. GitLab cannot represent a GitHub-style request-changes review: for a blocking verdict use `event: "comment"` and start the body with `Verdict: request changes`, followed by concrete findings and evidence. Do not claim that a comment blocks merging. Use the approval fallback only for a confirmed authorization rejection — for example, the current account authored the MR or lacks review permission. In that case re-read the MR with `source_control_get_change_request` and confirm its head still equals the head you reviewed — if it moved, do not post the fallback; refresh and re-run the review gates against the new head. Otherwise make a separate `source_control_comment_change_request` call with a body beginning `Verdict: approve (approval not recorded)`, then `Reviewed head: <full SHA>` naming that verified head, and name the provider rejection in the handoff; never claim the MR was formally approved. For any other rejection (stale `commitId`, changed head, invalid request), refresh the checkout and re-establish the current head, then re-run the substantive review — the diff, finding validation, applicable verification, and approval gates — before retrying publication. Never publish an approval evaluated against a head that has since changed. If the gates cannot be re-run on the refreshed head, report that no verdict was posted. Confirm that the comment call succeeded before the handoff or verdict call. If publication fails, report that failure and do not imply a verdict was posted. Where useful, anchor a specific finding with `source_control_create_diff_comment`, but keep a complete verdict in the top-level review.
|
|
47
47
|
|
|
48
48
|
After the review publication attempt, call `factory_record_review_verdict` with the published `verdict` and the verified `reviewedHeadSha`. The card stays in Reviewing; never request a stage transition to end the review. If the call is rejected, address the stated reason before retrying. Then, as the terminal action, give the session handoff and report whether the card was updated. In the session handoff, include MR URL and head SHA, verdict and whether GitLab recorded it, goal, findings with file/line evidence, prior-review disposition, commands/results, assumptions, open questions, any verification limitation, the `Factory Session` block (`sessionUrl` only, matching the MR body), and a **Factory routing** block that records `triggeredBy` verbatim, `reviewTarget` verbatim (`integrationId`, `type`, `externalId`, `url`), `boundRepository` verbatim, and the cross-check outcome — "matched" with the compared MR value or "mismatch: <blocking-finding-ref>".
|
|
@@ -152,7 +152,7 @@ On any mismatch, stop and record it as a blocking security finding with both val
|
|
|
152
152
|
|
|
153
153
|
Compose two artifacts, in order — the **published body** goes on the PR, the **session handoff** goes back into the run's conversation. Don't send either to the conversation yet; both are drafted here, the published body is sent to the PR, the verdict is recorded, and only then is the session handoff posted.
|
|
154
154
|
|
|
155
|
-
The **published body** (what `gh pr review --body-file` receives) **must open with the verdict line**: `Verdict: approve` or `Verdict: request changes`, followed by:
|
|
155
|
+
The **published body** (what `gh pr review --body-file` receives) **must open with the verdict line**: `Verdict: approve` or `Verdict: request changes`, then on the next line `Reviewed head: <full 40-character SHA>` naming the exact head you verified, followed by:
|
|
156
156
|
|
|
157
157
|
- **Prior pass disposition** — every substantive item from your previous review, classified: addressed, partially addressed, still open, refuted by the push, or invalidated by the push. Cite the commit or `file:line` proving each addressed/refuted/invalidated call. A prior blocking finding still open is called out plainly at the top of this section.
|
|
158
158
|
- **Findings** — lead with the mechanism of the most consequential finding; new-this-pass findings from the push and from the fresh whole-PR sweep are each labeled as `[push]` or `[fresh]` so the record is honest about where they came from. Distill — this is a handoff, not a transcript.
|
|
@@ -172,19 +172,28 @@ The **session handoff** (posted as the final conversation message after the verd
|
|
|
172
172
|
|
|
173
173
|
**The head must not have moved.** Immediately before publishing, run `gh pr view <number> --json headRefOid --jq .headRefOid` and compare it with the SHA your verification ran on (`git rev-parse HEAD`). A push can land while you verify or wait on bots, and a verdict on a superseded head misleads the author. If the head moved, do not publish: refresh the checkout to the new head, review the new commits and re-run the verification they affect, revise the handoff, then check again. Name the reviewed head SHA in the handoff.
|
|
174
174
|
|
|
175
|
-
Next, publish the re-review on the PR itself — this is part of every pass, not something to wait to be asked for. Write the published body to `.artifacts/factory-rereview/pr-<number>.md
|
|
175
|
+
Next, publish the re-review on the PR itself — this is part of every pass, not something to wait to be asked for. Write the published body to `.artifacts/factory-rereview/pr-<number>-<headSha>.md`, where `<headSha>` is the full SHA you reviewed. Always write this file fresh in the current pass — never reuse, copy, or edit a body file from an earlier pass, which describes a different head and may carry the opposite verdict.
|
|
176
|
+
|
|
177
|
+
**Gate the file immediately before posting.** Run these checks against the exact file you are about to pass to `--body-file`, and post nothing if any fails — regenerate the body from this pass's findings instead:
|
|
178
|
+
|
|
179
|
+
1. `head -n1 <file>` is exactly `Verdict: approve` when you will use `--approve`, or exactly `Verdict: request changes` when you will use `--request-changes`.
|
|
180
|
+
2. The `Reviewed head:` SHA in the file equals `gh pr view <number> --json headRefOid --jq .headRefOid` and the `reviewedHeadSha` you will pass to `factory_record_review_verdict`.
|
|
181
|
+
|
|
182
|
+
Then submit a PR review matching the verdict:
|
|
176
183
|
|
|
177
184
|
- approve → `gh pr review <number> --approve --body-file <file>`
|
|
178
185
|
- request changes → `gh pr review <number> --request-changes --body-file <file>`
|
|
179
186
|
|
|
180
187
|
**Author-identity misconfiguration must be visible, never silent.** GitHub refuses both approve and request changes from the PR's author, so a review token that authored the PR can never record a verdict in `reviewDecision` or satisfy branch protection. Before submitting, compare the reviewing identity (`gh api user --jq .login`; for an App installation token that call may fail — then treat a submission rejected with GitHub's "Can not approve/request changes on your own pull request" error as the same signal) with the PR's `.author.login`. When they match:
|
|
181
188
|
|
|
182
|
-
1. Add this line to the published body immediately after the
|
|
189
|
+
1. Add this line to the published body immediately after the `Reviewed head:` line (the verdict line stays first and `Reviewed head:` stays second): `> ⚠️ **Factory misconfiguration:** the review token is the PR author, so GitHub cannot record this verdict as an approving or changes-requested review (it will not satisfy branch protection or workflows that require an approving or changes-requested review). Configure a separate reviewer token for Factory reviews.`
|
|
183
190
|
2. Publish with `gh pr comment <number> --body-file <file>`. Do not use `gh pr review --comment`: Factory's repair loop only routes a request-changes verdict from a plain PR comment whose first line is the verdict, and ignores `COMMENTED` reviews.
|
|
184
191
|
3. Report the misconfiguration and the publish method under **Verification** and in the **Factory routing** block of the handoff.
|
|
185
192
|
|
|
186
193
|
If submission fails for any other reason, fall back to `gh pr comment <number> --body-file <file>` so the verdict still lands on the PR, and report the fallback under **Verification** — how the verdict was published is an operational outcome, not an assumption.
|
|
187
194
|
|
|
195
|
+
After the body is posted successfully (review or comment fallback), delete the body file so no later pass can post it.
|
|
196
|
+
|
|
188
197
|
After publishing, reconcile the verdict label: approve adds `status:auto-approved` and removes `status:changes-requested`; request changes adds `status:changes-requested` and removes `status:auto-approved`.
|
|
189
198
|
|
|
190
199
|
**Non-blocking follow-ups become a PR, not homework.** After publishing the re-review, if it produced non-blocking findings with concrete mechanical fixes — typos, small hardening, a supplemental test case, doc touch-ups — implement them yourself instead of leaving them as a burden on the author. Supplemental means coverage beyond what the behavior-tested gate required: a test gap that failed that gate is a requested change on the reviewed PR, never follow-up work:
|
|
@@ -145,7 +145,7 @@ On any mismatch, stop and record it as a blocking security finding with both val
|
|
|
145
145
|
|
|
146
146
|
Compose two artifacts, in order — the **published body** goes on the PR, the **session handoff** goes back into the run's conversation. Don't send either to the conversation yet; both are drafted here, the published body is sent to the PR, the verdict is recorded, and only then is the session handoff posted.
|
|
147
147
|
|
|
148
|
-
The **published body** (what `gh pr review --body-file` receives) **must open with the verdict line**: `Verdict: approve` or `Verdict: request changes`, followed by:
|
|
148
|
+
The **published body** (what `gh pr review --body-file` receives) **must open with the verdict line**: `Verdict: approve` or `Verdict: request changes`, then on the next line `Reviewed head: <full 40-character SHA>` naming the exact head you verified, followed by:
|
|
149
149
|
|
|
150
150
|
- **Findings** — lead with the mechanism of the most consequential finding, then correctness, tests, scope, and pattern-consistency, each grounded in the history you traced. Distill — this is a handoff, not a transcript.
|
|
151
151
|
- **Approach** — the required outcome and the simplest sufficient design from your Phase 1 record, and whether the PR's approach and scope are justified against it. Agreement stated in one line; disagreement with the evidence that supports the alternative.
|
|
@@ -164,19 +164,28 @@ The **session handoff** (posted as the final conversation message after the verd
|
|
|
164
164
|
|
|
165
165
|
**The head must not have moved.** Immediately before publishing, run `gh pr view <number> --json headRefOid --jq .headRefOid` and compare it with the SHA your verification ran on (`git rev-parse HEAD`). A push can land while you verify or wait on bots, and a verdict on a superseded head misleads the author. If the head moved, do not publish: refresh the checkout to the new head, review the new commits and re-run the verification they affect, revise the handoff, then check again. Name the reviewed head SHA in the handoff.
|
|
166
166
|
|
|
167
|
-
Next, publish the review on the PR itself — this is part of every pass, not something to wait to be asked for. Write the published body to `.artifacts/factory-review/pr-<number>.md
|
|
167
|
+
Next, publish the review on the PR itself — this is part of every pass, not something to wait to be asked for. Write the published body to `.artifacts/factory-review/pr-<number>-<headSha>.md`, where `<headSha>` is the full SHA you reviewed. Always write this file fresh in the current pass — never reuse, copy, or edit a body file from an earlier pass, which describes a different head and may carry the opposite verdict.
|
|
168
|
+
|
|
169
|
+
**Gate the file immediately before posting.** Run these checks against the exact file you are about to pass to `--body-file`, and post nothing if any fails — regenerate the body from this pass's findings instead:
|
|
170
|
+
|
|
171
|
+
1. `head -n1 <file>` is exactly `Verdict: approve` when you will use `--approve`, or exactly `Verdict: request changes` when you will use `--request-changes`.
|
|
172
|
+
2. The `Reviewed head:` SHA in the file equals `gh pr view <number> --json headRefOid --jq .headRefOid` and the `reviewedHeadSha` you will pass to `factory_record_review_verdict`.
|
|
173
|
+
|
|
174
|
+
Then submit a PR review matching the verdict:
|
|
168
175
|
|
|
169
176
|
- approve → `gh pr review <number> --approve --body-file <file>`
|
|
170
177
|
- request changes → `gh pr review <number> --request-changes --body-file <file>`
|
|
171
178
|
|
|
172
179
|
**Author-identity misconfiguration must be visible, never silent.** GitHub refuses both approve and request changes from the PR's author, so a review token that authored the PR can never record a verdict in `reviewDecision` or satisfy branch protection. Before submitting, compare the reviewing identity (`gh api user --jq .login`; for an App installation token that call may fail — then treat a submission rejected with GitHub's "Can not approve/request changes on your own pull request" error as the same signal) with the PR's `.author.login`. When they match:
|
|
173
180
|
|
|
174
|
-
1. Add this line to the published body immediately after the
|
|
181
|
+
1. Add this line to the published body immediately after the `Reviewed head:` line (the verdict line stays first and `Reviewed head:` stays second): `> ⚠️ **Factory misconfiguration:** the review token is the PR author, so GitHub cannot record this verdict as an approving or changes-requested review (it will not satisfy branch protection or workflows that require an approving or changes-requested review). Configure a separate reviewer token for Factory reviews.`
|
|
175
182
|
2. Publish with `gh pr comment <number> --body-file <file>`. Do not use `gh pr review --comment`: Factory's repair loop only routes a request-changes verdict from a plain PR comment whose first line is the verdict, and ignores `COMMENTED` reviews.
|
|
176
183
|
3. Report the misconfiguration and the publish method under **Verification** and in the **Factory routing** block of the handoff.
|
|
177
184
|
|
|
178
185
|
If submission fails for any other reason, fall back to `gh pr comment <number> --body-file <file>` so the verdict still lands on the PR, and report the fallback under **Verification** — how the verdict was published is an operational outcome, not an assumption.
|
|
179
186
|
|
|
187
|
+
After the body is posted successfully (review or comment fallback), delete the body file so no later pass can post it.
|
|
188
|
+
|
|
180
189
|
After publishing, reconcile the verdict label: approve adds `status:auto-approved` and removes `status:changes-requested`; request changes adds `status:changes-requested` and removes `status:auto-approved`.
|
|
181
190
|
|
|
182
191
|
**Non-blocking follow-ups become a PR, not homework.** After publishing the review, if it produced non-blocking findings with concrete mechanical fixes — typos, small hardening, a supplemental test case, doc touch-ups — implement them yourself instead of leaving them as a burden on the author. Supplemental means coverage beyond what the behavior-tested gate required: a test gap that failed that gate is a requested change on the reviewed PR, never follow-up work:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/factory",
|
|
3
|
-
"version": "0.18.0",
|
|
3
|
+
"version": "0.18.1-alpha.0",
|
|
4
4
|
"description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"posthog-node": "^5.46.1",
|
|
59
59
|
"zod": "^4.6.4",
|
|
60
60
|
"@mastra/auth-studio": "1.3.7",
|
|
61
|
-
"@mastra/
|
|
62
|
-
"@mastra/
|
|
63
|
-
"@mastra/core": "1.
|
|
64
|
-
"@mastra/
|
|
61
|
+
"@mastra/auth-workos": "1.6.6",
|
|
62
|
+
"@mastra/code-sdk": "1.10.0-alpha.0",
|
|
63
|
+
"@mastra/core": "1.73.0-alpha.0",
|
|
64
|
+
"@mastra/slack": "1.7.1-alpha.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@types/node": "22.20.1",
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
"typescript": "^6.0.3",
|
|
71
71
|
"typescript-eslint": "^8.57.0",
|
|
72
72
|
"vitest": "4.1.11",
|
|
73
|
-
"@
|
|
74
|
-
"@
|
|
75
|
-
"@mastra/pg": "1.28.0",
|
|
73
|
+
"@internal/lint": "0.0.138",
|
|
74
|
+
"@mastra/libsql": "1.24.1-alpha.0",
|
|
75
|
+
"@mastra/pg": "1.28.1-alpha.0",
|
|
76
76
|
"@internal/workspace": "0.0.10",
|
|
77
|
-
"@internal/
|
|
77
|
+
"@internal/types-builder": "0.0.113"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
80
80
|
"node": ">=22.19.0"
|