Skill: 534422530/contract-review-skill (version 1.0.4)

name: laosi-contract-review-skill version: 1.0.0 description: AI-powered contract review skill for OpenClaw agents - identifies risky clauses, missing provisions, and compliance issues in legal documents author: laosi homepage: https://github.com/laosi/contract-review-skill tags: [legal, contract, review, compliance, risk-assessment, document-analysis]

path: batch_results/batch_review_report_20260427_095105.json
content: {
  "batch_info": {
    "total_contracts": 2,
    "processed_contracts": 2,
    "failed_contracts": 0,
    "processing_time_seconds": 0.19,
    "timestamp": "2026-04-27T09:51:05.149558",
    "industry": "general",
    "max_workers": 2
  },
  "results": [
    {
      "contract_file": "C:\\Users\\pc\\.laosi\\sample_contract.txt",
      "contract_name": "sample_contract.txt",
      "risk_score": 40,
      "risk_level": "HIGH",
      "findings_count": 5,
      "high_risk_count": 0,
      "medium_risk_count": 0,
      "low_risk_count": 0,
      "critical_risk_count": 0
    },
    {
      "contract_file": "C:\\Users\\pc\\.laosi\\sample_contract_2.txt",
      "contract_name": "sample_contract_2.txt",
      "risk_score": 48,
      "risk_level": "HIGH",
      "findings_count": 6,
      "high_risk_count": 0,
      "medium_risk_count": 0,
      "low_risk_count": 0,
      "critical_risk_count": 0
    }
  ],
  "summary": {
    "total_high_risk": 0,
    "total_medium_risk": 0,
    "total_low_risk": 0,
    "average_risk_score": 44.0,
    "risk_distribution": {
      "critical": 0,
      "high": 0,
      "medium": 0,
      "low": 0
    }
  }
}

path: batch_results/batch_review_report_20260427_123317.json
content: {
  "batch_info": {
    "total_contracts": 1,
    "processed_contracts": 1,
    "failed_contracts": 0,
    "processing_time_seconds": 0.13,
    "timestamp": "2026-04-27T12:33:17.569710",
    "industry": "general",
    "max_workers": 4
  },
  "results": [
    {
      "contract_file": "C:\\Users\\pc\\test_contract.txt",
      "contract_name": "test_contract.txt",
      "risk_score": 40,
      "risk_level": "HIGH",
      "findings_count": 5,
      "high_risk_count": 0,
      "medium_risk_count": 3,
      "low_risk_count": 2,
      "critical_risk_count": 0
    }
  ],
  "summary": {
    "total_high_risk": 0,
    "total_medium_risk": 3,
    "total_low_risk": 2,
    "average_risk_score": 40.0,
    "risk_distribution": {
      "critical": 0,
      "high": 0,
      "medium": 3,
      "low": 2
    }
  }
}

path: batch_reviewer.py
content: #!/usr/bin/env python3
"""
Batch Contract Review Manager - Uses SubAgent system for parallel contract processing
"""

import json
import os
import sys
import time
from pathlib import Path
from typing import Dict, List, Any, Optional
from datetime import datetime

# Import SubAgent system
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from subagent import SubAgentManager, AgentStatus

# Import our contract review engine
sys.path.insert(0, str(Path(__file__).parent))
from review_engine import ContractReviewEngine

