Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

## Scope

Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contract currently defines Security Agent language and notification ownership boundaries used across sync, web, email, remediation, tests, and product documentation.
Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contract defines Code Reviewer and Security Agent language plus ownership boundaries used across review execution, analytics, sync, web, email, remediation, tests, and product documentation.

## Contexts

| Context | Owns | Location | Notes |
|---|---|---|---|
| **Code Reviewer** | Pull request and merge request review execution, Code Review Findings, review settings, and Review Analytics | `apps/web/src/lib/code-reviews/`, `apps/web/src/components/code-reviews/` | A Code Reviewer owner is either one user or one organization; Review Analytics collection is organization- and platform-scoped |
| **Security Agent** | Security Findings, owner-scoped policy, settings, Auto Remediation, and user-visible outcomes | `apps/web/src/lib/security-agent/`, `apps/web/src/components/security-agent/`, `.specs/security-agent.md` | A Security Agent owner is either one user or one organization |
| **Security Sync** | Dependabot synchronization, finding persistence, notification eligibility, recipient intent materialization, and durable notification state | `services/security-sync/` | Event state remains owner-scoped; email sending does not occur inside finding persistence transactions |
| **Security Agent Email Delivery** | Dispatch-time revalidation, email rendering, owner-aware links, and Mailgun delivery | `apps/web/src/app/api/internal/security-agent/`, `apps/web/src/lib/email.ts`, `apps/web/src/emails/` | Accepts notification identity only and loads current data before sending |
Expand All @@ -17,6 +18,10 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr

| Term | Agent meaning | Use this when | Avoid |
|---|---|---|---|
| **Code Reviewer** | Agent that reviews pull requests and merge requests and may raise Code Review Findings | Naming the product capability, settings, review execution, and analytics | Security Agent, review bot |
| **Code Review Finding** | Model-generated issue newly raised by Code Reviewer during one review execution | Referring to Code Reviewer output or its controlled analytics taxonomy | Security Finding, confirmed bug, verified vulnerability |
| **Review Analytics** | Organization-only, opt-in prospective collection of bounded classifications for completed reviews and newly raised Code Review Findings | Referring to the Code Reviewer Analytics tab, collection setting, coverage, or aggregate metrics | Security Agent analytics, historical backfill |
| **AI-estimated impact** | Code Reviewer's low, medium, or high estimate of a change's reach and consequence, independent of diff size, change type, complexity, and finding count | Referring to impact classifications or derived impact points | Developer quality, individual performance, delivered impact |
| **Security Agent** | Agent that syncs, analyzes, and helps resolve repository Security Findings | Naming product capability, settings, routes, and behavior | Security Reviews |
| **Security Finding** | Vulnerability item owned by one user or organization for a repository, usually synced from Dependabot | Referring to Kilo's persisted vulnerability domain object | Security review, alert |
| **Auto Remediation** | Security Agent feature that automatically starts Security Remediations for eligible Security Findings | Referring to policy-driven remediation admission | Auto Fix |
Expand All @@ -32,6 +37,9 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr

## Relationships

- A **Code Review Finding** belongs to one captured Code Reviewer review result and contains only controlled taxonomy values in Review Analytics.
- **Review Analytics** enrollment is available only to organization-owned reviews and is snapshotted when a Code Reviewer execution attempt is dispatched; changing the setting does not change an in-flight attempt.
- **AI-estimated impact** describes a reviewed change and remains independent from Code Review Finding counts.
- A **Security Finding** belongs to exactly one Security Agent owner: one user or one organization.
- A **Security Finding** can create at most one **Security Agent Notification** of each kind per **Notification Recipient**.
- A **New-finding Notification** depends on first insertion into Kilo, not source alert creation time.
Expand All @@ -42,6 +50,10 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr

## Agent Rules

- Use **Code Review Finding** for an issue raised by Code Reviewer. Never call it a **Security Finding**, even when its category is `security`.
- Describe Review Analytics values as model-generated signals: use "findings raised" and **AI-estimated impact**, not confirmed bugs, verified vulnerabilities, or developer quality.
- Keep Review Analytics organization-only, prospective, and opt-in. Missing, invalid, or omitted structured results are unavailable coverage states, not zero-finding reviews.
- Do not persist finding prose, code, paths, lines, symbols, prompts, raw manifests, or full assistant output in Review Analytics.
- Use **Security Finding** for Kilo's persisted domain object. Use "Dependabot alert" only for external source object at GitHub boundary.
- Use exact notification kind when discussing eligibility or history: **New-finding Notification**, **SLA Warning Notification**, or **SLA Breach Notification**.
- Treat "new" as first insertion for owner in Kilo. Updates and reopening do not make finding new again.
Expand All @@ -55,6 +67,8 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr

