Swadhin Biswas Feature/ai code reviewer (#80) · 8/17/2026
9740d3d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* AI Code Reviews Schema
* LLM-powered code review with inline suggestions
*/
import { relations } from "drizzle-orm";
import { boolean, integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { index } from "drizzle-orm/pg-core";
import { pullRequests } from "./pull-requests";
import { users } from "./users";
// AI Reviews - one per PR run
export const aiReviews = pgTable("ai_reviews", {
id: text("id").primaryKey(),
pullRequestId: text("pull_request_id")
.notNull()
.references(() => pullRequests.id, { onDelete: "cascade" }),
// Status
status: text("status").notNull().default("pending"), // pending, running, completed, failed
// Model info
model: text("model").notNull(), // gpt-4, claude-3-opus, etc.
provider: text("provider").notNull(), // openai, anthropic, local
// Stack context
stackContext: text("stack_context"), // JSON: context from parent PRs
includesStackContext: boolean("includes_stack_context").default(false),
// Results
summary: text("summary"), // Overall review summary
overallSeverity: text("overall_severity"), // info, warning, error, critical
suggestionsCount: integer("suggestions_count").default(0),
// Usage tracking
tokensUsed: integer("tokens_used"),
promptTokens: integer("prompt_tokens"),
completionTokens: integer("completion_tokens"),
costCents: integer("cost_cents"), // Cost in cents
// Raw data
rawResponse: text("raw_response"), // Full JSON response
// Timing
triggeredById: text("triggered_by_id").references(() => users.id),
createdAt: timestamp("created_at").notNull().defaultNow(),
startedAt: timestamp("started_at"),
completedAt: timestamp("completed_at"),
// Error
errorMessage: text("error_message"),
}, (table) => [
index("ai_reviews_pull_request_id_idx").on(table.pullRequestId),
]);
// AI Review Suggestions - individual findings
export const aiReviewSuggestions = pgTable("ai_review_suggestions", {
id: text("id").primaryKey(),
aiReviewId: text("ai_review_id")
.notNull()
.references(() => aiReviews.id, { onDelete: "cascade" }),
// Location
path: text("path").notNull(),
line: integer("line"),
endLine: integer("end_line"),
// Classification
severity: text("severity").notNull(), // info, warning, error, critical
type: text("type").notNull(), // bug, security, performance, style, documentation, suggestion
category: text("category"), // Optional sub-category
// Content
title: text("title").notNull(),
message: text("message").notNull(),
suggestedFix: text("suggested_fix"), // Code suggestion
explanation: text("explanation"), // Why this matters
// Actions
isApplied: boolean("is_applied").default(false),
isDismissed: boolean("is_dismissed").default(false),
appliedAt: timestamp("applied_at"),
appliedById: text("applied_by_id").references(() => users.id),
dismissedAt: timestamp("dismissed_at"),
dismissedById: text("dismissed_by_id").references(() => users.id),
dismissReason: text("dismiss_reason"),
createdAt: timestamp("created_at").notNull().defaultNow(),
});
// Relations
export const aiReviewsRelations = relations(aiReviews, ({ one, many }) => ({
pullRequest: one(pullRequests, {
fields: [aiReviews.pullRequestId],
references: [pullRequests.id],
}),
triggeredBy: one(users, {
fields: [aiReviews.triggeredById],
references: [users.id],
}),
suggestions: many(aiReviewSuggestions),
}));
export const aiReviewSuggestionsRelations = relations(aiReviewSuggestions, ({ one }) => ({
aiReview: one(aiReviews, {
fields: [aiReviewSuggestions.aiReviewId],
references: [aiReviews.id],
}),
appliedBy: one(users, {
fields: [aiReviewSuggestions.appliedById],
references: [users.id],
}),
}));
// Types
export type AiReview = typeof aiReviews.$inferSelect;
export type NewAiReview = typeof aiReviews.$inferInsert;
export type AiReviewSuggestion = typeof aiReviewSuggestions.$inferSelect;
export type NewAiReviewSuggestion = typeof aiReviewSuggestions.$inferInsert;