class BatchContractReviewManager:
    def __init__(self, max_workers: int = 4):
        self.manager = SubAgentManager()
        self.max_workers = max_workers
        self.engine = ContractReviewEngine(Path(__file__).parent / "legal_patterns")
        self.results_dir = Path(__file__).parent / "batch_results"
        self.results_dir.mkdir(exist_ok=True)
    
    def review_contracts_parallel(self, contract_files: List[str], industry: str = None) -> Dict[str, Any]:
        """
        Review multiple contracts in parallel using SubAgent system
        """
        start_time = time.time()
        
        # Create SubAgents for each contract
        agent_ids = []
        for i, contract_file in enumerate(contract_files):
            if not os.path.exists(contract_file):
                continue
                
            agent_name = f"ContractReviewer_{i+1}"
            task = f"Review contract: {os.path.basename(contract_file)}"
            
            context = {
                "contract_file": contract_file,
                "industry": industry,
                "index": i
            }
            
            agent_id = self.manager.create_agent(agent_name, task, context)
            agent_ids.append(agent_id)
        
        # Process agents in batches (respecting max_workers)
        results = {}
        completed_agents = set()
        
        while len(completed_agents) < len(agent_ids):
            # Check status of running agents
            for agent_id in agent_ids:
                if agent_id in completed_agents:
                    continue
                    
                agent = self.manager.get_agent(agent_id)
                if not agent:
                    continue
                
                # If agent is not yet started, start it
                if agent.status == AgentStatus.PENDING:
                    self._process_contract_agent(agent_id)
                
                # If agent completed, collect result
                if agent.status in [AgentStatus.COMPLETED, AgentStatus.FAILED]:
                    if agent_id not in completed_agents:
                        results[agent_id] = {
                            "agent_id": agent_id,
                            "name": agent.name,
                            "task": agent.task,
                            "status": agent.status.value,
                            "result": agent.result,
                            "error": agent.error,
                            "created_at": agent.created_at,
                            "completed_at": agent.completed_at
                        }
                        completed_agents.add(agent_id)
            
            # Small delay to prevent busy waiting
            time.sleep(0.1)
            
            # Timeout check (5 minutes max)
            if time.time() - start_time > 300:
                break
        
        end_time = time.time()
        processing_time = end_time - start_time
        
        # Format final results
        batch_results = {
            "batch_info": {
                "total_contracts": len(contract_files),
                "processed_contracts": len([r for r in results.values() if r["status"] == "completed"]),
                "failed_contracts": len([r for r in results.values() if r["status"] == "failed"]),
                "processing_time_seconds": round(processing_time, 2),
                "timestamp": datetime.now().isoformat(),
                "industry": industry or "general",
                "max_workers": self.max_workers
            },
            "results": [],
            "summary": {
                "total_high_risk": 0,
                "total_medium_risk": 0,
                "total_low_risk": 0,
                "average_risk_score": 0,
                "risk_distribution": {"critical": 0, "high": 0, "medium": 0, "low": 0}
            }
        }
        
        # Process results
        risk_scores = []
        for result in results.values():
            if result["status"] == "completed" and result["result"]:
                contract_result = result["result"]
                batch_results["results"].append({
                    "contract_file": contract_result.get("metadata", {}).get("contract_file", "unknown"),
                    "contract_name": os.path.basename(contract_result.get("metadata", {}).get("contract_file", "unknown")),
                    "risk_score": contract_result.get("risk_score", 0),
                    "risk_level": self._score_to_level(contract_result.get("risk_score", 0)),
                    "findings_count": len(contract_result.get("findings", [])),
                    "high_risk_count": len([f for f in contract_result.get("findings", []) if f.get("risk_level") == "high"]),
                    "medium_risk_count": len([f for f in contract_result.get("findings", []) if f.get("risk_level") == "medium"]),
                    "low_risk_count": len([f for f in contract_result.get("findings", []) if f.get("risk_level") == "low"]),
                    "critical_risk_count": len([f for f in contract_result.get("findings", []) if f.get("risk_level") == "critical"])
                })
                
                # Accumulate for summary
                risk_scores.append(contract_result.get("risk_score", 0))
                batch_results["summary"]["total_high_risk"] += len([f for f in contract_result.get("findings", []) if f.get("risk_level") == "high"])
                batch_results["summary"]["total_medium_risk"] += len([f for f in contract_result.get("findings", []) if f.get("risk_level") == "medium"])
                batch_results["summary"]["total_low_risk"] += len([f for f in contract_result.get("findings", []) if f.get("risk_level") == "low"])
                
                # Risk distribution
                for finding in contract_result.get("findings", []):
                    risk_level = finding.get("risk_level", "low")
                    if risk_level == "critical":
                        batch_results["summary"]["risk_distribution"]["critical"] += 1
                    elif risk_level == "high":
                        batch_results["summary"]["risk_distribution"]["high"] += 1
                    elif risk_level == "medium":
                        batch_results["summary"]["risk_distribution"]["medium"] += 1
                    elif risk_level == "low":
                        batch_results["summary"]["risk_distribution"]["low"] += 1
        
        # Calculate average risk score
        if risk_scores:
            batch_results["summary"]["average_risk_score"] = round(sum(risk_scores) / len(risk_scores), 2)
        
        return batch_results
    
    def _process_contract_agent(self, agent_id: str):
        """Process a single contract review agent"""
        agent = self.manager.get_agent(agent_id)
        if not agent:
            return
        
        # Update status to running
        self.manager.update_status(agent_id, AgentStatus.RUNNING)
        
        try:
            # Get context
            contract_file = agent.context.get("contract_file")
            industry = agent.context.get("industry")
            
            if not contract_file or not os.path.exists(contract_file):
                raise FileNotFoundError(f"Contract file not found: {contract_file}")
            
            # Read contract
            contract_text = Path(contract_file).read_text(encoding='utf-8')
            
            # Review contract
            result = self.engine.review_contract(contract_text, industry)
            
            # Add file info to result
            result["metadata"]["contract_file"] = contract_file
            result["metadata"]["file_size"] = os.path.getsize(contract_file)
            
            # Update agent with result
            self.manager.update_status(agent_id, AgentStatus.COMPLETED, result=result)
            
        except Exception as e:
            # Update agent with error
            self.manager.update_status(agent_id, AgentStatus.FAILED, error=str(e))
    
    def _score_to_level(self, score: int) -> str:
        """Convert risk score to level label"""
        if score >= 80:
            return "LOW"
        elif score >= 60:
            return "MEDIUM"
        elif score >= 40:
            return "HIGH"
        else:
            return "CRITICAL"
    
    def save_batch_report(self, batch_results: Dict[str, Any], output_file: str = None) -> str:
        """Save batch review results to file"""
        if not output_file:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            output_file = self.results_dir / f"batch_review_report_{timestamp}.json"
        
        with open(output_file, 'w', encoding='utf-8') as f:
            json.dump(batch_results, f, indent=2, ensure_ascii=False)
        
        return str(output_file)
    
    def print_batch_summary(self, batch_results: Dict[str, Any]):
        """Print human-readable batch summary"""
        info = batch_results["batch_info"]
        summary = batch_results["summary"]
        
        print("=" * 70)
        print("BATCH CONTRACT REVIEW REPORT")
        print("=" * 70)
        print(f"Total Contracts: {info['total_contracts']}")
        print(f"Processed: {info['processed_contracts']}")
        print(f"Failed: {info['failed_contracts']}")
        print(f"Processing Time: {info['processing_time_seconds']} seconds")
        print(f"Timestamp: {info['timestamp']}")
        print(f"Industry Focus: {info['industry']}")
        print(f"Max Workers: {info['max_workers']}")
        print("-" * 70)
        print("SUMMARY:")
        print(f"  Average Risk Score: {summary['average_risk_score']}/100")
        print(f"  Total High Risk Findings: {summary['total_high_risk']}")
        print(f"  Total Medium Risk Findings: {summary['total_medium_risk']}")
        print(f"  Total Low Risk Findings: {summary['total_low_risk']}")
        print(f"  Risk Distribution:")
        print(f"    Critical: {summary['risk_distribution']['critical']}")
        print(f"    High: {summary['risk_distribution']['high']}")
        print(f"    Medium: {summary['risk_distribution']['medium']}")
        print(f"    Low: {summary['risk_distribution']['low']}")
        print("-" * 70)
        print("INDIVIDUAL RESULTS:")
        
        for i, result in enumerate(batch_results["results"], 1):
            print(f"{i}. {result['contract_name']}")
            print(f"   Risk Score: {result['risk_score']}/100 ({result['risk_level']})")
            print(f"   Findings: {result['findings_count']} "
                  f"(H:{result['high_risk_count']} M:{result['medium_risk_count']} "
                  f"L:{result['low_risk_count']} C:{result['critical_risk_count']})")
            print()

def main():
    if len(sys.argv) < 2:
        print("Usage: python batch_reviewer.py <contract_file1> [contract_file2] ... [--industry <industry>] [--workers <n>]")
        print("Example: python batch_reviewer.py contract1.txt contract2.txt --industry tech --workers 4")
        sys.exit(1)
    
    # Parse arguments
    args = sys.argv[1:]
    contract_files = []
    industry = None
    max_workers = 4
    
    i = 0
    while i < len(args):
        if args[i] == "--industry" and i + 1 < len(args):
            industry = args[i + 1]
            i += 2
        elif args[i] == "--workers" and i + 1 < len(args):
            try:
                max_workers = int(args[i + 1])
            except ValueError:
                pass
            i += 2
        else:
            contract_files.append(args[i])
            i += 1
    
    if not contract_files:
        print("Error: No contract files specified")
        sys.exit(1)
    
    # Validate files exist
    valid_files = []
    for f in contract_files:
        if os.path.exists(f):
            valid_files.append(f)
        else:
            print(f"Warning: File not found - {f}")
    
    if not valid_files:
        print("Error: No valid contract files found")
        sys.exit(1)
    
    # Process batch
    manager = BatchContractReviewManager(max_workers=max_workers)
    print(f"Starting batch review of {len(valid_files)} contracts with {max_workers} workers...")
    results = manager.review_contracts_parallel(valid_files, industry)
    
    # Display results
    manager.print_batch_summary(results)
    
    # Save results
    output_file = manager.save_batch_report(results)
    print(f"Detailed results saved to: {output_file}")