| Ambiguous term | Problem | Canonical decision |
|---|---|---|
| finding | Can mean Code Reviewer output or the Security Agent's persisted vulnerability object | Use **Code Review Finding** for Code Reviewer output and **Security Finding** only for the Security Agent domain object |
| impact | Can imply delivered business value, diff size, complexity, or individual performance | Use **AI-estimated impact** only for the model-generated reach-and-consequence classification |
| alert | Can mean external Dependabot alert, persisted Security Finding, or outgoing notification | Use "Dependabot alert" at source boundary, **Security Finding** after persistence, and exact notification kind for outgoing event |
| notification email | Conflates durable semantic event with retryable provider side effect | Use **Security Agent Notification** for event and **Email Delivery** for send attempt |
| new finding | Can mean newly created at source, first observed, inserted, updated, or reopened | For notification policy, it means first insertion into Kilo for owner |
Expand All @@ -63,6 +77,8 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr

## Context Boundaries

- **Code Reviewer** owns review execution, Code Review Findings, Review Analytics settings, and user-visible aggregate review signals.
- Review Analytics stores bounded taxonomy observations separately from Security Agent `security_findings` and does not establish a cross-review finding lifecycle.
- **Security Agent** owns product policy, settings, permissions, and user-visible finding/remediation outcomes.
- **Security Sync** owns finding synchronization, notification event admission, recipient intent materialization, deduplication, and durable state transitions.
- **Security Agent Email Delivery** may revalidate and deliver an existing notification but must not create notification eligibility or copy mutable finding data into Worker request.
Expand All @@ -71,5 +87,6 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr

## Decision References

