Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    jgtolentino

    odoo-knowledge-agent

    jgtolentino/odoo-knowledge-agent
    AI & ML
    14
    2 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Scrape Odoo forum solved threads to build error prevention guardrails and auto-fix scripts...

    SKILL.md

    Odoo Knowledge Agent - Error Prevention & Auto-Fix System

    When to Use This Skill

    Use this skill when you need to:

    • Prevent Odoo errors before deployment with guardrails
    • Auto-heal production issues using community-validated fixes
    • Build intelligence from Odoo forum solved threads (1000+ cases)
    • Generate error code reference and troubleshooting guides
    • Create preventive checks for module development
    • Implement auto-patch systems for known failure patterns
    • Reduce debugging time by learning from community solutions
    • Build SOP library for common Odoo development errors

    Core Capabilities

    1. Forum Scraping Intelligence

    Scrape Odoo Forum for Solved Threads

    • Extract 1000+ solved issues across all categories
    • Parse accepted answers and code fixes
    • Identify error patterns and root causes
    • Build searchable knowledge base

    2. Guardrails Generation

    Preventive Error Checks

    • Block common mistakes before deployment
    • Validate manifest files automatically
    • Check field synchronization issues
    • Enforce best practices (OCA standards)

    3. Auto-Patch Scripts

    Automated Error Fixes

    • Apply community-validated solutions
    • Fix known issues automatically
    • Handle common migration problems
    • Self-healing production systems

    4. Vision-Based UI Automation

    askui-Compatible Automation

    • UI actions without brittle selectors
    • Cross-browser compatibility
    • Screenshot-based validation
    • Self-healing test scripts

    Prerequisites

    Required Software

    • Python 3.11+
    • Beautiful Soup 4 / Firecrawl
    • Odoo 19 instance for testing
    • Supabase for knowledge storage

    Optional Integrations

    • OpenAI API for pattern extraction
    • GitHub Actions for CI/CD integration
    • Perplexity for research enhancement

    Python Dependencies

    beautifulsoup4
    requests
    firecrawl-py
    supabase-py
    openai
    pyyaml
    gitpython
    

    Implementation Patterns

    Forum Scraper

    # scrape_solved_threads.py
    import requests
    from bs4 import BeautifulSoup
    from supabase import create_client
    import time
    from datetime import datetime
    
    class OdooForumScraper:
        def __init__(self, supabase_url, supabase_key):
            self.base_url = "https://www.odoo.com/forum"
            self.supabase = create_client(supabase_url, supabase_key)
            self.session = requests.Session()
            self.session.headers.update({
                'User-Agent': 'OdooKnowledgeBot/1.0'
            })
        
        def scrape_solved_threads(self, pages=100):
            """
            Scrape Odoo forum for solved threads
            """
            threads = []
            
            for page in range(1, pages + 1):
                print(f"Scraping page {page}/{pages}...")
                
                url = f"{self.base_url}/questions?filters=solved&page={page}"
                response = self.session.get(url)
                soup = BeautifulSoup(response.content, 'html.parser')
                
                # Extract thread links
                thread_elements = soup.select('.o_wforum_question')
                
                for thread in thread_elements:
                    thread_url = thread.find('a')['href']
                    if not thread_url.startswith('http'):
                        thread_url = f"https://www.odoo.com{thread_url}"
                    
                    # Get thread details
                    thread_data = self.scrape_thread_details(thread_url)
                    if thread_data:
                        threads.append(thread_data)
                        self.store_thread(thread_data)
                    
                    time.sleep(2)  # Rate limiting
                
                time.sleep(5)  # Pause between pages
            
            return threads
        
        def scrape_thread_details(self, url):
            """
            Extract question, accepted answer, and code snippets
            """
            try:
                response = self.session.get(url)
                soup = BeautifulSoup(response.content, 'html.parser')
                
                # Extract question
                question_elem = soup.select_one('.o_wforum_question_text')
                question = question_elem.text.strip() if question_elem else ""
                
                # Extract title
                title_elem = soup.select_one('h1.o_wforum_title')
                title = title_elem.text.strip() if title_elem else ""
                
                # Extract accepted answer
                accepted_answer = soup.select_one('.o_wforum_answer.o_wforum_answer_correct')
                if not accepted_answer:
                    return None  # Skip if no accepted answer
                
                answer_text = accepted_answer.select_one('.o_wforum_answer_text').text.strip()
                
                # Extract code snippets
                code_blocks = accepted_answer.select('pre code')
                code_snippets = [block.text.strip() for block in code_blocks]
                
                # Extract tags/category
                tags = [tag.text.strip() for tag in soup.select('.o_wforum_tag')]
                
                # Extract metadata
                views_elem = soup.select_one('.o_wforum_views')
                views = int(views_elem.text.strip()) if views_elem else 0
                
                return {
                    'url': url,
                    'title': title,
                    'question': question,
                    'answer': answer_text,
                    'code_snippets': code_snippets,
                    'tags': tags,
                    'views': views,
                    'scraped_at': datetime.now().isoformat()
                }
            
            except Exception as e:
                print(f"Error scraping {url}: {e}")
                return None
        
        def store_thread(self, thread_data):
            """
            Store thread in Supabase knowledge base
            """
            self.supabase.table('odoo_solved_threads').upsert({
                'thread_url': thread_data['url'],
                'title': thread_data['title'],
                'question': thread_data['question'],
                'answer': thread_data['answer'],
                'code_snippets': thread_data['code_snippets'],
                'tags': thread_data['tags'],
                'views': thread_data['views'],
                'scraped_at': thread_data['scraped_at']
            }).execute()
        
        def extract_error_patterns(self):
            """
            Use AI to extract common error patterns
            """
            # Fetch all threads
            threads = self.supabase.table('odoo_solved_threads').select('*').execute()
            
            # Group by error type using AI
            from openai import OpenAI
            client = OpenAI()
            
            patterns = {}
            
            for thread in threads.data:
                # Ask GPT to categorize the error
                response = client.chat.completions.create(
                    model="gpt-4",
                    messages=[
                        {"role": "system", "content": "You are an Odoo expert. Categorize this error and extract the fix pattern."},
                        {"role": "user", "content": f"Title: {thread['title']}\n\nQuestion: {thread['question']}\n\nAnswer: {thread['answer']}"}
                    ]
                )
                
                pattern = response.choices[0].message.content
                
                # Extract category
                category = self.extract_category(pattern)
                
                if category not in patterns:
                    patterns[category] = []
                
                patterns[category].append({
                    'thread': thread,
                    'pattern': pattern
                })
            
            return patterns
        
        def extract_category(self, pattern_text):
            """
            Extract error category from AI analysis
            """
            # Common Odoo error categories
            categories = {
                'manifest': ['__manifest__', 'module', 'dependency'],
                'field': ['field', 'column', 'attribute'],
                'view': ['view', 'xml', 'template'],
                'security': ['access', 'rights', 'permission'],
                'orm': ['orm', 'recordset', 'browse'],
                'workflow': ['workflow', 'state', 'transition'],
                'accounting': ['invoice', 'payment', 'journal'],
                'inventory': ['stock', 'picking', 'quant']
            }
            
            pattern_lower = pattern_text.lower()
            
            for category, keywords in categories.items():
                if any(keyword in pattern_lower for keyword in keywords):
                    return category
            
            return 'general'
    
    # Usage
    scraper = OdooForumScraper(
        supabase_url=os.getenv('SUPABASE_URL'),
        supabase_key=os.getenv('SUPABASE_KEY')
    )
    
    # Scrape 100 pages (~1,100 solved threads)
    threads = scraper.scrape_solved_threads(pages=100)
    
    # Extract patterns
    patterns = scraper.extract_error_patterns()
    
    print(f"Scraped {len(threads)} solved threads")
    print(f"Identified {len(patterns)} error patterns")
    

    Guardrail Generator

    # generate_guardrails.py
    import yaml
    from pathlib import Path
    
    class GuardrailGenerator:
        def __init__(self, patterns):
            self.patterns = patterns
            self.guardrails_dir = Path('guardrails')
            self.guardrails_dir.mkdir(exist_ok=True)
        
        def generate_manifest_guardrail(self):
            """
            GR-INSTALL-004: Manifest validation
            """
            guardrail = {
                'id': 'GR-INSTALL-004',
                'name': 'Manifest Validation',
                'description': 'Prevent module installation failures due to invalid __manifest__.py',
                'category': 'manifest',
                'severity': 'HIGH',
                'checks': [
                    {
                        'name': 'Required Keys Present',
                        'pattern': r"'name':|\"name\":",
                        'error_message': "Missing required 'name' key in __manifest__.py",
                        'fix': 'Add name = "Module Name" to __manifest__.py'
                    },
                    {
                        'name': 'Valid Dependencies',
                        'pattern': r"'depends':\s*\[.*?\]",
                        'error_message': "Invalid or missing 'depends' list",
                        'fix': 'Ensure depends list contains only installed modules'
                    },
                    {
                        'name': 'Version Format',
                        'pattern': r"'version':\s*'\\d+\\.\\d+'",
                        'error_message': "Invalid version format (should be X.Y)",
                        'fix': 'Use semantic versioning: version = "1.0"'
                    }
                ],
                'prevention_script': '''
    def validate_manifest(manifest_path):
        with open(manifest_path) as f:
            manifest = eval(f.read())
        
        required_keys = ['name', 'version', 'depends', 'data']
        missing = [k for k in required_keys if k not in manifest]
        
        if missing:
            raise ValueError(f"Missing required keys: {missing}")
        
        # Validate version format
        version = manifest['version']
        if not re.match(r'^\\d+\\.\\d+$', version):
            raise ValueError(f"Invalid version format: {version}")
        
        return True
    ''',
                'auto_fix_script': '''
    def fix_manifest(manifest_path):
        with open(manifest_path) as f:
            content = f.read()
        
        # Add missing keys with defaults
        if "'name'" not in content and '"name"' not in content:
            content = "{'name': 'My Module',\n" + content
        
        if "'version'" not in content:
            content += "\\n'version': '1.0',"
        
        if "'depends'" not in content:
            content += "\\n'depends': ['base'],"
        
        with open(manifest_path, 'w') as f:
            f.write(content)
    '''
            }
            
            output_path = self.guardrails_dir / 'GR-INSTALL-004.yaml'
            with open(output_path, 'w') as f:
                yaml.dump(guardrail, f, default_flow_style=False)
            
            return guardrail
        
        def generate_field_sync_guardrail(self):
            """
            GR-POS-001: POS field synchronization
            """
            guardrail = {
                'id': 'GR-POS-001',
                'name': 'POS Field Sync Prevention',
                'description': 'Prevent POS order/line desync when adding custom fields',
                'category': 'field',
                'severity': 'CRITICAL',
                'background': '''
    Common Issue: Adding fields to pos.order.line but forgetting to add 
    export/import in pos.order causes data loss on session closure.
    
    Affected Models:
    - pos.order (parent)
    - pos.order.line (child)
    
    Root Cause: POS uses JSON export/import for order data persistence.
    Custom fields not included in _export_for_ui() are silently dropped.
    ''',
                'checks': [
                    {
                        'name': 'Check POS Line Fields',
                        'pattern': r'class PosOrderLine.*?:',
                        'error_message': 'Added field to pos.order.line without updating pos.order export',
                        'fix': 'Add field to _export_for_ui() in pos.order'
                    }
                ],
                'prevention_script': '''
    def check_pos_field_sync(module_path):
        """Validate POS field synchronization"""
        pos_order_line_file = module_path / 'models' / 'pos_order_line.py'
        pos_order_file = module_path / 'models' / 'pos_order.py'
        
        if pos_order_line_file.exists():
            # Extract added fields
            with open(pos_order_line_file) as f:
                line_content = f.read()
                line_fields = re.findall(r'(\\w+)\\s*=\\s*fields\\.', line_content)
            
            # Check if fields are in pos.order export
            with open(pos_order_file) as f:
                order_content = f.read()
                export_func = re.search(
                    r'def _export_for_ui.*?return.*?\\}',
                    order_content,
                    re.DOTALL
                )
                
                if export_func:
                    missing_fields = [
                        f for f in line_fields 
                        if f not in export_func.group()
                    ]
                    
                    if missing_fields:
                        raise ValueError(
                            f"Fields {missing_fields} not in _export_for_ui()"
                        )
    ''',
                'auto_fix_script': '''
    def fix_pos_field_sync(pos_order_file, new_field):
        """Auto-add field to POS export"""
        with open(pos_order_file) as f:
            content = f.read()
        
        # Find _export_for_ui method
        pattern = r'(def _export_for_ui.*?return\\s*\\{)(.*?)(\\})'
        
        def add_field(match):
            prefix, fields, suffix = match.groups()
            return f"{prefix}{fields}\\n            '{new_field}': self.{new_field},{suffix}"
        
        fixed_content = re.sub(pattern, add_field, content, flags=re.DOTALL)
        
        with open(pos_order_file, 'w') as f:
            f.write(fixed_content)
    '''
            }
            
            output_path = self.guardrails_dir / 'GR-POS-001.yaml'
            with open(output_path, 'w') as f:
                yaml.dump(guardrail, f, default_flow_style=False)
            
            return guardrail
        
        def generate_all_guardrails(self):
            """
            Generate complete guardrail library
            """
            guardrails = [
                self.generate_manifest_guardrail(),
                self.generate_field_sync_guardrail(),
                # Add more based on scraped patterns...
            ]
            
            # Generate index
            index = {
                'total': len(guardrails),
                'categories': {},
                'severity_distribution': {}
            }
            
            for gr in guardrails:
                category = gr['category']
                severity = gr['severity']
                
                if category not in index['categories']:
                    index['categories'][category] = []
                index['categories'][category].append(gr['id'])
                
                if severity not in index['severity_distribution']:
                    index['severity_distribution'][severity] = 0
                index['severity_distribution'][severity] += 1
            
            # Save index
            with open(self.guardrails_dir / 'index.json', 'w') as f:
                json.dump(index, f, indent=2)
            
            return guardrails
    
    # Usage
    generator = GuardrailGenerator(patterns)
    guardrails = generator.generate_all_guardrails()
    
    print(f"Generated {len(guardrails)} guardrails")
    

    Auto-Patch System

    # apply_autopatch.py
    import os
    import re
    from pathlib import Path
    
    class AutoPatcher:
        def __init__(self, module_path):
            self.module_path = Path(module_path)
            self.applied_patches = []
        
        def detect_issues(self):
            """
            Scan module for known issues
            """
            issues = []
            
            # Check for POS field sync issue
            if self.has_pos_field_issue():
                issues.append({
                    'type': 'POS_FIELD_SYNC',
                    'severity': 'HIGH',
                    'file': 'models/pos_order.py',
                    'fix': 'apply_pos_export_import_fix'
                })
            
            # Check for manifest issues
            if self.has_manifest_issue():
                issues.append({
                    'type': 'MANIFEST_VALIDATION',
                    'severity': 'CRITICAL',
                    'file': '__manifest__.py',
                    'fix': 'fix_manifest_validation'
                })
            
            # Check for ir.sequence vs. auto-increment
            if self.has_sequence_issue():
                issues.append({
                    'type': 'SEQUENCE_NUMBER',
                    'severity': 'MEDIUM',
                    'file': 'models/*.py',
                    'fix': 'switch_to_ir_sequence'
                })
            
            return issues
        
        def has_pos_field_issue(self):
            """
            Detect POS field synchronization issue
            """
            pos_line_file = self.module_path / 'models' / 'pos_order_line.py'
            pos_order_file = self.module_path / 'models' / 'pos_order.py'
            
            if not pos_line_file.exists():
                return False
            
            with open(pos_line_file) as f:
                line_content = f.read()
                # Check if custom fields were added
                if 'fields.' in line_content:
                    # Check if _export_for_ui is updated
                    if pos_order_file.exists():
                        with open(pos_order_file) as f:
                            order_content = f.read()
                            if '_export_for_ui' not in order_content:
                                return True
            
            return False
        
        def apply_pos_export_import_fix(self):
            """
            Auto-fix: Add field to POS export/import
            """
            pos_order_file = self.module_path / 'models' / 'pos_order.py'
            
            # Extract custom fields from pos.order.line
            pos_line_file = self.module_path / 'models' / 'pos_order_line.py'
            with open(pos_line_file) as f:
                line_content = f.read()
                custom_fields = re.findall(
                    r'(\\w+)\\s*=\\s*fields\\.(Char|Integer|Float|Boolean)',
                    line_content
                )
            
            # Add to _export_for_ui
            with open(pos_order_file) as f:
                order_content = f.read()
            
            # Generate export code
            export_code = "\\n        ".join([
                f"'{field}': line.{field},"
                for field, _ in custom_fields
            ])
            
            # Insert into _export_for_ui
            fixed_content = order_content.replace(
                "'amount_tax': line.price_subtotal_incl - line.price_subtotal,",
                f"'amount_tax': line.price_subtotal_incl - line.price_subtotal,\\n        {export_code}"
            )
            
            with open(pos_order_file, 'w') as f:
                f.write(fixed_content)
            
            self.applied_patches.append('POS_FIELD_SYNC')
            
            return True
        
        def apply_all_fixes(self):
            """
            Detect and apply all available fixes
            """
            issues = self.detect_issues()
            
            for issue in issues:
                fix_method = getattr(self, issue['fix'])
                success = fix_method()
                
                if success:
                    print(f"✅ Applied fix: {issue['type']}")
                else:
                    print(f"❌ Failed to apply: {issue['type']}")
            
            return self.applied_patches
    
    # Usage
    patcher = AutoPatcher('/path/to/custom_module')
    applied = patcher.apply_all_fixes()
    
    print(f"Applied {len(applied)} patches")
    

    Integration Points

    With CI/CD (GitHub Actions)

    # .github/workflows/odoo-guardrails.yml
    name: Odoo Guardrails Check
    
    on: [push, pull_request]
    
    jobs:
      validate:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          
          - name: Run Guardrails
            run: |
              python guardrails/check_all.py
          
          - name: Apply Auto-Fixes
            if: failure()
            run: |
              python autopatches/apply_all.py
              git add .
              git commit -m "🤖 Auto-applied fixes"
              git push
    

    With Odoo Development

    # Integrate into Odoo module development workflow
    from odoo_knowledge_agent import AutoPatcher
    
    # Before deployment
    patcher = AutoPatcher('./custom_modules/my_module')
    issues = patcher.detect_issues()
    
    if issues:
        print("⚠️ Issues detected:")
        for issue in issues:
            print(f"  - {issue['type']}: {issue['file']}")
        
        # Auto-fix
        patcher.apply_all_fixes()
        print("✅ All issues resolved")
    

    Output Formats

    Guardrail YAML

    id: GR-POS-001
    name: POS Field Sync Prevention
    description: Prevent POS order/line desync when adding custom fields
    category: field
    severity: CRITICAL
    checks:
      - name: Check POS Line Fields
        pattern: 'class PosOrderLine.*?:'
        error_message: Added field without updating export
        fix: Add field to _export_for_ui()
    prevention_script: |
      def check_pos_field_sync(module_path):
          # Validation logic
    auto_fix_script: |
      def fix_pos_field_sync(pos_order_file):
          # Auto-fix logic
    

    Error Knowledge Base

    Supabase schema:

    CREATE TABLE odoo_solved_threads (
        id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
        thread_url TEXT UNIQUE NOT NULL,
        title TEXT NOT NULL,
        question TEXT,
        answer TEXT NOT NULL,
        code_snippets JSONB,
        tags TEXT[],
        views INTEGER,
        error_category TEXT,
        scraped_at TIMESTAMPTZ DEFAULT NOW(),
        embedding vector(1536)
    );
    
    CREATE INDEX idx_threads_category ON odoo_solved_threads(error_category);
    CREATE INDEX idx_threads_tags ON odoo_solved_threads USING GIN(tags);
    CREATE INDEX idx_threads_embedding ON odoo_solved_threads USING ivfflat(embedding vector_cosine_ops);
    

    Examples

    Example 1: Pre-Deployment Validation

    # Check module before deployment
    python guardrails/check_all.py ./custom_modules/expense_management
    
    # Output:
    ⚠️ 3 issues detected:
      - [CRITICAL] GR-INSTALL-004: Invalid __manifest__.py (missing version)
      - [HIGH] GR-POS-001: POS field sync missing for 'receipt_url'
      - [MEDIUM] GR-ACCT-002: Invoice numbering using auto-increment
    
    ✅ Auto-fixes available for all issues. Apply? [y/N]
    

    Example 2: Production Auto-Heal

    # Monitor production for known issues
    from odoo_knowledge_agent import ProductionMonitor
    
    monitor = ProductionMonitor(odoo_db_uri)
    
    # Detect issues
    issues = monitor.check_for_known_issues()
    
    # Auto-apply community-validated fixes
    for issue in issues:
        if issue['confidence'] > 0.8:
            monitor.apply_fix(issue['fix_id'])
            monitor.log_fix(issue)
    

    Example 3: Knowledge Base Search

    # Search for similar issues
    from odoo_knowledge_agent import KnowledgeBase
    
    kb = KnowledgeBase(supabase_url, supabase_key)
    
    # Semantic search using pgvector
    similar_issues = kb.search(
        query="POS session won't close after adding custom field",
        limit=5
    )
    
    for issue in similar_issues:
        print(f"Thread: {issue['title']}")
        print(f"Solution: {issue['answer'][:200]}...")
        print(f"Similarity: {issue['similarity']:.2f}")
    

    Cost Savings

    vs. Manual Debugging

    Activity Manual Time With Agent Savings
    Debug POS issue 4 hours 5 minutes $150
    Fix manifest error 1 hour 2 minutes $40
    Research solution 2 hours Instant $80

    Monthly Savings (10 issues/month): $2,700
    Annual Savings: $32,400

    vs. Paid Support

    • Odoo Partner Support: $5,000/year
    • Self-Healing System: $0 (open source)
    • Savings: $5,000/year

    Total Annual Value: $37,400

    Best Practices

    Scraping Frequency

    ✅ Weekly updates: Keep knowledge base current
    ✅ Monitor new threads: Catch emerging patterns
    ✅ Version-specific: Filter by Odoo version

    Guardrail Deployment

    ✅ Pre-commit hooks: Block bad code before commit
    ✅ CI/CD integration: Validate in pipeline
    ✅ IDE plugins: Real-time error prevention

    Auto-Patch Safety

    ✅ Test in staging first: Never auto-patch production directly
    ✅ Version control: All patches in Git
    ✅ Rollback plan: Keep fix history

    Troubleshooting

    Scraping Blocked

    Issue: Odoo forum blocks scraper IP
    Fix: Use Firecrawl with rotating proxies, add delays

    False Positives

    Issue: Guardrail blocks valid code
    Fix: Whitelist patterns, tune regex

    Auto-Fix Breaks Code

    Issue: Patch causes new errors
    Fix: Improve test coverage, manual review high-risk fixes

    License

    Apache 2.0

    References

    • Odoo Forum
    • OCA Contribution Guidelines
    • askui Vision Agent
    • Odoo Development Documentation

    Learn from 1000+ community solutions. Prevent errors before they happen. Auto-heal production issues. Build better Odoo systems. 🚀

    Recommended Servers
    ScrapeGraph AI Integration Server
    ScrapeGraph AI Integration Server
    Bright Data
    Bright Data
    Apify
    Apify
    Repository
    jgtolentino/insightpulse-odoo
    Files