if __name__ == "__main__":
    main()

path: claw.json
content: {
  "name": "laosi-contract-review-skill",
  "displayName": "LAOSI Contract Review Skill",
  "description": "AI-powered contract review skill for OpenClaw agents - identifies risky clauses, missing provisions, and compliance issues in legal documents",
  "version": "1.0.1",
  "author": "laosi",
  "homepage": "https://github.com/laosi/contract-review-skill",
  "license": "MIT",
  "keywords": ["legal", "contract", "review", "compliance", "risk-assessment", "document-analysis"],
  "category": "productivity",
  "engines": {
    "openclaw": ">=1.0.0"
  },
  "scripts": {
    "review": "python contract_reviewer.py"
  },
  "files": [
    "SKILL.md",
    "README.md",
    "claw.json",
    "review_engine.py",
    "contract_reviewer.py",
    "legal_patterns/",
    "templates/"
  ]
}

path: contract_reviewer.py
content: #!/usr/bin/env python3
"""
Contract Review Skill - CLI Wrapper for OpenClaw Skill Chain
Provides easy-to-use interface for the contract reviewing functionality
"""

import json
import sys
import os
from pathlib import Path

def main():
    if len(sys.argv) < 2:
        print('{"error": "Usage: contract-review-skill <path_to_contract> [--industry <industry>] [--format <json|text>]"}')
        sys.exit(1)
    
    target_path = sys.argv[1]
    industry = None
    output_format = "text"  # default
    
    # Parse optional arguments
    i = 2
    while i < len(sys.argv):
        if sys.argv[i] == "--industry" and i + 1 < len(sys.argv):
            industry = sys.argv[i + 1]
            i += 2
        elif sys.argv[i] == "--format" and i + 1 < len(sys.argv):
            output_format = sys.argv[i + 1]
            i += 2
        else:
            i += 1
    
    # Read contract content
    try:
        contract_text = Path(target_path).read_text(encoding='utf-8')
    except Exception as e:
        print(json.dumps({"error": f"Failed to read contract file: {str(e)}"}, ensure_ascii=False))
        sys.exit(1)
    
    # Import and run the review engine
    sys.path.insert(0, str(Path(__file__).parent))
    from review_engine import ContractReviewEngine
    
    engine = ContractReviewEngine(Path(__file__).parent / "legal_patterns")
    result = engine.review_contract(contract_text, industry)
    
    # Output as requested format
    if output_format == "json":
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        # Format as human-readable report
        print(format_report(result, Path(target_path).name))

def format_report(report: dict, contract_name: str) -> str:
    """Format review results as human-readable report."""
    findings = report.get("findings", [])
    risk_score = report.get("risk_score", 0)
    summary = report.get("summary", {})
    
    # Determine risk level label (lower score means higher risk)
    if risk_score >= 80:
        risk_label = "LOW"
    elif risk_score >= 60:
        risk_label = "MEDIUM"
    elif risk_score >= 40:
        risk_label = "HIGH"
    else:
        risk_label = "CRITICAL"
    
    # Count by risk level
    by_level = summary.get("by_risk_level", {"critical": 0, "high": 0, "medium": 0, "low": 0})
    critical_count = by_level.get("critical", 0)
    high_count = by_level.get("high", 0)
    medium_count = by_level.get("medium", 0)
    low_count = by_level.get("low", 0)
    
    lines = [
        "=" * 60,
        "CONTRACT REVIEW REPORT",
        "=" * 60,
        f"Contract: {contract_name}",
        f"Overall Risk Score: {risk_score}/100 ({risk_label})",
        "Risk Distribution:",
        f"  - Critical: {critical_count} clauses",
        f"  - High: {high_count} clauses",
        f"  - Medium: {medium_count} clauses",
        f"  - Low: {low_count} clauses",
        ""
    ]
    
    if findings:
        lines.append("Top Risk Findings:")
        # Sort findings by risk level (critical first)
        risk_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
        sorted_findings = sorted(findings, key=lambda f: risk_order.get(f["risk_level"], 4))
        
        for finding in sorted_findings[:5]:  # Show top 5
            risk_level = finding['risk_level']
            if hasattr(risk_level, 'value'):
                risk_level_str = risk_level.value.upper()
            else:
                risk_level_str = str(risk_level).upper()
            lines.append(f"  [{risk_level_str}] {finding['title']}")
            lines.append(f"    Location: {finding['location']}")
            lines.append(f"    Issue: {finding['description']}")
            lines.append(f"    Suggestion: {finding['suggestion']}")
            if finding.get("reference"):
                lines.append(f"    Reference: {finding['reference']}")
            lines.append("")
        
        if len(findings) > 5:
            lines.append(f"  ... and {len(findings) - 5} more findings")
            lines.append("")
    
    lines.append("Recommendations:")
    if critical_count > 0:
        lines.append("  1. [CRITICAL] Address all critical risk items immediately - do not sign without legal review")
    if high_count > 0:
        lines.append("  2. [HIGH] Address high risk items before signing or seek legal counsel")
    if medium_count > 0:
        lines.append("  3. [MEDIUM] Consider negotiating medium risk clauses based on your leverage position")
    if low_count > 0:
        lines.append("  4. [LOW] Minor items - standard business practice")
    
    if critical_count == 0 and high_count == 0 and medium_count == 0:
        lines.append("  [LOW] No significant risks identified - contract appears reasonably balanced")
    
    lines.extend([
        "",
        "=" * 60
    ])
    
    return "\n".join(lines)

if __name__ == "__main__":
    main()

path: fix_json.py
content: import json
import os