- `.plans/code-review-analytics.md` defines prospective Review Analytics collection, taxonomy, persistence, and metric semantics.
- `.specs/security-agent.md` defines Security Agent Auto Remediation and notification guarantees.
- `.plans/security-agent-notifications.md` records notification implementation and rollout design.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { SetPageTitle } from '@/components/SetPageTitle';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Rocket, ExternalLink, Settings2, ListChecks, Brain } from 'lucide-react';
import { Brain, ExternalLink, ListChecks, Rocket, Settings2 } from 'lucide-react';
import { useTRPC } from '@/lib/trpc/utils';
import { useQuery } from '@tanstack/react-query';
import Link from 'next/link';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ import { ReviewConfigForm } from '@/components/code-reviews/ReviewConfigForm';
import { CodeReviewActionRequiredAlert } from '@/components/code-reviews/CodeReviewActionRequiredAlert';
import { CodeReviewJobsCard } from '@/components/code-reviews/CodeReviewJobsCard';
import { ReviewMemoryPanel } from '@/components/code-reviews/ReviewMemoryPanel';
import { CodeReviewAnalyticsPanel } from '@/components/code-reviews/analytics/CodeReviewAnalyticsPanel';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { SetPageTitle } from '@/components/SetPageTitle';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Rocket, ExternalLink, Settings2, ListChecks, Brain } from 'lucide-react';
import {
Brain,
ChartColumnIncreasing,
ExternalLink,
ListChecks,
Rocket,
Settings2,
} from 'lucide-react';
import { useTRPC } from '@/lib/trpc/utils';
import { useQuery } from '@tanstack/react-query';
import Link from 'next/link';
Expand Down Expand Up @@ -177,7 +185,7 @@ export function ReviewAgentPageClient({

{/* GitHub Configuration Tabs */}
<Tabs defaultValue="config" className="w-full">
<TabsList className="grid w-full max-w-2xl grid-cols-3">
<TabsList className="grid h-auto w-full max-w-2xl grid-cols-2 sm:grid-cols-4">
<TabsTrigger value="config" className="flex items-center gap-2">
<Settings2 className="h-4 w-4" />
Config
Expand All @@ -198,6 +206,10 @@ export function ReviewAgentPageClient({
<Brain className="h-4 w-4" />
Memory
</TabsTrigger>
<TabsTrigger value="analytics" className="flex items-center gap-2">
<ChartColumnIncreasing className="h-4 w-4" />
Analytics
</TabsTrigger>
</TabsList>

<TabsContent value="config" className="mt-6 space-y-4">
Expand Down Expand Up @@ -232,6 +244,10 @@ export function ReviewAgentPageClient({
</Alert>
)}
</TabsContent>

<TabsContent value="analytics" className="mt-6 space-y-4">
<CodeReviewAnalyticsPanel organizationId={organizationId} platform="github" />
</TabsContent>
</Tabs>
</TabsContent>

Expand Down Expand Up @@ -266,7 +282,7 @@ export function ReviewAgentPageClient({

{/* GitLab Configuration Tabs */}
<Tabs defaultValue="config" className="w-full">
<TabsList className="grid w-full max-w-2xl grid-cols-2">
<TabsList className="grid h-auto w-full max-w-2xl grid-cols-2 sm:grid-cols-3">
<TabsTrigger value="config" className="flex items-center gap-2">
<Settings2 className="h-4 w-4" />
Config
Expand All @@ -279,6 +295,10 @@ export function ReviewAgentPageClient({
<ListChecks className="h-4 w-4" />
Jobs
</TabsTrigger>
<TabsTrigger value="analytics" className="flex items-center gap-2">
<ChartColumnIncreasing className="h-4 w-4" />
Analytics
</TabsTrigger>
</TabsList>

<TabsContent value="config" className="mt-6 space-y-4">
Expand Down Expand Up @@ -315,6 +335,10 @@ export function ReviewAgentPageClient({
</Alert>
)}
</TabsContent>

<TabsContent value="analytics" className="mt-6 space-y-4">
<CodeReviewAnalyticsPanel organizationId={organizationId} platform="gitlab" />
</TabsContent>
</Tabs>
</TabsContent>
</Tabs>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it, jest, beforeEach } from '@jest/globals';
import type { NextRequest } from 'next/server';
import type * as codeReviewsDbModule from '@/lib/code-reviews/db/code-reviews';
import type * as analyticsDbModule from '@/lib/code-reviews/analytics/db';
import type * as platformIntegrationsModule from '@/lib/integrations/db/platform-integrations';
import type {
CloudAgentCodeReview,
Expand Down Expand Up @@ -35,6 +36,9 @@ const mockGetLatestCodeReviewAttempt = jest.fn() as jest.MockedFunction<
const mockCreateInfraRetryAttemptIfMissing = jest.fn() as jest.MockedFunction<
typeof codeReviewsDbModule.createInfraRetryAttemptIfMissing
>;
const mockFinalizeCompletedCodeReviewWithAnalytics = jest.fn() as jest.MockedFunction<
typeof analyticsDbModule.finalizeCompletedCodeReviewWithAnalytics
>;
const mockGetIntegrationById = jest.fn() as jest.MockedFunction<
typeof platformIntegrationsModule.getIntegrationById
>;
Expand Down Expand Up @@ -98,6 +102,10 @@ jest.mock('@/lib/code-reviews/db/code-reviews', () => ({
createInfraRetryAttemptIfMissing: mockCreateInfraRetryAttemptIfMissing,
}));

jest.mock('@/lib/code-reviews/analytics/db', () => ({
finalizeCompletedCodeReviewWithAnalytics: mockFinalizeCompletedCodeReviewWithAnalytics,
}));

jest.mock('@/lib/code-reviews/client/code-review-worker-client', () => ({
codeReviewWorkerClient: {
retryReviewFresh: mockRetryReviewFresh,
Expand Down Expand Up @@ -261,6 +269,7 @@ function makeAttempt(
session_id: null,
cli_session_id: null,
execution_id: null,
analytics_enabled_at_dispatch: null,
status: 'running',
error_message: null,
terminal_reason: null,
Expand Down Expand Up @@ -391,6 +400,7 @@ beforeEach(async () => {
mockGetSessionUsageFromBilling.mockResolvedValue(null);
mockUpdateCodeReviewUsage.mockResolvedValue(undefined);
mockUpdateCodeReviewStatusIfNonTerminal.mockResolvedValue(true);
mockFinalizeCompletedCodeReviewWithAnalytics.mockResolvedValue({ outcome: 'applied' });
mockAppendPreviousReviewSummaryHistory.mockImplementation((body: string) => body);
mockBuildReviewSummaryFooter.mockImplementation(
(footer: { usage?: unknown; reviewGuidance?: { used: boolean } }) =>
Expand Down Expand Up @@ -459,6 +469,91 @@ describe('POST /api/internal/code-review-status/[reviewId]', () => {
});
});

describe('analytics completion callbacks', () => {
it('rejects invalid callback payloads at runtime', async () => {
const response = await POST(makeRequest({ status: 'unknown' }), makeParams(REVIEW_ID));

expect(response.status).toBe(400);
await expect(response.json()).resolves.toEqual({ error: 'Invalid callback payload' });
expect(mockGetCodeReviewById).not.toHaveBeenCalled();
});

it('finalizes an enrolled captured result before provider side effects', async () => {
mockGetCodeReviewById.mockResolvedValue(makeReview());
const attempt = makeAttempt({ analytics_enabled_at_dispatch: true });
mockGetLatestCodeReviewAttempt.mockResolvedValue(attempt);
const marker =
'<!-- kilo-review-analytics:v1 {"schemaVersion":1,"taxonomyVersion":1,"change":{"type":"feature","impact":"medium","complexity":"high","confidence":"high"},"findings":[{"severity":"warning","category":"correctness","securityClass":null}]} -->';

const response = await POST(
makeRequest({
status: 'completed',
sessionId: 'agent-session',
lastAssistantMessageText: `Review complete.\n${marker}`,
}),
makeParams(REVIEW_ID)
);

expect(response.status).toBe(200);
expect(mockFinalizeCompletedCodeReviewWithAnalytics).toHaveBeenCalledWith(
expect.objectContaining({
codeReviewId: REVIEW_ID,
capture: expect.objectContaining({
status: 'captured',
manifest: expect.objectContaining({ findings: [expect.any(Object)] }),
}),
})
);
expect(mockUpdateCodeReviewAttemptForCallback).not.toHaveBeenCalled();
expect(mockUpdateCodeReviewStatus).not.toHaveBeenCalled();
expect(mockUpdateCheckRun).toHaveBeenCalled();
});

it.each([
['missing', { lastAssistantMessageText: 'Review complete.' }],
['invalid', { lastAssistantMessageText: '<!-- kilo-review-analytics:v1 {bad-json} -->' }],
[
'omitted',
{
lastAssistantMessageTextTruncation: {
originalUtf8ByteLength: 200000,
retainedUtf8ByteLength: 0,
},
},
],
] as const)('maps assistant output to %s coverage', async (expectedStatus, payload) => {
mockGetCodeReviewById.mockResolvedValue(makeReview());
mockGetLatestCodeReviewAttempt.mockResolvedValue(
makeAttempt({ analytics_enabled_at_dispatch: true })
);

await POST(makeRequest({ status: 'completed', ...payload }), makeParams(REVIEW_ID));

expect(mockFinalizeCompletedCodeReviewWithAnalytics).toHaveBeenCalledWith(
expect.objectContaining({ capture: { status: expectedStatus } })
);
});

it('does not replay provider completion side effects for analytics repair', async () => {
mockGetCodeReviewById.mockResolvedValue(makeReview({ status: 'completed' }));
mockGetLatestCodeReviewAttempt.mockResolvedValue(
makeAttempt({ status: 'completed', analytics_enabled_at_dispatch: true })
);
mockFinalizeCompletedCodeReviewWithAnalytics.mockResolvedValue({ outcome: 'repaired' });

const response = await POST(
makeRequest({ status: 'completed', lastAssistantMessageText: 'Review complete.' }),
makeParams(REVIEW_ID)
);

expect(response.status).toBe(200);
expect(mockGetIntegrationById).not.toHaveBeenCalled();
expect(mockUpdateCheckRun).not.toHaveBeenCalled();
expect(mockAddReactionToPR).not.toHaveBeenCalled();
expect(mockTryDispatchPendingReviews).not.toHaveBeenCalled();
});
});

describe('normalization', () => {
it('maps interrupted status to cancelled with interrupted terminal reason', async () => {
mockGetCodeReviewById.mockResolvedValue(makeReview());
Expand Down
Loading
Loading