Conversation
commit: |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a significant architectural shift in the AI agent system, moving towards a more robust and modular design. The core change involves implementing an orchestrator agent that intelligently delegates tasks to specialized subagents, such as a Strategy Advisor and a Writer. This refactor aims to improve the clarity, maintainability, and scalability of AI-driven workflows. Concurrently, a new evaluation framework has been integrated, allowing for systematic assessment and scoring of AI-generated content and strategies against predefined fixtures. This enhancement provides a structured approach to measure and improve agent performance, ensuring higher quality and more consistent outputs. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant and well-executed refactoring of the AI agent architecture, moving from a monolithic agent to a more structured, hierarchical system with an orchestrator and specialized sub-agents. Key changes include the removal of the SeoPlannerWorkflow and the introduction of a sophisticated, multi-phase writer agent pipeline. A comprehensive evaluation framework for content and strategy generation has also been added, which is a great step towards ensuring AI output quality. The tool definitions have been updated to use the ai SDK's jsonSchema helper, improving consistency. My review has identified a bug in a utility function and a potential UI improvement for data refetching after a destructive action. Overall, this is a high-quality refactoring that greatly improves the agent system's design and maintainability.
| .join("")}` | ||
| } | ||
| - Sample Competitor's site: ${background.competitorsWebsites.length === 0 ? "None Present" : `${background.competitorsWebsites.length} Present`} | ||
| - Competitor websites: ${background.competitorsWebsites.length === 0 ? "None Present" : `${background.competitorsWebsites.map((website, index) => `\n - ${index + 1}. ${website}`).join("")}`} |
There was a problem hiding this comment.
The competitorsWebsites array contains objects of shape { url: string }. The current implementation ...map((website, index) => ... ${website}) will result in [object Object] being rendered. You should access the url property of the website object.
| - Competitor websites: ${background.competitorsWebsites.length === 0 ? "None Present" : `${background.competitorsWebsites.map((website, index) => `\n - ${index + 1}. ${website}`).join("")}`} | |
| - Competitor websites: ${background.competitorsWebsites.length === 0 ? "None Present" : `${background.competitorsWebsites.map((website, index) => `\n - ${index + 1}. ${website.url}`).join("")}`} |
| - `pnpm db:mp:preview` - Apply migrations to preview database | ||
| - `pnpm db:mp:production` - Apply migrations to production database | ||
| - `pnpm db:studio` - Open Drizzle Studio | ||
| check the root level package.json for the list of supported commands |
There was a problem hiding this comment.
While pointing to package.json for commands is more maintainable, removing the explicit list of common commands, database commands, and repository layout makes this document less helpful for developers trying to quickly get an overview of the project. Consider restoring some of the key commands and the repository layout summary to improve developer onboarding and quick reference.
| function DeleteDataToolPart({ | ||
| part, | ||
| onApprove, | ||
| onReject, | ||
| onRespond, | ||
| }: { | ||
| part: ChatToolPart; | ||
| onApprove: () => void; | ||
| onReject: () => void; | ||
| part: DeleteDataToolPart; | ||
| onRespond: (args: { id: string; approved: boolean; reason?: string }) => void; | ||
| }) { | ||
| const [open, setOpen] = useState(false); | ||
| if ( | ||
| part.type !== "tool-create_plan" || | ||
| part.state === "input-streaming" || | ||
| part.state === "output-error" | ||
| ) | ||
| return null; | ||
| const plan = part.input; | ||
| const title = plan.name?.trim() || "Proposed plan"; | ||
| const overview = plan.overview?.trim(); | ||
| const markdown = plan.plan ?? ""; | ||
| const [denialReason, setDenialReason] = useState(""); | ||
|
|
||
| return ( | ||
| <div className="mb-4 w-full rounded-md border bg-background p-4"> | ||
| <DialogDrawer | ||
| onOpenChange={setOpen} | ||
| open={open} | ||
| trigger={ | ||
| <button | ||
| className={cn( | ||
| "w-full text-left", | ||
| "rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", | ||
| )} | ||
| type="button" | ||
| > | ||
| <div className="flex items-start justify-between gap-3"> | ||
| <div className="min-w-0"> | ||
| <div className="truncate font-medium text-sm">{title}</div> | ||
| {overview ? ( | ||
| <div className="mt-1 line-clamp-2 text-muted-foreground text-xs"> | ||
| {overview} | ||
| </div> | ||
| ) : ( | ||
| <div className="mt-1 text-muted-foreground text-xs"> | ||
| Click to view details | ||
| </div> | ||
| )} | ||
| </div> | ||
| <div className="shrink-0 text-muted-foreground text-xs"> | ||
| {part.state === "output-available" ? "Ready" : "Running"} | ||
| </div> | ||
| </div> | ||
| </button> | ||
| } | ||
| > | ||
| <DialogDrawerHeader className="space-y-1"> | ||
| <DialogDrawerTitle>{title}</DialogDrawerTitle> | ||
| {overview ? ( | ||
| <div className="text-muted-foreground text-sm">{overview}</div> | ||
| ) : null} | ||
| </DialogDrawerHeader> | ||
| if (part.type !== "tool-delete_existing_data") return null; | ||
|
|
||
| <div className="max-h-[60vh] overflow-auto px-4 pb-4"> | ||
| <Response>{markdown}</Response> | ||
| </div> | ||
| const input = part.input; | ||
| if (!input) { | ||
| return ( | ||
| <div className="mb-4 w-full rounded-md border bg-background p-4 text-sm"> | ||
| Delete request is missing input parameters. | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| <DialogDrawerFooter className="gap-2"> | ||
| <Button | ||
| disabled={part.state !== "output-available"} | ||
| onClick={() => { | ||
| setOpen(false); | ||
| onReject(); | ||
| }} | ||
| size="sm" | ||
| type="button" | ||
| variant="outline" | ||
| > | ||
| Reject | ||
| </Button> | ||
| <Button | ||
| disabled={part.state !== "output-available"} | ||
| onClick={() => { | ||
| setOpen(false); | ||
| onApprove(); | ||
| }} | ||
| size="sm" | ||
| type="button" | ||
| > | ||
| Approve | ||
| </Button> | ||
| </DialogDrawerFooter> | ||
| </DialogDrawer> | ||
| const entityLabel = | ||
| input.entityType === "strategy" ? "strategy" : "content draft"; | ||
| const isApprovalRequested = part.state === "approval-requested"; | ||
|
|
||
| <div className="mt-3 flex justify-end gap-2"> | ||
| <Button | ||
| disabled={part.state !== "output-available"} | ||
| onClick={onReject} | ||
| size="sm" | ||
| type="button" | ||
| variant="outline" | ||
| > | ||
| Reject | ||
| </Button> | ||
| <Button | ||
| disabled={part.state !== "output-available"} | ||
| onClick={onApprove} | ||
| size="sm" | ||
| type="button" | ||
| > | ||
| Approve | ||
| </Button> | ||
| if (part.state === "input-streaming" || part.state === "input-available") { | ||
| return ( | ||
| <Shimmer className="text-sm" key={part.toolCallId}> | ||
| Preparing delete request | ||
| </Shimmer> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="mb-4 w-full rounded-md border bg-background p-4"> | ||
| <div className="space-y-2"> | ||
| <div className="font-medium text-sm">Delete {entityLabel}</div> | ||
| <div className="text-muted-foreground text-sm"> | ||
| ID: <code>{input.id}</code> | ||
| </div> | ||
| {input.reason ? ( | ||
| <div className="text-muted-foreground text-sm"> | ||
| Reason: {input.reason} | ||
| </div> | ||
| ) : null} | ||
| {input.entityType === "strategy" ? ( | ||
| <div className="text-amber-700 text-sm"> | ||
| This only removes the strategy. Content previously linked to it will | ||
| remain in your project. | ||
| </div> | ||
| ) : ( | ||
| <div className="text-amber-700 text-sm"> | ||
| Deleting this content also unpublishes it from your site. | ||
| </div> | ||
| )} | ||
|
|
||
| {part.state === "output-available" && | ||
| part.output && | ||
| typeof part.output === "object" && | ||
| "success" in part.output && | ||
| part.output.success === true ? ( | ||
| <div className="text-green-700 text-sm"> | ||
| Deletion approved and completed. | ||
| </div> | ||
| ) : null} | ||
| {part.state === "output-error" ? ( | ||
| <div className="text-destructive text-sm"> | ||
| Delete failed: {part.errorText} | ||
| </div> | ||
| ) : null} | ||
| {part.state === "output-denied" ? ( | ||
| <div className="text-orange-700 text-sm"> | ||
| Delete request denied. | ||
| {part.approval.reason ? ` Reason: ${part.approval.reason}` : ""} | ||
| </div> | ||
| ) : null} | ||
| </div> | ||
|
|
||
| {isApprovalRequested ? ( | ||
| <div className="mt-4 space-y-2"> | ||
| <Input | ||
| onChange={(event) => setDenialReason(event.target.value)} | ||
| placeholder="Optional denial reason" | ||
| value={denialReason} | ||
| /> | ||
| <div className="flex justify-end gap-2"> | ||
| <Button | ||
| onClick={() => | ||
| onRespond({ | ||
| id: part.approval.id, | ||
| approved: false, | ||
| reason: denialReason.trim() || undefined, | ||
| }) | ||
| } | ||
| size="sm" | ||
| type="button" | ||
| variant="outline" | ||
| > | ||
| Deny | ||
| </Button> | ||
| <Button | ||
| onClick={() => | ||
| onRespond({ | ||
| id: part.approval.id, | ||
| approved: true, | ||
| }) | ||
| } | ||
| size="sm" | ||
| type="button" | ||
| > | ||
| Approve delete | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| ) : null} | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
After a successful deletion via the DeleteDataToolPart, the UI should reflect this change by refetching the relevant data (e.g., the list of strategies or content drafts). Currently, the component shows a success message but doesn't seem to trigger any data invalidation. Consider using useQueryClient from @tanstack/react-query to invalidate the queries for the deleted entity type upon successful deletion. This will ensure the UI stays in sync with the backend state.
There was a problem hiding this comment.
Pull request overview
Refactors the SEO product’s AI/chat and workflow architecture toward an orchestrator + subagent model, while removing the legacy “planner/plan-keyword” workflow path. Also tightens AI tool schema wiring around jsonSchema(...), adds new evaluation scaffolding, introduces soft-delete DB operations, and bumps shared dependency versions across the monorepo.
Changes:
- Replace legacy strategist/writer agent + planner workflow with a ToolLoopAgent orchestrator and a Strategy Advisor subagent, and update workflows/routes accordingly.
- Introduce JSON schema exports for project settings (for AI tool validation) and remove ArkType→JSON-schema transformer utilities.
- Add new DB soft-delete operations for SEO strategies/drafts and add eval framework files for content/strategy scoring.
Reviewed changes
Copilot reviewed 82 out of 83 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/task/package.json | Bumps @types/node and @t3-oss/env-core. |
| packages/google-apis/package.json | Bumps @types/node and @t3-oss/env-core. |
| packages/emails/package.json | Bumps @types/node. |
| packages/db/src/operations/seo/strategy-operations.ts | Adds softDeleteStrategy DB operation. |
| packages/db/src/operations/seo/content-operations.ts | Adds softDeleteDraft DB operation. |
| packages/db/package.json | Bumps @types/node and @t3-oss/env-core. |
| packages/dataforseo/src/index.ts | Extracts exported request arg types for DataForSEO helpers. |
| packages/dataforseo/package.json | Bumps @types/node and @t3-oss/env-core. |
| packages/core/src/schemas/task-parsers.ts | Removes seo-plan-keyword task schemas from unions. |
| packages/core/src/schemas/project-parsers.ts | Adds explicit JSON Schemas for settings; adjusts ArkType validators. |
| packages/core/src/schema/arktype-json-schema-transformer.ts | Deletes ArkType JSON schema transformer utility. |
| packages/content/package.json | Bumps @types/node. |
| packages/auth/package.json | Bumps @types/node and @t3-oss/env-core. |
| packages/api-user-vm/package.json | Bumps @types/node and @t3-oss/env-core. |
| packages/api-seo/src/workflows/strategy-suggestions-workflow.ts | Switches suggestion generation to Strategy Advisor agent + telemetry/auth helper. |
| packages/api-seo/src/workflows/onboarding-workflow.ts | Moves AI outputs to jsonSchema(...) and updates web tools factory usage. |
| packages/api-seo/src/workflows/index.ts | Removes planner workflow binding/export. |
| packages/api-seo/src/types.ts | Updates chat message typing to agent-based message inference; removes planner workflow typing. |
| packages/api-seo/src/routes/task.ts | Removes planner workflow instance lookup for seo-plan-keyword. |
| packages/api-seo/src/routes/chat.sendMessage.ts | Switches chat streaming to orchestrator agent; updates step logging. |
| packages/api-seo/src/lib/workspace/constants.ts | Adjusts default writing settings (custom instructions now empty). |
| packages/api-seo/src/lib/task.ts | Removes seo-plan-keyword workflow creation. |
| packages/api-seo/src/lib/dataforseo/utils.ts | Reuses exported DataForSEO request arg types to reduce duplication. |
| packages/api-seo/src/lib/content/write-content-draft.ts | Removes automatic seo-plan-keyword task creation on “suggested” status. |
| packages/api-seo/src/lib/ai/writer-agent.ts | Deletes legacy writer agent implementation. |
| packages/api-seo/src/lib/ai/utils/wrapped-language-model.ts | Adds wrapper for injecting tool input examples via middleware. |
| packages/api-seo/src/lib/ai/utils/store-optimized-or-original-image.ts | Makes Cloudflare binding resolution dynamic/optional. |
| packages/api-seo/src/lib/ai/utils/review.ts | Adds Gemini-based article review utility with structured output. |
| packages/api-seo/src/lib/ai/utils/project-context.ts | Adds shared project-context formatter utility. |
| packages/api-seo/src/lib/ai/utils/format-business-background.ts | Updates business background formatting for prompts. |
| packages/api-seo/src/lib/ai/utils/auth-init.ts | Adds workflow auth initialization helper. |
| packages/api-seo/src/lib/ai/utils/agent-telemetry.ts | Adds token/cost telemetry summarizers for agent invocations. |
| packages/api-seo/src/lib/ai/tools/web-tools.ts | Reworks tool schemas to jsonSchema(...) + input examples; renames factory. |
| packages/api-seo/src/lib/ai/tools/utils.ts | Deletes tool-definition/skills formatting helper. |
| packages/api-seo/src/lib/ai/tools/todo-tool.ts | Deletes legacy todo tool. |
| packages/api-seo/src/lib/ai/tools/strategy-tools.ts | Deletes legacy strategy tools wrapper. |
| packages/api-seo/src/lib/ai/tools/skill-tools.ts | Deletes legacy “skills” meta-tools. |
| packages/api-seo/src/lib/ai/tools/settings-tools.ts | Reworks schemas to jsonSchema(...) and adds wrapped OpenAI model usage. |
| packages/api-seo/src/lib/ai/tools/screenshot-script.ts | Deletes local screenshot script. |
| packages/api-seo/src/lib/ai/tools/planner-tools.ts | Deletes legacy planner tools. |
| packages/api-seo/src/lib/ai/tools/internal-links-tool.ts | Reworks internal-links tool to jsonSchema(...) and project-derived locale. |
| packages/api-seo/src/lib/ai/tools/image-tools.ts | Reworks image tools schemas; normalizes media types; input examples. |
| packages/api-seo/src/lib/ai/tools/google-search-console-tool.ts | Reworks GSC tool schema to jsonSchema(...) + input examples. |
| packages/api-seo/src/lib/ai/tools/file-tool.ts | Deletes legacy file workspace tool. |
| packages/api-seo/src/lib/ai/tools/dataforseo-tool.ts | Reworks DataForSEO tools to jsonSchema(...) + examples + defaults. |
| packages/api-seo/src/lib/ai/tools/data-analysis-agent-tool.ts | Deletes legacy data analysis subagent tool. |
| packages/api-seo/src/lib/ai/tools/create-article-tool.ts | Deletes legacy create-article tool. |
| packages/api-seo/src/lib/ai/tools/ask-question-tool.ts | Adds ask-questions tool using jsonSchema(...) + examples. |
| packages/api-seo/src/lib/ai/strategist-agent.ts | Deletes legacy strategist agent. |
| packages/api-seo/src/lib/ai/instructions/strategy-advisor.ts | Adds Strategy Advisor system prompt/instructions. |
| packages/api-seo/src/lib/ai/instructions/orchestrator.ts | Adds orchestrator system prompt/instructions. |
| packages/api-seo/src/lib/ai/arktype-json-schema.ts | Deletes ArkType→AI JSON schema adapter. |
| packages/api-seo/src/lib/ai/agents/strategy-advisor.ts | Adds ToolLoopAgent-based Strategy Advisor agent construction. |
| packages/api-seo/src/lib/ai/agents/orchestrator.ts | Adds ToolLoopAgent-based orchestrator with advise/write delegation tools. |
| packages/api-seo/src/eval/types.ts | Adds shared eval framework types. |
| packages/api-seo/src/eval/strategy/score.ts | Adds LLM-judge strategy scorer + pairwise comparison. |
| packages/api-seo/src/eval/strategy/fixtures/index.ts | Adds strategy eval fixtures. |
| packages/api-seo/src/eval/content/score.ts | Adds mixed deterministic + LLM-judge content scorer. |
| packages/api-seo/src/context.ts | Removes planner workflow from API context typing. |
| packages/api-seo/package.json | Bumps @types/node and @t3-oss/env-core. |
| packages/api-core/package.json | Bumps @types/node. |
| apps/www/package.json | Bumps @types/node and @t3-oss/env-core. |
| apps/seo/wrangler.jsonc | Removes planner workflow bindings across envs. |
| apps/seo/src/server.ts | Removes planner workflow export wiring. |
| apps/seo/src/routes/_authed/admin/route.tsx | Deletes old admin route file. |
| apps/seo/src/routeTree.gen.ts | Updates generated routes for admin index route location. |
| apps/seo/package.json | Bumps @types/node and @t3-oss/env-core. |
| apps/seo-www/package.json | Bumps @types/node and @t3-oss/env-core. |
| apps/seo-contact/package.json | Bumps @types/node and @t3-oss/env-core. |
| AGENTS.md | Updates repo guidance, including AI tool schema convention. |
| .cursor/rules/base-rules.mdc | Removes Cursor base rules doc. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
| export function buildStrategyAdvisorInstructions(args: { | ||
| project: typeof schema.seoProject.$inferSelect; | ||
| }): string { | ||
| const projectContext = buildProjectContext(args.project); | ||
|
|
||
| return `<role> | ||
| You are a world-class SEO/GEO strategist, analyst, and diagnostician for ${args.project.name ?? args.project.websiteUrl}. | ||
|
|
There was a problem hiding this comment.
Same as orchestrator: buildProjectContext() returns a string[] but ${projectContext} will stringify with commas. Join with \n (or have buildProjectContext return a single string) so the Strategy Advisor prompt stays readable and consistent.
| ## Tool Selection Guide | ||
|
|
||
| - **Keyword research**: Start with get_keyword_suggestions for expanding a seed term, then get_keywords_overview to compare volume/difficulty/intent across candidates. | ||
| - **SERP analysis**: Use gxist, and what content format wins. This is the most underused and met_serp_for_keyword to see who actually ranks, what SERP features eost valuable tool — use it liberally. |
There was a problem hiding this comment.
This instruction line looks corrupted/garbled ("Use gxist... met_serp_for_keyword... eost valuable tool") and references a non-existent tool name. Since this text is part of the agent's system prompt, it can materially harm tool usage. Please rewrite it to clearly reference the actual tool (get_serp_for_keyword) and remove the stray words.
| - **SERP analysis**: Use gxist, and what content format wins. This is the most underused and met_serp_for_keyword to see who actually ranks, what SERP features eost valuable tool — use it liberally. | |
| - **SERP analysis**: Use get_serp_for_keyword to see who actually ranks, which SERP features appear, and what content format wins for a query. This is one of the most valuable tools — use it liberally. |
|
|
||
| <project-context> | ||
| ${projectContext} | ||
| - Google Search Console: ${args.gscConnected ? `Connected (${args.gscDomain})` : "Not connected. If the the user asks for performance/decay/CTR, prioritize connecting via `manage_integrations`"} |
There was a problem hiding this comment.
There is a duplicated word in the GSC status line ("If the the user asks..."). Since this is system-prompt text, please fix the typo to keep prompts clean.
| - Google Search Console: ${args.gscConnected ? `Connected (${args.gscDomain})` : "Not connected. If the the user asks for performance/decay/CTR, prioritize connecting via `manage_integrations`"} | |
| - Google Search Console: ${args.gscConnected ? `Connected (${args.gscDomain})` : "Not connected. If the user asks for performance/decay/CTR, prioritize connecting via `manage_integrations`"} |
| - Competitor websites: ${background.competitorsWebsites.length === 0 ? "None Present" : `${background.competitorsWebsites.map((website, index) => `\n - ${index + 1}. ${website}`).join("")}`} | ||
| `; |
There was a problem hiding this comment.
background.competitorsWebsites is an array of objects ({ url: string } per businessBackgroundSchema), but this formatter interpolates each item as ${website}, which will render as [object Object] and degrade prompt/context quality. Map to website.url (and consider formatting as plain lines without nested template literals for readability).
| competitorsWebsites: { | ||
| type: "array", | ||
| description: "List of URLs of direct competitors. Leave blank if none.", | ||
| items: { | ||
| type: "object", | ||
| additionalProperties: false, | ||
| required: ["url"] satisfies string[], | ||
| properties: { | ||
| url: { type: "string" }, | ||
| }, |
There was a problem hiding this comment.
businessBackgroundSchema now validates competitorsWebsites.url as string.url, but businessBackgroundJsonSchema defines it as just { type: "string" } (no format: "uri"). This makes the AI-output validation weaker than the runtime validator and can let invalid URLs through when using jsonSchema(...). Align the JSON schema with the ArkType schema by adding a URI format constraint.
| const serpResult = await fetchSerp({ | ||
| keyword: input.query, | ||
| keyword: `${input.query} site:${targetUrl}`, | ||
| locationName, | ||
| languageCode: input.languageCode, | ||
| targetUrl, | ||
| languageCode, | ||
| }); |
There was a problem hiding this comment.
internal_links currently constrains results by appending site:${targetUrl} where targetUrl is project.websiteUrl (likely includes protocol and possibly a path). This can lead to poor/incorrect SERP constraints. Prefer constraining via DataForSEO's targetUrl param (supported by fetchSerp) or at least parse project.websiteUrl to a hostname before using the site: operator.
| description: | ||
| "Run a live web search for up-to-date information. This tool uses the web to find the latest information on the given queries.", | ||
| inputSchema: webSearchInputSchema, | ||
| "Run a live web search for up-to-date information. This tool uses the web to find the latest information based off the the given instruction and queries.", | ||
| inputSchema: jsonSchema<{ |
There was a problem hiding this comment.
Tool description has a grammatical typo: "based off the the" (double "the"). Please fix the string to avoid confusing agents/users and to keep logs/docs clean.
| </core-behavior> | ||
|
|
||
| <project-context> | ||
| ${projectContext} |
There was a problem hiding this comment.
buildProjectContext() returns a string[], but it's interpolated directly (${projectContext}) which will stringify with commas between items. This makes the system prompt harder to read and can reduce agent performance. Join the array with newlines (or change buildProjectContext to return a single newline-joined string).
| ${projectContext} | |
| ${projectContext.join("\n")} |
|
Cloudflare Preview URL for WWW 🎈 : https://pr-403.rectangularlabs.com (custom domain) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4b81f7aad9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| schema: jsonSchema< | ||
| Omit<typeof businessBackgroundSchema.infer, "version"> | ||
| >(businessBackgroundJsonSchema), |
There was a problem hiding this comment.
Keep settings version when applying AI updates
The manage_settings update path now generates businessBackground, imageSettings, and writingSettings with schemas typed as Omit<..., "version">, then writes that object back to seoProject. This means any update from this tool can drop the required version: "v1" field, and subsequent settings reads that validate against businessBackgroundSchema/imageSettingsSchema/writingSettingsSchema will fail for those projects. This is a data-corrupting regression for any user who asks chat to update project settings.
Useful? React with 👍 / 👎.
| status: "completed", | ||
| type: "content", | ||
| fixtureId: fixture.id, | ||
| output: draft.text, |
There was a problem hiding this comment.
Extract markdown before storing content eval output
runEvalGenerationJob saves draft.text directly as the generated article, but the writer agent now returns text as a JSON stringified object ({ markdown, heroImage, heroImageCaption }). As a result, content eval scoring and downloaded .md output operate on serialized JSON instead of raw markdown, which skews deterministic checks (word count, headings, keyword placement) and corrupts experiment results.
Useful? React with 👍 / 👎.
No description provided.