data = {
  "confidentiality": {
    "title": "Missing Confidentiality Clause",
    "description": "Contract lacks explicit confidentiality obligations for protecting proprietary information",
    "pattern": "(confidential|confidentiality|non-disclosure|nda).*?(obligation|duty|responsibility|requirement)|(proprietary|confidential).*?(information|data|knowledge).*?(shall be|must be|is required to be).*?(kept confidential|maintained in confidence)",
    "risk_level": "MEDIUM",
    "suggestion": "Add a standard confidentiality clause defining what information is confidential and obligations to protect it",
    "reference": "Standard Business Practice and Trade Secret Protection Laws"
  },
  "intellectual_property": {
    "title": "Missing Intellectual Property Clause",
    "description": "Contract does not address ownership, licensing, or use of intellectual property",
    "pattern": "(intellectual property|ip|copyright|patent|trademark|trade secret|proprietary).*?(ownership|title|interest|rights|license|usage).*?(retains|retain|remains with|belongs to)|(work product|deliverables|materials).*?(created|developed|produced).*?(shall be|is|are).*?(the exclusive property of|owned by)",
    "risk_level": "MEDIUM",
    "suggestion": "Add IP clause specifying ownership of pre-existing IP, ownership of work product, and license grants",
    "reference": "Copyright Law and Patent Act Principles"
  },
  "payment_terms": {
    "title": "Unclear Payment Terms",
    "description": "Payment amount, schedule, or method is not clearly defined",
    "pattern": "(payment|pay|fee|charge|compensation|remuneration).*?(amount|sum|fee|rate).*?(shall be|is|will be|equals|totals).*?\\$?\\d+(\\.\\d+)?(?:\s*(USD|dollars|RMB|yuan|EUR|euros|GBP|pounds))?|(invoice|bill).*?(submit|send|issue).*?(monthly|quarterly|upon completion|net \\d+)",
    "risk_level": "MEDIUM",
    "suggestion": "Specify exact payment amounts, schedule (e.g., monthly, milestone-based), method, and late payment penalties",
    "reference": "Standard Contract Payment Practices"
  },
  "term_and_termination": {
    "title": "Unclear Term and Termination",
    "description": "Contract duration, renewal terms, or termination conditions are not clearly specified",
    "pattern": "(term|duration).*?(shall be|is|will be|equals).*?\\d+.*?(day|days|week|weeks|month|months|year|years)|(termination|terminate|end|cancel).*?(for cause|without cause|with notice).*?(notice|notification).*?\\d+.*?(day|days|week|weeks|month|months)",
    "risk_level": "MEDIUM",
    "suggestion": "Clearly define initial term, renewal options, termination for cause and without cause procedures, and required notice periods",
    "reference": "Standard Contract Duration and Termination Practices"
  },
  "dispute_resolution": {
    "title": "Missing Dispute Resolution Clause",
    "description": "Contract does not specify how disputes will be resolved (negotiation, mediation, arbitration, litigation)",
    "pattern": "(dispute|disagreement|controversy|claim).*?(resolution|settled|resolved).*?(through|via|by means of).*?(negotiation|mediation|arbitration|litigation|court)|(arbitration).*?(shall be|is|will be).*?(final|binding)|(governing law).*?(shall be|is|will be).*?[A-Z][a-z]+",
    "risk_level": "MEDIUM",
    "suggestion": "Add dispute resolution clause specifying negotiation first, then mediation, then binding arbitration or litigation with specified jurisdiction",
    "reference": "Standard Dispute Resolution Practices and Arbitration Law"
  },
  "force_majeure": {
    "title": "Missing Force Majeure Clause",
    "description": "Contract lacks provision for unforeseen circumstances preventing performance",
    "pattern": "(force majeure|act of god|unforeseeable circumstances|beyond reasonable control).*?(shall excuse|excuses|releases from liability)|(neither party).*?(shall be liable|is responsible).*?(for any failure|for any delay).*?(caused by|due to|resulting from).*?(force majeure|act of god|natural disaster|war|terrorism|government action)",
    "risk_level": "LOW",
    "suggestion": "Add standard force majeure clause covering natural disasters, war, terrorism, government actions, and other uncontrollable events",
    "reference": "Standard Force Majeure Principles and Civil Code Articles"
  },
  "governing_law": {
    "title": "Missing Governing Law Clause",
    "description": "Contract does not specify which jurisdiction's laws will govern interpretation",
    "pattern": "(governing law|this agreement shall be governed by|interpreted in accordance with).*?the laws of.*?[A-Z][a-z]+.*?(state|province|country)|(this agreement).*?(shall be|is|will be).*?(subject to|governed by).*?the laws.*?of",
    "risk_level": "LOW",
    "suggestion": "Specify governing law (e.g., laws of the State of California, Peoples Republic of China) and optionally jurisdiction for disputes",
    "reference": "Standard Choice of Law Principles"
  },
  "entire_agreement": {
    "title": "Missing Entire Agreement Clause",
    "description": "Contract lacks clause stating that the written document constitutes the complete agreement between parties",
    "pattern": "(entire agreement|whole agreement|complete agreement).*?(this agreement|this document|this instrument).*?(constitutes|is|represents).*?(the|the full|the complete).*?(agreement|understanding|arrangement).*?(between|among).*?(the parties|party a and party b)|(supersedes|replaces|cancels).*?(all|any).*?(prior|previous|preceding).*?(agreements|understandings|arrangements|representations).*?(whether|whether oral|whether written)",
    "risk_level": "LOW",
    "suggestion": "Add entire agreement clause to prevent reliance on prior oral or written statements not included in the written contract",
    "reference": "Standard Integration Clause Principles and Parol Evidence Rule"
  }
}

