diff --git a/apps/api/src/handlers/tasks/automation-work-items/auto-act.ts b/apps/api/src/handlers/tasks/automation-work-items/auto-act.ts index ef4668113..9887b38cb 100644 --- a/apps/api/src/handlers/tasks/automation-work-items/auto-act.ts +++ b/apps/api/src/handlers/tasks/automation-work-items/auto-act.ts @@ -55,6 +55,12 @@ const AUTO_ACT_AUTOMATION_CONFIG: Record< executionTaskBootstrap: '$update-dependencies', enforceUniqueTargetEnvironments: true, }, + codeql_triage: { + label: 'CodeQL triage', + maxActItems: 3, + executionTaskBootstrap: '$implement-changes', + enforceUniqueTargetEnvironments: true, + }, security_auditor: { label: 'Security Auditor', maxActItems: 5, diff --git a/apps/api/src/handlers/tasks/automation-work-items/launch.ts b/apps/api/src/handlers/tasks/automation-work-items/launch.ts index a9c98ce4d..ab1957661 100644 --- a/apps/api/src/handlers/tasks/automation-work-items/launch.ts +++ b/apps/api/src/handlers/tasks/automation-work-items/launch.ts @@ -63,6 +63,7 @@ function buildLateBoundChatReplyInstructions( 'When the linked outcome is a draft PR, keep that PR mention in the main sentence or same paragraph instead of adding a second paragraph just to point at the PR. Link the draft PR number if the URL is available; otherwise keep the PR identifier plain text. Prefer wording like "I reviewed [alert #275](...) and opened [draft PR #4783](...)" or "I reviewed the Sentry issue SENTRY-123 and opened the resulting draft PR ..." over "That shipped in draft PR #4783."', 'For blocker, no-op, deferred, or input-needed outcomes, keep the same single-message shape but report only the outcome-relevant details and do not invent remediation or verification sections.', 'For example, a Dependabot closeout should read like: "I reviewed [alert #275](...) and opened [draft PR #4783](...) to address a high and two medium `undici` vulnerabilities in the API dependency bundle."', + 'A CodeQL closeout should read like: "I reviewed [CodeQL alert #42](...) and opened [draft PR #4783](...) to fix an XSS pattern in the public comments renderer."', `A code-quality closeout should read like: "I reviewed the latest merged PRs and noticed the main ${surface} route had taken on auth, validation, logging, and several posting flows at once, which makes changes there risky. I pulled the posting logic into its own module so the route stays focused (#4707)."`, 'A security closeout should read like: "I reviewed the latest merged PRs and caught a background job running with full model credentials and network access over untrusted code, guarded only by prompt text. Unattended runs now skip that path until it is properly isolated, and its output is treated as untrusted (#4711); attended runs are unaffected."', `Notice that these example closeouts stop as soon as the outcome and its impact are stated. Do not append a verification or validation sentence such as "I re-ran the targeted tests and typecheck and cleared the push-time gates before the PR was opened"; that reassurance lives in the linked PR, not the ${surface} closeout.`, diff --git a/apps/api/src/handlers/tasks/automation-work-items/source.ts b/apps/api/src/handlers/tasks/automation-work-items/source.ts index 4b592bd75..3bf6f306b 100644 --- a/apps/api/src/handlers/tasks/automation-work-items/source.ts +++ b/apps/api/src/handlers/tasks/automation-work-items/source.ts @@ -4,6 +4,7 @@ export type AutomationKey = Extract< TaskSuggestionSource, | 'sentry_triage' | 'dependabot_triage' + | 'codeql_triage' | 'security_auditor' | 'code_quality_auditor' | 'ci_failure_triage' @@ -15,6 +16,7 @@ export function isAutomationWorkItemSource( return ( source === 'sentry_triage' || source === 'dependabot_triage' || + source === 'codeql_triage' || source === 'security_auditor' || source === 'code_quality_auditor' || source === 'ci_failure_triage' diff --git a/apps/api/src/handlers/tasks/background-automation-slack.ts b/apps/api/src/handlers/tasks/background-automation-slack.ts index 700400e9e..a70b13d45 100644 --- a/apps/api/src/handlers/tasks/background-automation-slack.ts +++ b/apps/api/src/handlers/tasks/background-automation-slack.ts @@ -21,6 +21,7 @@ type ScheduledSuggestionSurfaceConfig = { | 'suggested_tasks' | 'sentry_triage' | 'dependabot_triage' + | 'codeql_triage' | 'security_auditor' | 'code_quality_auditor' | 'ci_failure_triage'; @@ -28,6 +29,7 @@ type ScheduledSuggestionSurfaceConfig = { | 'suggested_tasks' | 'sentry_triage' | 'dependabot_triage' + | 'codeql_triage' | 'security_auditor' | 'code_quality_auditor' | 'ci_failure_triage'; @@ -44,6 +46,7 @@ export type ScheduledSuggestionSlackConfig = { | 'suggester' | 'sentry_triage' | 'dependabot_triage' + | 'codeql_triage' | 'security_auditor' | 'code_quality_auditor' | 'ci_failure_triage'; @@ -54,6 +57,7 @@ const SCHEDULED_SUGGESTION_SURFACE_CONFIG: Record< | 'suggester' | 'sentry_triage' | 'dependabot_triage' + | 'codeql_triage' | 'security_auditor' | 'code_quality_auditor' | 'ci_failure_triage', @@ -119,6 +123,26 @@ const SCHEDULED_SUGGESTION_SURFACE_CONFIG: Record< 'I went through the open Dependabot alerts for updates worth taking now, and a few low-risk ones stood out.', }, }, + codeql_triage: { + suggestionType: 'codeql_triage', + summaryKind: 'codeql_triage', + actionFooterText: + 'I pulled the strongest CodeQL remediation candidates into the thread for review.', + prompt: { + automationDescription: 'a CodeQL triage automation', + mainActionLine: 'Summarize the CodeQL findings worth fixing.', + highlightLabel: 'CodeQL findings worth fixing', + openerSignal: 'a CodeQL / code-scanning alert triage pass', + openerExamples: [ + 'I went through the open CodeQL alerts for security findings worth fixing before they pile up', + 'I did a CodeQL pass, separating the high-confidence insecure patterns from noisier static findings', + 'I triaged the current code-scanning alerts, watching for anything with real security exposure', + ], + overflowLabel: 'CodeQL finding', + fallbackLead: + 'I went through the open CodeQL alerts for security findings worth fixing now, and a few stood out.', + }, + }, security_auditor: { suggestionType: 'security_auditor', summaryKind: 'security_auditor', @@ -197,6 +221,7 @@ function getScheduledSuggestionSurfaceConfig( automationKey === 'suggester' || automationKey === 'sentry_triage' || automationKey === 'dependabot_triage' || + automationKey === 'codeql_triage' || automationKey === 'security_auditor' || automationKey === 'code_quality_auditor' || automationKey === 'ci_failure_triage' diff --git a/apps/api/src/handlers/tasks/submitTaskSuggestions.ts b/apps/api/src/handlers/tasks/submitTaskSuggestions.ts index 43c619774..e65a53a89 100644 --- a/apps/api/src/handlers/tasks/submitTaskSuggestions.ts +++ b/apps/api/src/handlers/tasks/submitTaskSuggestions.ts @@ -148,6 +148,7 @@ type TaskSuggestionType = | 'suggested_tasks' | 'sentry_triage' | 'dependabot_triage' + | 'codeql_triage' | 'security_auditor' | 'code_quality_auditor' | 'ci_failure_triage'; diff --git a/apps/bullmq/src/scheduler.ts b/apps/bullmq/src/scheduler.ts index 1361cff1d..997a1e990 100644 --- a/apps/bullmq/src/scheduler.ts +++ b/apps/bullmq/src/scheduler.ts @@ -3,6 +3,7 @@ import { Queue, QueueEvents, Worker, Job } from 'bullmq'; import { announcerJob, codeQualityAuditorJob, + codeqlTriageJob, conflictScanJob, dependabotTriageJob, managerStatsJob, @@ -61,6 +62,7 @@ const AUTOMATION_JOBS: Record< manager_stats: managerStatsJob, sentry_triage: sentryTriageJob, dependabot_triage: dependabotTriageJob, + codeql_triage: codeqlTriageJob, security_auditor: securityAuditorJob, code_quality_auditor: codeQualityAuditorJob, }; @@ -126,6 +128,11 @@ async function createJobs(queue: Queue): Promise { { every: 60 * 60 * 1000 }, // Every 60 minutes. ); + await queue.upsertJobScheduler( + 'codeql_triage' satisfies ScheduledAutomationJobName, + { every: 60 * 60 * 1000 }, // Every 60 minutes. + ); + await queue.upsertJobScheduler( 'security_auditor' satisfies ScheduledAutomationJobName, { every: 60 * 60 * 1000 }, // Every 60 minutes. diff --git a/apps/docs/automations.mdx b/apps/docs/automations.mdx index 83900b112..fa9f14540 100644 --- a/apps/docs/automations.mdx +++ b/apps/docs/automations.mdx @@ -79,6 +79,7 @@ manager-facing updates and suggestions. | **Weekly Manager Stats** | A weekly summary of Roomote activity | Weekly | | **Triage Sentry Issues** | Prioritized Sentry follow-up work | Daily or weekly | | **Triage Dependabot Alerts** | Suggested follow-up tasks for open dependency alerts | Daily or weekly | +| **Triage CodeQL Alerts** | Remediation follow-up tasks for open code-scanning / CodeQL alerts | Daily or weekly | | **Security Auditor** | Security follow-up work from recently merged PRs | Every hour, every 6 hours, daily, or weekly | | **Code Quality Auditor** | Code quality follow-up work from recently merged PRs | Every hour, every 6 hours, daily, or weekly | | **Suggest Ideas** | Useful coding work Roomote thinks the team could do | Daily or weekly | @@ -100,11 +101,11 @@ surfaces). Cards also show capability badges for what each automation supports today: the chat surfaces it can report to and the source-control providers it works -with. Triage Dependabot Alerts and CI Failure Triage are GitHub-only by -nature. Security Auditor, Code Quality Auditor, Weekly Manager Stats, Suggest -Ideas, Summarize Merged PRs, and Triage Sentry Issues work with any connected -source-control provider. Resolve PR Conflicts supports GitHub, GitLab, and -Azure DevOps. +with. Triage Dependabot Alerts, Triage CodeQL Alerts, and CI Failure Triage are +GitHub-only by nature. Security Auditor, Code Quality Auditor, Weekly Manager +Stats, Suggest Ideas, Summarize Merged PRs, and Triage Sentry Issues work with +any connected source-control provider. Resolve PR Conflicts supports GitHub, +GitLab, and Azure DevOps. **Code Quality Auditor** inspects recently merged PR diffs and only posts high-confidence maintainability issues worth a real follow-up task. It is @@ -120,6 +121,11 @@ projects. active repositories and suggests tightly scoped follow-up update tasks. It does not open PRs directly from the scheduled scan. +**Triage CodeQL Alerts** scans open GitHub code-scanning alerts (especially +CodeQL findings) across your active repositories and launches tightly scoped +remediation follow-up tasks. It does not open PRs directly from the scheduled +scan. + **Security Auditor** reviews recently merged PRs for concrete security issues and secure-by-default gaps that are worth a real follow-up task. diff --git a/apps/docs/providers/source-control/github.mdx b/apps/docs/providers/source-control/github.mdx index 2118327b4..767c8d6ba 100644 --- a/apps/docs/providers/source-control/github.mdx +++ b/apps/docs/providers/source-control/github.mdx @@ -77,6 +77,7 @@ Grant these repository permissions: - **Commit statuses**: Read-only - **Deployments**: Read-only - **Dependabot alerts**: Read-only +- **Code scanning alerts**: Read-only - **Issues**: Read and write - **Metadata**: Read-only - **Pull requests**: Read and write diff --git a/apps/web/src/components/settings/automations/AutomationsSettings.client.test.tsx b/apps/web/src/components/settings/automations/AutomationsSettings.client.test.tsx index 310b99eac..5de8dd0bb 100644 --- a/apps/web/src/components/settings/automations/AutomationsSettings.client.test.tsx +++ b/apps/web/src/components/settings/automations/AutomationsSettings.client.test.tsx @@ -60,6 +60,9 @@ const baseFormState: FormState = { dependabotTriageFrequency: 'off' as const, dependabotTriageSlackChannel: '', dependabotTriageDiscordChannel: '', + codeqlTriageFrequency: 'off' as const, + codeqlTriageSlackChannel: '', + codeqlTriageDiscordChannel: '', securityAuditorFrequency: 'off' as const, securityAuditorSlackChannel: '', securityAuditorDiscordChannel: '', @@ -553,6 +556,10 @@ describe('Automations selection helpers', () => { ); }); + it('maps #codeql-triage hash to the CodeQL triage section', () => { + expect(resolveAutomationHashTarget('#codeql-triage')).toBe('codeqlTriage'); + }); + it('maps #security-auditor hash to the security auditor section', () => { expect(resolveAutomationHashTarget('#security-auditor')).toBe( 'securityAuditor', diff --git a/apps/web/src/components/settings/automations/AutomationsSettings.render.client.test.tsx b/apps/web/src/components/settings/automations/AutomationsSettings.render.client.test.tsx index dd5781f47..1d279b3f7 100644 --- a/apps/web/src/components/settings/automations/AutomationsSettings.render.client.test.tsx +++ b/apps/web/src/components/settings/automations/AutomationsSettings.render.client.test.tsx @@ -71,6 +71,9 @@ const state = vi.hoisted(() => ({ dependabotTriageFrequency: 'off' as const, dependabotTriageSlackChannelId: null, dependabotTriageDiscordChannelId: null, + codeqlTriageFrequency: 'off' as const, + codeqlTriageSlackChannelId: null, + codeqlTriageDiscordChannelId: null, securityAuditorFrequency: 'off' as const, securityAuditorSlackChannelId: null, securityAuditorDiscordChannelId: null, @@ -104,6 +107,7 @@ const state = vi.hoisted(() => ({ platformIssueSlackChannel: null, sentryTriageSlackChannel: null, dependabotTriageSlackChannel: null, + codeqlTriageSlackChannel: null, securityAuditorSlackChannel: null, codeQualityAuditorSlackChannel: null, ciFailureTriageSlackChannel: null, @@ -117,6 +121,7 @@ const state = vi.hoisted(() => ({ platformIssueSlackChannel: null, sentryTriageSlackChannel: null, dependabotTriageSlackChannel: null, + codeqlTriageSlackChannel: null, securityAuditorSlackChannel: null, codeQualityAuditorSlackChannel: null, ciFailureTriageSlackChannel: null, @@ -136,6 +141,7 @@ const state = vi.hoisted(() => ({ 'manager_stats', 'sentry_triage', 'dependabot_triage', + 'codeql_triage', 'security_auditor', 'code_quality_auditor', 'ci_failure_triage', @@ -566,9 +572,9 @@ describe('AutomationsSettings', () => { it('shows exception-only capability badges from the shared descriptors', async () => { render(); - // Dependabot and CI failure triage stay GitHub-only; manager stats is - // provider-neutral now and shows no source-control badge. - expect((await screen.findAllByText('GitHub only')).length).toBe(2); + // Dependabot, CodeQL, and CI failure triage stay GitHub-only; manager + // stats is provider-neutral now and shows no source-control badge. + expect((await screen.findAllByText('GitHub only')).length).toBe(3); // The suggester supports Slack and Discord destinations; the other // manager automations post to all configured communication providers. expect(screen.queryByText('Slack only')).toBeNull(); diff --git a/apps/web/src/components/settings/automations/AutomationsSettings.tsx b/apps/web/src/components/settings/automations/AutomationsSettings.tsx index f9683dad4..bafce8f86 100644 --- a/apps/web/src/components/settings/automations/AutomationsSettings.tsx +++ b/apps/web/src/components/settings/automations/AutomationsSettings.tsx @@ -47,6 +47,7 @@ import { type ChannelAutoStartFormRow, type ConflictResolverFrequency, type DependabotTriageFrequency, + type CodeqlTriageFrequency, type FormState, isAutomationDirty, type ManagerStatsFrequency, @@ -133,12 +134,14 @@ type FieldErrors = Partial< | 'platformIssueSlackChannel' | 'sentryTriageSlackChannel' | 'dependabotTriageSlackChannel' + | 'codeqlTriageSlackChannel' | 'securityAuditorSlackChannel' | 'codeQualityAuditorSlackChannel' | 'ciFailureTriageSlackChannel' | 'managerStatsDiscordChannel' | 'sentryTriageDiscordChannel' | 'dependabotTriageDiscordChannel' + | 'codeqlTriageDiscordChannel' | 'securityAuditorDiscordChannel' | 'codeQualityAuditorDiscordChannel' | 'ciFailureTriageDiscordChannel' @@ -168,6 +171,7 @@ type SlackChannelAccessWarnings = { platformIssueSlackChannel: string | null; sentryTriageSlackChannel: string | null; dependabotTriageSlackChannel: string | null; + codeqlTriageSlackChannel: string | null; securityAuditorSlackChannel: string | null; codeQualityAuditorSlackChannel: string | null; ciFailureTriageSlackChannel: string | null; @@ -177,6 +181,7 @@ type AutomationSlackDestinationField = | 'managerStatsSlackChannel' | 'sentryTriageSlackChannel' | 'dependabotTriageSlackChannel' + | 'codeqlTriageSlackChannel' | 'securityAuditorSlackChannel' | 'codeQualityAuditorSlackChannel' | 'ciFailureTriageSlackChannel' @@ -188,6 +193,7 @@ const SLACK_DESTINATION_FIELD_AUTOMATION_KEYS = { managerStatsSlackChannel: 'manager_stats', sentryTriageSlackChannel: 'sentry_triage', dependabotTriageSlackChannel: 'dependabot_triage', + codeqlTriageSlackChannel: 'codeql_triage', securityAuditorSlackChannel: 'security_auditor', codeQualityAuditorSlackChannel: 'code_quality_auditor', ciFailureTriageSlackChannel: 'ci_failure_triage', @@ -203,6 +209,7 @@ const SLACK_DESTINATION_FIELD_AUTOMATION_IDS = { managerStatsSlackChannel: 'managerStats', sentryTriageSlackChannel: 'sentryTriage', dependabotTriageSlackChannel: 'dependabotTriage', + codeqlTriageSlackChannel: 'codeqlTriage', securityAuditorSlackChannel: 'securityAuditor', codeQualityAuditorSlackChannel: 'codeQualityAuditor', ciFailureTriageSlackChannel: 'ciFailureTriage', @@ -215,6 +222,7 @@ type AutomationDiscordDestinationField = | 'managerStatsDiscordChannel' | 'sentryTriageDiscordChannel' | 'dependabotTriageDiscordChannel' + | 'codeqlTriageDiscordChannel' | 'securityAuditorDiscordChannel' | 'codeQualityAuditorDiscordChannel' | 'ciFailureTriageDiscordChannel' @@ -228,6 +236,7 @@ const SLACK_TO_DISCORD_DESTINATION_FIELDS = { managerStatsSlackChannel: 'managerStatsDiscordChannel', sentryTriageSlackChannel: 'sentryTriageDiscordChannel', dependabotTriageSlackChannel: 'dependabotTriageDiscordChannel', + codeqlTriageSlackChannel: 'codeqlTriageDiscordChannel', securityAuditorSlackChannel: 'securityAuditorDiscordChannel', codeQualityAuditorSlackChannel: 'codeQualityAuditorDiscordChannel', ciFailureTriageSlackChannel: 'ciFailureTriageDiscordChannel', @@ -313,6 +322,7 @@ const EMPTY_SLACK_CHANNEL_ACCESS_WARNINGS: SlackChannelAccessWarnings = { platformIssueSlackChannel: null, sentryTriageSlackChannel: null, dependabotTriageSlackChannel: null, + codeqlTriageSlackChannel: null, securityAuditorSlackChannel: null, codeQualityAuditorSlackChannel: null, ciFailureTriageSlackChannel: null, @@ -341,6 +351,8 @@ const TRIGGERABLE_AUTOMATION_DESCRIPTIONS = { sentry_triage: 'Scan Sentry issues and post a prioritized triage report.', dependabot_triage: 'Scan open Dependabot alerts and suggest the safest updates.', + codeql_triage: + 'Scan open CodeQL/code-scanning alerts and launch focused remediation tasks.', security_auditor: 'Review recently merged PRs for concrete security issues and secure-by-default gaps.', code_quality_auditor: @@ -425,6 +437,10 @@ function DependabotIcon({ className }: { className?: string }) { ); } +function CodeqlIcon({ className }: { className?: string }) { + return ; +} + function SentryIcon({ className }: { className?: string }) { return ; } @@ -526,6 +542,9 @@ const AUTOMATION_DEFINITIONS: Record = { DependabotIcon, ), }, + codeqlTriage: { + ...getAutomationDefinition('codeqlTriage', 'codeql_triage', CodeqlIcon), + }, ...SCHEDULE_ONLY_AUTOMATION_DEFINITIONS, reviewer: { id: 'reviewer', @@ -572,6 +591,9 @@ const HASH_ALIAS_TO_AUTOMATION_ID: Record = { 'triage-dependabot-alerts': 'dependabotTriage', 'dependabot-triage': 'dependabotTriage', dependabottriage: 'dependabotTriage', + 'triage-codeql-alerts': 'codeqlTriage', + 'codeql-triage': 'codeqlTriage', + codeqltriage: 'codeqlTriage', ...Object.fromEntries( SCHEDULE_ONLY_BACKGROUND_AUTOMATION_LIST.flatMap((automation) => automation.hashAliases.map((hashAlias) => [hashAlias, automation.id]), @@ -599,6 +621,7 @@ const AUTOMATION_RUN_KEYS_BY_ID: Partial< managerStats: 'manager_stats', sentryTriage: 'sentry_triage', dependabotTriage: 'dependabot_triage', + codeqlTriage: 'codeql_triage', ...Object.fromEntries( SCHEDULE_ONLY_BACKGROUND_AUTOMATION_LIST.map((automation) => [ automation.id, @@ -714,6 +737,10 @@ function mapSettingsToFormState( dependabotTriageSlackChannelId: string | null; dependabotTriageSlackChannelName?: string | null; dependabotTriageDiscordChannelId: string | null; + codeqlTriageFrequency: CodeqlTriageFrequency; + codeqlTriageSlackChannelId: string | null; + codeqlTriageSlackChannelName?: string | null; + codeqlTriageDiscordChannelId: string | null; suggesterFrequency: SuggesterFrequency; suggesterSlackChannelId: string | null; suggesterSlackChannelName?: string | null; @@ -796,6 +823,12 @@ function mapSettingsToFormState( '', dependabotTriageDiscordChannel: settings.dependabotTriageDiscordChannelId ?? '', + codeqlTriageFrequency: settings.codeqlTriageFrequency, + codeqlTriageSlackChannel: + settings.codeqlTriageSlackChannelName ?? + settings.codeqlTriageSlackChannelId ?? + '', + codeqlTriageDiscordChannel: settings.codeqlTriageDiscordChannelId ?? '', ...mapScheduleOnlyAutomationFormState(settings), suggesterFrequency: settings.suggesterFrequency, suggesterSlackChannel: @@ -1761,6 +1794,8 @@ export function AutomationsSettings() { dependabotTriageSlackChannelName: settingsQuery.data.slackChannelDisplayNames .dependabotTriageSlackChannel, + codeqlTriageSlackChannelName: + settingsQuery.data.slackChannelDisplayNames.codeqlTriageSlackChannel, suggesterSlackChannelName: settingsQuery.data.slackChannelDisplayNames.suggesterSlackChannel, announcerSlackChannelName: @@ -1878,6 +1913,8 @@ export function AutomationsSettings() { result.slackChannelDisplayNames.sentryTriageSlackChannel, dependabotTriageSlackChannelName: result.slackChannelDisplayNames.dependabotTriageSlackChannel, + codeqlTriageSlackChannelName: + result.slackChannelDisplayNames.codeqlTriageSlackChannel, suggesterSlackChannelName: result.slackChannelDisplayNames.suggesterSlackChannel, announcerSlackChannelName: @@ -1987,6 +2024,7 @@ export function AutomationsSettings() { managerStats: false, sentryTriage: false, dependabotTriage: false, + codeqlTriage: false, ...scheduleOnlyAutomationDirtyState, reviewer: false, conflictResolver: false, @@ -2014,6 +2052,7 @@ export function AutomationsSettings() { savedState, 'dependabotTriage', ), + codeqlTriage: isAutomationDirty(formState, savedState, 'codeqlTriage'), ...scheduleOnlyAutomationDirtyState, reviewer: isAutomationDirty(formState, savedState, 'reviewer'), conflictResolver: isAutomationDirty( @@ -2249,6 +2288,7 @@ export function AutomationsSettings() { const sentryTriageIsEnabled = formState?.sentryTriageFrequency !== 'off'; const dependabotTriageIsEnabled = formState?.dependabotTriageFrequency !== 'off'; + const codeqlTriageIsEnabled = formState?.codeqlTriageFrequency !== 'off'; const scheduleOnlyAutomationEnabledState = buildScheduleOnlyAutomationEnabledState(formState); const sentryTriageSaveDisabled = !canSaveSentryTriageSettings({ @@ -2418,6 +2458,7 @@ export function AutomationsSettings() { managerStats: managerStatsIsEnabled, sentryTriage: sentryTriageIsEnabled, dependabotTriage: dependabotTriageIsEnabled, + codeqlTriage: codeqlTriageIsEnabled, ...scheduleOnlyAutomationEnabledState, reviewer: reviewerIsEnabled, conflictResolver: conflictResolverIsEnabled, @@ -2458,6 +2499,7 @@ export function AutomationsSettings() { ? 'Connect Sentry first' : null; const dependabotTriageBlockedReason = null; + const codeqlTriageBlockedReason = null; const scheduleOnlyAutomationBlockedReasons = Object.fromEntries( SCHEDULE_ONLY_BACKGROUND_AUTOMATION_LIST.map((automation) => [ automation.id, @@ -3170,6 +3212,78 @@ export function AutomationsSettings() { + setAutomationOpen('codeqlTriage', open)} + iconEnabled={iconEnabled.codeqlTriage} + debugSection={renderDebugRunsSection('codeqlTriage')} + runTooltip={getRunTooltip( + 'codeqlTriage', + codeqlTriageIsEnabled, + codeqlTriageBlockedReason, + )} + runDisabled={isRunDisabled( + 'codeqlTriage', + codeqlTriageIsEnabled, + codeqlTriageBlockedReason != null, + )} + onRun={() => + triggerMutation.mutate({ automationKey: 'codeql_triage' }) + } + isDirty={isDirty.codeqlTriage} + isPending={ + updateMutation.isPending && savingAutomation === 'codeqlTriage' + } + onSave={() => saveAgent('codeqlTriage')} + onReset={() => resetAgent('codeqlTriage')} + frequency={formState.codeqlTriageFrequency} + onFrequencyChange={(frequency) => + setFormState((prev) => + prev + ? { + ...prev, + codeqlTriageFrequency: frequency, + } + : prev, + ) + } + scheduleOptions={ + SENTRY_TRIAGE_FREQUENCY_OPTIONS as Array<{ + value: CodeqlTriageFrequency; + label: string; + }> + } + selectId="codeql-triage-frequency" + selectAriaLabel="Triage CodeQL Alerts schedule" + > +
+ {codeqlTriageIsEnabled + ? renderSlackDestinationField({ + field: 'codeqlTriageSlackChannel', + inputId: 'codeql-triage-slack-channel', + label: 'Post follow-up work to this Slack channel', + helperText: + 'Choose where Roomote should post actionable CodeQL follow-up work.', + savedChannelId: + settingsQuery.data?.settings + .codeqlTriageSlackChannelId ?? null, + savedDiscordChannelId: + settingsQuery.data?.settings + .codeqlTriageDiscordChannelId ?? null, + warningChannelId: + slackChannelAccessWarnings.codeqlTriageSlackChannel, + }) + : null} + +

+ Scans current open code-scanning/CodeQL alerts across active + repositories and launches implement-changes follow-up tasks + instead of opening PRs in the scan itself. +

+
+
+ {SCHEDULE_ONLY_BACKGROUND_AUTOMATION_LIST.map((automation) => { const automationUi = SCHEDULE_ONLY_AUTOMATION_UI_DEFINITIONS[automation.id]; diff --git a/apps/web/src/components/settings/automations/formState.ts b/apps/web/src/components/settings/automations/formState.ts index 5b448b4f2..543d0b5e0 100644 --- a/apps/web/src/components/settings/automations/formState.ts +++ b/apps/web/src/components/settings/automations/formState.ts @@ -18,6 +18,7 @@ export type AnnouncerFrequency = 'off' | 'daily' | 'weekly'; export type ManagerStatsFrequency = 'off' | 'weekly'; export type SentryTriageFrequency = 'off' | 'daily' | 'weekly'; export type DependabotTriageFrequency = 'off' | 'daily' | 'weekly'; +export type CodeqlTriageFrequency = 'off' | 'daily' | 'weekly'; export type ReviewerEnvironmentScope = 'all' | 'specific'; export type ReviewerAuthorReviewMode = 'all' | 'specific' | 'none'; @@ -71,6 +72,9 @@ export type FormState = { dependabotTriageFrequency: DependabotTriageFrequency; dependabotTriageSlackChannel: string; dependabotTriageDiscordChannel: string; + codeqlTriageFrequency: CodeqlTriageFrequency; + codeqlTriageSlackChannel: string; + codeqlTriageDiscordChannel: string; suggesterFrequency: SuggesterFrequency; suggesterSlackChannel: string; suggesterDiscordChannel: string; @@ -97,6 +101,7 @@ export type AutomationId = | 'managerStats' | 'sentryTriage' | 'dependabotTriage' + | 'codeqlTriage' | ScheduleOnlyBackgroundAutomationId | 'reviewer' | 'conflictResolver' @@ -150,6 +155,12 @@ const DEPENDABOT_TRIAGE_FIELDS: Array = [ 'dependabotTriageDiscordChannel', ]; +const CODEQL_TRIAGE_FIELDS: Array = [ + 'codeqlTriageFrequency', + 'codeqlTriageSlackChannel', + 'codeqlTriageDiscordChannel', +]; + const SUGGESTER_FIELDS: Array = [ 'suggesterFrequency', 'suggesterSlackChannel', @@ -194,6 +205,7 @@ const AUTOMATION_FIELDS: Record> = { managerStats: MANAGER_STATS_FIELDS, sentryTriage: SENTRY_TRIAGE_FIELDS, dependabotTriage: DEPENDABOT_TRIAGE_FIELDS, + codeqlTriage: CODEQL_TRIAGE_FIELDS, ...SCHEDULE_ONLY_AUTOMATION_FIELDS, reviewer: REVIEWER_FIELDS, conflictResolver: CONFLICT_RESOLVER_FIELDS, @@ -314,6 +326,11 @@ export function buildAutomationSettingsSaveInput( stateToSave.dependabotTriageSlackChannel.trim() || null, dependabotTriageDiscordChannel: stateToSave.dependabotTriageDiscordChannel.trim() || null, + codeqlTriageFrequency: stateToSave.codeqlTriageFrequency, + codeqlTriageSlackChannel: + stateToSave.codeqlTriageSlackChannel.trim() || null, + codeqlTriageDiscordChannel: + stateToSave.codeqlTriageDiscordChannel.trim() || null, ...buildScheduleOnlyAutomationSaveInput(stateToSave), suggesterFrequency: stateToSave.suggesterFrequency, suggesterSlackChannel: stateToSave.suggesterSlackChannel.trim() || null, diff --git a/apps/web/src/trpc/commands/automations/__tests__/settings-update-discord.test.ts b/apps/web/src/trpc/commands/automations/__tests__/settings-update-discord.test.ts index 0ab3f7341..c6221b728 100644 --- a/apps/web/src/trpc/commands/automations/__tests__/settings-update-discord.test.ts +++ b/apps/web/src/trpc/commands/automations/__tests__/settings-update-discord.test.ts @@ -98,6 +98,9 @@ function buildInput( dependabotTriageFrequency: 'off', dependabotTriageSlackChannel: null, dependabotTriageDiscordChannel: null, + codeqlTriageFrequency: 'off', + codeqlTriageSlackChannel: null, + codeqlTriageDiscordChannel: null, securityAuditorFrequency: 'off', securityAuditorSlackChannel: null, securityAuditorDiscordChannel: null, diff --git a/apps/web/src/trpc/commands/automations/settings-read.ts b/apps/web/src/trpc/commands/automations/settings-read.ts index d63e5f5b7..b90e36a33 100644 --- a/apps/web/src/trpc/commands/automations/settings-read.ts +++ b/apps/web/src/trpc/commands/automations/settings-read.ts @@ -80,6 +80,7 @@ const RUN_HISTORY_KEYS: BackgroundAutomationKey[] = [ 'manager_stats', 'sentry_triage', 'dependabot_triage', + 'codeql_triage', 'security_auditor', 'code_quality_auditor', 'ci_failure_triage', @@ -269,6 +270,7 @@ export async function getBackgroundAgentSettingsCommand( platformIssueSlackChannel: string | null; sentryTriageSlackChannel: string | null; dependabotTriageSlackChannel: string | null; + codeqlTriageSlackChannel: string | null; securityAuditorSlackChannel: string | null; codeQualityAuditorSlackChannel: string | null; ciFailureTriageSlackChannel: string | null; @@ -334,6 +336,7 @@ export async function getBackgroundAgentSettingsCommand( sentryTriageSlackChannelId: visibleSettings.sentryTriageSlackChannelId, dependabotTriageSlackChannelId: visibleSettings.dependabotTriageSlackChannelId, + codeqlTriageSlackChannelId: visibleSettings.codeqlTriageSlackChannelId, securityAuditorSlackChannelId: visibleSettings.securityAuditorSlackChannelId, codeQualityAuditorSlackChannelId: @@ -355,6 +358,7 @@ export async function getBackgroundAgentSettingsCommand( sentryTriageSlackChannelId: visibleSettings.sentryTriageSlackChannelId, dependabotTriageSlackChannelId: visibleSettings.dependabotTriageSlackChannelId, + codeqlTriageSlackChannelId: visibleSettings.codeqlTriageSlackChannelId, securityAuditorSlackChannelId: visibleSettings.securityAuditorSlackChannelId, codeQualityAuditorSlackChannelId: diff --git a/apps/web/src/trpc/commands/automations/settings-update.ts b/apps/web/src/trpc/commands/automations/settings-update.ts index 448858005..08e16a5ee 100644 --- a/apps/web/src/trpc/commands/automations/settings-update.ts +++ b/apps/web/src/trpc/commands/automations/settings-update.ts @@ -225,6 +225,7 @@ export async function updateBackgroundAgentSettingsCommand( platformIssueSlackChannel: string | null; sentryTriageSlackChannel: string | null; dependabotTriageSlackChannel: string | null; + codeqlTriageSlackChannel: string | null; securityAuditorSlackChannel: string | null; codeQualityAuditorSlackChannel: string | null; ciFailureTriageSlackChannel: string | null; @@ -245,6 +246,7 @@ export async function updateBackgroundAgentSettingsCommand( const shouldUpdateSentryTriage = input.savingAutomation === 'sentryTriage'; const shouldUpdateDependabotTriage = input.savingAutomation === 'dependabotTriage'; + const shouldUpdateCodeqlTriage = input.savingAutomation === 'codeqlTriage'; const shouldUpdateSuggester = input.savingAutomation === 'suggester'; const shouldUpdateAnnouncer = input.savingAutomation === 'announcer'; const shouldUpdatePlatformIssueAlerts = @@ -365,6 +367,9 @@ export async function updateBackgroundAgentSettingsCommand( const dependabotTriageDiscordChannel = shouldUpdateDependabotTriage ? normalizeOptionalText(input.dependabotTriageDiscordChannel) : null; + const codeqlTriageDiscordChannel = shouldUpdateCodeqlTriage + ? normalizeOptionalText(input.codeqlTriageDiscordChannel) + : null; const securityAuditorDiscordChannel = shouldUpdateSecurityAuditor ? normalizeOptionalText(input.securityAuditorDiscordChannel) : null; @@ -411,6 +416,10 @@ export async function updateBackgroundAgentSettingsCommand( shouldUpdateDependabotTriage && !dependabotTriageDiscordChannel ? normalizeOptionalText(input.dependabotTriageSlackChannel) : null; + const codeqlTriageSlackChannel = + shouldUpdateCodeqlTriage && !codeqlTriageDiscordChannel + ? normalizeOptionalText(input.codeqlTriageSlackChannel) + : null; const suggesterSlackChannel = shouldUpdateSuggester && !suggesterDiscordChannel ? normalizeOptionalText(input.suggesterSlackChannel) @@ -448,6 +457,7 @@ export async function updateBackgroundAgentSettingsCommand( Boolean(managerStatsSlackChannel) || Boolean(sentryTriageSlackChannel) || Boolean(dependabotTriageSlackChannel) || + Boolean(codeqlTriageSlackChannel) || Boolean(suggesterSlackChannel) || Boolean(announcerSlackChannel) || Boolean(platformIssueSlackChannel) || @@ -474,6 +484,7 @@ export async function updateBackgroundAgentSettingsCommand( managerStatsChannelResult, sentryTriageChannelResult, dependabotTriageChannelResult, + codeqlTriageChannelResult, suggesterChannelResult, announcerChannelResult, platformIssueChannelResult, @@ -542,6 +553,17 @@ export async function updateBackgroundAgentSettingsCommand( ? null : existingSettings?.dependabotTriageSlackChannelId, ), + shouldUpdateCodeqlTriage + ? resolveChannelId({ + field: 'codeqlTriageSlackChannel', + input: codeqlTriageSlackChannel, + notifier, + }) + : keepPersistedSlackChannel( + existingSettings?.codeqlTriageDiscordChannelId + ? null + : existingSettings?.codeqlTriageSlackChannelId, + ), shouldUpdateSuggester ? resolveChannelId({ field: 'suggesterSlackChannel', @@ -604,6 +626,7 @@ export async function updateBackgroundAgentSettingsCommand( managerStatsDiscordResult, sentryTriageDiscordResult, dependabotTriageDiscordResult, + codeqlTriageDiscordResult, securityAuditorDiscordResult, codeQualityAuditorDiscordResult, ciFailureTriageDiscordResult, @@ -635,6 +658,14 @@ export async function updateBackgroundAgentSettingsCommand( : keepPersistedDiscordChannel( existingSettings?.dependabotTriageDiscordChannelId, ), + shouldUpdateCodeqlTriage + ? resolveDiscordChannelId({ + field: 'codeqlTriageDiscordChannel', + input: codeqlTriageDiscordChannel, + }) + : keepPersistedDiscordChannel( + existingSettings?.codeqlTriageDiscordChannelId, + ), shouldUpdateSecurityAuditor ? resolveDiscordChannelId({ field: 'securityAuditorDiscordChannel', @@ -689,6 +720,7 @@ export async function updateBackgroundAgentSettingsCommand( managerStatsDiscordResult, sentryTriageDiscordResult, dependabotTriageDiscordResult, + codeqlTriageDiscordResult, securityAuditorDiscordResult, codeQualityAuditorDiscordResult, ciFailureTriageDiscordResult, @@ -713,6 +745,7 @@ export async function updateBackgroundAgentSettingsCommand( managerStatsChannelResult, sentryTriageChannelResult, dependabotTriageChannelResult, + codeqlTriageChannelResult, suggesterChannelResult, announcerChannelResult, platformIssueChannelResult, @@ -847,6 +880,7 @@ export async function updateBackgroundAgentSettingsCommand( }); const sentryTriageFrequency = input.sentryTriageFrequency ?? 'off'; const dependabotTriageFrequency = input.dependabotTriageFrequency ?? 'off'; + const codeqlTriageFrequency = input.codeqlTriageFrequency ?? 'off'; const securityAuditorFrequency = input.securityAuditorFrequency ?? 'off'; const codeQualityAuditorFrequency = input.codeQualityAuditorFrequency ?? 'off'; @@ -864,6 +898,7 @@ export async function updateBackgroundAgentSettingsCommand( | 'managerStatsSlackChannel' | 'sentryTriageSlackChannel' | 'dependabotTriageSlackChannel' + | 'codeqlTriageSlackChannel' | 'securityAuditorSlackChannel' | 'codeQualityAuditorSlackChannel' | 'ciFailureTriageSlackChannel' @@ -910,6 +945,14 @@ export async function updateBackgroundAgentSettingsCommand( dependabotTriageDiscordResult.channelId, field: 'dependabotTriageSlackChannel', }, + { + key: 'codeql_triage', + frequency: codeqlTriageFrequency, + channelId: + codeqlTriageChannelResult.channelId ?? + codeqlTriageDiscordResult.channelId, + field: 'codeqlTriageSlackChannel', + }, { key: 'security_auditor', frequency: securityAuditorFrequency, @@ -996,6 +1039,21 @@ export async function updateBackgroundAgentSettingsCommand( fieldErrors.general || 'Add at least one active repository before enabling Triage Dependabot Alerts.'; } + + if ( + codeqlTriageFrequency !== 'off' && + !(await hasActiveGitHubInstallation()) + ) { + fieldErrors.general = + fieldErrors.general || + 'Connect GitHub before enabling Triage CodeQL Alerts.'; + } + + if (codeqlTriageFrequency !== 'off' && !(await hasActiveRepository())) { + fieldErrors.general = + fieldErrors.general || + 'Add at least one active repository before enabling Triage CodeQL Alerts.'; + } if (Object.keys(fieldErrors).length > 0) { return { success: false, @@ -1129,6 +1187,18 @@ export async function updateBackgroundAgentSettingsCommand( updatedAt: now, }); + await upsertAutomation(tx, { + key: 'codeql_triage', + enabled: codeqlTriageFrequency !== 'off', + schedule: { mode: codeqlTriageFrequency }, + targets: buildDestinationChannelTargets( + codeqlTriageChannelResult.channelId, + codeqlTriageDiscordResult.channelId, + ), + managedTargetKinds: ['slack_channel', 'discord_channel'], + updatedAt: now, + }); + await upsertAutomation(tx, { key: 'security_auditor', enabled: securityAuditorFrequency !== 'off', @@ -1229,6 +1299,7 @@ export async function updateBackgroundAgentSettingsCommand( Boolean(updatedSettings.platformIssueSlackChannelId) || Boolean(updatedSettings.sentryTriageSlackChannelId) || Boolean(updatedSettings.dependabotTriageSlackChannelId) || + Boolean(updatedSettings.codeqlTriageSlackChannelId) || Boolean(updatedSettings.securityAuditorSlackChannelId) || Boolean(updatedSettings.codeQualityAuditorSlackChannelId) || Boolean(updatedSettings.ciFailureTriageSlackChannelId); @@ -1252,6 +1323,7 @@ export async function updateBackgroundAgentSettingsCommand( sentryTriageSlackChannelId: updatedSettings.sentryTriageSlackChannelId, dependabotTriageSlackChannelId: updatedSettings.dependabotTriageSlackChannelId, + codeqlTriageSlackChannelId: updatedSettings.codeqlTriageSlackChannelId, securityAuditorSlackChannelId: updatedSettings.securityAuditorSlackChannelId, codeQualityAuditorSlackChannelId: @@ -1270,6 +1342,7 @@ export async function updateBackgroundAgentSettingsCommand( sentryTriageSlackChannelId: updatedSettings.sentryTriageSlackChannelId, dependabotTriageSlackChannelId: updatedSettings.dependabotTriageSlackChannelId, + codeqlTriageSlackChannelId: updatedSettings.codeqlTriageSlackChannelId, securityAuditorSlackChannelId: updatedSettings.securityAuditorSlackChannelId, codeQualityAuditorSlackChannelId: diff --git a/apps/web/src/trpc/commands/automations/slack-channels.ts b/apps/web/src/trpc/commands/automations/slack-channels.ts index 304d58ad9..75b59ee46 100644 --- a/apps/web/src/trpc/commands/automations/slack-channels.ts +++ b/apps/web/src/trpc/commands/automations/slack-channels.ts @@ -138,6 +138,7 @@ export async function getSlackChannelAccessWarnings({ platformIssueSlackChannelId, sentryTriageSlackChannelId, dependabotTriageSlackChannelId, + codeqlTriageSlackChannelId, securityAuditorSlackChannelId, codeQualityAuditorSlackChannelId, ciFailureTriageSlackChannelId, @@ -150,6 +151,7 @@ export async function getSlackChannelAccessWarnings({ platformIssueSlackChannelId: string | null; sentryTriageSlackChannelId: string | null; dependabotTriageSlackChannelId: string | null; + codeqlTriageSlackChannelId: string | null; securityAuditorSlackChannelId: string | null; codeQualityAuditorSlackChannelId: string | null; ciFailureTriageSlackChannelId: string | null; @@ -164,6 +166,7 @@ export async function getSlackChannelAccessWarnings({ platformIssueSlackChannel: null, sentryTriageSlackChannel: null, dependabotTriageSlackChannel: null, + codeqlTriageSlackChannel: null, securityAuditorSlackChannel: null, codeQualityAuditorSlackChannel: null, ciFailureTriageSlackChannel: null, @@ -178,6 +181,7 @@ export async function getSlackChannelAccessWarnings({ platformIssueSlackChannelId, sentryTriageSlackChannelId, dependabotTriageSlackChannelId, + codeqlTriageSlackChannelId, securityAuditorSlackChannelId, codeQualityAuditorSlackChannelId, ciFailureTriageSlackChannelId, @@ -229,6 +233,11 @@ export async function getSlackChannelAccessWarnings({ membershipByChannelId.get(dependabotTriageSlackChannelId) !== true ? dependabotTriageSlackChannelId : null, + codeqlTriageSlackChannel: + codeqlTriageSlackChannelId && + membershipByChannelId.get(codeqlTriageSlackChannelId) !== true + ? codeqlTriageSlackChannelId + : null, securityAuditorSlackChannel: securityAuditorSlackChannelId && membershipByChannelId.get(securityAuditorSlackChannelId) !== true @@ -257,6 +266,7 @@ export async function getSlackChannelDisplayNames({ platformIssueSlackChannelId, sentryTriageSlackChannelId, dependabotTriageSlackChannelId, + codeqlTriageSlackChannelId, securityAuditorSlackChannelId, codeQualityAuditorSlackChannelId, ciFailureTriageSlackChannelId, @@ -270,6 +280,7 @@ export async function getSlackChannelDisplayNames({ platformIssueSlackChannelId: string | null; sentryTriageSlackChannelId: string | null; dependabotTriageSlackChannelId: string | null; + codeqlTriageSlackChannelId: string | null; securityAuditorSlackChannelId: string | null; codeQualityAuditorSlackChannelId: string | null; ciFailureTriageSlackChannelId: string | null; @@ -284,6 +295,7 @@ export async function getSlackChannelDisplayNames({ platformIssueSlackChannel: null, sentryTriageSlackChannel: null, dependabotTriageSlackChannel: null, + codeqlTriageSlackChannel: null, securityAuditorSlackChannel: null, codeQualityAuditorSlackChannel: null, ciFailureTriageSlackChannel: null, @@ -300,6 +312,7 @@ export async function getSlackChannelDisplayNames({ platformIssueSlackChannel, sentryTriageSlackChannel, dependabotTriageSlackChannel, + codeqlTriageSlackChannel, securityAuditorSlackChannel, codeQualityAuditorSlackChannel, ciFailureTriageSlackChannel, @@ -331,6 +344,9 @@ export async function getSlackChannelDisplayNames({ dependabotTriageSlackChannelId ? notifier.getChannelName(dependabotTriageSlackChannelId) : Promise.resolve(null), + codeqlTriageSlackChannelId + ? notifier.getChannelName(codeqlTriageSlackChannelId) + : Promise.resolve(null), securityAuditorSlackChannelId ? notifier.getChannelName(securityAuditorSlackChannelId) : Promise.resolve(null), @@ -368,6 +384,9 @@ export async function getSlackChannelDisplayNames({ dependabotTriageSlackChannel: dependabotTriageSlackChannel ? `#${dependabotTriageSlackChannel}` : null, + codeqlTriageSlackChannel: codeqlTriageSlackChannel + ? `#${codeqlTriageSlackChannel}` + : null, securityAuditorSlackChannel: securityAuditorSlackChannel ? `#${securityAuditorSlackChannel}` : null, diff --git a/apps/web/src/trpc/commands/automations/types.ts b/apps/web/src/trpc/commands/automations/types.ts index 18e75c7fb..c336276cb 100644 --- a/apps/web/src/trpc/commands/automations/types.ts +++ b/apps/web/src/trpc/commands/automations/types.ts @@ -6,6 +6,7 @@ import type { ConflictResolverMaxPrAgeDays, ConflictResolverFrequency, DependabotTriageFrequency, + CodeqlTriageFrequency, ManagerStatsFrequency, PrReviewSettings, ScheduleOnlyBackgroundAutomationFrequency, @@ -34,12 +35,14 @@ export type BackgroundAgentFieldErrorKey = | 'platformIssueSlackChannel' | 'sentryTriageSlackChannel' | 'dependabotTriageSlackChannel' + | 'codeqlTriageSlackChannel' | 'securityAuditorSlackChannel' | 'codeQualityAuditorSlackChannel' | 'ciFailureTriageSlackChannel' | 'managerStatsDiscordChannel' | 'sentryTriageDiscordChannel' | 'dependabotTriageDiscordChannel' + | 'codeqlTriageDiscordChannel' | 'securityAuditorDiscordChannel' | 'codeQualityAuditorDiscordChannel' | 'ciFailureTriageDiscordChannel' @@ -65,6 +68,7 @@ export type SlackChannelFieldErrorKey = Extract< | 'platformIssueSlackChannel' | 'sentryTriageSlackChannel' | 'dependabotTriageSlackChannel' + | 'codeqlTriageSlackChannel' | 'securityAuditorSlackChannel' | 'codeQualityAuditorSlackChannel' | 'ciFailureTriageSlackChannel' @@ -75,6 +79,7 @@ export type DiscordChannelFieldErrorKey = Extract< | 'managerStatsDiscordChannel' | 'sentryTriageDiscordChannel' | 'dependabotTriageDiscordChannel' + | 'codeqlTriageDiscordChannel' | 'securityAuditorDiscordChannel' | 'codeQualityAuditorDiscordChannel' | 'ciFailureTriageDiscordChannel' @@ -101,6 +106,7 @@ export interface SlackChannelAccessWarnings { platformIssueSlackChannel: string | null; sentryTriageSlackChannel: string | null; dependabotTriageSlackChannel: string | null; + codeqlTriageSlackChannel: string | null; securityAuditorSlackChannel: string | null; codeQualityAuditorSlackChannel: string | null; ciFailureTriageSlackChannel: string | null; @@ -115,6 +121,7 @@ export interface SlackChannelDisplayNames { platformIssueSlackChannel: string | null; sentryTriageSlackChannel: string | null; dependabotTriageSlackChannel: string | null; + codeqlTriageSlackChannel: string | null; securityAuditorSlackChannel: string | null; codeQualityAuditorSlackChannel: string | null; ciFailureTriageSlackChannel: string | null; @@ -131,6 +138,7 @@ export const MANAGER_REPORTING_AUTOMATION_KEYS = [ 'manager_stats', 'sentry_triage', 'dependabot_triage', + 'codeql_triage', 'security_auditor', 'code_quality_auditor', 'ci_failure_triage', @@ -196,6 +204,7 @@ export interface UpdateBackgroundAgentSettingsInput extends ScheduleOnlyAutomati | 'suggester' | 'sentryTriage' | 'dependabotTriage' + | 'codeqlTriage' | ScheduleOnlyBackgroundAutomationId | 'announcer' | 'platformIssueAlerts'; @@ -229,6 +238,9 @@ export interface UpdateBackgroundAgentSettingsInput extends ScheduleOnlyAutomati dependabotTriageFrequency?: DependabotTriageFrequency; dependabotTriageSlackChannel?: string | null; dependabotTriageDiscordChannel?: string | null; + codeqlTriageFrequency?: CodeqlTriageFrequency; + codeqlTriageSlackChannel?: string | null; + codeqlTriageDiscordChannel?: string | null; suggesterFrequency: SuggesterFrequency; suggesterSlackChannel: string | null; suggesterDiscordChannel?: string | null; diff --git a/apps/web/src/trpc/commands/github/mutations.test.ts b/apps/web/src/trpc/commands/github/mutations.test.ts index bb39ee97b..07ec4be0d 100644 --- a/apps/web/src/trpc/commands/github/mutations.test.ts +++ b/apps/web/src/trpc/commands/github/mutations.test.ts @@ -151,6 +151,7 @@ describe('GitHub App manifest commands', () => { merge_queues: 'read', metadata: 'read', pull_requests: 'write', + security_events: 'read', statuses: 'read', vulnerability_alerts: 'read', workflows: 'write', diff --git a/apps/web/src/trpc/commands/github/mutations.ts b/apps/web/src/trpc/commands/github/mutations.ts index c893dc46f..57e3941cc 100644 --- a/apps/web/src/trpc/commands/github/mutations.ts +++ b/apps/web/src/trpc/commands/github/mutations.ts @@ -98,6 +98,7 @@ type GitHubAppManifest = { merge_queues: 'read'; metadata: 'read'; pull_requests: 'write'; + security_events: 'read'; statuses: 'read'; vulnerability_alerts: 'read'; workflows: 'write'; @@ -243,6 +244,7 @@ function buildGitHubAppManifest(): GitHubAppManifest { merge_queues: 'read', metadata: 'read', pull_requests: 'write', + security_events: 'read', statuses: 'read', vulnerability_alerts: 'read', workflows: 'write', diff --git a/apps/web/src/trpc/routers/_app.ts b/apps/web/src/trpc/routers/_app.ts index 642842ff8..f4dbb9bc4 100644 --- a/apps/web/src/trpc/routers/_app.ts +++ b/apps/web/src/trpc/routers/_app.ts @@ -352,6 +352,7 @@ const UPDATE_SETTINGS_SAVING_AUTOMATION_VALUES = [ 'suggester', 'sentryTriage', 'dependabotTriage', + 'codeqlTriage', ...SCHEDULE_ONLY_BACKGROUND_AUTOMATION_IDS, 'announcer', 'platformIssueAlerts', @@ -463,6 +464,14 @@ const automationsRouter = createRouter({ .min(1) .max(160) .nullable(), + codeqlTriageFrequency: z.enum(['off', 'daily', 'weekly']), + codeqlTriageSlackChannel: z.string().trim().min(1).max(160).nullable(), + codeqlTriageDiscordChannel: z + .string() + .trim() + .min(1) + .max(160) + .nullable(), ...SCHEDULE_ONLY_FREQUENCY_FIELD_SHAPE, suggesterFrequency: z.enum(['off', 'daily', 'weekly']), suggesterSlackChannel: z.string().trim().min(1).max(160).nullable(), @@ -2023,6 +2032,7 @@ export const appRouter = createRouter({ 'suggest_ideas', 'sentry_triage', 'dependabot_triage', + 'codeql_triage', 'security_auditor', 'code_quality_auditor', ]) diff --git a/packages/cloud-agents/src/packaged-skill-invocations.ts b/packages/cloud-agents/src/packaged-skill-invocations.ts index d914c9b4b..378323772 100644 --- a/packages/cloud-agents/src/packaged-skill-invocations.ts +++ b/packages/cloud-agents/src/packaged-skill-invocations.ts @@ -16,6 +16,7 @@ const CORE_PACKAGED_SKILL_INVOCATIONS = [ 'create-pr', 'debug-reported-bug', 'dependabot-triage', + 'codeql-triage', 'environment-setup', 'explain-repo-code', 'fix-pr', diff --git a/packages/cloud-agents/src/server/task-suggestion-prompts.ts b/packages/cloud-agents/src/server/task-suggestion-prompts.ts index d7c14c5c7..5b543e850 100644 --- a/packages/cloud-agents/src/server/task-suggestion-prompts.ts +++ b/packages/cloud-agents/src/server/task-suggestion-prompts.ts @@ -109,6 +109,14 @@ export function buildSuggestionTaskPromptText(params: { }); } + if (params.suggestionType === 'codeql_triage') { + return buildCodeqlTriageSuggestionTaskPromptText({ + ...params, + baseText, + investigationContext, + }); + } + return appendOptionalSections(baseText, [ { heading: 'Workspace readiness', @@ -204,3 +212,46 @@ Use the repository's native package manager and run the validation required by t ], ); } + +function buildCodeqlTriageSuggestionTaskPromptText(params: { + title: string; + brief: string; + baseText: string; + investigationContext: string | null | undefined; + readinessMessage?: string | null; + targetRepositoryFullName?: string | null; +}): string { + const repositoryScope = params.targetRepositoryFullName?.trim() || 'unknown'; + const investigationContext = params.investigationContext?.trim(); + + return appendOptionalSections( + `$implement-changes + + + codeql_triage_suggestion + alert_follow_up + ${repositoryScope} + ${params.title} + + +A user chose to implement this CodeQL triage follow-up suggestion: + +${params.baseText} + +Re-verify the exact open CodeQL or code-scanning alert before changing source code. Confirm the alert is still open, capture the rule ID, severity, category, affected path, and line range when available, and then choose the smallest secure remediation that clears the named alert or tightly related alert family. + +Prefer narrow security fixes over broad rewrites. If the cited alert is already closed, dismissed, or no longer relevant, report that and stop unless the request still names another open alert that clearly belongs in the same remediation. + +Run the repository's normal validation before delivery. Do not ship changes that fail the required validation gate.`, + [ + { + heading: 'Workspace readiness', + body: params.readinessMessage, + }, + { + heading: 'Investigation context from the scheduled triage run', + body: investigationContext, + }, + ], + ); +} diff --git a/packages/cloud-agents/src/server/workflows/__tests__/standardTaskExplicitInvocationRouting.test.ts b/packages/cloud-agents/src/server/workflows/__tests__/standardTaskExplicitInvocationRouting.test.ts index 275e5ed56..f37a1b88f 100644 --- a/packages/cloud-agents/src/server/workflows/__tests__/standardTaskExplicitInvocationRouting.test.ts +++ b/packages/cloud-agents/src/server/workflows/__tests__/standardTaskExplicitInvocationRouting.test.ts @@ -135,6 +135,17 @@ describe('Standard Task explicit invocation routing', () => { expect(prompt.startsWith('$dependabot-triage\n')).toBe(true); }); + it('treats productized CodeQL triage invocations as packaged-skill entry in customer repos', () => { + const { prompt } = standardTask({ + description: + '$codeql-triage\n\nbackground-automation', + repo: 'acme/widgets', + requestFormat: 'structured', + }); + + expect(prompt.startsWith('$codeql-triage\n')).toBe(true); + }); + it('treats dependency update follow-up invocations as packaged-skill entry in customer repos', () => { const { prompt } = standardTask({ description: diff --git a/packages/cloud-agents/src/server/workflows/skills/standard/codeql-triage/SKILL.md b/packages/cloud-agents/src/server/workflows/skills/standard/codeql-triage/SKILL.md new file mode 100644 index 000000000..f2223e6e1 --- /dev/null +++ b/packages/cloud-agents/src/server/workflows/skills/standard/codeql-triage/SKILL.md @@ -0,0 +1,57 @@ +--- +name: codeql-triage +description: Review current open CodeQL / GitHub code-scanning alerts with GitHub data, keep scheduled runs read-only, and submit a small set of environment-backed `act` work items that auto-start remediation execution tasks. +--- + +# CodeQL Triage + + +You are a static-analysis security triage specialist. Use GitHub's code-scanning alert data (especially CodeQL) to identify the remediations worth doing now, separate high-signal insecurity fixes from low-value noise, and produce concise operational output. + + + + Use the GitHub access already available in the task environment. Prefer `gh api` for code-scanning alert retrieval, keep scheduled/background runs read-only, and honor any repository scope, Slack channel, run mode, or automation policy supplied in the request. Human-triggered or automation-started follow-up execution work belongs in focused implement-changes tasks, not in this triage scan itself. + + + + Parse the request for `repository_scope`, `slack_channel_id`, `run_mode`, trigger source, any optional `Repository environments` section, and any recent thread feedback. + Verify GitHub CLI or API readiness with a narrow read-only command before scanning alerts. Report a clear setup blocker when GitHub access is missing or the repository scope cannot be queried safely. + For scheduled runs, keep the scan read-only even when an alert looks easy to fix. It is allowed to submit later follow-up work items, but the scan itself must not mutate repositories, open PRs, dismiss alerts, or change GitHub state. + + + + + + Inspect current open code-scanning alerts for each repository in scope, preferring CodeQL-backed findings. Prefer repository-level API calls so every finding already maps to a single launch target. Useful patterns include `gh api repos/{owner}/{repo}/code-scanning/alerts --jq ...` with `state=open`. + Collect only the evidence needed to rank each alert: repository, alert URL or number, rule ID and name, tool name, severity or security severity, category, affected path, start/end lines when available, and a short description of the insecure pattern. + Prioritize alerts by severity, real exploitability or data-exposure impact, availability of a clear secure fix path, number of repositories or files affected, and confidence that a focused code change can land without broad churn. + Deprioritize already-closed or dismissed alerts, pure style findings with no security impact, duplicated alerts that share one root cause already covered by a better candidate, and alerts whose fix path is unclear or requires product decisions first. + Do not change files, open PRs, dismiss alerts, or mutate GitHub state during triage. + + + + + + Start with the repository scope, overall risk, and the highest-priority remediation candidate or no-op result. + Submit actionable candidates with `submit_automation_work_items`. Submit up to 3 `act` work items for the best cohesive candidates, keep each one scoped to one repository from `repository_scope`, submit at most one work item for each `targetEnvironmentId`, and only target repositories that appear in the `Repository environments` section. + Do not submit suggestion work items (they are rejected), do not fall back to bare-repo execution, and do not post a Slack launch announcement after the item is submitted. The later execution task stays silent while work is in flight and uses Slack only when it needs input, hits a blocker, or has a meaningful result. + Write action-first titles such as `Sanitize untrusted HTML in web comments to clear CodeQL XSS alert` or `Bound parameterized query in billing API to clear SQL injection CodeQL alert`. Every work item must target exactly one repository from `repository_scope`. + When a `Repository environments` section is present, copy the matching `targetEnvironmentId` only for repositories explicitly listed there. Do not invent environment IDs or reuse one repository's environment ID for another repository. + Use `security` for alerts that represent real vulnerability remediation work and `chore` for lower-risk hardening that still clears an open CodeQL alert. + In `investigationContext`, include `$implement-changes`, the alert URL or number, rule ID, severity, category, affected path and line range, short alert summary, the exact GitHub CLI commands used during triage, whether the vulnerable path likely touches a running service or user-facing web surface, and what the follow-up task must verify before shipping the fix. + Make the `executionPrompt` start with `$implement-changes` and describe the smallest cohesive remediation: one alert when enough, or one tightly related alert family when they share a single fix surface. Do not submit a broad repository security sweep. + If `submit_automation_work_items` succeeds for one or more work items, do not call `post_to_slack_channel` and do not post a separate Slack summary unless the request explicitly says the scan itself should report launch outcomes. + If `slack_channel_id` is present and there is a GitHub setup/auth blocker (for example missing or suspended access to code-scanning alerts), post a concise report there with `post_to_slack_channel` so the broken run does not disappear silently. Treat repository-level gaps such as code scanning being disabled for a repository, a repository returning zero open alerts, or a repository falling outside configured environment coverage as non-blocking no-op findings for this run, not as Slack-worthy blockers. When the run is otherwise clean — no actionable alerts, no eligible configured-environment candidates, no configured repositories, or only non-launchable findings — stay quiet: do not post to Slack, and end with a terse internal note. A clean read-only run is not worth a channel message. + Keep any `post_to_slack_channel` blocker report plain-language and manager-readable, and do not paste raw GitHub CLI commands, `gh api` invocations, or command transcripts into Slack. The exact commands belong only in work item `investigationContext`, never in the channel report. + End the task response with a terse internal note when follow-up items were submitted or the run was clean, or the concise blocker report when a Slack post was needed. + + + + + +The workflow used GitHub code-scanning alert data as the primary source or reported a clear GitHub/auth/setup blocker. +The scan stayed read-only for scheduled/background runs. +Actionable scheduled findings were submitted as environment-backed `act` work items. +The final report or submitted work items were concise, prioritized, plain-language, and free of raw command transcripts, so they are safe to post in Slack. +Clean scans stayed silent in Slack; only setup/auth blockers were reported there. + diff --git a/packages/db/src/lib/automations.ts b/packages/db/src/lib/automations.ts index c451810ab..a43739398 100644 --- a/packages/db/src/lib/automations.ts +++ b/packages/db/src/lib/automations.ts @@ -8,6 +8,7 @@ import { type BackgroundAutomationProvider, type BackgroundAutomationTargetKind, type CodeQualityAuditorFrequency, + type CodeqlTriageFrequency, type ConflictResolverFrequency, type ConflictResolverMaxPrAgeDays, type DependabotTriageFrequency, @@ -73,6 +74,9 @@ export const SENTRY_TRIAGE_FREQUENCIES = export const DEPENDABOT_TRIAGE_FREQUENCIES = getScheduleModes('dependabot_triage'); +export const CODEQL_TRIAGE_FREQUENCIES = + getScheduleModes('codeql_triage'); + export const SECURITY_AUDITOR_FREQUENCIES = getScheduleModes('security_auditor'); @@ -672,6 +676,7 @@ export function normalizeBackgroundAgentSettings( const platformIssueAlerts = automationMap.get('platform_issue_alerts'); const sentryTriage = automationMap.get('sentry_triage'); const dependabotTriage = automationMap.get('dependabot_triage'); + const codeqlTriage = automationMap.get('codeql_triage'); const securityAuditor = automationMap.get('security_auditor'); const codeQualityAuditor = automationMap.get('code_quality_auditor'); const ciFailureTriage = automationMap.get('ci_failure_triage'); @@ -784,6 +789,18 @@ export function normalizeBackgroundAgentSettings( getAutomationDiscordChannelTarget(dependabotTriage), dependabotTriageLastRunAt: dependabotTriage?.lastRunAt ?? null, + codeqlTriageFrequency: getAutomationFrequency( + codeqlTriage, + isFrequencyOf(CODEQL_TRIAGE_FREQUENCIES), + ), + codeqlTriageSlackChannelId: resolveAutomationSlackChannelId( + codeqlTriage, + managerSlackChannelId, + ), + codeqlTriageDiscordChannelId: + getAutomationDiscordChannelTarget(codeqlTriage), + codeqlTriageLastRunAt: codeqlTriage?.lastRunAt ?? null, + securityAuditorFrequency: getAutomationFrequency( securityAuditor, isFrequencyOf(SECURITY_AUDITOR_FREQUENCIES), diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 76afcb72c..b6ce88ab2 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -2631,6 +2631,7 @@ export type SuggestionType = | 'suggested_tasks' | 'sentry_triage' | 'dependabot_triage' + | 'codeql_triage' | 'security_auditor' | 'code_quality_auditor' | 'ci_failure_triage' diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts index 6c99d2c10..e313b8aac 100644 --- a/packages/db/src/types.ts +++ b/packages/db/src/types.ts @@ -6,6 +6,7 @@ import type { CodeQualityAuditorFrequency, ConflictResolverFrequency, ConflictResolverMaxPrAgeDays, + CodeqlTriageFrequency, DependabotTriageFrequency, ManagerStatsFrequency, PrReviewSettings, @@ -485,6 +486,10 @@ export type BackgroundAgentSettings = StoredBackgroundAgentSettings & { dependabotTriageSlackChannelId: string | null; dependabotTriageDiscordChannelId: string | null; dependabotTriageLastRunAt: Date | null; + codeqlTriageFrequency: CodeqlTriageFrequency; + codeqlTriageSlackChannelId: string | null; + codeqlTriageDiscordChannelId: string | null; + codeqlTriageLastRunAt: Date | null; securityAuditorFrequency: SecurityAuditorFrequency; securityAuditorSlackChannelId: string | null; securityAuditorDiscordChannelId: string | null; diff --git a/packages/sdk/src/server/automations/codeql-triage.ts b/packages/sdk/src/server/automations/codeql-triage.ts new file mode 100644 index 000000000..694346627 --- /dev/null +++ b/packages/sdk/src/server/automations/codeql-triage.ts @@ -0,0 +1,125 @@ +import { + buildRepositoryCoverage, + formatRepositoryEnvironmentLines, + getEnvironmentBackedCoverage, + type RepositoryCoverage, +} from '@roomote/cloud-agents/server'; +import { ALL_REPOSITORIES } from '@roomote/types'; + +import { loadAutomationThreadFeedbackContext } from './automation-thread-feedback'; +import { + buildDestinationPromptContext, + type ResolvedAutomationDestination, +} from './destination'; +import { + getActiveRepositoryFullNames, + hasActiveGitHubInstallation, +} from './github-deployment-scope'; +import { createScheduledTriageJob } from './scheduled-triage-runner'; + +function buildCodeqlTriagePrompt({ + channelId, + destination, + repositoryFullNames, + repositoryCoverage, + manualTrigger, + recentThreadFeedback, +}: { + channelId: string; + destination: ResolvedAutomationDestination; + repositoryFullNames: string[]; + repositoryCoverage: RepositoryCoverage[]; + manualTrigger: boolean; + recentThreadFeedback?: string | null; +}): string { + const promptContext = buildDestinationPromptContext(destination); + const repositoryScope = + repositoryFullNames.length > 0 + ? repositoryFullNames.map((fullName) => `- ${fullName}`).join('\n') + : 'No repositories from configured Roomote environments are eligible for action-taking follow-up tasks.'; + const repositoryEnvironmentScope = + formatRepositoryEnvironmentLines(repositoryCoverage); + const repositoryEnvironmentSection = repositoryEnvironmentScope + ? `\nRepository environments:\n${repositoryEnvironmentScope}\n` + : ''; + const followUpInstructions = `If you find actionable candidates, submit up to 3 \`act\` automation work items with \`submit_automation_work_items\`. Do not submit any suggestion work items; they are rejected. Submit at most one work item for each \`targetEnvironmentId\`. Only consider repositories that appear in the "Repository environments" list below. Do not fall back to bare-repo launches. Pick the highest-priority cohesive CodeQL alert remediations across the eligible repositories. Prefer one alert or one tightly related alert family per work item. Start with the narrowest secure fix that is likely to work; do not turn one alert into a broad security sweep unless separate findings truly share one root cause and fix surface. + +Each submitted act item must: +- target exactly one repository from repository_scope +- copy the matching \`targetEnvironmentId\` from the "Repository environments" list +- include \`executionPrompt\` that starts with \`$implement-changes\` +- include investigationContext with the alert URL or number, rule ID, severity, category if available, affected path and line range when known, the exact GitHub CLI or API commands used during triage, and the validation the execution task must perform before opening a PR +- use category "security" for real vulnerability remediation and "chore" for lower-risk hardening that still clears an open CodeQL alert + +If \`submit_automation_work_items\` succeeds for one or more act items, do not call \`${promptContext.postToolName}\` and do not post a launch announcement. Each execution task starts silently and creates ${promptContext.surfaceLabel} output only later if it needs input, hits a blocker, or finishes with a result. End the task response with a terse internal note that action items were submitted. + +If there are no actionable alerts, no eligible configured-environment candidates, or no configured environment coverage, do not post to ${promptContext.surfaceLabel}; end with a terse internal note. Treat repository-level gaps such as code scanning or CodeQL being disabled for a repository, a repository returning zero open alerts, or a repository falling outside configured environment coverage as non-blocking no-op findings for this run, not as GitHub setup/auth blockers worth a ${promptContext.surfaceLabel} post. A clean read-only run is not worth a channel message. Post a concise report to the configured ${promptContext.surfaceLabel} channel with \`${promptContext.postToolName}\` only for GitHub setup/auth blockers (for example missing or suspended code-scanning alert access), so configuration failures do not disappear silently. Keep any such report plain-language and manager-readable, and do not paste the raw GitHub CLI commands, \`gh api\` invocations, or command transcripts into ${promptContext.surfaceLabel}; the exact commands belong only in work item \`investigationContext\`.`; + + return `$codeql-triage + + + background-automation + read_only + ${manualTrigger ? 'manual' : 'scheduled'} + current_open_codeql_code_scanning_alerts + <${promptContext.channelTag}>${channelId} + +${repositoryScope} + + + +Run CodeQL triage with the GitHub access already available in the task environment. Keep this run read-only. + +${followUpInstructions} + +${repositoryEnvironmentSection} + +${recentThreadFeedback?.trim() ? `Recent feedback from earlier CodeQL triage threads:\n${recentThreadFeedback.trim()}\n` : ''}`; +} + +export const codeqlTriageJob = createScheduledTriageJob({ + automationKey: 'codeql_triage', + async buildScanTask({ channelId, destination, manualTrigger }) { + if (!(await hasActiveGitHubInstallation())) { + return { kind: 'skip', reason: 'GitHub is not configured' }; + } + + const selectedRepositories = await getActiveRepositoryFullNames(); + const repositoryCoverage = + await buildRepositoryCoverage(selectedRepositories); + // CodeQL follow-ups must run validation before opening PRs, so the scan + // only targets repositories backed by a configured environment. + const environmentBackedRepositories = getEnvironmentBackedCoverage( + repositoryCoverage, + ).map((coverage) => coverage.repositoryFullName); + const recentThreadFeedback = await loadAutomationThreadFeedbackContext({ + automationKey: 'codeql_triage', + slackChannelId: channelId, + surface: destination.provider, + }); + + return { + kind: 'scan', + payload: { + repo: ALL_REPOSITORIES, + ...(environmentBackedRepositories.length > 0 + ? { selectedRepositories: environmentBackedRepositories } + : {}), + description: buildCodeqlTriagePrompt({ + channelId, + destination, + repositoryFullNames: environmentBackedRepositories, + repositoryCoverage, + manualTrigger, + recentThreadFeedback, + }), + trigger: 'scheduled', + ...(destination.provider === 'slack' + ? { notifySlack: true, slackChannel: channelId } + : {}), + suggestionSource: 'codeql_triage', + visibleInTranscript: false, + }, + }; + }, +}); diff --git a/packages/sdk/src/server/automations/index.ts b/packages/sdk/src/server/automations/index.ts index 2f585a506..02a815e49 100644 --- a/packages/sdk/src/server/automations/index.ts +++ b/packages/sdk/src/server/automations/index.ts @@ -1,6 +1,7 @@ export { announcerJob } from './announcer'; export { ciFailureTriageJob } from './ci-failure-triage'; export { codeQualityAuditorJob } from './code-quality-auditor'; +export { codeqlTriageJob } from './codeql-triage'; export { conflictScanJob } from './conflict-scan'; export { dependabotTriageJob } from './dependabot-triage'; export { managerStatsJob, formatManagerStatsMessage } from './manager-stats'; diff --git a/packages/sdk/src/server/automations/run-now.ts b/packages/sdk/src/server/automations/run-now.ts index 2feec8a30..d973ed7fc 100644 --- a/packages/sdk/src/server/automations/run-now.ts +++ b/packages/sdk/src/server/automations/run-now.ts @@ -3,6 +3,7 @@ import type { TriggerableBackgroundAutomationKey } from '@roomote/types'; import { announcerJob } from './announcer'; import { ciFailureTriageJob } from './ci-failure-triage'; import { codeQualityAuditorJob } from './code-quality-auditor'; +import { codeqlTriageJob } from './codeql-triage'; import { conflictScanJob } from './conflict-scan'; import { dependabotTriageJob } from './dependabot-triage'; import { managerStatsJob } from './manager-stats'; @@ -25,6 +26,7 @@ const AUTOMATION_RUNNERS: Record< manager_stats: managerStatsJob, sentry_triage: sentryTriageJob, dependabot_triage: dependabotTriageJob, + codeql_triage: codeqlTriageJob, security_auditor: securityAuditorJob, code_quality_auditor: codeQualityAuditorJob, ci_failure_triage: ciFailureTriageJob, diff --git a/packages/sdk/src/server/automations/scheduled-triage-runner.ts b/packages/sdk/src/server/automations/scheduled-triage-runner.ts index e974ea097..84750f618 100644 --- a/packages/sdk/src/server/automations/scheduled-triage-runner.ts +++ b/packages/sdk/src/server/automations/scheduled-triage-runner.ts @@ -40,7 +40,11 @@ export type TriageScanBuild = | { kind: 'skip'; reason: string }; type ScheduledTriageAutomationConfig = { - automationKey: 'sentry_triage' | 'dependabot_triage' | 'ci_failure_triage'; + automationKey: + | 'sentry_triage' + | 'dependabot_triage' + | 'codeql_triage' + | 'ci_failure_triage'; /** * Builds the scan task payload for the deployment, or skips with a logged * reason (missing credentials, no eligible repositories, ...). diff --git a/packages/sdk/src/server/lib/manager-slack.ts b/packages/sdk/src/server/lib/manager-slack.ts index 1356998a3..b09f70f0b 100644 --- a/packages/sdk/src/server/lib/manager-slack.ts +++ b/packages/sdk/src/server/lib/manager-slack.ts @@ -1,6 +1,7 @@ import { AUTO_RESPOND_CHANNELS_SETTINGS_HASH, CODE_QUALITY_AUDITOR_SETTINGS_HASH, + CODEQL_TRIAGE_SETTINGS_HASH, DEPENDABOT_TRIAGE_SETTINGS_HASH, getBackgroundAutomationSettingsDescriptor, MANAGER_CHANNEL_SETTINGS_HASH, @@ -19,6 +20,7 @@ const DEFAULT_LOCAL_R_APP_URL = 'http://localhost:13000'; export { AUTO_RESPOND_CHANNELS_SETTINGS_HASH, CODE_QUALITY_AUDITOR_SETTINGS_HASH, + CODEQL_TRIAGE_SETTINGS_HASH, DEPENDABOT_TRIAGE_SETTINGS_HASH, MANAGER_CHANNEL_SETTINGS_HASH, MANAGER_STATS_SETTINGS_HASH, diff --git a/packages/types/src/background-agents.ts b/packages/types/src/background-agents.ts index 2f0d506b9..05d336bd2 100644 --- a/packages/types/src/background-agents.ts +++ b/packages/types/src/background-agents.ts @@ -23,6 +23,8 @@ export type ManagerStatsFrequency = 'off' | 'weekly'; export type SentryTriageFrequency = 'off' | 'daily' | 'weekly'; export type DependabotTriageFrequency = 'off' | 'daily' | 'weekly'; + +export type CodeqlTriageFrequency = 'off' | 'daily' | 'weekly'; export const SCHEDULE_ONLY_BACKGROUND_AUTOMATION_FREQUENCIES = [ 'off', 'every_hour', @@ -85,6 +87,7 @@ export const USER_FACING_AUTOMATION_KEYS = [ 'platform_issue_alerts', 'sentry_triage', 'dependabot_triage', + 'codeql_triage', 'security_auditor', 'code_quality_auditor', 'ci_failure_triage', @@ -251,6 +254,7 @@ export const SCHEDULE_ONLY_BACKGROUND_AUTOMATION_LIST = Object.values( export const BETA_BACKGROUND_AUTOMATION_KEYS = [ 'sentry_triage', 'dependabot_triage', + 'codeql_triage', 'security_auditor', 'code_quality_auditor', 'ci_failure_triage', diff --git a/packages/types/src/background-automation-registry.ts b/packages/types/src/background-automation-registry.ts index f76386330..0ee4d73d8 100644 --- a/packages/types/src/background-automation-registry.ts +++ b/packages/types/src/background-automation-registry.ts @@ -5,6 +5,7 @@ import type { CiFailureTriageFrequency, CodeQualityAuditorFrequency, ConflictResolverFrequency, + CodeqlTriageFrequency, DependabotTriageFrequency, ManagerStatsFrequency, SecurityAuditorFrequency, @@ -22,6 +23,7 @@ export const MANAGER_STATS_SETTINGS_HASH = 'weekly-manager-stats'; export const SUGGEST_IDEAS_SETTINGS_HASH = 'suggest-ideas'; export const SENTRY_TRIAGE_SETTINGS_HASH = 'sentry-triage'; export const DEPENDABOT_TRIAGE_SETTINGS_HASH = 'dependabot-triage'; +export const CODEQL_TRIAGE_SETTINGS_HASH = 'codeql-triage'; export const SECURITY_AUDITOR_SETTINGS_HASH = 'security-auditor'; export const CODE_QUALITY_AUDITOR_SETTINGS_HASH = 'code-quality-auditor'; export const CI_FAILURE_TRIAGE_SETTINGS_HASH = 'ci-failure-triage'; @@ -34,6 +36,7 @@ export type BackgroundAutomationSettingsHash = | typeof SUGGEST_IDEAS_SETTINGS_HASH | typeof SENTRY_TRIAGE_SETTINGS_HASH | typeof DEPENDABOT_TRIAGE_SETTINGS_HASH + | typeof CODEQL_TRIAGE_SETTINGS_HASH | typeof SECURITY_AUDITOR_SETTINGS_HASH | typeof CODE_QUALITY_AUDITOR_SETTINGS_HASH | typeof CI_FAILURE_TRIAGE_SETTINGS_HASH @@ -94,6 +97,7 @@ const DAILY_WEEKLY_SCHEDULE_MODES = [ | AnnouncerFrequency | SentryTriageFrequency | DependabotTriageFrequency + | CodeqlTriageFrequency )[]; // CI failure triage is webhook-driven; 'daily' only means enabled. @@ -191,6 +195,17 @@ export const TRIGGERABLE_BACKGROUND_AUTOMATION_DESCRIPTORS = [ supportedSourceControlProviders: ['github'], scheduledSuggestionSource: 'dependabot_triage', }, + { + automationKey: 'codeql_triage', + label: 'Triage CodeQL Alerts', + availability: 'stable', + scheduleModes: DAILY_WEEKLY_SCHEDULE_MODES, + manualTriggerRequirements: ['slack', 'github', 'repository'], + usesManagerChannel: true, + supportedCommunicationProviders: ['slack', 'teams', 'telegram', 'discord'], + supportedSourceControlProviders: ['github'], + scheduledSuggestionSource: 'codeql_triage', + }, { automationKey: 'security_auditor', label: 'Security Auditor', @@ -294,6 +309,10 @@ const BACKGROUND_AUTOMATION_SETTINGS_CATALOG = [ hash: DEPENDABOT_TRIAGE_SETTINGS_HASH, automationKey: 'dependabot_triage', }, + { + hash: CODEQL_TRIAGE_SETTINGS_HASH, + automationKey: 'codeql_triage', + }, { hash: SECURITY_AUDITOR_SETTINGS_HASH, automationKey: 'security_auditor', diff --git a/packages/types/src/task-runs.ts b/packages/types/src/task-runs.ts index 706925c66..926c77ae3 100644 --- a/packages/types/src/task-runs.ts +++ b/packages/types/src/task-runs.ts @@ -259,6 +259,7 @@ export const TASK_SUGGESTION_SOURCES = [ 'suggest_ideas', 'sentry_triage', 'dependabot_triage', + 'codeql_triage', 'security_auditor', 'code_quality_auditor', 'ci_failure_triage',