Skip to content

Conversation

@bbarni2020
Copy link
Collaborator

It does require some changes on server side:

  • New environment variable: AI_API_KEY (HackClub AI API key)
  • Need to run a db migrate

Copilot AI review requested due to automatic review settings December 14, 2025 13:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds AI-powered pre-review functionality for devlogs and projects submitted to Hack Club Construct. When projects are shipped or viewed by moderators, an AI model analyzes devlogs and project details to flag potential over-reporting of time spent, providing moderators with preliminary context before manual review. The feature gracefully degrades when the AI_API_KEY is not configured.

Key changes:

  • New database tables (ai_devlog_review and ai_project_review) to store AI-generated review results
  • Integration with HackClub AI API using the qwen/qwen3-vl-235b-a22b-instruct model
  • UI updates in admin review panel and devlog components to display AI review status and rationale

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
src/lib/server/db/schema.ts Adds two new database tables for storing AI devlog reviews and project reviews with foreign keys to devlog and project tables
src/lib/server/ai/reviewer.ts Implements AI review logic by calling HackClub AI API to analyze devlogs and projects for time over-reporting
src/lib/server/ai/ensure.ts Provides caching and persistence layer for AI reviews, only re-reviewing when content is updated
src/routes/dashboard/projects/[id]/ship/+page.server.ts Triggers fire-and-forget AI pre-review when projects are shipped
src/routes/dashboard/admin/review/[id]/+page.server.ts Fetches and ensures AI reviews are available for moderators viewing projects
src/routes/dashboard/admin/review/[id]/+page.svelte Displays AI review summary showing approval status and flagged devlog counts
src/lib/components/Devlog.svelte Shows AI review status badges and rationale for individual devlogs
drizzle/meta/_journal.json Adds migration entry for AI review tables
drizzle/meta/0004_snapshot.json Complete database schema snapshot including new AI review tables
drizzle/0004_ai_prechecks.sql Migration SQL to create ai_devlog_review and ai_project_review tables with constraints
README.md Documents the new AI pre-review feature and AI_API_KEY environment variable requirement
.env.example Adds AI_API_KEY configuration example

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +28
CREATE TABLE "ai_devlog_review" (
"id" serial PRIMARY KEY NOT NULL,
"devlogId" integer NOT NULL,
"projectId" integer NOT NULL,
"approved" boolean NOT NULL,
"rationale" text NOT NULL,
"prompt" text NOT NULL,
"model" text NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "ai_devlog_review_devlogId_unique" UNIQUE("devlogId")
);
--> statement-breakpoint
CREATE TABLE "ai_project_review" (
"id" serial PRIMARY KEY NOT NULL,
"projectId" integer NOT NULL,
"overallApproved" boolean NOT NULL,
"summary" text NOT NULL,
"prompt" text NOT NULL,
"model" text NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "ai_project_review_projectId_unique" UNIQUE("projectId")
);
--> statement-breakpoint
ALTER TABLE "ai_devlog_review" ADD CONSTRAINT "ai_devlog_review_devlogId_devlog_id_fk" FOREIGN KEY ("devlogId") REFERENCES "public"."devlog"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ai_devlog_review" ADD CONSTRAINT "ai_devlog_review_projectId_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "public"."project"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ai_project_review" ADD CONSTRAINT "ai_project_review_projectId_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "public"."project"("id") ON DELETE no action ON UPDATE no action; No newline at end of file
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a discrepancy between migration files 0004_milky_iron_patriot.sql and 0010_swift_korg.sql. The former uses ON DELETE cascade for foreign keys while the latter uses ON DELETE no action. Since 0010 appears to be the final/correct migration that should be run, this inconsistency suggests 0004_milky_iron_patriot.sql is an outdated duplicate that should be removed to avoid confusion.