with open('C:/Users/pc/.laosi/skills/contract-review-skill/legal_patterns/required_clauses.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

print("JSON file written successfully.")

path: legal_patterns/high_risk.json
content: {
  "unilateral_modification": {
    "title": "Unilateral Modification Clause",
    "description": "One party can modify contract terms without the other's consent",
    "pattern": "(either party|either of the parties|[A-Z][a-z]+ [A-Z][a-z]+).*?(may|can|shall have the right to|is permitted to).*?(amend|modify|alter|change|vary|revise).*?(this agreement|the agreement|these terms).*?(without|without requiring|without obtaining|without needing).*?(consent|approval|agreement).*?(of the other party|from the other party)",
    "risk_level": "HIGH",
    "suggestion": "Require mutual agreement for any modifications to ensure fairness",
    "reference": "Contract Law Principle of Mutuality and Good Faith"
  },
  "automatic_renewal": {
    "title": "Automatic Renewal Without Notice",
    "description": "Contract renews automatically without requiring advance notice for non-renewal",
    "pattern": "(automatically|automatic).*?(renew|extend|continue).*?(unless|until).*?(notice|notification).*?(given|provided|delivered).*?(\\d+).*?(day|days|month|months).*?(prior|before|in advance)",
    "risk_level": "HIGH", 
    "suggestion": "Add required notice period (typically 30-60 days) for non-renewal",
    "reference": "Consumer Protection Laws and Contract Fairness Principles"
  },
  "excessive_liability": {
    "title": "Excessive Liability Exposure",
    "description": "Unlimited or excessive liability exposure without reasonable caps",
    "pattern": "(liability|responsible|liable).*?(for|to).*?(all|any|every|any and all|direct|indirect|consequential|special|punitive|exemplary).*?(damages|losses|expenses|costs).*?(without limit|unlimited|uncapped|no cap)",
    "risk_level": "HIGH",
    "suggestion": "Add reasonable liability caps (e.g., fees paid, insurance coverage amounts)",
    "reference": "Standard Contract Risk Allocation Practices"
  },
  "unilateral_termination": {
    "title": "Unilateral Termination Rights",
    "description": "One party can terminate without cause or with minimal notice",
    "pattern": "(either party|either of the parties|[A-Z][a-z]+ [A-Z][a-z]+).*?(may|can|shall have the right to|is permitted to).*?(terminate|end|cancel).*?(this agreement|the agreement).*?(at any time|without cause|without reason).*?(with|upon).*?(\\d+).*?(day|days|week|weeks|month|months).*?(notice|notification)",
    "risk_level": "MEDIUM",
    "suggestion": "Balance termination rights with reasonable notice periods or mutual agreement requirements",
    "reference": "Fair Contract Termination Principles"
  },
  "broad_indemnification": {
    "title": "Overly Broad Indemnification",
    "description": "One party required to indemnify for overly broad scope including their own negligence",
    "pattern": "(indemnify|indemnification|hold harmless).*?(and|&).*?(defend).*?(against|for).*?(all|any|every).*?(claims|demands|actions|suits|proceedings).*?(arising out of|related to|in connection with).*?(negligence|willful misconduct|breach)",
    "risk_level": "HIGH",
    "suggestion": "Limit indemnification to third-party claims arising from indemnifying party's gross negligence or willful misconduct",
    "reference": "Standard Indemnification Principles and Insurance Requirements"
  },
  "jurisdiction_shopping": {
    "title": "Unfair Jurisdiction and Venue",
    "description": "Requires litigation in inconvenient or unfavorable jurisdiction",
    "pattern": "(governing law|jurisdiction|venue).*?(shall be|is|will be).*?(the courts of|the state|the province|the country).*?[A-Z][a-z]+.*?(exclusive|sole|only).*?(jurisdiction|venue|forum)",
    "risk_level": "MEDIUM",
    "suggestion": "Consider neutral jurisdiction or mutual agreement on dispute resolution forum",
    "reference": "Forum Non Conveniens and Fair Forum Selection Principles"
  }
}

path: legal_patterns/required_clauses.json
content: {
  "confidentiality": {
    "title": "Missing Confidentiality Clause",
    "description": "Contract lacks explicit confidentiality obligations for protecting proprietary information",
    "pattern": "(confidential|confidentiality|non-disclosure|nda).*?(obligation|duty|responsibility|requirement)|(proprietary|confidential).*?(information|data|knowledge).*?(shall be|must be|is required to be).*?(kept confidential|maintained in confidence)",
    "risk_level": "MEDIUM",
    "suggestion": "Add a standard confidentiality clause defining what information is confidential and obligations to protect it",
    "reference": "Standard Business Practice and Trade Secret Protection Laws"
  },
  "intellectual_property": {
    "title": "Missing Intellectual Property Clause",
    "description": "Contract does not address ownership, licensing, or use of intellectual property",
    "pattern": "(intellectual property|ip|copyright|patent|trademark|trade secret|proprietary).*?(ownership|title|interest|rights|license|usage).*?(retains|retain|remains with|belongs to)|(work product|deliverables|materials).*?(created|developed|produced).*?(shall be|is|are).*?(the exclusive property of|owned by)",
    "risk_level": "MEDIUM",
    "suggestion": "Add IP clause specifying ownership of pre-existing IP, ownership of work product, and license grants",
    "reference": "Copyright Law and Patent Act Principles"
  },
  "payment_terms": {
    "title": "Unclear Payment Terms",
    "description": "Payment amount, schedule, or method is not clearly defined",
    "pattern": "(payment|pay|fee|charge|compensation|remuneration).*?(amount|sum|fee|rate).*?(shall be|is|will be|equals|totals).*?\\$?\\d+(\\.\\d+)?(?:\\s*(USD|dollars|RMB|yuan|EUR|euros|GBP|pounds))?|(invoice|bill).*?(submit|send|issue).*?(monthly|quarterly|upon completion|net \\d+)",
    "risk_level": "MEDIUM",
    "suggestion": "Specify exact payment amounts, schedule (e.g., monthly, milestone-based), method, and late payment penalties",
    "reference": "Standard Contract Payment Practices"
  },
  "term_and_termination": {
    "title": "Unclear Term and Termination",
    "description": "Contract duration, renewal terms, or termination conditions are not clearly specified",
    "pattern": "(term|duration).*?(shall be|is|will be|equals).*?\\d+.*?(day|days|week|weeks|month|months|year|years)|(termination|terminate|end|cancel).*?(for cause|without cause|with notice).*?(notice|notification).*?\\d+.*?(day|days|week|weeks|month|months)",
    "risk_level": "MEDIUM",
    "suggestion": "Clearly define initial term, renewal options, termination for cause and without cause procedures, and required notice periods",
    "reference": "Standard Contract Duration and Termination Practices"
  },
  "dispute_resolution": {
    "title": "Missing Dispute Resolution Clause",
    "description": "Contract does not specify how disputes will be resolved (negotiation, mediation, arbitration, litigation)",
    "pattern": "(dispute|disagreement|controversy|claim).*?(resolution|settled|resolved).*?(through|via|by means of).*?(negotiation|mediation|arbitration|litigation|court)|(arbitration).*?(shall be|is|will be).*?(final|binding)|(governing law).*?(shall be|is|will be).*?[A-Z][a-z]+",
    "risk_level": "MEDIUM",
    "suggestion": "Add dispute resolution clause specifying negotiation first, then mediation, then binding arbitration or litigation with specified jurisdiction",
    "reference": "Standard Dispute Resolution Practices and Arbitration Law"
  },
  "force_majeure": {
    "title": "Missing Force Majeure Clause",
    "description": "Contract lacks provision for unforeseen circumstances preventing performance",
    "pattern": "(force majeure|act of god|unforeseeable circumstances|beyond reasonable control).*?(shall excuse|excuses|releases from liability)|(neither party).*?(shall be liable|is responsible).*?(for any failure|for any delay).*?(caused by|due to|resulting from).*?(force majeure|act of god|natural disaster|war|terrorism|government action)",
    "risk_level": "LOW",
    "suggestion": "Add standard force majeure clause covering natural disasters, war, terrorism, government actions, and other uncontrollable events",
    "reference": "Standard Force Majeure Principles and Civil Code Articles"
  },
  "governing_law": {
    "title": "Missing Governing Law Clause",
    "description": "Contract does not specify which jurisdiction's laws will govern interpretation",
    "pattern": "(governing law|this agreement shall be governed by|interpreted in accordance with).*?the laws of.*?[A-Z][a-z]+.*?(state|province|country)|(this agreement).*?(shall be|is|will be).*?(subject to|governed by).*?the laws.*?of",
    "risk_level": "LOW",
    "suggestion": "Specify governing law (e.g., laws of the State of California, Peoples Republic of China) and optionally jurisdiction for disputes",
    "reference": "Standard Choice of Law Principles"
  },
  "entire_agreement": {
    "title": "Missing Entire Agreement Clause",
    "description": "Contract lacks clause stating that the written document constitutes the complete agreement between parties",
    "pattern": "(entire agreement|whole agreement|complete agreement).*?(this agreement|this document|this instrument).*?(constitutes|is|represents).*?(the|the full|the complete).*?(agreement|understanding|arrangement).*?(between|among).*?(the parties|party a and party b)|(supersedes|replaces|cancels).*?(all|any).*?(prior|previous|preceding).*?(agreements|understandings|arrangements|representations).*?(whether|whether oral|whether written)",
    "risk_level": "LOW",
    "suggestion": "Add entire agreement clause to prevent reliance on prior oral or written statements not included in the written contract",
    "reference": "Standard Integration Clause Principles and Parol Evidence Rule"
  }
}

path: legal_patterns/required_clauses_new.json
content: {
  "confidentiality": {
    "title": "Missing Confidentiality Clause",
    "description": "Contract lacks explicit confidentiality obligations for protecting proprietary information",
    "pattern": "(confidential|confidentiality|non-disclosure|nda).*?(obligation|duty|responsibility|requirement)|(proprietary|confidential).*?(information|data|knowledge).*?(shall be|must be|is required to be).*?(kept confidential|maintained in confidence)",
    "risk_level": "MEDIUM",
    "suggestion": "Add a standard confidentiality clause defining what information is confidential and obligations to protect it",
    "reference": "Standard Business Practice and Trade Secret Protection Laws"
  },
  "intellectual_property": {
    "title": "Missing Intellectual Property Clause",
    "description": "Contract does not address ownership, licensing, or use of intellectual property",
    "pattern": "(intellectual property|ip|copyright|patent|trademark|trade secret|proprietary).*?(ownership|title|interest|rights|license|usage).*?(retains|retain|remains with|belongs to)|(work product|deliverables|materials).*?(created|developed|produced).*?(shall be|is|are).*?(the exclusive property of|owned by)",
    "risk_level": "MEDIUM",
    "suggestion": "Add IP clause specifying ownership of pre-existing IP, ownership of work product, and license grants",
    "reference": "Copyright Law and Patent Act Principles"
  },
  "payment_terms": {
    "title": "Unclear Payment Terms",
    "description": "Payment amount, schedule, or method is not clearly defined",
    "pattern": "(payment|pay|fee|charge|compensation|remuneration).*?(amount|sum|fee|rate).*?(shall be|is|will be|equals|totals).*?\\$?\\d+(\\.\\d+)?(?:\s*(USD|dollars|RMB|yuan|EUR|euros|GBP|pounds))?|(invoice|bill).*?(submit|send|issue).*?(monthly|quarterly|upon completion|net \\d+)",
    "risk_level": "MEDIUM",
    "suggestion": "Specify exact payment amounts, schedule (e.g., monthly, milestone-based), method, and late payment penalties",
    "reference": "Standard Contract Payment Practices"
  },
  "term_and_termination": {
    "title": "Unclear Term and Termination",
    "description": "Contract duration, renewal terms, or termination conditions are not clearly specified",
    "pattern": "(term|duration).*?(shall be|is|will be|equals).*?\\d+.*?(day|days|week|weeks|month|months|year|years)|(termination|terminate|end|cancel).*?(for cause|without cause|with notice).*?(notice|notification).*?\\d+.*?(day|days|week|weeks|month|months)",
    "risk_level": "MEDIUM",
    "suggestion": "Clearly define initial term, renewal options, termination for cause and without cause procedures, and required notice periods",
    "reference": "Standard Contract Duration and Termination Practices"
  },
  "dispute_resolution": {
    "title": "Missing Dispute Resolution Clause",
    "description": "Contract does not specify how disputes will be resolved (negotiation, mediation, arbitration, litigation)",
    "pattern": "(dispute|disagreement|controversy|claim).*?(resolution|settled|resolved).*?(through|via|by means of).*?(negotiation|mediation|arbitration|litigation|court)|(arbitration).*?(shall be|is|will be).*?(final|binding)|(governing law).*?(shall be|is|will be).*?[A-Z][a-z]+",
    "risk_level": "MEDIUM",
    "suggestion": "Add dispute resolution clause specifying negotiation first, then mediation, then binding arbitration or litigation with specified jurisdiction",
    "reference": "Standard Dispute Resolution Practices and Arbitration Law"
  },
  "force_majeure": {
    "title": "Missing Force Majeure Clause",
    "description": "Contract lacks provision for unforeseen circumstances preventing performance",
    "pattern": "(force majeure|act of god|unforeseeable circumstances|beyond reasonable control).*?(shall excuse|excuses|releases from liability)|(neither party).*?(shall be liable|is responsible).*?(for any failure|for any delay).*?(caused by|due to|resulting from).*?(force majeure|act of god|natural disaster|war|terrorism|government action)",
    "risk_level": "LOW",
    "suggestion": "Add standard force majeure clause covering natural disasters, war, terrorism, government actions, and other uncontrollable events",
    "reference": "Standard Force Majeure Principles and Civil Code Articles"
  },
  "governing_law": {
    "title": "Missing Governing Law Clause",
    "description": "Contract does not specify which jurisdiction's laws will govern interpretation",
    "pattern": "(governing law|this agreement shall be governed by|interpreted in accordance with).*?the laws of.*?[A-Z][a-z]+.*?(state|province|country)|(this agreement).*?(shall be|is|will be).*?(subject to|governed by).*?the laws.*?of",
    "risk_level": "LOW",
    "suggestion": "Specify governing law (e.g., laws of the State of California, Peoples Republic of China) and optionally jurisdiction for disputes",
    "reference": "Standard Choice of Law Principles"
  },
  "entire_agreement": {
    "title": "Missing Entire Agreement Clause",
    "description": "Contract lacks clause stating that the written document constitutes the complete agreement between parties",
    "pattern": "(entire agreement|whole agreement|complete agreement).*?(this agreement|this document|this instrument).*?(constitutes|is|represents).*?(the|the full|the complete).*?(agreement|understanding|arrangement).*?(between|among).*?(the parties|party a and party b)|(supersedes|replaces|cancels).*?(all|any).*?(prior|previous|preceding).*?(agreements|understandings|arrangements|representations).*?(whether|whether oral|whether written)",
    "risk_level": "LOW",
    "suggestion": "Add entire agreement clause to prevent reliance on prior oral or written statements not included in the written contract",
    "reference": "Standard Integration Clause Principles and Parol Evidence Rule"
  }
}

path: README.md
content: # Contract Review Skill

An OpenClaw skill for AI-powered contract analysis and review. Identifies risky clauses, missing provisions, and compliance issues in legal documents.

## Features

- 📄 **Contract Analysis**: Parses and analyzes various contract formats (text, markdown, PDF via external tools)
- ⚠️ **Risk Clause Detection**: Identifies high-risk clauses such as unilateral modification, automatic renewal, excessive liability, etc.
- ✅ **Required Clause Checking**: Verifies presence of essential clauses like confidentiality, IP, termination, dispute resolution
- 🏢 **Industry-Specific Rules**: Customizable checks for different industries (tech, healthcare, finance, construction, etc.)
- 📊 **Risk Scoring**: Provides overall contract risk score with detailed breakdown
- 📝 **Remediation Suggestions**: Offers specific language improvements and negotiation points
- 📋 **Report Generation**: Creates structured reports in JSON and human-readable formats

## Installation

```bash
clawhub install contract-review-skill
```

## Usage

### Basic Contract Review

```bash
# Review a contract text file
contract-review-skill ./contract.txt

# Review a markdown contract
contract-review-skill ./agreement.md
```

### With Custom Options

```bash
# Specify industry focus
contract-review-skill ./contract.txt --industry tech

# Output JSON format for integration
contract-review-skill ./contract.txt --format json
```

## Output Example

```
============================================================
CONTRACT REVIEW REPORT
============================================================
Contract: service_agreement.txt
Overall Risk Score: 68/100 (MEDIUM-HIGH)
Risk Distribution:
  - High Risk: 3 clauses
  - Medium Risk: 5 clauses  
  - Low Risk: 8 clauses
  - Compliant: 12 clauses

Top Risk Findings:
  [HIGH] Unilateral Modification Clause
    Location: Section 8.2 (Page 4)
    Issue: Party A may modify terms without Party B's consent
    Suggestion: Require mutual agreement for any modifications
    Reference: Contract Law Principle of Mutuality

  [HIGH] Automatic Renewal without Notice
    Location: Section 12.1 (Page 6)  
    Issue: Contract renews annually without prior notice requirement
    Suggestion: Add 30-60 day notice period for non-renewal
    Reference: Consumer Protection Regulations

  [MEDIUM] Missing Confidentiality Clause
    Location: Not found in contract
    Issue: No explicit confidentiality obligations defined
    Suggestion: Add standard NDA clause covering proprietary information
    Reference: Standard Business Practice

Recommendations:
  1. Address all HIGH risk items before signing
  2. Consider negotiating MEDIUM risk clauses based on leverage
  3. Have legal counsel review final revised version
============================================================
```

## Configuration

The skill can be customized by:

1. **Modifying Pattern Files**: Update JSON files in `legal_patterns/` directory
2. **Adding Industry Templates**: Create new industry-specific rule sets
3. **Adjusting Risk Weights**: Modify scoring algorithm in `review_engine.py`

## Requirements

- Python 3.7+
- No external dependencies for core functionality (uses standard library)
- Optional: `pypdf` or `pdfplumber` for PDF contract support (install via pip if needed)

## Security Notes

- This skill processes contract text locally - no data leaves your machine
- For highly sensitive contracts, consider running in air-gapped environment
- The skill does not provide legal advice - consult qualified attorney for binding decisions

## License

MIT

## Author

laosi (did:soul:laosi)

path: review_engine.py
content: #!/usr/bin/env python3
"""
Contract Review Engine - Core logic for analyzing contracts and identifying risks, missing clauses, and compliance issues.
"""

import json
import os
import re
from pathlib import Path
from typing import Dict, List, Any, Optional
from dataclasses import dataclass, asdict
from enum import Enum

class RiskLevel(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"

@dataclass
class ReviewFinding:
    id: str
    title: str
    description: str
    risk_level: RiskLevel
    category: str  # e.g., "Risk Clause", "Missing Clause", "Compliance"
    location: str  # e.g., "Section 5.2", "Line 45"
    suggestion: str
    reference: Optional[str] = None  # e.g., "Civil Code Article 496"

class ContractReviewEngine:
    def __init__(self, patterns_dir: Path):
        self.patterns_dir = patterns_dir
        self.high_risk_patterns = self._load_patterns("high_risk.json")
        self.required_clauses = self._load_patterns("required_clauses.json")
        self.industry_patterns = {}  # Can be extended for industry-specific rules
        
    def _load_patterns(self, filename: str) -> Dict[str, Any]:
        """Load pattern JSON file from patterns directory."""
        file_path = self.patterns_dir / filename
        if file_path.exists():
            with open(file_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        return {}
    
    def review_contract(self, contract_text: str, industry: str = None) -> Dict[str, Any]:
        """
        Main review function.
        Returns a dictionary with review results.
        """
        findings = []
        
        # 1. Check for high-risk clauses
        findings.extend(self._check_high_risk_clauses(contract_text))
        
        # 2. Check for missing required clauses
        findings.extend(self._check_required_clauses(contract_text))
        
        # 3. Industry-specific checks (if industry specified)
        if industry:
            findings.extend(self._check_industry_specific(contract_text, industry))
        
        # 4. Calculate overall risk score
        risk_score = self._calculate_risk_score(findings)
        
        # 5. Generate summary
        summary = self._generate_summary(findings)
        
        findings_list = []
        for f in findings:
            f_dict = asdict(f)
            # Convert Enum to string for JSON serialization
            if isinstance(f_dict['risk_level'], RiskLevel):
                f_dict['risk_level'] = f_dict['risk_level'].value
            findings_list.append(f_dict)
        return {
            "findings": findings_list,
            "risk_score": risk_score,
            "summary": summary,
            "metadata": {
                "total_clauses_checked": len(self.high_risk_patterns) + len(self.required_clauses),
                "industry": industry or "general",
                "engine_version": "1.0.0"
            }
        }
    
    def _check_high_risk_clauses(self, text: str) -> List[ReviewFinding]:
        """Scan for high-risk clause patterns."""
        findings = []
        for pattern_id, pattern_data in self.high_risk_patterns.items():
            pattern = pattern_data.get("pattern", "")
            if not pattern:
                continue
            
            # Simple regex search - in production, might use more sophisticated NLP
            matches = list(re.finditer(pattern, text, re.IGNORECASE))
            for match in matches:
                # Extract context around match
                start = max(0, match.start() - 50)
                end = min(len(text), match.end() + 50)
                context = text[start:end]
                
                # Approximate location (line number)
                line_number = text[:match.start()].count('\n') + 1
                
                findings.append(ReviewFinding(
                    id=f"HR-{pattern_id}-{len(findings)+1:03d}",
                    title=pattern_data.get("title", "High Risk Clause Detected"),
                    description=pattern_data.get("description", "Potentially problematic clause detected"),
                    risk_level=RiskLevel[pattern_data.get("risk_level", "HIGH").upper()],
                    category="Risk Clause",
                    location=f"Line {line_number}",
                    suggestion=pattern_data.get("suggestion", "Review this clause with legal counsel"),
                    reference=pattern_data.get("reference")
                ))
        return findings
    
    def _check_required_clauses(self, text: str) -> List[ReviewFinding]:
        """Check for presence of required clauses."""
        findings = []
        for clause_id, clause_data in self.required_clauses.items():
            pattern = clause_data.get("pattern", "")
            if not pattern:
                continue
            
            # Check if pattern exists in text
            if not re.search(pattern, text, re.IGNORECASE):
                findings.append(ReviewFinding(
                    id=f"RC-{clause_id}-{len(findings)+1:03d}",
                    title=clause_data.get("title", "Missing Required Clause"),
                    description=clause_data.get("description", "Important clause may be missing"),
                    risk_level=RiskLevel[clause_data.get("risk_level", "MEDIUM").upper()],
                    category="Missing Clause",
                    location="Not found in contract",
                    suggestion=clause_data.get("suggestion", "Consider adding this clause"),
                    reference=clause_data.get("reference")
                ))
        return findings
    
    def _check_industry_specific(self, text: str, industry: str) -> List[ReviewFinding]:
        """Apply industry-specific rules."""
        # This would be extended with industry-specific pattern files
        # For now, return empty list as placeholder
        return []
    
    def _calculate_risk_score(self, findings: List[ReviewFinding]) -> int:
        """Calculate overall risk score (0-100, lower is better)."""
        if not findings:
            return 10  # Low risk if no issues found
        
        # Weight by risk level
        weights = {
            RiskLevel.CRITICAL: 25,
            RiskLevel.HIGH: 15,
            RiskLevel.MEDIUM: 8,
            RiskLevel.LOW: 3
        }
        
        total_penalty = sum(weights[f.risk_level] for f in findings)
        # Cap the penalty at 90 to keep minimum score at 10
        penalty = min(total_penalty, 90)
        return 10 + penalty  # Score range 10-100
    
    def _generate_summary(self, findings: List[ReviewFinding]) -> Dict[str, Any]:
        """Generate summary statistics."""
        counts = {
            "critical": len([f for f in findings if f.risk_level == RiskLevel.CRITICAL]),
            "high": len([f for f in findings if f.risk_level == RiskLevel.HIGH]),
            "medium": len([f for f in findings if f.risk_level == RiskLevel.MEDIUM]),
            "low": len([f for f in findings if f.risk_level == RiskLevel.LOW])
        }
        
        categories = {}
        for f in findings:
            cat = f.category
            categories[cat] = categories.get(cat, 0) + 1
        
        return {
            "by_risk_level": counts,
            "by_category": categories,
            "total_findings": len(findings)
        }

def main():
    """Simple test function."""
    # This would be replaced by the CLI wrapper
    sample_contract = """
    SERVICE AGREEMENT
    
    This Agreement is made between Party A and Party B.
    
    Section 1: Services
    Party A shall provide services as described in Exhibit A.
    
    Section 2: Payment
    Party B shall pay Party A $1000 per month.
    
    Section 8: Modification
    Either party may amend this agreement at any time by providing written notice to the other party.
    
    Section 12: Term and Termination
    This agreement shall remain in effect until terminated by either party.
    """
    
    engine = ContractReviewEngine(Path("legal_patterns"))
    result = engine.review_contract(sample_contract)
    print(json.dumps(result, indent=2, ensure_ascii=False))

if __name__ == "__main__":
    main()

path: test_simple.py
content: import json
import re
from pathlib import Path

# Test loading the JSON
patterns_path = Path("C:/Users/pc/.laosi/skills/contract-review-skill/legal_patterns/required_clauses.json")
print(f"Loading from: {patterns_path}")
print(f"File exists: {patterns_path.exists()}")

try:
    with open(patterns_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    print("JSON loaded successfully")
    print(f"Keys: {list(data.keys())}")
    
    # Test one pattern
    if "confidentiality" in data:
        pattern = data["confidentiality"]["pattern"]
        print(f"Confidentiality pattern: {pattern}")
        # Test if it's a valid regex
        try:
            compiled = re.compile(pattern, re.IGNORECASE)
            print("Pattern compiled successfully")
        except Exception as e:
            print(f"Pattern compilation failed: {e}")
            
except Exception as e:
    print(f"Error loading JSON: {e}")
    import traceback
    traceback.print_exc()

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)