-
Notifications
You must be signed in to change notification settings - Fork 1
Backend ai #220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Backend ai #220
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "version": 1, | ||
| "lastRunAtMs": 0, | ||
| "turnsSinceLastRun": 1, | ||
| "lastTranscriptMtimeMs": null, | ||
| "lastProcessedGenerationId": "02f2727d-db77-45c6-bb2d-4163883b764f", | ||
| "trialStartedAtMs": null | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -484,6 +484,24 @@ export const createForUser = mutation({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const setHasBackendForUser = mutation({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: This mutation is only called from Inngest but is exposed as a public Note: the existing Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId: v.string(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Authorization check is bypassable — Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| projectId: v.id("projects"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hasBackend: v.boolean(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handler: async (ctx, args) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const project = await ctx.db.get(args.projectId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!project || project.userId !== args.userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Unauthorized"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await ctx.db.patch(args.projectId, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hasBackend: args.hasBackend, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedAt: Date.now(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+487
to
+502
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Authorization is bypassable because identity is caller-controlled. Line 477 takes Suggested fix (bind auth to session, not args) export const setHasBackendForUser = mutation({
args: {
- userId: v.string(),
projectId: v.id("projects"),
hasBackend: v.boolean(),
},
handler: async (ctx, args) => {
+ const userId = await requireAuth(ctx);
const project = await ctx.db.get(args.projectId);
- if (!project || project.userId !== args.userId) {
+ if (!project || project.userId !== userId) {
throw new Error("Unauthorized");
}
await ctx.db.patch(args.projectId, {
hasBackend: args.hasBackend,
updatedAt: Date.now(),
});
},
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Internal: Create a project for a specific user (for use from actions/background jobs) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { mutation } from "./_generated/server"; | ||
| import { v } from "convex/values"; | ||
| import { schemaProposalStatusEnum } from "./schema"; | ||
|
|
||
| export const createForUser = mutation({ | ||
| args: { | ||
| userId: v.string(), | ||
| projectId: v.id("projects"), | ||
| messageId: v.id("messages"), | ||
| proposal: v.string(), | ||
| parsedTables: v.optional( | ||
| v.array( | ||
| v.object({ | ||
| name: v.string(), | ||
| purpose: v.string(), | ||
| fields: v.array(v.string()), | ||
| indexes: v.array(v.string()), | ||
| }) | ||
| ) | ||
| ), | ||
| parsedRelationships: v.optional(v.array(v.string())), | ||
| status: schemaProposalStatusEnum, | ||
| }, | ||
| handler: async (ctx, args) => { | ||
| const project = await ctx.db.get(args.projectId); | ||
| if (!project || project.userId !== args.userId) { | ||
| throw new Error("Unauthorized"); | ||
| } | ||
| const message = await ctx.db.get(args.messageId); | ||
| if (!message || message.projectId !== args.projectId) { | ||
| throw new Error("Message not found"); | ||
| } | ||
| const now = Date.now(); | ||
| return await ctx.db.insert("schemaProposals", { | ||
| projectId: args.projectId, | ||
| messageId: args.messageId, | ||
| userId: args.userId, | ||
| proposal: args.proposal, | ||
| status: args.status, | ||
| parsedTables: args.parsedTables, | ||
| parsedRelationships: args.parsedRelationships, | ||
| createdAt: now, | ||
| updatedAt: now, | ||
| }); | ||
| }, | ||
| }); | ||
|
|
||
| export const markImplementedForUser = mutation({ | ||
| args: { | ||
| userId: v.string(), | ||
| schemaProposalId: v.id("schemaProposals"), | ||
| }, | ||
| handler: async (ctx, args) => { | ||
| const row = await ctx.db.get(args.schemaProposalId); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Missing status guard allows a Prompt for AI agents |
||
| if (!row || row.userId !== args.userId) { | ||
| throw new Error("Unauthorized"); | ||
| } | ||
| const now = Date.now(); | ||
| await ctx.db.patch(args.schemaProposalId, { | ||
| status: "IMPLEMENTED", | ||
| approvedAt: row.approvedAt ?? now, | ||
| implementedAt: now, | ||
| updatedAt: now, | ||
| }); | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import { generateText } from "ai"; | ||
| import { openrouter } from "./client"; | ||
| import { CONVEX_BACKEND_PROMPT } from "@/prompts/backend/convex-backend"; | ||
|
|
||
| const BACKEND_MODEL = "moonshotai/kimi-k2.5:nitro"; | ||
|
|
||
| export interface BackendAgentResult { | ||
| files: Record<string, string>; | ||
| success: boolean; | ||
| error?: string; | ||
| summary?: string; | ||
| } | ||
|
|
||
| export async function runBackendImplementerAgent( | ||
| userPrompt: string, | ||
| schemaProposal: string, | ||
| plan?: string | ||
| ): Promise<BackendAgentResult> { | ||
| console.log("[BACKEND] Starting implementation..."); | ||
|
|
||
| try { | ||
| const augmentedPrompt = [ | ||
| "## User Request", | ||
| userPrompt, | ||
| "", | ||
| "## Approved Schema Design", | ||
| schemaProposal, | ||
| "", | ||
| plan ? `## Implementation Plan\n${plan}\n` : "", | ||
| "## Your Task", | ||
| "Generate the complete Convex backend implementation based on the approved schema.", | ||
| "Create all necessary files with their full content.", | ||
| "", | ||
| "Output format:", | ||
| '1. First, output all file contents using <zapdev_file path="convex/schema.ts"> tags', | ||
| "2. Each file should contain the complete, production-ready code", | ||
| "3. End with a <task_summary> describing what was created", | ||
| "", | ||
| "Example:", | ||
| '<zapdev_file path="convex/schema.ts">', | ||
| "// schema content here", | ||
| "</zapdev_file>", | ||
| '<zapdev_file path="convex/tasks/queries.ts">', | ||
| "// queries content here", | ||
| "</zapdev_file>", | ||
| "", | ||
| "<task_summary>", | ||
| "Created Convex backend with schema and CRUD operations for tasks", | ||
| "</task_summary>", | ||
| ].join("\n"); | ||
|
|
||
| const { text } = await generateText({ | ||
| model: openrouter(BACKEND_MODEL), | ||
| system: CONVEX_BACKEND_PROMPT, | ||
| prompt: augmentedPrompt, | ||
| temperature: 0.2, | ||
| maxOutputTokens: 8192, | ||
| }); | ||
|
|
||
| const files = parseGeneratedFiles(text); | ||
|
|
||
| const summary = text.includes("<task_summary>") | ||
| ? text.match(/<task_summary>([\s\S]*?)<\/task_summary>/)?.[1]?.trim() | ||
| : "Generated Convex backend files"; | ||
|
|
||
| if (Object.keys(files).length === 0) { | ||
| return { | ||
| files, | ||
| success: false, | ||
| error: "No files were generated", | ||
| summary, | ||
| }; | ||
| } | ||
|
|
||
| console.log("[BACKEND] Completed successfully"); | ||
|
|
||
| return { | ||
| files, | ||
| success: true, | ||
| summary, | ||
| }; | ||
| } catch (error) { | ||
| const errorMessage = error instanceof Error ? error.message : String(error); | ||
| console.error("[BACKEND] Error:", errorMessage); | ||
|
|
||
| return { | ||
| files: {}, | ||
| success: false, | ||
| error: errorMessage, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| function parseGeneratedFiles(text: string): Record<string, string> { | ||
| const files: Record<string, string> = {}; | ||
|
|
||
| const fileRegex = /<zapdev_file path="([^"]+)">([\s\S]*?)<\/zapdev_file>/g; | ||
| let match; | ||
|
|
||
| while ((match = fileRegex.exec(text)) !== null) { | ||
| const path = match[1]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: File paths parsed from AI output are not validated or sanitized. Since the user prompt influences the AI response, a crafted prompt could induce the model to emit paths containing Prompt for AI agents |
||
| const content = match[2].trim(); | ||
| files[path] = content; | ||
| } | ||
|
Comment on lines
+100
to
+104
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate generated file paths before accepting them.
🤖 Prompt for AI Agents |
||
|
|
||
| return files; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: Zapdev-labs/zapdev
Length of output: 110
🏁 Script executed:
Repository: Zapdev-labs/zapdev
Length of output: 321
Add
.cursor/hooks/state/to.gitignoreto prevent committing IDE state files.The continual-learning.json file has been removed from the PR. However, to prevent similar auto-generated IDE state files from being accidentally committed in the future, add the following to
.gitignore:Note: The other
.cursor/files (rules and configuration) appear to be intentional and should remain in the repository. Only the state files in.cursor/hooks/state/should be excluded.🤖 Prompt for AI Agents