Suggested change
CREATE TABLE "ai_devlog_review" (
"id" serial PRIMARY KEY NOT NULL,
"devlogId" integer NOT NULL,
"projectId" integer NOT NULL,
"approved" boolean NOT NULL,
"rationale" text NOT NULL,
"prompt" text NOT NULL,
"model" text NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "ai_devlog_review_devlogId_unique" UNIQUE("devlogId")
);
--> statement-breakpoint
CREATE TABLE "ai_project_review" (
"id" serial PRIMARY KEY NOT NULL,
"projectId" integer NOT NULL,
"overallApproved" boolean NOT NULL,
"summary" text NOT NULL,
"prompt" text NOT NULL,
"model" text NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "ai_project_review_projectId_unique" UNIQUE("projectId")
);
--> statement-breakpoint
ALTER TABLE "ai_devlog_review" ADD CONSTRAINT "ai_devlog_review_devlogId_devlog_id_fk" FOREIGN KEY ("devlogId") REFERENCES "public"."devlog"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ai_devlog_review" ADD CONSTRAINT "ai_devlog_review_projectId_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "public"."project"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ai_project_review" ADD CONSTRAINT "ai_project_review_projectId_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "public"."project"("id") ON DELETE no action ON UPDATE no action;

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +119
if (REVIEWABLE_STATUSES.has(queriedProject.project.status)) {
try {
const ensured = await ensureAiReviewsForProject({
project: {
id: queriedProject.project.id,
name: queriedProject.project.name,
description: queriedProject.project.description,
status: queriedProject.project.status,
updatedAt: queriedProject.project.updatedAt
},
devlogs: devlogs.map((log) => ({
...log,
s3PublicUrl: env.S3_PUBLIC_URL ?? ''
}))
});

latestDevlogReviews = ensured.devlogReviews;
latestProjectReview = ensured.projectReview ?? latestProjectReview;
} catch (err) {
console.error('AI review ensure failed', err);
}
}
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AI review calls happen synchronously during page load for the admin review page, which could significantly slow down page load times if the AI API is slow or if there are many devlogs. Consider moving this to a background job or showing a loading state while reviews are being generated.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +88
await db.transaction(async (tx) => {
await Promise.all(
devlogReviews.map((review) =>
tx
.insert(aiDevlogReview)
.values({
devlogId: review.devlogId,
projectId: review.projectId,
approved: review.approved,
rationale: review.rationale,
prompt: review.prompt,
model: review.model
})
.onConflictDoUpdate({
target: aiDevlogReview.devlogId,
set: {
projectId: review.projectId,
approved: review.approved,
rationale: review.rationale,
prompt: review.prompt,
model: review.model,
updatedAt: sql`now()`
}
})
)
);
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The closing parenthesis for Promise.all is missing on line 87. This causes the transaction callback to be malformed. The Promise.all should be closed before the transaction continues to insert the project review.

Copilot uses AI. Check for mistakes.
devlogReviews: Pick<AiDevlogReview, 'devlogId' | 'projectId' | 'approved' | 'rationale' | 'prompt' | 'model'>[];
}> {
const projectReview = await reviewProjectOverall(project, devlogs);
const devlogReviews = await Promise.all(devlogs.map(log => reviewSingleDevlog(project.id, log)));
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing arrow function syntax for the map callback. The code has 'log =>' but should be '(log) =>' for consistency with the project's coding style, though both are valid JavaScript. However, more importantly, this function processes all devlogs in parallel which could be inefficient or hit rate limits when there are many devlogs. Consider adding batching or sequential processing with delays to avoid overwhelming the AI API.

Suggested change
const devlogReviews = await Promise.all(devlogs.map(log => reviewSingleDevlog(project.id, log)));
const devlogReviews = [];
for (const log of devlogs) {
devlogReviews.push(await reviewSingleDevlog(project.id, log));
}

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +86
latestDevlogReviews = await db
.select()
.from(aiDevlogReview)
.where(and(eq(aiDevlogReview.projectId, queriedProject.project.id), inArray(aiDevlogReview.devlogId, devlogs.map((log) => log.id))));
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inArray query on line 86 will fail if devlogs is an empty array. Most SQL databases require at least one value in an IN clause. This code should check if devlogs.length is greater than 0 before executing this query, or handle the empty array case separately.

Suggested change
latestDevlogReviews = await db
.select()
.from(aiDevlogReview)
.where(and(eq(aiDevlogReview.projectId, queriedProject.project.id), inArray(aiDevlogReview.devlogId, devlogs.map((log) => log.id))));
if (devlogs.length > 0) {
latestDevlogReviews = await db
.select()
.from(aiDevlogReview)
.where(and(eq(aiDevlogReview.projectId, queriedProject.project.id), inArray(aiDevlogReview.devlogId, devlogs.map((log) => log.id))));
} else {
latestDevlogReviews = [];
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant