Conversation
WalkthroughA modular rewrite replaces monolithic agents with per-responsibility agent modules and a stateful, multi-step blog-generation pipeline (structure → content → validation → internal links → finalization), updates schemas, migrations and models to track pipeline state/metadata, and adds frontend controllers/templates to drive the pipeline UI. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Frontend UI
participant API as Pipeline API
participant Agents as Agent Modules
participant DB as Database
UI->>API: POST /pipeline/start (suggestion_id)
API->>DB: create pipeline_state (initialized)
API-->>UI: PipelineStartOut
loop steps: structure → content → preliminary_validation → internal_links → final_validation
UI->>API: POST /pipeline/step (pipeline_id, step_name)
API->>Agents: invoke agent for step_name
Agents-->>API: step_result (output, needs_fix?)
API->>DB: update pipeline_state & pipeline_metadata
API-->>UI: PipelineStepOut (status, result)
alt needs_fix
UI->>API: POST /pipeline/retry (pipeline_id, step_name)
API->>Agents: invoke fix agent / re-run step
Agents-->>API: fixed_result
API->>DB: update retry count/status
API-->>UI: PipelineRetryOut
end
end
UI->>API: POST /pipeline/complete (pipeline_id)
API->>DB: finalize pipeline_state, persist raw_content
API-->>UI: PipelineCompleteOut (post_id, view_url)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Areas to focus on:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Looks good. Worth considering though. View full project report here.
| @@ -1,3 +1,4 @@ | |||
| import json | |||
There was a problem hiding this comment.
There's a lot of models here. Consider splitting into multiple files. More info.
Pro model prefix is used quite a lot. Maybe split the models into multiple files. Explained here.
There was a problem hiding this comment.
Greptile Overview
Greptile Summary
This PR implements a comprehensive revamp of the blog post generation system, transforming it from a single-step process into a sophisticated multi-step pipeline with validation and retry capabilities.
Key Changes
Architecture Improvements:
- Refactored monolithic
core/agents.py(233 lines) into focused, single-responsibility agent modules incore/agents/directory - Extracted system prompts into reusable functions in
core/agents/system_prompts.py - Removed deprecated files:
core/prompts.py,core/constants.py
Pipeline Implementation:
- Added 5-step generation pipeline: structure → content → preliminary validation → internal links → final validation
- Implemented pipeline state tracking with JSON-based status, retry counts, and error messages in
GeneratedBlogPostmodel - Added
pipeline_state,generation_structure,raw_content, andpipeline_metadatafields via migrations
New AI Agents:
blog_structure_agent.py: Generates comprehensive outline before content creationcontent_validation_agent.py: AI-powered validation replacing rule-based checksfix_validation_issue_agent.py: Automatically fixes validation issuesinternal_links_agent.py: Intelligently inserts internal links to project pages
API & Frontend:
- Added 5 new pipeline endpoints:
/start,/step/{step_name},/status,/retry/{step_name},/complete - New Stimulus controller
blog_generation_pipeline_controller.jsfor real-time pipeline progress UI - Removed old single-step endpoints:
/generate-blog-content,/fix-generated-blog-post,/generate-competitor-vs-title
Simplified Content Type:
- Removed
ContentType.SHARINGoption, keeping onlyContentType.SEO - Updated all title generation to use SEO-focused approach
Benefits
This refactoring significantly improves:
- Code maintainability: Modular agents easier to test and modify
- Content quality: Structure-first approach and AI validation ensure better output
- User experience: Progressive UI with retry capability on failures
- Flexibility: Each pipeline step can be executed independently or as a complete flow
Confidence Score: 4/5
- This PR is safe to merge with minor spelling corrections needed
- Score reflects excellent architectural improvements and comprehensive implementation, but two spelling errors need correction before merge: 'gnerate' in system_prompts.py:119 and 'NICH_AUDIENCE' in choices.py:10. The refactoring follows Django best practices with proper error handling, logging, and database migrations. The multi-step pipeline design is well-structured with retry logic and state management. No logical errors or security issues detected.
- core/agents/system_prompts.py and core/choices.py require spelling corrections
Important Files Changed
File Analysis
| Filename | Score | Overview |
|---|---|---|
| core/agents/blog_structure_agent.py | 5/5 | New agent for generating blog post structure/outline, well-documented system prompt with clear guidelines |
| core/agents/seo_content_generator_agent.py | 5/5 | Comprehensive SEO content generation agent with structure-based guidance, properly configured with high token limits |
| core/agents/system_prompts.py | 5/5 | Centralized reusable system prompt functions, promotes DRY principle and consistency across agents |
| core/models.py | 4/5 | Major refactoring to implement multi-step pipeline: extracted agent logic to separate modules, added pipeline state tracking with retry logic, new methods for structure generation and validation steps |
| core/api/views.py | 4/5 | Removed old generate-blog-content endpoint, added new pipeline endpoints (start, step execution, status, retry, complete) with comprehensive error handling and logging |
| frontend/src/controllers/blog_generation_pipeline_controller.js | 4/5 | New Stimulus controller for managing UI during multi-step pipeline execution with progress tracking, retry logic, and visual step status updates |
| core/tasks.py | 5/5 | Updated to use new pipeline method (execute_complete_pipeline) instead of old generate_content, removed ContentType randomization |
| core/schemas.py | 5/5 | Added new schemas for pipeline (BlogPostStructure, BlogPostSection, InternalLinkContext, ContentValidationResult, ContentFixContext), updated ProjectDetails with is_on_free_plan field |
| core/choices.py | 4/5 | Removed ContentType.SHARING option, keeping only SEO type; contains spelling error in NICH_AUDIENCE constant |
Sequence Diagram
sequenceDiagram
participant User
participant UI as Stimulus Controller
participant API as Django API
participant Model as BlogPostTitleSuggestion
participant Agent as AI Agents
participant DB as Database
User->>UI: Click Generate Blog Post
UI->>API: POST /pipeline/{id}/start
API->>Model: start_generation_pipeline()
Model->>DB: Create GeneratedBlogPost
Model->>DB: initialize_pipeline()
DB-->>Model: Pipeline state initialized
Model-->>API: Return blog_post_id
API-->>UI: Return success + blog_post_id
loop For each pipeline step
UI->>API: POST /pipeline/{id}/step/{step_name}
alt Step: generate_structure
API->>Model: generate_structure(blog_post)
Model->>Agent: blog_structure_agent.run()
Agent-->>Model: BlogPostStructure
Model->>DB: Save structure as JSON
Model->>DB: update_pipeline_step("completed")
else Step: generate_content
API->>Model: generate_content_from_structure(blog_post)
Model->>DB: Load structure
Model->>Agent: seo_content_generator_agent.run()
Agent-->>Model: Generated content
Model->>DB: Save content
Model->>DB: update_pipeline_step("completed")
else Step: preliminary_validation
API->>Model: run_preliminary_validation(blog_post)
Model->>Agent: content_validation_agent.run()
Agent-->>Model: ValidationResult
alt Validation fails
Model->>DB: update_pipeline_step("failed", issues)
Model-->>API: Return needs_fix=true
UI->>API: POST /pipeline/{id}/step/fix_preliminary_validation
API->>Model: fix_preliminary_validation(blog_post, issues)
Model->>Agent: fix_validation_issue_agent.run()
Agent-->>Model: Fixed content
Model->>DB: Save fixed content
else Validation passes
Model->>DB: update_pipeline_step("completed")
end
else Step: insert_internal_links
API->>Model: insert_internal_links(blog_post)
Model->>Agent: internal_links_agent.run()
Agent-->>Model: Content with links
Model->>DB: Save updated content
Model->>DB: update_pipeline_step("completed")
else Step: final_validation
API->>Model: run_final_validation(blog_post)
Model->>Agent: content_validation_agent.run()
Agent-->>Model: ValidationResult
alt Validation fails
Model->>DB: update_pipeline_step("failed", issues)
Model-->>API: Return needs_fix=true
UI->>API: POST /pipeline/{id}/step/fix_final_validation
API->>Model: fix_final_validation(blog_post, issues)
Model->>Agent: fix_validation_issue_agent.run()
Agent-->>Model: Fixed content
Model->>DB: Save fixed content
Model->>DB: Re-run validation
else Validation passes
Model->>DB: update_pipeline_step("completed")
end
end
API-->>UI: Return step result + pipeline state
UI->>UI: Update progress UI
end
UI->>User: Show completion + link to blog post
Additional Comments (2)
35 files reviewed, 2 comments
There was a problem hiding this comment.
Actionable comments posted: 16
🧹 Nitpick comments (6)
frontend/src/controllers/content_idea_controller.js (1)
14-17: Consider simplifying the redundant method.Since
getCurrentTab()now always returns"SEO", the method is redundant. Consider one of these options:
- Replace with a constant and use it directly:
- getCurrentTab() { - // Always return SEO - return "SEO"; - } + static get CONTENT_TYPE() { + return "SEO"; + }Then update line 24:
- const contentType = this.getCurrentTab(); + const contentType = this.constructor.CONTENT_TYPE;
- Or inline the value directly at line 24 since it's now a constant:
- const contentType = this.getCurrentTab(); + const contentType = "SEO";And remove the
getCurrentTab()method entirely.core/agents/blog_structure_agent.py (1)
39-39: Remove unusednoqadirective.The
noqa: E501directive on line 39 is unused since E501 is not enabled in your Ruff configuration.Apply this diff:
- """, # noqa: E501 + """,core/agents/fix_validation_issue_agent.py (1)
27-27: Remove unusednoqadirective.The
noqa: E501directive on line 27 is unused since E501 is not enabled in your Ruff configuration.Apply this diff:
- """, # noqa: E501 + """,core/api/views.py (1)
1077-1077: Consider using f-string conversion for explicit str() calls.Static analysis suggests using explicit conversion flags in f-strings (e.g.,
f"{error}"instead ofstr(error)) for clarity.Example for line 1077:
- "message": f"Failed to start pipeline: {str(error)}", + "message": f"Failed to start pipeline: {error}",Apply similar changes to lines 1206 and 1349.
Also applies to: 1206-1206, 1349-1349
core/models.py (2)
819-819: Consider handling JSON decode errors explicitly.If
blog_post.generation_structurecontains invalid JSON,json.loads()will raiseJSONDecodeError. This would be caught by the general exception handler, but explicitly catching and logging JSON decode errors would provide clearer diagnostics.try: # Load the structure - structure_dict = json.loads(blog_post.generation_structure) + try: + structure_dict = json.loads(blog_post.generation_structure) + except json.JSONDecodeError as json_error: + logger.error( + "[Pipeline] Invalid JSON in generation_structure", + blog_post_id=blog_post.id, + error=str(json_error), + ) + raise ValueError(f"Invalid structure JSON: {json_error}") from json_error
1504-1504: Add explicit type hint for optional parameter.Static analysis suggests making the
Optionaltype explicit per PEP 484.- def update_pipeline_step(self, step_name: str, status: str, error: str = None): + def update_pipeline_step(self, step_name: str, status: str, error: str | None = None):
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (35)
CHANGELOG.md(1 hunks)core/agents.py(0 hunks)core/agents/analyze_project_agent.py(1 hunks)core/agents/blog_structure_agent.py(1 hunks)core/agents/competitor_finder_agent.py(1 hunks)core/agents/competitor_vs_blog_post_agent.py(1 hunks)core/agents/content_editor_agent.py(1 hunks)core/agents/content_validation_agent.py(1 hunks)core/agents/fix_validation_issue_agent.py(1 hunks)core/agents/internal_links_agent.py(1 hunks)core/agents/seo_content_generator_agent.py(1 hunks)core/agents/summarize_page_agent.py(1 hunks)core/agents/system_prompts.py(1 hunks)core/agents/title_suggestions_agent.py(1 hunks)core/api/schemas.py(2 hunks)core/api/views.py(5 hunks)core/choices.py(0 hunks)core/constants.py(0 hunks)core/migrations/0042_generatedblogpost_generation_structure_and_more.py(1 hunks)core/migrations/0043_remove_generatedblogpost_content_too_short_and_more.py(1 hunks)core/models.py(17 hunks)core/prompts.py(0 hunks)core/schemas.py(3 hunks)core/tasks.py(4 hunks)core/utils.py(2 hunks)frontend/src/controllers/blog_generation_pipeline_controller.js(1 hunks)frontend/src/controllers/content_idea_controller.js(1 hunks)frontend/src/controllers/fix-blog-post-controller.js(0 hunks)frontend/src/controllers/generate-content-controller.js(4 hunks)frontend/src/controllers/scan_progress_controller.js(1 hunks)frontend/src/controllers/title_suggestions_controller.js(1 hunks)frontend/templates/blog/generated_blog_post_detail.html(0 hunks)frontend/templates/components/blog_post_suggestion_card.html(3 hunks)frontend/templates/components/blog_post_validation_warning.html(0 hunks)frontend/templates/project/project_detail.html(0 hunks)
💤 Files with no reviewable changes (8)
- frontend/src/controllers/fix-blog-post-controller.js
- frontend/templates/components/blog_post_validation_warning.html
- core/choices.py
- frontend/templates/project/project_detail.html
- frontend/templates/blog/generated_blog_post_detail.html
- core/prompts.py
- core/constants.py
- core/agents.py
🧰 Additional context used
📓 Path-based instructions (15)
**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)
**/*.py: Prioritize readability and maintainability; follow PEP 8
Use descriptive variable/function names with underscoresIn Python, use try-except blocks that catch specific exception types; do not catch broad Exception.
**/*.py: Follow PEP 8 for Python code style
Use descriptive variable names with underscores (snake_case) in Python
Prefer Django's built-in features over external libraries
**/*.py: Use structlog for logging in Python code (avoid print and the standard logging module)
Include contextual key-value fields in log messages (e.g., error=str(e), exc_info=True, profile_id=profile.id)
**/*.py: Use descriptive, full-word variable names; avoid abbreviations and single-letter variables in Python
Provide context in variable names when format or type matters (e.g., include_iso_format,date)
Extract unchanging values into UPPER_CASE constants
Use intermediary variables to name parsed groups instead of using index access directly
Naming conventions: use is_/has_/can_ for booleans; include 'date' for dates; snake_case for variables/functions; PascalCase for classes
Define variables close to where they are used to keep lifespan short
Name things after what they do, not how they're used; ensure names make sense without extra context
Avoid generic names like data, info, manager; use specific, intention-revealing names
Function names should include necessary context without being verbose
If naming is hard, split functions into smaller focused parts
Maintain consistency: reuse the same verbs and nouns for the same concepts; name variables after the functions that create them
Use more descriptive names for longer-lived variables
Avoid else statements by using guard clauses for early returns
Replace simple conditionals with direct assignment when both branches call the same function with different values
Use dictionaries as dispatch tables instead of multiple equal-probability elif chains
Validate input before processing to prevent errors propagating in...
Files:
core/agents/system_prompts.pycore/agents/internal_links_agent.pycore/agents/fix_validation_issue_agent.pycore/agents/analyze_project_agent.pycore/agents/content_validation_agent.pycore/agents/blog_structure_agent.pycore/agents/content_editor_agent.pycore/migrations/0043_remove_generatedblogpost_content_too_short_and_more.pycore/agents/seo_content_generator_agent.pycore/agents/competitor_vs_blog_post_agent.pycore/agents/summarize_page_agent.pycore/api/schemas.pycore/migrations/0042_generatedblogpost_generation_structure_and_more.pycore/api/views.pycore/agents/title_suggestions_agent.pycore/tasks.pycore/schemas.pycore/agents/competitor_finder_agent.pycore/utils.pycore/models.py
core/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)
Leverage Django's ORM; avoid raw SQL when possible
Files:
core/agents/system_prompts.pycore/agents/internal_links_agent.pycore/agents/fix_validation_issue_agent.pycore/agents/analyze_project_agent.pycore/agents/content_validation_agent.pycore/agents/blog_structure_agent.pycore/agents/content_editor_agent.pycore/migrations/0043_remove_generatedblogpost_content_too_short_and_more.pycore/agents/seo_content_generator_agent.pycore/agents/competitor_vs_blog_post_agent.pycore/agents/summarize_page_agent.pycore/api/schemas.pycore/migrations/0042_generatedblogpost_generation_structure_and_more.pycore/api/views.pycore/agents/title_suggestions_agent.pycore/tasks.pycore/schemas.pycore/agents/competitor_finder_agent.pycore/utils.pycore/models.py
core/agents/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Implement AI agents using pydantic-ai under core/agents/
Files:
core/agents/system_prompts.pycore/agents/internal_links_agent.pycore/agents/fix_validation_issue_agent.pycore/agents/analyze_project_agent.pycore/agents/content_validation_agent.pycore/agents/blog_structure_agent.pycore/agents/content_editor_agent.pycore/agents/seo_content_generator_agent.pycore/agents/competitor_vs_blog_post_agent.pycore/agents/summarize_page_agent.pycore/agents/title_suggestions_agent.pycore/agents/competitor_finder_agent.py
frontend/src/controllers/**/*.js
📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)
frontend/src/controllers/**/*.js: Use Stimulus controllers to encapsulate JavaScript behavior and keep it separate from HTML structure
New controllers should be created in thefrontend/src/controllersdirectoryUse Stimulus.js controllers for interactive frontend behavior
Files:
frontend/src/controllers/scan_progress_controller.jsfrontend/src/controllers/title_suggestions_controller.jsfrontend/src/controllers/generate-content-controller.jsfrontend/src/controllers/blog_generation_pipeline_controller.jsfrontend/src/controllers/content_idea_controller.js
**/*.js
📄 CodeRabbit inference engine (.cursor/rules/stimulus-general.mdc)
**/*.js: Add semicolons at the end of statements in JavaScript files
Use double quotes instead of single quotes for string literals in JavaScript files
Files:
frontend/src/controllers/scan_progress_controller.jsfrontend/src/controllers/title_suggestions_controller.jsfrontend/src/controllers/generate-content-controller.jsfrontend/src/controllers/blog_generation_pipeline_controller.jsfrontend/src/controllers/content_idea_controller.js
{**/*.{css,scss},**/*_controller.@(js|ts)}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Never create new styles without explicitly receiving permission to do so
Files:
frontend/src/controllers/scan_progress_controller.jsfrontend/src/controllers/title_suggestions_controller.jsfrontend/src/controllers/blog_generation_pipeline_controller.jsfrontend/src/controllers/content_idea_controller.js
{**/*.{html,htm,css,scss},**/*_controller.@(js|ts)}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always favor the utility-first Tailwind approach; avoid creating reusable style classes and prefer reuse via template components
Files:
frontend/src/controllers/scan_progress_controller.jsfrontend/src/controllers/title_suggestions_controller.jsfrontend/templates/components/blog_post_suggestion_card.htmlfrontend/src/controllers/blog_generation_pipeline_controller.jsfrontend/src/controllers/content_idea_controller.js
frontend/src/controllers/**/*_controller.js
📄 CodeRabbit inference engine (.cursor/rules/stimulus-events.mdc)
frontend/src/controllers/**/*_controller.js: Dispatch a CustomEvent from the actor Stimulus controller to signal actions to other controllers
Name custom events descriptively and namespaced (e.g., "suggestion:move")
Set bubbles: true on CustomEvent so ancestor listeners (window/document) can receive it
Include all necessary payload in event.detail (e.g., element and destination)
Dispatch the event from the controller element (this.element.dispatchEvent(event))
In listener controllers, register global event listeners in connect() and remove them in disconnect()
Bind handler methods and keep a reference (e.g., this.boundMove = this.move.bind(this)) to correctly remove listeners
Attach listeners to window or document to catch bubbled custom events from anywhere on the page
Inspect event.detail in the handler and act conditionally based on its contents (e.g., match destination)
Files:
frontend/src/controllers/scan_progress_controller.jsfrontend/src/controllers/title_suggestions_controller.jsfrontend/src/controllers/blog_generation_pipeline_controller.jsfrontend/src/controllers/content_idea_controller.js
**/*.html
📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)
**/*.html: Prefer Stimulus JS for adding interactivity to Django templates instead of raw script elements
Leverage Stimulus data attributes to connect HTML elements with JavaScript functionality
Utilize Stimulus targets to reference specific elements within a controller
Employ Stimulus actions to handle user interactions and events
Files:
frontend/templates/components/blog_post_suggestion_card.html
**/*.{html,htm}
📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)
Always generate semantic HTML
Files:
frontend/templates/components/blog_post_suggestion_card.html
frontend/templates/**/*.html
📄 CodeRabbit inference engine (.cursor/rules/stimulus-events.mdc)
Avoid data-*-outlet links for sibling controller communication when using the event-based approach; keep controllers self-contained
Use semantic HTML elements (e.g., dialog, details/summary) in templates
Files:
frontend/templates/components/blog_post_suggestion_card.html
core/api/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)
core/api/**/*.py: django-ninja generate openapi documentation, so make sure to populate views with relevant data in order for it to show up in the OpenAPI docs.
Use Pydantic models for schema validation
Use django-ninja's authentication classesImplement REST APIs with django-ninja using Pydantic schemas under core/api/
Files:
core/api/schemas.pycore/api/views.py
core/tasks.py
📄 CodeRabbit inference engine (CLAUDE.md)
Define background tasks using django-q2 in core/tasks.py
Files:
core/tasks.py
**/{utils,repository}.py
📄 CodeRabbit inference engine (AGENTS.md)
Avoid generic filenames like utils.py and repository.py; use specific names instead
Files:
core/utils.py
core/models.py
📄 CodeRabbit inference engine (CLAUDE.md)
core/models.py: Place business logic in Django models (fat models pattern)
Validate simple constraints in the database and place complex domain logic in Django models
Files:
core/models.py
🧠 Learnings (5)
📚 Learning: 2025-10-04T08:52:58.590Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: .cursor/rules/agent-rules.mdc:0-0
Timestamp: 2025-10-04T08:52:58.590Z
Learning: Always add AGENTS.md into AI context
Applied to files:
core/agents/internal_links_agent.pycore/agents/fix_validation_issue_agent.pycore/agents/analyze_project_agent.pycore/agents/content_validation_agent.pycore/agents/blog_structure_agent.pycore/agents/content_editor_agent.pycore/agents/seo_content_generator_agent.pycore/agents/competitor_vs_blog_post_agent.pycore/agents/summarize_page_agent.pycore/agents/title_suggestions_agent.py
📚 Learning: 2025-10-04T08:52:58.590Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: .cursor/rules/agent-rules.mdc:0-0
Timestamp: 2025-10-04T08:52:58.590Z
Learning: Always add AGENTS.md into context
Applied to files:
core/agents/internal_links_agent.pycore/agents/fix_validation_issue_agent.pycore/agents/competitor_vs_blog_post_agent.pycore/agents/summarize_page_agent.py
📚 Learning: 2025-10-04T08:52:37.437Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T08:52:37.437Z
Learning: Applies to core/agents/**/*.py : Implement AI agents using pydantic-ai under core/agents/
Applied to files:
core/agents/analyze_project_agent.pycore/agents/content_validation_agent.pycore/agents/blog_structure_agent.pycore/agents/competitor_vs_blog_post_agent.pycore/agents/summarize_page_agent.pycore/agents/competitor_finder_agent.pycore/utils.pycore/models.py
📚 Learning: 2025-10-04T08:52:37.437Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T08:52:37.437Z
Learning: Applies to frontend/src/controllers/**/*.js : Use Stimulus.js controllers for interactive frontend behavior
Applied to files:
frontend/src/controllers/blog_generation_pipeline_controller.js
📚 Learning: 2025-10-04T08:52:37.437Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T08:52:37.437Z
Learning: Applies to core/tasks.py : Define background tasks using django-q2 in core/tasks.py
Applied to files:
core/tasks.pycore/models.py
🧬 Code graph analysis (20)
frontend/src/controllers/scan_progress_controller.js (1)
frontend/src/controllers/content_idea_controller.js (1)
contentType(24-24)
core/agents/internal_links_agent.py (3)
core/agents/system_prompts.py (1)
inline_link_formatting(165-179)core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (1)
InternalLinkContext(312-318)
core/agents/fix_validation_issue_agent.py (2)
core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (1)
ContentFixContext(331-337)
core/agents/analyze_project_agent.py (3)
core/agents/system_prompts.py (1)
add_webpage_content(156-162)core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (2)
ProjectDetails(17-102)WebPageContent(11-14)
core/agents/content_validation_agent.py (2)
core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (1)
ContentValidationResult(321-328)
core/agents/blog_structure_agent.py (3)
core/agents/system_prompts.py (5)
add_language_specification(132-137)add_project_details(49-62)add_project_pages(65-113)add_target_keywords(140-153)add_title_details(116-129)core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (2)
BlogPostGenerationContext(185-192)BlogPostStructure(292-309)
frontend/src/controllers/generate-content-controller.js (2)
frontend/src/controllers/blog_generation_pipeline_controller.js (6)
step(65-65)step(133-133)response(48-53)response(89-97)data(55-55)data(99-99)frontend/src/utils/messages.js (1)
showMessage(2-39)
core/agents/content_editor_agent.py (3)
core/agents/system_prompts.py (6)
add_language_specification(132-137)add_project_details(49-62)add_project_pages(65-113)add_target_keywords(140-153)add_title_details(116-129)inline_link_formatting(165-179)core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (1)
BlogPostGenerationContext(185-192)
core/agents/seo_content_generator_agent.py (3)
core/agents/system_prompts.py (9)
add_language_specification(132-137)add_project_details(49-62)add_target_keywords(140-153)add_title_details(116-129)add_todays_date(7-8)filler_content(39-46)inline_link_formatting(165-179)markdown_lists(30-36)post_structure(22-27)core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (2)
BlogPostGenerationContext(185-192)GeneratedBlogPostSchema(195-205)
frontend/src/controllers/blog_generation_pipeline_controller.js (3)
frontend/src/controllers/content_idea_controller.js (2)
response(36-47)data(53-53)frontend/src/controllers/generate-content-controller.js (4)
response(311-316)data(323-323)error(69-69)error(319-319)frontend/src/utils/messages.js (1)
showMessage(2-39)
core/agents/competitor_vs_blog_post_agent.py (3)
core/agents/system_prompts.py (3)
add_project_pages(65-113)inline_link_formatting(165-179)markdown_lists(30-36)core/choices.py (1)
AIModel(127-129)core/schemas.py (1)
CompetitorVsPostContext(260-273)
core/agents/summarize_page_agent.py (3)
core/agents/system_prompts.py (1)
add_webpage_content(156-162)core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (2)
ProjectPageDetails(105-132)WebPageContent(11-14)
core/api/schemas.py (1)
core/choices.py (1)
ContentType(4-5)
core/api/views.py (2)
core/api/schemas.py (5)
PipelineCompleteOut(286-290)PipelineRetryOut(280-283)PipelineStartOut(257-261)PipelineStatusOut(272-277)PipelineStepOut(264-269)core/models.py (7)
BlogPostTitleSuggestion(648-1275)start_generation_pipeline(694-720)get_pipeline_status(1564-1584)GeneratedBlogPost(1316-1592)can_retry_step(1586-1592)update_pipeline_step(1504-1562)execute_complete_pipeline(1200-1275)
core/agents/title_suggestions_agent.py (3)
core/agents/system_prompts.py (3)
add_todays_date(7-8)add_project_details(49-62)add_language_specification(132-137)core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (2)
TitleSuggestionContext(135-151)TitleSuggestions(168-171)
core/tasks.py (2)
core/api/views.py (1)
generate_title_suggestions(214-254)core/models.py (2)
generate_title_suggestions(516-563)execute_complete_pipeline(1200-1275)
core/schemas.py (1)
core/models.py (1)
is_on_free_plan(161-162)
core/agents/competitor_finder_agent.py (2)
core/choices.py (1)
AIModel(127-129)core/schemas.py (1)
ProjectDetails(17-102)
core/utils.py (1)
core/choices.py (3)
EmailType(121-124)KeywordDataSource(106-108)OGImageStyle(86-94)
core/models.py (5)
core/model_utils.py (1)
run_agent_synchronously(18-75)core/api/views.py (2)
generate_title_suggestions(214-254)get_pipeline_status(1218-1248)core/choices.py (1)
ContentType(4-5)core/schemas.py (5)
ProjectPageContext(174-182)BlogPostGenerationContext(185-192)ContentFixContext(331-337)InternalLinkContext(312-318)CompetitorVsPostContext(260-273)core/agents/seo_content_generator_agent.py (1)
create_structure_guidance_prompt(90-115)
🪛 Ruff (0.14.3)
core/agents/fix_validation_issue_agent.py
27-27: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
core/agents/content_validation_agent.py
32-32: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
core/agents/blog_structure_agent.py
39-39: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
core/migrations/0043_remove_generatedblogpost_content_too_short_and_more.py
8-10: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
12-34: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
core/agents/seo_content_generator_agent.py
66-66: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
101-101: Unused function argument: ctx
(ARG001)
core/migrations/0042_generatedblogpost_generation_structure_and_more.py
8-10: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
12-33: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
core/api/views.py
1041-1041: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
1060-1065: Consider moving this statement to an else block
(TRY300)
1077-1077: Use explicit conversion flag
Replace with conversion flag
(RUF010)
1206-1206: Use explicit conversion flag
Replace with conversion flag
(RUF010)
1306-1306: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
1349-1349: Use explicit conversion flag
Replace with conversion flag
(RUF010)
core/agents/title_suggestions_agent.py
46-46: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
core/utils.py
393-393: Unused function argument: max_links
(ARG001)
core/models.py
784-784: Consider moving this statement to an else block
(TRY300)
851-851: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
873-873: Consider moving this statement to an else block
(TRY300)
933-936: Consider moving this statement to an else block
(TRY300)
991-991: Consider moving this statement to an else block
(TRY300)
1066-1066: Consider moving this statement to an else block
(TRY300)
1126-1129: Consider moving this statement to an else block
(TRY300)
1184-1184: Consider moving this statement to an else block
(TRY300)
1265-1265: Consider moving this statement to an else block
(TRY300)
1504-1504: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
🔇 Additional comments (15)
frontend/src/controllers/content_idea_controller.js (1)
24-24: No backend compatibility issues exist—backend already enforces SEO-only content type.The backend has already been refactored to support only the SEO content type. The
ContentTypeenum incore/choices.pydefines only SEO, and theBlogPostTitleSuggestionmodel restricts thecontent_typefield toContentType.choices(SEO-only) with a default ofContentType.SEO. The frontend change is fully compatible with the current backend implementation.core/agents/competitor_finder_agent.py (1)
1-86: LGTM! Well-structured agent implementation.The competitor finder agent is cleanly implemented with focused prompt helpers, proper use of pydantic-ai patterns, and intelligent differentiation between free and paid plans. The Perplexity integration is correctly configured, and the system prompts compose well to provide comprehensive context.
core/agents/system_prompts.py (1)
165-179: LGTM! Clear formatting guidance.The
inline_link_formatting()helper provides clear, actionable guidance with both correct and incorrect examples for link formatting.core/agents/analyze_project_agent.py (1)
1-23: LGTM! Clean agent configuration.The analyze project agent is properly configured with appropriate system prompts and follows the modular agent pattern introduced in this PR.
frontend/src/controllers/title_suggestions_controller.js (1)
13-14: LGTM! Simplified to SEO-only generation.Hardcoding to "SEO" aligns with the PR's shift toward a pipeline-driven, SEO-focused content generation flow. The value is still appropriately sent to the backend API.
frontend/templates/components/blog_post_suggestion_card.html (1)
47-100: LGTM! Improved state handling for pipeline-driven generation.The updated UI logic correctly leverages
is_ready_to_viewto differentiate between ready, in-progress, and not-yet-generated states. The "Continue" button provides a clear path to resume incomplete generation, aligning well with the multi-step pipeline architecture.core/agents/blog_structure_agent.py (1)
13-48: LGTM! Comprehensive blog structure agent.The agent is well-configured with detailed guidelines for creating blog post outlines. The temperature setting (0.7) provides a good balance for creative structure generation while maintaining consistency.
core/agents/summarize_page_agent.py (1)
1-19: LGTM! Well-configured summarization agent.The agent is properly configured with an appropriate temperature (0.5) for summarization tasks and clear instructions for concise summaries.
core/agents/fix_validation_issue_agent.py (1)
6-42: LGTM! Effective content fixing agent.The agent is well-configured for fixing validation issues with clear guidelines to preserve style while addressing problems. The
add_content_and_issuessystem prompt provider cleanly formats the context. Temperature (0.5) is appropriate for editing tasks.core/models.py (6)
694-720: LGTM: Clean pipeline initialization.The method properly creates a blog post and initializes the pipeline state with appropriate logging.
722-798: LGTM: Well-structured pipeline step with proper error handling.The method correctly updates pipeline state, builds context, runs the agent, and handles errors by updating the pipeline state before re-raising. This allows the pipeline to track failures while still propagating exceptions.
1034-1040: Good use of early return for edge case.The early return when no project pages are available avoids unnecessary agent calls and clearly logs the skip reason.
1200-1275: LGTM: Complete pipeline orchestration is well-structured.The method properly orchestrates all pipeline steps with appropriate validation and fixing logic. The one-retry approach for validation failures is reasonable, and async OG image generation is a good choice.
1382-1392: Verify is_ready_to_view logic matches intended behavior.The
is_ready_to_viewproperty returnsTruefor posts without a pipeline_state (line 1388). This means legacy posts or posts created outside the pipeline are considered ready to view. Confirm this is the intended behavior.If legacy posts without pipeline tracking should not be automatically marked as ready, consider:
if not self.pipeline_state: return False # Or check other fields for legacy validation
1156-1158: Remove redundant imports.All three imports here are redundant. Remove them.
- from core.agents.fix_validation_issue_agent import agent - from core.model_utils import run_agent_synchronously - from core.schemas import ContentFixContext + from core.agents.fix_validation_issue_agent import agent + from core.schemas import ContentFixContextLikely an incorrect or invalid review comment.
| except Exception as error: | ||
| logger.error( | ||
| "[Pipeline API] Failed to start pipeline", | ||
| error=str(error), | ||
| exc_info=True, | ||
| suggestion_id=suggestion_id, | ||
| profile_id=profile.id, | ||
| ) | ||
| return { | ||
| "status": "error", | ||
| "message": f"Failed to start pipeline: {str(error)}", | ||
| "blog_post_id": None, | ||
| "pipeline_state": None, | ||
| } |
There was a problem hiding this comment.
Catch specific exception types instead of broad Exception.
The coding guidelines specify avoiding broad Exception catches. Consider catching specific exceptions (e.g., DatabaseError, ValidationError) to handle different failure scenarios appropriately.
Apply this diff to improve exception handling:
- except Exception as error:
+ except (DatabaseError, ValidationError, IntegrityError) as error:
logger.error(
"[Pipeline API] Failed to start pipeline",
error=str(error),
exc_info=True,
suggestion_id=suggestion_id,
profile_id=profile.id,
)
return {
"status": "error",
"message": f"Failed to start pipeline: {str(error)}",
"blog_post_id": None,
"pipeline_state": None,
}
+ except Exception as error:
+ logger.error(
+ "[Pipeline API] Unexpected error starting pipeline",
+ error=str(error),
+ exc_info=True,
+ suggestion_id=suggestion_id,
+ profile_id=profile.id,
+ )
+ raiseCommittable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.14.3)
1077-1077: Use explicit conversion flag
Replace with conversion flag
(RUF010)
| except Exception as error: | ||
| logger.error( | ||
| "[Pipeline API] Step execution failed", | ||
| error=str(error), | ||
| exc_info=True, | ||
| blog_post_id=blog_post_id, | ||
| step_name=step_name, | ||
| profile_id=profile.id, | ||
| ) | ||
|
|
||
| # Update pipeline step to failed | ||
| blog_post.refresh_from_db() | ||
| pipeline_state = blog_post.get_pipeline_status() | ||
|
|
||
| return { | ||
| "status": "error", | ||
| "message": f"Step failed: {str(error)}", | ||
| "step_name": step_name, | ||
| "pipeline_state": pipeline_state, | ||
| "result": None, | ||
| } |
There was a problem hiding this comment.
Catch specific exception types instead of broad Exception.
Similar to the start_pipeline endpoint, catching broad Exception violates the coding guidelines.
Apply the same pattern as suggested for start_pipeline to catch specific Django/database exceptions first, then re-raise unexpected exceptions.
🧰 Tools
🪛 Ruff (0.14.3)
1206-1206: Use explicit conversion flag
Replace with conversion flag
(RUF010)
🤖 Prompt for AI Agents
In core/api/views.py around lines 1190 to 1210, replace the broad "except
Exception" handler with specific exception handlers like Django/DB-related
exceptions (e.g., ObjectDoesNotExist, ValidationError, DatabaseError,
OperationalError) that you expect from the pipeline step, log each specific
error with the same logger fields and update the blog_post/pipeline_state as
currently done, and then add a final generic except Exception as e: raise to
re-raise unexpected errors so they bubble up (do not swallow unknown
exceptions).
| def find_internal_link_opportunities( | ||
| content: str, project_pages: list, max_links: int = 10 | ||
| ) -> list[dict]: | ||
| """ | ||
| Use embeddings to find relevant spots for internal links. | ||
|
|
||
| Args: | ||
| content: The blog post content | ||
| project_pages: List of ProjectPage objects with embeddings | ||
| max_links: Maximum number of link opportunities to find | ||
|
|
||
| Returns: | ||
| List of link opportunities with position and relevance info | ||
| """ | ||
| # This is a simplified version - in production, you would: | ||
| # 1. Split content into paragraphs | ||
| # 2. Get embeddings for each paragraph | ||
| # 3. Use cosine similarity to find relevant pages | ||
| # 4. Return top matches with position information | ||
|
|
||
| opportunities = [] | ||
|
|
||
| # For now, return empty list - actual implementation would use vector similarity | ||
| # This would be filled out when embeddings are ready | ||
| logger.info( | ||
| "[FindInternalLinkOpportunities] Finding link opportunities", | ||
| content_length=len(content), | ||
| available_pages=len(project_pages), | ||
| ) | ||
|
|
||
| return opportunities | ||
|
|
There was a problem hiding this comment.
Implement the internal link opportunity finder.
This helper always returns an empty list and never honors max_links, so the new internal-link pipeline step will never surface any links. Please add at least a minimal implementation (paragraph split + embedding similarity capped by max_links) or gate the pipeline until this logic exists.
🧰 Tools
🪛 Ruff (0.14.3)
393-393: Unused function argument: max_links
(ARG001)
| def insert_link_at_position(content: str, link_url: str, link_text: str, position: int) -> str: | ||
| """ | ||
| Smart link insertion at a specific position in the content. | ||
|
|
||
| Args: | ||
| content: The original content | ||
| link_url: URL to link to | ||
| link_text: Anchor text for the link | ||
| position: Character position where to insert the link | ||
|
|
||
| Returns: | ||
| Content with link inserted | ||
| """ | ||
| # Find the text to replace with a link | ||
| start_pos = max(0, position - len(link_text) // 2) | ||
| end_pos = min(len(content), position + len(link_text) // 2) | ||
|
|
||
| # Create the markdown link | ||
| markdown_link = f"[{link_text}]({link_url})" | ||
|
|
||
| # Insert the link | ||
| new_content = content[:start_pos] + markdown_link + content[end_pos:] | ||
|
|
||
| return new_content |
There was a problem hiding this comment.
Fix link insertion to avoid deleting surrounding text.
Replacing a symmetric slice around position drops part of the paragraph and collapses spacing (e.g., "The product is affordable..." becomes "The[affordable solution](...)able..."). Insert the anchor cleanly without discarding adjacent characters.
- start_pos = max(0, position - len(link_text) // 2)
- end_pos = min(len(content), position + len(link_text) // 2)
-
- markdown_link = f"[{link_text}]({link_url})"
-
- new_content = content[:start_pos] + markdown_link + content[end_pos:]
-
- return new_content
+ markdown_link = f"[{link_text}]({link_url})"
+
+ before = content[:position]
+ after = content[position:]
+
+ needs_space_before = bool(before) and not before[-1].isspace()
+ needs_space_after = bool(after) and not after[0].isspace()
+
+ insertion = markdown_link
+ if needs_space_before:
+ insertion = f" {insertion}"
+ if needs_space_after:
+ insertion = f"{insertion} "
+
+ return before + insertion + after🤖 Prompt for AI Agents
In core/utils.py around lines 425 to 448, the current implementation replaces a
symmetric slice around the position which deletes surrounding text; instead
compute a safe insert_pos = clamp(position, 0, len(content)) and insert
markdown_link at that index (content[:insert_pos] + markdown_link +
content[insert_pos:]) rather than replacing characters; also normalize spacing
so the link is separated from adjacent alphanumeric characters (add a space
before or after the link if the neighboring characters are alphanumeric) and
return the resulting string.
| for (let i = 0; i < this.stepNames.length; i++) { | ||
| const step = this.stepNames[i]; | ||
| this.currentStep = step.key; | ||
|
|
||
| const success = await this.executeStep(step, i); | ||
|
|
||
| if (!success) { | ||
| // Step failed, show retry option if available | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // All steps completed successfully | ||
| this.onPipelineComplete(); | ||
|
|
There was a problem hiding this comment.
Prevent false “pipeline complete” states
If executeStep returns false and we break out of the loop, we still call onPipelineComplete(), so the dialog shows a success message even though a step failed. Guard the completion call with a flag.
- for (let i = 0; i < this.stepNames.length; i++) {
+ let allStepsCompleted = true;
+ for (let i = 0; i < this.stepNames.length; i++) {
const step = this.stepNames[i];
this.currentStep = step.key;
const success = await this.executeStep(step, i);
if (!success) {
- // Step failed, show retry option if available
- break;
+ allStepsCompleted = false;
+ break;
}
}
- // All steps completed successfully
- this.onPipelineComplete();
+ if (allStepsCompleted) {
+ this.onPipelineComplete();
+ }🤖 Prompt for AI Agents
In frontend/src/controllers/blog_generation_pipeline_controller.js around lines
64 to 78, the loop breaks when executeStep returns false but
onPipelineComplete() is still called, causing a false success state; add a
success flag (e.g., allSucceeded = true) before the loop, set it to false when a
step fails (when executeStep returns false) and break, then only call
this.onPipelineComplete() if allSucceeded is true (or alternatively return early
on failure) so the completion dialog is shown only when every step succeeds.
| // Continue with remaining steps | ||
| for (let i = parseInt(stepIndex) + 1; i < this.stepNames.length; i++) { | ||
| const nextStep = this.stepNames[i]; | ||
| const nextSuccess = await this.executeStep(nextStep, i); | ||
|
|
||
| if (!nextSuccess) { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // Check if all steps completed | ||
| if (parseInt(stepIndex) === this.stepNames.length - 1) { | ||
| this.onPipelineComplete(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Signal completion after a successful retry
When a mid-pipeline retry succeeds, we run the remaining steps but never reach onPipelineComplete(), so the dialog stays stuck. Track whether the remaining steps all succeed and only then call onPipelineComplete().
- const success = await this.executeStep(step, parseInt(stepIndex));
-
- if (success) {
- // Continue with remaining steps
- for (let i = parseInt(stepIndex) + 1; i < this.stepNames.length; i++) {
+ const success = await this.executeStep(step, parseInt(stepIndex));
+
+ if (success) {
+ let allRemainingStepsCompleted = true;
+ for (let i = parseInt(stepIndex) + 1; i < this.stepNames.length; i++) {
const nextStep = this.stepNames[i];
const nextSuccess = await this.executeStep(nextStep, i);
if (!nextSuccess) {
- break;
+ allRemainingStepsCompleted = false;
+ break;
}
}
- // Check if all steps completed
- if (parseInt(stepIndex) === this.stepNames.length - 1) {
- this.onPipelineComplete();
- }
+ if (allRemainingStepsCompleted) {
+ this.onPipelineComplete();
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Continue with remaining steps | |
| for (let i = parseInt(stepIndex) + 1; i < this.stepNames.length; i++) { | |
| const nextStep = this.stepNames[i]; | |
| const nextSuccess = await this.executeStep(nextStep, i); | |
| if (!nextSuccess) { | |
| break; | |
| } | |
| } | |
| // Check if all steps completed | |
| if (parseInt(stepIndex) === this.stepNames.length - 1) { | |
| this.onPipelineComplete(); | |
| } | |
| } | |
| let allRemainingStepsCompleted = true; | |
| for (let i = parseInt(stepIndex) + 1; i < this.stepNames.length; i++) { | |
| const nextStep = this.stepNames[i]; | |
| const nextSuccess = await this.executeStep(nextStep, i); | |
| if (!nextSuccess) { | |
| allRemainingStepsCompleted = false; | |
| break; | |
| } | |
| } | |
| if (allRemainingStepsCompleted) { | |
| this.onPipelineComplete(); | |
| } | |
| } |
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (3)
core/models.py (1)
900-900: Remove redundant imports ofrun_agent_synchronously.The function
run_agent_synchronouslyis already imported at line 33 at the module level. These local imports within methods (lines 900, 986, 1121) are redundant and should be removed to keep imports organized.Also applies to: 986-986, 1121-1121
core/api/views.py (2)
1136-1140: Fragile validation issue extraction from error message.This code extracts validation issues by parsing the error message string with
error_message.replace("Content validation failed - ", ""). This tight coupling between error message format and parsing logic is brittle—if the error format changes in the model layer, this breaks silently.Consider storing validation issues in a structured field within the blog_post's
pipeline_state(e.g.,pipeline_state["steps"][step_name]["validation_issues"]) rather than embedding them in error messages. Then read directly from that structured field instead of parsing strings.
1067-1080: Catch specific exception types instead of broadException.All four new pipeline endpoints catch broad
Exception, which violates the coding guidelines. Consider catching specific Django/database exceptions (e.g.,DatabaseError,ValidationError,IntegrityError,ObjectDoesNotExist) to handle different failure scenarios appropriately, then re-raise unexpected exceptions so they bubble up rather than being swallowed.As per coding guidelines
Also applies to: 1198-1218, 1242-1256, 1347-1360
🧹 Nitpick comments (4)
frontend/src/controllers/generate-content-controller.js (3)
75-75: Unnecessary assignment to instance property.
pipelineStepsis only used within thegenerate()method and doesn't need to be stored onthis. Consider keeping it as a local variable.Apply this diff:
- this.pipelineSteps = ["structure", "content", "preliminary_validation", "internal_links", "final_validation"]; + const pipelineSteps = ["structure", "content", "preliminary_validation", "internal_links", "final_validation"];And update line 78:
- for (const step of this.pipelineSteps) { + for (const step of pipelineSteps) {
208-208: Unnecessary assignment to instance property.Same issue as in the
generate()method:pipelineStepsis only used locally and doesn't need to be stored onthis.Apply this diff:
- this.pipelineSteps = ["structure", "content", "preliminary_validation", "internal_links", "final_validation"]; + const pipelineSteps = ["structure", "content", "preliminary_validation", "internal_links", "final_validation"];And update line 211:
- for (const frontend_step of this.pipelineSteps) { + for (const frontend_step of pipelineSteps) {
368-400: Consider using helper method for step display names.The progress dialog has hardcoded step display text (lines 378, 382, 386, 390, 394) that should ideally match the mappings in
_getStepDisplayName(). While not critical, using the helper method would ensure consistency if display names change.core/models.py (1)
1560-1631: Fix type hint for optional parameter.Line 1560: The
errorparameter has type hinterror: str = Nonewhich uses implicit Optional. PEP 484 requires explicit union syntax.Apply this diff:
- def update_pipeline_step(self, step_name: str, status: str, error: str = None): + def update_pipeline_step(self, step_name: str, status: str, error: str | None = None):The method implementation is excellent—it handles all step transitions correctly:
- Tracks retry counts for failed steps
- Records timestamps (started_at, completed_at, failed_at, needs_fix_at)
- Advances current_step through the sequence
- Increments fix_attempt_count for validation steps needing fixes
- Distinguishes between main steps and fix steps for progress tracking
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
core/api/views.py(5 hunks)core/models.py(17 hunks)frontend/src/controllers/generate-content-controller.js(4 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)
**/*.py: Prioritize readability and maintainability; follow PEP 8
Use descriptive variable/function names with underscoresIn Python, use try-except blocks that catch specific exception types; do not catch broad Exception.
**/*.py: Follow PEP 8 for Python code style
Use descriptive variable names with underscores (snake_case) in Python
Prefer Django's built-in features over external libraries
**/*.py: Use structlog for logging in Python code (avoid print and the standard logging module)
Include contextual key-value fields in log messages (e.g., error=str(e), exc_info=True, profile_id=profile.id)
**/*.py: Use descriptive, full-word variable names; avoid abbreviations and single-letter variables in Python
Provide context in variable names when format or type matters (e.g., include_iso_format,date)
Extract unchanging values into UPPER_CASE constants
Use intermediary variables to name parsed groups instead of using index access directly
Naming conventions: use is_/has_/can_ for booleans; include 'date' for dates; snake_case for variables/functions; PascalCase for classes
Define variables close to where they are used to keep lifespan short
Name things after what they do, not how they're used; ensure names make sense without extra context
Avoid generic names like data, info, manager; use specific, intention-revealing names
Function names should include necessary context without being verbose
If naming is hard, split functions into smaller focused parts
Maintain consistency: reuse the same verbs and nouns for the same concepts; name variables after the functions that create them
Use more descriptive names for longer-lived variables
Avoid else statements by using guard clauses for early returns
Replace simple conditionals with direct assignment when both branches call the same function with different values
Use dictionaries as dispatch tables instead of multiple equal-probability elif chains
Validate input before processing to prevent errors propagating in...
Files:
core/api/views.pycore/models.py
core/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)
Leverage Django's ORM; avoid raw SQL when possible
Files:
core/api/views.pycore/models.py
core/api/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)
core/api/**/*.py: django-ninja generate openapi documentation, so make sure to populate views with relevant data in order for it to show up in the OpenAPI docs.
Use Pydantic models for schema validation
Use django-ninja's authentication classesImplement REST APIs with django-ninja using Pydantic schemas under core/api/
Files:
core/api/views.py
frontend/src/controllers/**/*.js
📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)
frontend/src/controllers/**/*.js: Use Stimulus controllers to encapsulate JavaScript behavior and keep it separate from HTML structure
New controllers should be created in thefrontend/src/controllersdirectoryUse Stimulus.js controllers for interactive frontend behavior
Files:
frontend/src/controllers/generate-content-controller.js
**/*.js
📄 CodeRabbit inference engine (.cursor/rules/stimulus-general.mdc)
**/*.js: Add semicolons at the end of statements in JavaScript files
Use double quotes instead of single quotes for string literals in JavaScript files
Files:
frontend/src/controllers/generate-content-controller.js
core/models.py
📄 CodeRabbit inference engine (CLAUDE.md)
core/models.py: Place business logic in Django models (fat models pattern)
Validate simple constraints in the database and place complex domain logic in Django models
Files:
core/models.py
🧠 Learnings (2)
📚 Learning: 2025-10-04T08:52:37.437Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T08:52:37.437Z
Learning: Applies to core/tasks.py : Define background tasks using django-q2 in core/tasks.py
Applied to files:
core/models.py
📚 Learning: 2025-10-04T08:52:37.437Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T08:52:37.437Z
Learning: Applies to core/agents/**/*.py : Implement AI agents using pydantic-ai under core/agents/
Applied to files:
core/models.py
🧬 Code graph analysis (3)
core/api/views.py (2)
core/api/schemas.py (5)
PipelineCompleteOut(286-290)PipelineRetryOut(280-283)PipelineStartOut(257-261)PipelineStatusOut(272-277)PipelineStepOut(264-269)core/models.py (7)
BlogPostTitleSuggestion(648-1331)start_generation_pipeline(694-720)get_pipeline_status(1633-1653)GeneratedBlogPost(1372-1661)can_retry_step(1655-1661)update_pipeline_step(1560-1631)execute_complete_pipeline(1256-1331)
frontend/src/controllers/generate-content-controller.js (2)
frontend/src/controllers/blog_generation_pipeline_controller.js (6)
step(65-65)step(133-133)response(48-53)response(89-97)data(55-55)data(99-99)frontend/src/utils/messages.js (1)
showMessage(2-39)
core/models.py (5)
core/model_utils.py (1)
run_agent_synchronously(18-75)core/api/views.py (2)
generate_title_suggestions(214-254)get_pipeline_status(1226-1256)core/choices.py (1)
ContentType(4-5)core/schemas.py (5)
ProjectPageContext(174-182)BlogPostGenerationContext(185-192)ContentFixContext(331-337)InternalLinkContext(312-318)CompetitorVsPostContext(260-273)core/agents/seo_content_generator_agent.py (1)
create_structure_guidance_prompt(90-115)
🪛 Ruff (0.14.3)
core/api/views.py
1041-1041: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
1060-1065: Consider moving this statement to an else block
(TRY300)
1077-1077: Use explicit conversion flag
Replace with conversion flag
(RUF010)
1214-1214: Use explicit conversion flag
Replace with conversion flag
(RUF010)
1314-1314: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
1357-1357: Use explicit conversion flag
Replace with conversion flag
(RUF010)
core/models.py
784-784: Consider moving this statement to an else block
(TRY300)
851-851: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
873-873: Consider moving this statement to an else block
(TRY300)
935-935: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
955-958: Consider moving this statement to an else block
(TRY300)
1019-1019: Consider moving this statement to an else block
(TRY300)
1094-1094: Consider moving this statement to an else block
(TRY300)
1156-1156: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
1176-1179: Consider moving this statement to an else block
(TRY300)
1240-1240: Consider moving this statement to an else block
(TRY300)
1321-1321: Consider moving this statement to an else block
(TRY300)
1560-1560: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
🔇 Additional comments (13)
frontend/src/controllers/generate-content-controller.js (2)
8-8: LGTM!The
blogPostIdvalue is properly declared and follows Stimulus conventions.
473-497: LGTM!The helper methods provide a clean centralized mapping for step names with sensible fallbacks. Good practice to have these extracted as reusable methods.
core/api/views.py (2)
28-32: LGTM! Pipeline schema integration and ContentType removal are consistent.The new Pipeline schema imports and the removal of content_type parameters from title suggestion calls align well with the pipeline revamp described in the PR objectives.
Also applies to: 236-236, 272-272
1264-1291: LGTM! Clean implementation with appropriate error handling.The retry endpoint has a straightforward implementation with proper defensive checks and logging. The absence of broad exception handling here is actually preferable since Django's get_object_or_404 and the can_retry_step method provide adequate error boundaries.
core/models.py (9)
516-563: LGTM! Simplified title generation aligns with ContentType removal.The removal of the
content_typeparameter and hardcodingContentType.SEO(line 549) is consistent with the broader architectural change to streamline the content generation flow. Since only one content type exists, this simplification is appropriate.
694-720: LGTM! Clean pipeline initialization.The
start_generation_pipelinemethod provides a clear entry point for the pipeline with proper logging and initialization. Creating the GeneratedBlogPost first and then callinginitialize_pipeline()ensures the state is ready for subsequent steps.
722-798: LGTM! Well-structured structure and content generation steps.Both
generate_structureandgenerate_content_from_structurefollow a consistent pattern:
- Update step to in_progress
- Build context with project pages and keywords
- Run agent synchronously
- Save results
- Update step to completed
- Comprehensive error handling with logging
The structure-guided content generation (lines 819-823) is a nice touch for ensuring consistency.
Also applies to: 800-887
889-972: LGTM! Validation methods include good defensive logic.Both
run_preliminary_validationandrun_final_validationimplement smart fix-attempt limiting (lines 924-936, 1145-1163). Capping at 2 fix attempts prevents infinite validation-fix loops while still allowing reasonable retry attempts. The distinction betweenneeds_fix(retriable) andfailed(max attempts exceeded) is well-designed.Also applies to: 1110-1193
1256-1331: LGTM! Complete pipeline orchestration is thorough.The
execute_complete_pipelinemethod properly sequences all steps and handles validation failures gracefully. The conditional OG image generation (lines 1308-1313) respects project settings. The logging at key points provides good observability.
1398-1418: LGTM! Well-designed pipeline state management.The new pipeline fields provide comprehensive state tracking:
pipeline_state: Current step and per-step status/errorsgeneration_structure: Persists the outline for content generationraw_content: Preserves content before link insertionpipeline_metadata: Timestamps and progress metricsThe
initialize_pipelinemethod sets up a clear initial state with all 7 steps (5 main + 2 fix steps) properly initialized. The total_steps count of 5 correctly excludes fix steps from progress calculation.Also applies to: 1527-1558
1633-1661: LGTM! Clean status and retry logic.The
get_pipeline_statusmethod returns a well-structured API response with progress percentage calculation, andcan_retry_stepprovides a simple retry limit check (max 3 attempts). Both methods handle missing pipeline_state gracefully.
479-479: LGTM! Consistent local agent imports.The pattern of importing agents within methods (rather than at module level) is applied consistently across the file. This approach avoids potential circular import issues and keeps dependencies localized to where they're actually used.
Also applies to: 594-594, 1779-1779, 2106-2108
1438-1448: LGTM! Clean migration to pipeline-based validation.The
is_ready_to_viewproperty replaces multiple legacy validation flags with a single, clear check: is final_validation completed? The backward compatibility fallback (line 1444) ensures existing blog posts without pipeline_state still work correctly.
| if (!startResponse.ok) { | ||
| const error = await startResponse.json(); | ||
| throw new Error(error.message || "Failed to start generation"); | ||
| } |
There was a problem hiding this comment.
Add error handling for JSON parsing.
If the response body is not valid JSON, await startResponse.json() will throw an error. This should be wrapped in a try-catch block.
Apply this diff:
- if (!startResponse.ok) {
- const error = await startResponse.json();
- throw new Error(error.message || "Failed to start generation");
- }
+ if (!startResponse.ok) {
+ let errorMessage = "Failed to start generation";
+ try {
+ const error = await startResponse.json();
+ errorMessage = error.message || errorMessage;
+ } catch (e) {
+ // Response body is not JSON, use default message
+ }
+ throw new Error(errorMessage);
+ }🤖 Prompt for AI Agents
In frontend/src/controllers/generate-content-controller.js around lines 68 to
71, the code calls await startResponse.json() without guarding against invalid
JSON; wrap the JSON parsing in a try-catch so parsing failures are handled: try
to parse JSON, and if it fails fall back to await startResponse.text() (or a
default message) and include startResponse.status in the thrown Error; rethrow a
new Error containing either parsed error.message or the fallback text plus
status so callers get a clear, non-crashing error when the response body is not
valid JSON.
| if (stepResult && stepResult.needs_fix) { | ||
| const fixStepName = `fix_${step}`; | ||
| await this._executeStep(fixStepName); | ||
|
|
||
| // Re-run validation after fixing | ||
| await this._executeStep(step); | ||
| } | ||
| } |
There was a problem hiding this comment.
Handle persistent validation failures after fix.
After fixing validation issues and re-running validation, the code doesn't check if validation still fails. This could result in incomplete or invalid content being marked as complete. Consider adding a check similar to the one in the continue() method (lines 242-244).
Apply this diff:
// Check if validation failed and needs fixing
if (stepResult && stepResult.needs_fix) {
const fixStepName = `fix_${step}`;
await this._executeStep(fixStepName);
// Re-run validation after fixing
- await this._executeStep(step);
+ const revalidationResult = await this._executeStep(step);
+
+ // If validation still needs fixing after one fix attempt, stop here
+ if (revalidationResult && revalidationResult.needs_fix) {
+ throw new Error("Validation still has issues after fix. Please review the content and try again.");
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (stepResult && stepResult.needs_fix) { | |
| const fixStepName = `fix_${step}`; | |
| await this._executeStep(fixStepName); | |
| // Re-run validation after fixing | |
| await this._executeStep(step); | |
| } | |
| } | |
| if (stepResult && stepResult.needs_fix) { | |
| const fixStepName = `fix_${step}`; | |
| await this._executeStep(fixStepName); | |
| // Re-run validation after fixing | |
| const revalidationResult = await this._executeStep(step); | |
| // If validation still needs fixing after one fix attempt, stop here | |
| if (revalidationResult && revalidationResult.needs_fix) { | |
| throw new Error("Validation still has issues after fix. Please review the content and try again."); | |
| } | |
| } |
🤖 Prompt for AI Agents
In frontend/src/controllers/generate-content-controller.js around lines 82 to
89, after executing the fix step and re-running the original validation step,
re-check the validation result and handle persistent failures the same way as
the continue() method (see lines 242-244): capture the second stepResult from
the re-run, and if it still indicates needs_fix or failure, stop progression by
returning/throwing or marking the content as invalid (matching the existing
continue() behavior) so incomplete/invalid content is not treated as complete.
| const step_name_map = { | ||
| "generate_structure": "structure", | ||
| "generate_content": "content", | ||
| "preliminary_validation": "preliminary_validation", | ||
| "insert_internal_links": "internal_links", | ||
| "final_validation": "final_validation" | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Extract duplicated step name mapping.
This step name mapping is duplicated in the _getBackendStepName() method (lines 486-497). Consider using the helper method instead to maintain consistency and reduce duplication.
Apply this diff:
- // Map backend step names to frontend step names
- const step_name_map = {
- "generate_structure": "structure",
- "generate_content": "content",
- "preliminary_validation": "preliminary_validation",
- "insert_internal_links": "internal_links",
- "final_validation": "final_validation"
- };
+ // Map backend step names to frontend step names
+ const backend_to_frontend_map = {};
+ const frontend_steps = ["structure", "content", "preliminary_validation", "internal_links", "final_validation"];
+ frontend_steps.forEach(step => {
+ backend_to_frontend_map[this._getBackendStepName(step)] = step;
+ });And update line 200:
- const frontend_step_name = step_name_map[backend_step_name];
+ const frontend_step_name = backend_to_frontend_map[backend_step_name];Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/src/controllers/generate-content-controller.js around lines 189 to
195 and update at line 200, the step_name_map object is duplicated (same mapping
exists in _getBackendStepName at lines 486-497); remove this local step_name_map
and replace its usage on line 200 with a call to the existing helper
_getBackendStepName(step) (or _getBackendStepName(this, step) if it’s an
instance method), ensuring you pass the same step value and handle undefined
returns the same way as before so behavior is preserved.
| async _executeStep(step_name) { | ||
| const step_display_names = { | ||
| "structure": "Generating Structure", | ||
| "content": "Generating Content", | ||
| "preliminary_validation": "Validating Content", | ||
| "fix_preliminary_validation": "Fixing Validation Issues", | ||
| "internal_links": "Adding Internal Links", | ||
| "final_validation": "Final Validation", | ||
| "fix_final_validation": "Fixing Validation Issues" | ||
| }; | ||
|
|
||
| // Map frontend step names to backend internal step names | ||
| const backend_step_names = { | ||
| "structure": "generate_structure", | ||
| "content": "generate_content", | ||
| "preliminary_validation": "preliminary_validation", | ||
| "fix_preliminary_validation": "fix_preliminary_validation", | ||
| "internal_links": "insert_internal_links", | ||
| "final_validation": "final_validation", | ||
| "fix_final_validation": "fix_final_validation" | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Extract duplicated mappings to use existing helper methods.
The step display names (lines 300-308) and backend step names (lines 311-319) are duplicated in _getStepDisplayName() and _getBackendStepName() helper methods. Use the helper methods instead to maintain a single source of truth.
Apply this diff:
async _executeStep(step_name) {
- const step_display_names = {
- "structure": "Generating Structure",
- "content": "Generating Content",
- "preliminary_validation": "Validating Content",
- "fix_preliminary_validation": "Fixing Validation Issues",
- "internal_links": "Adding Internal Links",
- "final_validation": "Final Validation",
- "fix_final_validation": "Fixing Validation Issues"
- };
-
- // Map frontend step names to backend internal step names
- const backend_step_names = {
- "structure": "generate_structure",
- "content": "generate_content",
- "preliminary_validation": "preliminary_validation",
- "fix_preliminary_validation": "fix_preliminary_validation",
- "internal_links": "insert_internal_links",
- "final_validation": "final_validation",
- "fix_final_validation": "fix_final_validation"
- };
-
// Update progress dialog to show current step
- this._updateProgressStep(step_name, "in_progress", step_display_names[step_name]);
+ this._updateProgressStep(step_name, "in_progress", this._getStepDisplayName(step_name));
try {
const response = await fetch(`/api/generate-blog-content-pipeline/${this.blogPostId}/step/${step_name}`, {
@@ -344,7 +332,7 @@
}
// Get the backend step name to check status
- const backend_step_name = backend_step_names[step_name];
+ const backend_step_name = this._getBackendStepName(step_name);
const step_status = data.pipeline_state?.steps?.[backend_step_name];
// Check if step failed and needs retry
if (step_status && step_status.status === "failed") {
- this._updateProgressStep(step_name, "failed", `${step_display_names[step_name]} Failed`);
+ this._updateProgressStep(step_name, "failed", `${this._getStepDisplayName(step_name)} Failed`);
throw new Error(step_status.error || `Step ${step_name} failed`);
}
// Update progress dialog to show step completed
- this._updateProgressStep(step_name, "completed", step_display_names[step_name]);
+ this._updateProgressStep(step_name, "completed", this._getStepDisplayName(step_name));
// Return the result data for validation checks
return data;
} catch (error) {
- this._updateProgressStep(step_name, "failed", step_display_names[step_name]);
+ this._updateProgressStep(step_name, "failed", this._getStepDisplayName(step_name));
throw error;
}
}🤖 Prompt for AI Agents
In frontend/src/controllers/generate-content-controller.js around lines 299 to
319, remove the duplicated step_display_names and backend_step_names maps and
replace their usage by calling the existing helper methods
_getStepDisplayName(step_name) and _getBackendStepName(step_name) respectively;
update any references in this function to call those helpers (pass step_name)
before making API calls or updating UI so the mappings come from the
single-source helper methods and eliminate the duplicated objects.
| _getCsrfToken() { | ||
| return document.querySelector("[name=csrfmiddlewaretoken]").value; | ||
| } |
There was a problem hiding this comment.
Add error handling for missing CSRF token.
If the CSRF token element is not present in the DOM, this method will throw an error. Add a null check to provide a clearer error message.
Apply this diff:
_getCsrfToken() {
- return document.querySelector("[name=csrfmiddlewaretoken]").value;
+ const tokenElement = document.querySelector("[name=csrfmiddlewaretoken]");
+ if (!tokenElement) {
+ throw new Error("CSRF token not found in page");
+ }
+ return tokenElement.value;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| _getCsrfToken() { | |
| return document.querySelector("[name=csrfmiddlewaretoken]").value; | |
| } | |
| _getCsrfToken() { | |
| const tokenElement = document.querySelector("[name=csrfmiddlewaretoken]"); | |
| if (!tokenElement) { | |
| throw new Error("CSRF token not found in page"); | |
| } | |
| return tokenElement.value; | |
| } |
🤖 Prompt for AI Agents
In frontend/src/controllers/generate-content-controller.js around lines 469 to
471, _getCsrfToken() directly accesses document.querySelector(...).value which
will throw if the CSRF element is missing; update the method to check for a null
result, and if missing throw or return a clear error (e.g., throw new
Error("CSRF token not found in DOM") or return null), otherwise return the
element.value — keep the check simple and ensure callers handle the
error/nullable return.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (5)
core/agents/content_validation_agent.py (1)
48-48: Remove unused# noqa: E501.The linter no longer requires this directive as the prompt string satisfies the line length limit. Remove the trailing comment to resolve the Ruff warning.
Apply this diff:
- """, # noqa: E501 + """,core/models.py (4)
899-901: Remove redundant imports.Both
run_agent_synchronously(already imported at line 33) and the agent import pattern duplicate earlier imports.Apply this diff:
from core.agents.content_validation_agent import agent - from core.model_utils import run_agent_synchronously - from core.schemas import ContentValidationContext
993-995: Remove redundant imports.
run_agent_synchronouslyis already imported at line 33. Remove the redundant import.Apply this diff:
from core.agents.fix_validation_issue_agent import agent - from core.model_utils import run_agent_synchronously from core.schemas import ContentFixContext
1128-1130: Remove redundant imports.Both
run_agent_synchronouslyand the schemas are already imported earlier in the file.Apply this diff:
from core.agents.content_validation_agent import agent - from core.model_utils import run_agent_synchronously - from core.schemas import ContentValidationContext
1222-1224: Remove redundant import.
run_agent_synchronouslyis already imported at line 33.Apply this diff:
from core.agents.fix_validation_issue_agent import agent - from core.model_utils import run_agent_synchronously from core.schemas import ContentFixContext
🧹 Nitpick comments (1)
core/models.py (1)
1576-1576: Use explicit type annotation for optional parameter.PEP 484 prohibits implicit
Optional. Usestr | Nonefor theerrorparameter.Apply this diff:
- def update_pipeline_step(self, step_name: str, status: str, error: str = None): + def update_pipeline_step(self, step_name: str, status: str, error: str | None = None):
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
core/agents/content_validation_agent.py(1 hunks)core/models.py(17 hunks)core/schemas.py(3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)
**/*.py: Prioritize readability and maintainability; follow PEP 8
Use descriptive variable/function names with underscoresIn Python, use try-except blocks that catch specific exception types; do not catch broad Exception.
**/*.py: Follow PEP 8 for Python code style
Use descriptive variable names with underscores (snake_case) in Python
Prefer Django's built-in features over external libraries
**/*.py: Use structlog for logging in Python code (avoid print and the standard logging module)
Include contextual key-value fields in log messages (e.g., error=str(e), exc_info=True, profile_id=profile.id)
**/*.py: Use descriptive, full-word variable names; avoid abbreviations and single-letter variables in Python
Provide context in variable names when format or type matters (e.g., include_iso_format,date)
Extract unchanging values into UPPER_CASE constants
Use intermediary variables to name parsed groups instead of using index access directly
Naming conventions: use is_/has_/can_ for booleans; include 'date' for dates; snake_case for variables/functions; PascalCase for classes
Define variables close to where they are used to keep lifespan short
Name things after what they do, not how they're used; ensure names make sense without extra context
Avoid generic names like data, info, manager; use specific, intention-revealing names
Function names should include necessary context without being verbose
If naming is hard, split functions into smaller focused parts
Maintain consistency: reuse the same verbs and nouns for the same concepts; name variables after the functions that create them
Use more descriptive names for longer-lived variables
Avoid else statements by using guard clauses for early returns
Replace simple conditionals with direct assignment when both branches call the same function with different values
Use dictionaries as dispatch tables instead of multiple equal-probability elif chains
Validate input before processing to prevent errors propagating in...
Files:
core/schemas.pycore/models.pycore/agents/content_validation_agent.py
core/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)
Leverage Django's ORM; avoid raw SQL when possible
Files:
core/schemas.pycore/models.pycore/agents/content_validation_agent.py
core/models.py
📄 CodeRabbit inference engine (CLAUDE.md)
core/models.py: Place business logic in Django models (fat models pattern)
Validate simple constraints in the database and place complex domain logic in Django models
Files:
core/models.py
core/agents/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Implement AI agents using pydantic-ai under core/agents/
Files:
core/agents/content_validation_agent.py
🧠 Learnings (3)
📚 Learning: 2025-10-04T08:52:37.437Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T08:52:37.437Z
Learning: Applies to core/tasks.py : Define background tasks using django-q2 in core/tasks.py
Applied to files:
core/models.py
📚 Learning: 2025-10-04T08:52:37.437Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T08:52:37.437Z
Learning: Applies to core/agents/**/*.py : Implement AI agents using pydantic-ai under core/agents/
Applied to files:
core/models.pycore/agents/content_validation_agent.py
📚 Learning: 2025-10-04T08:52:58.590Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: .cursor/rules/agent-rules.mdc:0-0
Timestamp: 2025-10-04T08:52:58.590Z
Learning: Always add AGENTS.md into AI context
Applied to files:
core/agents/content_validation_agent.py
🧬 Code graph analysis (3)
core/schemas.py (1)
core/models.py (1)
is_on_free_plan(161-162)
core/models.py (5)
core/model_utils.py (1)
run_agent_synchronously(18-75)core/api/views.py (2)
generate_title_suggestions(214-254)get_pipeline_status(1226-1256)core/choices.py (1)
ContentType(4-5)core/schemas.py (6)
ProjectPageContext(174-182)BlogPostGenerationContext(185-192)ContentValidationContext(321-330)ContentFixContext(343-349)InternalLinkContext(312-318)CompetitorVsPostContext(260-273)core/agents/seo_content_generator_agent.py (1)
create_structure_guidance_prompt(90-115)
core/agents/content_validation_agent.py (2)
core/choices.py (1)
get_default_ai_model(135-137)core/schemas.py (2)
ContentValidationContext(321-330)ContentValidationResult(333-340)
🪛 Ruff (0.14.3)
core/models.py
784-784: Consider moving this statement to an else block
(TRY300)
851-851: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
873-873: Consider moving this statement to an else block
(TRY300)
943-943: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
963-966: Consider moving this statement to an else block
(TRY300)
1027-1027: Consider moving this statement to an else block
(TRY300)
1102-1102: Consider moving this statement to an else block
(TRY300)
1172-1172: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
1192-1195: Consider moving this statement to an else block
(TRY300)
1256-1256: Consider moving this statement to an else block
(TRY300)
1337-1337: Consider moving this statement to an else block
(TRY300)
1576-1576: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
core/agents/content_validation_agent.py
48-48: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
🔇 Additional comments (14)
core/schemas.py (7)
62-64: LGTM: Free plan flag properly integrated.The
is_on_free_planfield is correctly typed with an appropriate default and integrates well with the Profile model's property at line 161.
192-192: LGTM: Description accurately reflects SEO-only content type.The updated description correctly indicates that SEO is the only content type option, consistent with the ContentType.SEO default at models.py line 549.
276-310: LGTM: Well-designed schemas for structured content generation.The
BlogPostSectionandBlogPostStructureschemas provide a clear, hierarchical structure for the pipeline's outline phase, with appropriate field types and comprehensive descriptions.
312-319: LGTM: Clean context schema for internal linking.The
InternalLinkContextprovides the necessary content and available pages for the internal links agent, with clear field descriptions.
321-330: LGTM: Comprehensive validation context.The
ContentValidationContextschema includes all necessary fields for content quality validation, with clear descriptions and appropriate defaults.
333-340: LGTM: Clear validation result structure.The
ContentValidationResultschema effectively communicates validation status with both a boolean flag and detailed issue descriptions.
343-349: LGTM: Appropriate context for fix operations.The
ContentFixContextschema provides the original content and specific issues to address, enabling targeted content corrections.core/models.py (7)
1-1: LGTM: JSON import necessary for pipeline structure handling.The
jsonimport is correctly used at lines 773 and 819 for structure serialization and deserialization.
406-406: LGTM: Free plan status correctly propagated.The
is_on_free_planfield properly passes the profile's subscription status to agents, enabling tier-based generation decisions.
479-491: LGTM: Agent import and call pattern correct.The analyze_content method properly imports the agent locally and uses the correct calling pattern with the
depsparameter.
516-563: LGTM: Title generation method well-implemented.The method correctly imports the agent, uses proper calling conventions with
deps, and wraps bulk creation in a transaction. The ContentType.SEO default aligns with the updated schema.
694-720: LGTM: Pipeline initialization clean and well-logged.The
start_generation_pipelinemethod properly creates the blog post, initializes the pipeline state, and includes comprehensive logging for tracking.
1414-1434: LGTM: Pipeline fields well-designed.The new pipeline-tracking fields (
pipeline_state,generation_structure,raw_content,pipeline_metadata) have appropriate types, defaults, and clear help text for tracking multi-step generation.
1454-1464: LGTM: Ready-to-view logic handles both legacy and pipeline posts.The property correctly handles legacy posts (no pipeline_state) and checks final validation completion for new pipeline-generated posts.
|
|
||
| result = run_agent_synchronously( | ||
| agent, | ||
| "Please generate the full blog post content following the provided structure outline.", # noqa: E501 |
There was a problem hiding this comment.
Remove unused # noqa: E501.
The directive is unnecessary as the line satisfies length requirements.
Apply this diff:
- "Please generate the full blog post content following the provided structure outline.", # noqa: E501
+ "Please generate the full blog post content following the provided structure outline.",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "Please generate the full blog post content following the provided structure outline.", # noqa: E501 | |
| "Please generate the full blog post content following the provided structure outline.", |
🧰 Tools
🪛 Ruff (0.14.3)
851-851: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
🤖 Prompt for AI Agents
In core/models.py around line 851, remove the unnecessary inline comment " #
noqa: E501" appended to the string literal "Please generate the full blog post
content following the provided structure outline.", since the line length is
acceptable; simply delete the " # noqa: E501" portion so the line becomes the
string only and save the file.
| result = run_agent_synchronously( | ||
| agent, | ||
| validation_context, | ||
| function_name="run_preliminary_validation", | ||
| ) |
There was a problem hiding this comment.
Fix critical agent calling pattern bug.
The validation context is passed as a positional argument instead of using the deps parameter. Since content_validation_agent is defined with deps_type=ContentValidationContext, the call must use the deps keyword argument. The current code will fail at runtime.
Apply this diff:
result = run_agent_synchronously(
agent,
- validation_context,
+ "Please validate this blog post content for completeness and quality.",
+ deps=validation_context,
function_name="run_preliminary_validation",
)🤖 Prompt for AI Agents
In core/models.py around lines 913 to 917, the call to run_agent_synchronously
incorrectly passes validation_context as a positional argument; change the call
to pass the context using the deps keyword (e.g., deps=validation_context) so
that the agent defined with deps_type=ContentValidationContext receives its
dependencies correctly and the runtime failure is avoided.
| blog_post.update_pipeline_step( | ||
| "preliminary_validation", | ||
| "failed", | ||
| error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}", # noqa: E501 |
There was a problem hiding this comment.
Remove unused # noqa: E501.
Apply this diff:
- error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}", # noqa: E501
+ error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}", # noqa: E501 | |
| error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}", |
🧰 Tools
🪛 Ruff (0.14.3)
943-943: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
🤖 Prompt for AI Agents
In core/models.py around line 943, the trailing comment "# noqa: E501" is
unnecessary; remove the "# noqa: E501" from the end of the error= f-string line
so the line no longer contains the supressed lint directive and relies on normal
line-length checks (or wrap the string earlier if you want to satisfy
line-length rules instead of suppressing them).
| result = run_agent_synchronously( | ||
| agent, | ||
| validation_context, | ||
| function_name="run_final_validation", | ||
| ) |
There was a problem hiding this comment.
Fix critical agent calling pattern bug.
Same issue as in run_preliminary_validation: the validation context must be passed via the deps parameter, not as a positional argument.
Apply this diff:
result = run_agent_synchronously(
agent,
- validation_context,
+ "Please validate this blog post content for completeness and quality.",
+ deps=validation_context,
function_name="run_final_validation",
)🤖 Prompt for AI Agents
In core/models.py around lines 1142 to 1146, the call to run_agent_synchronously
currently passes validation_context as a positional argument; change it to pass
the context via the deps keyword instead (remove the positional argument and add
deps={"validation_context": validation_context}) so the agent receives the
validation context the same way run_preliminary_validation does; keep
function_name="run_final_validation" and any other kwargs unchanged.
| blog_post.update_pipeline_step( | ||
| "final_validation", | ||
| "failed", | ||
| error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}", # noqa: E501 |
There was a problem hiding this comment.
Remove unused # noqa: E501.
Apply this diff:
- error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}", # noqa: E501
+ error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}", # noqa: E501 | |
| error=f"Content validation failed after {fix_attempt_count} fix attempts - {issues_text}", |
🧰 Tools
🪛 Ruff (0.14.3)
1172-1172: Unused noqa directive (non-enabled: E501)
Remove unused noqa directive
(RUF100)
🤖 Prompt for AI Agents
In core/models.py around line 1172, the trailing inline comment "# noqa: E501"
on the error= f-string is unused and should be removed; edit the line to delete
the "# noqa: E501" suffix so the file no longer contains that unnecessary flake8
disable comment and save the file.
Summary by CodeRabbit
New Features
Removed Features