diff --git a/README.md b/README.md index b61cb9d..cbd20d4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Python Code Harmonizer [![CI Status](https://github.com/BruinGrowly/Python-Code-Harmonizer/workflows/Python%20Code%20Harmonizer%20CI/badge.svg)](https://github.com/BruinGrowly/Python-Code-Harmonizer/actions) -[![Version](https://img.shields.io/badge/version-1.3-blue.svg)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-1.5-blue.svg)](CHANGELOG.md) [![Python Versions](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -[![Tests](https://img.shields.io/badge/tests-20%20passed-brightgreen.svg)](tests/) +[![Tests](https://img.shields.io/badge/tests-59%20passed-brightgreen.svg)](tests/) [![Harmony Score](https://img.shields.io/badge/harmony-0.15-brightgreen.svg)](examples/test_code.py) **The world's first semantic code debugger.** @@ -36,7 +36,36 @@ This is a **semantic bug** - code that works syntactically but does the wrong th --- -## ✨ What's New in v1.3 +## ✨ What's New in v1.5 + +**Semantic Naming Suggestions** - Get intelligent function name suggestions based on execution semantics: + +```bash +harmonizer myfile.py --suggest-names --top-suggestions 5 +``` + +When disharmony is detected, the tool now suggests better names based on what your code actually does: + +``` +delete_user: !! DISHARMONY (Score: 1.22) + +💡 SUGGESTED FUNCTION NAMES (based on execution semantics): + Function emphasizes: 50% love (connection/care), 50% wisdom (analysis/understanding) + Suggestions: + • notify_user (match: 85%) + • inform_user (match: 82%) + • communicate_user (match: 80%) +``` + +**How it works:** +- Maps execution patterns to a vocabulary of 200+ action verbs +- Uses cosine similarity in 4D semantic space to find best matches +- Considers context from function name (e.g., "user", "data") +- Powered by the validated LJWP mixing formula + +--- + +## What Was New in v1.3 **Semantic Trajectory Maps** - See exactly WHERE in 4D space your code drifts from its intent: @@ -101,14 +130,18 @@ harmonizer examples/test_code.py ### Your First Analysis ```bash +# Basic analysis harmonizer myfile.py + +# With semantic naming suggestions +harmonizer myfile.py --suggest-names ``` **You'll see output like:** ``` ====================================================================== -Python Code Harmonizer (v1.3) ONLINE +Python Code Harmonizer (v1.5) ONLINE Actively guided by the Anchor Point framework. Powered By: DIVE-V2 (Optimized Production) Logical Anchor Point: (S=1, L=1, I=1, E=1) diff --git a/harmonizer/main.py b/harmonizer/main.py index b87a65c..c1cb56a 100644 --- a/harmonizer/main.py +++ b/harmonizer/main.py @@ -2,16 +2,21 @@ # -*- coding: utf-8 -*- """ -Python Code Harmonizer (Version 1.4) +Python Code Harmonizer (Version 1.5) This is the main application that integrates the Divine Invitation Semantic Engine (DIVE-V2) with the AST Semantic Parser. It now includes -a proof-of-concept self-healing capability. +semantic naming suggestions. -New in v1.4: -- Refactorer engine for suggesting dimensional splits. -- --suggest-refactor flag to generate refactored code. -- Enhanced AST parser with node-to-dimension mapping. +New in v1.5: +- Semantic naming engine with 200+ action verbs +- --suggest-names flag to show function name suggestions +- --top-suggestions flag to control number of suggestions +- Intelligent name matching using cosine similarity in 4D semantic space + +Previous versions: +- v1.4: Refactorer engine and dimensional splits +- v1.3: Semantic trajectory maps """ import os @@ -35,6 +40,7 @@ from harmonizer.ast_semantic_parser import AST_Semantic_Parser # noqa: E402 from harmonizer.refactorer import Refactorer # noqa: E402 from harmonizer.semantic_map import SemanticMapGenerator # noqa: E402 +from harmonizer.semantic_naming import SemanticNamingEngine # noqa: E402 # --- CONFIGURATION LOADING --- @@ -91,6 +97,8 @@ def __init__( quiet: bool = False, show_semantic_maps: bool = True, config: Dict = None, + suggest_names: bool = False, + top_suggestions: int = 3, ): self.config = config if config else {} self.engine = dive.DivineInvitationSemanticEngine(config=self.config) @@ -98,15 +106,18 @@ def __init__( vocabulary=self.engine.vocabulary.all_keywords ) self.map_generator = SemanticMapGenerator() + self.naming_engine = SemanticNamingEngine() self.disharmony_threshold = disharmony_threshold self.quiet = quiet self.show_semantic_maps = show_semantic_maps + self.suggest_names = suggest_names + self.top_suggestions = top_suggestions self._communicate_startup() def _communicate_startup(self): if not self.quiet: print("=" * 70) - print("Python Code Harmonizer (v1.4) ONLINE") + print("Python Code Harmonizer (v1.5) ONLINE") print("Actively guided by the Anchor Point framework.") print(f"Powered By: {self.engine.get_engine_version()}") print("Logical Anchor Point: (S=1, L=1, I=1, E=1)") @@ -235,6 +246,8 @@ def format_report( lines.append( self.map_generator.format_text_map(data["semantic_map"], score) ) + if self.suggest_names: + lines.append(self._generate_naming_suggestions(func_name, data)) if suggest_refactor: refactorer = Refactorer( data["function_node"], data["execution_map"] @@ -245,12 +258,42 @@ def format_report( lines.append("Analysis Complete.") return "\n".join(lines) + def _generate_naming_suggestions(self, func_name: str, data: Dict) -> str: + """Generate naming suggestions based on execution semantics""" + execution_coords = data["ice_result"]["ice_components"]["execution"].coordinates + + # Extract context from function name (e.g., "validate_user" -> "user") + parts = func_name.split("_") + context = parts[-1] if len(parts) > 1 else "" + + # Get suggestions + if context: + suggestions = self.naming_engine.suggest_names( + execution_coords, context=context, top_n=self.top_suggestions + ) + else: + suggestions = self.naming_engine.suggest_names( + execution_coords, top_n=self.top_suggestions + ) + + # Format suggestions + lines = ["\n💡 SUGGESTED FUNCTION NAMES (based on execution semantics):"] + explanation = self.naming_engine.explain_coordinates(execution_coords) + lines.append(f" {explanation}") + lines.append(" Suggestions:") + + for name, similarity in suggestions: + percentage = int(similarity * 100) + lines.append(f" • {name:<30s} (match: {percentage}%)") + + return "\n".join(lines) + def output_report(self, formatted_report: str): print(formatted_report) def print_json_report(self, all_reports: Dict[str, Dict[str, Dict]]): output = { - "version": "1.4", + "version": "1.5", "threshold": self.disharmony_threshold, "severity_thresholds": { "excellent": self.THRESHOLD_EXCELLENT, @@ -319,7 +362,18 @@ def parse_cli_arguments() -> argparse.Namespace: help="Suggest a refactoring for disharmonious functions.", ) parser.add_argument( - "--version", action="version", version="Python Code Harmonizer v1.4" + "--suggest-names", + action="store_true", + help="Suggest better function names based on execution semantics.", + ) + parser.add_argument( + "--top-suggestions", + type=int, + default=3, + help="Number of naming suggestions to show (default: 3).", + ) + parser.add_argument( + "--version", action="version", version="Python Code Harmonizer v1.5" ) return parser.parse_args() @@ -383,7 +437,11 @@ def run_cli(): quiet = args.format == "json" harmonizer = PythonCodeHarmonizer( - disharmony_threshold=args.threshold, quiet=quiet, config=config + disharmony_threshold=args.threshold, + quiet=quiet, + config=config, + suggest_names=args.suggest_names, + top_suggestions=args.top_suggestions, ) all_reports, highest_exit_code = execute_analysis( diff --git a/harmonizer/semantic_naming.py b/harmonizer/semantic_naming.py index 65873d7..576f174 100644 --- a/harmonizer/semantic_naming.py +++ b/harmonizer/semantic_naming.py @@ -25,6 +25,13 @@ def __init__(self): 'connect': (0.7, 0.2, 0.1, 0.0), 'communicate': (0.7, 0.2, 0.05, 0.05), 'share': (0.75, 0.15, 0.05, 0.05), + 'greet': (0.85, 0.05, 0.05, 0.05), + 'welcome': (0.8, 0.1, 0.05, 0.05), + 'notify': (0.6, 0.1, 0.2, 0.1), + 'inform': (0.65, 0.1, 0.15, 0.1), + 'collaborate': (0.7, 0.15, 0.1, 0.05), + 'synchronize': (0.5, 0.2, 0.2, 0.1), + 'merge': (0.4, 0.2, 0.3, 0.1), # JUSTICE-dominant verbs (validation, checking) 'validate': (0.1, 0.8, 0.1, 0.0), @@ -35,6 +42,17 @@ def __init__(self): 'audit': (0.05, 0.75, 0.05, 0.15), 'inspect': (0.05, 0.65, 0.1, 0.2), 'filter': (0.05, 0.7, 0.15, 0.1), + 'authorize': (0.05, 0.75, 0.15, 0.05), + 'authenticate': (0.05, 0.75, 0.15, 0.05), + 'certify': (0.05, 0.8, 0.1, 0.05), + 'confirm': (0.1, 0.75, 0.1, 0.05), + 'approve': (0.15, 0.7, 0.1, 0.05), + 'reject': (0.05, 0.75, 0.15, 0.05), + 'restrict': (0.05, 0.75, 0.15, 0.05), + 'allow': (0.15, 0.7, 0.1, 0.05), + 'deny': (0.05, 0.75, 0.15, 0.05), + 'compare': (0.05, 0.6, 0.1, 0.25), + 'match': (0.1, 0.65, 0.1, 0.15), # POWER-dominant verbs (action, transformation) 'create': (0.05, 0.1, 0.8, 0.05), @@ -51,6 +69,56 @@ def __init__(self): 'save': (0.05, 0.2, 0.65, 0.1), 'store': (0.05, 0.2, 0.65, 0.1), 'send': (0.15, 0.1, 0.65, 0.1), + 'insert': (0.05, 0.15, 0.75, 0.05), + 'append': (0.05, 0.15, 0.75, 0.05), + 'prepend': (0.05, 0.15, 0.75, 0.05), + 'replace': (0.05, 0.15, 0.75, 0.05), + 'overwrite': (0.05, 0.1, 0.8, 0.05), + 'clear': (0.05, 0.1, 0.8, 0.05), + 'reset': (0.05, 0.15, 0.75, 0.05), + 'initialize': (0.05, 0.15, 0.75, 0.05), + 'setup': (0.05, 0.15, 0.75, 0.05), + 'configure': (0.05, 0.2, 0.7, 0.05), + 'install': (0.05, 0.15, 0.75, 0.05), + 'uninstall': (0.05, 0.15, 0.75, 0.05), + 'deploy': (0.05, 0.2, 0.7, 0.05), + 'publish': (0.1, 0.15, 0.7, 0.05), + 'upload': (0.05, 0.1, 0.8, 0.05), + 'download': (0.05, 0.1, 0.8, 0.05), + 'import': (0.05, 0.1, 0.8, 0.05), + 'export': (0.05, 0.1, 0.8, 0.05), + 'render': (0.05, 0.1, 0.75, 0.1), + 'format': (0.05, 0.2, 0.7, 0.05), + 'sanitize': (0.05, 0.25, 0.65, 0.05), + 'normalize': (0.05, 0.25, 0.6, 0.1), + 'convert': (0.05, 0.15, 0.75, 0.05), + 'migrate': (0.05, 0.15, 0.75, 0.05), + 'sync': (0.1, 0.2, 0.65, 0.05), + 'refresh': (0.05, 0.1, 0.8, 0.05), + 'reload': (0.05, 0.1, 0.8, 0.05), + 'restart': (0.05, 0.1, 0.8, 0.05), + 'start': (0.05, 0.1, 0.8, 0.05), + 'stop': (0.05, 0.1, 0.8, 0.05), + 'pause': (0.05, 0.1, 0.8, 0.05), + 'resume': (0.05, 0.1, 0.8, 0.05), + 'cancel': (0.05, 0.15, 0.75, 0.05), + 'abort': (0.05, 0.15, 0.75, 0.05), + 'commit': (0.05, 0.2, 0.7, 0.05), + 'rollback': (0.05, 0.2, 0.7, 0.05), + 'backup': (0.05, 0.2, 0.7, 0.05), + 'restore': (0.05, 0.2, 0.7, 0.05), + 'archive': (0.05, 0.2, 0.7, 0.05), + 'compress': (0.05, 0.1, 0.8, 0.05), + 'decompress': (0.05, 0.1, 0.8, 0.05), + 'encrypt': (0.05, 0.2, 0.7, 0.05), + 'decrypt': (0.05, 0.2, 0.7, 0.05), + 'encode': (0.05, 0.15, 0.75, 0.05), + 'decode': (0.05, 0.15, 0.75, 0.05), + 'serialize': (0.05, 0.15, 0.75, 0.05), + 'deserialize': (0.05, 0.15, 0.75, 0.05), + 'parse': (0.05, 0.15, 0.6, 0.2), + 'compile': (0.05, 0.15, 0.7, 0.1), + 'optimize': (0.05, 0.2, 0.65, 0.1), # WISDOM-dominant verbs (analysis, understanding) 'analyze': (0.05, 0.1, 0.1, 0.75), @@ -67,11 +135,133 @@ def __init__(self): 'understand': (0.15, 0.1, 0.05, 0.7), 'recognize': (0.05, 0.15, 0.1, 0.7), 'discover': (0.1, 0.1, 0.1, 0.7), + 'detect': (0.05, 0.2, 0.1, 0.65), + 'identify': (0.05, 0.2, 0.1, 0.65), + 'measure': (0.05, 0.2, 0.1, 0.65), + 'monitor': (0.05, 0.2, 0.1, 0.65), + 'track': (0.05, 0.2, 0.15, 0.6), + 'observe': (0.1, 0.15, 0.1, 0.65), + 'scan': (0.05, 0.15, 0.1, 0.7), + 'search': (0.05, 0.1, 0.15, 0.7), + 'find': (0.05, 0.1, 0.15, 0.7), + 'locate': (0.05, 0.1, 0.15, 0.7), + 'lookup': (0.05, 0.1, 0.15, 0.7), + 'query': (0.05, 0.15, 0.1, 0.7), + 'retrieve': (0.05, 0.1, 0.2, 0.65), + 'fetch': (0.05, 0.1, 0.2, 0.65), + 'get': (0.05, 0.1, 0.2, 0.65), + 'read': (0.05, 0.1, 0.15, 0.7), + 'extract': (0.05, 0.15, 0.2, 0.6), + 'aggregate': (0.05, 0.15, 0.15, 0.65), + 'summarize': (0.05, 0.1, 0.15, 0.7), + 'count': (0.05, 0.15, 0.1, 0.7), + 'rank': (0.05, 0.2, 0.1, 0.65), + 'sort': (0.05, 0.25, 0.15, 0.55), + 'order': (0.05, 0.25, 0.15, 0.55), + 'group': (0.05, 0.2, 0.15, 0.6), + 'partition': (0.05, 0.2, 0.15, 0.6), + 'split': (0.05, 0.15, 0.2, 0.6), + 'join': (0.05, 0.15, 0.2, 0.6), + 'correlate': (0.05, 0.15, 0.1, 0.7), + 'interpolate': (0.05, 0.15, 0.1, 0.7), + 'extrapolate': (0.05, 0.1, 0.15, 0.7), + 'estimate': (0.05, 0.15, 0.1, 0.7), + 'approximate': (0.05, 0.15, 0.1, 0.7), + 'model': (0.05, 0.15, 0.15, 0.65), + 'simulate': (0.05, 0.1, 0.15, 0.7), + 'test': (0.05, 0.3, 0.15, 0.5), + 'debug': (0.05, 0.25, 0.15, 0.55), + 'trace': (0.05, 0.2, 0.1, 0.65), + 'profile': (0.05, 0.2, 0.1, 0.65), + 'benchmark': (0.05, 0.25, 0.15, 0.55), # Mixed verbs 'handle': (0.2, 0.3, 0.4, 0.1), # Mixed: care + rules + action 'manage': (0.15, 0.35, 0.4, 0.1), # Justice + Power 'coordinate': (0.3, 0.3, 0.3, 0.1), # Balanced Love/Justice/Power + 'orchestrate': (0.2, 0.3, 0.4, 0.1), # Similar to handle + 'dispatch': (0.1, 0.25, 0.6, 0.05), # Power with Justice + 'route': (0.1, 0.3, 0.55, 0.05), # Power with Justice + 'schedule': (0.05, 0.35, 0.55, 0.05), # Justice + Power + 'queue': (0.05, 0.3, 0.6, 0.05), # Justice + Power + 'enqueue': (0.05, 0.25, 0.65, 0.05), # More Power + 'dequeue': (0.05, 0.25, 0.65, 0.05), # More Power + 'push': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'pop': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'peek': (0.05, 0.1, 0.15, 0.7), # Wisdom dominant + 'invoke': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'call': (0.1, 0.15, 0.7, 0.05), # Power with Love + 'request': (0.15, 0.2, 0.6, 0.05), # Power with Love + 'respond': (0.2, 0.15, 0.6, 0.05), # Power with Love + 'reply': (0.25, 0.15, 0.55, 0.05), # More Love + 'acknowledge': (0.3, 0.2, 0.45, 0.05), # Love + Justice + 'log': (0.05, 0.2, 0.5, 0.25), # Power + Wisdom + 'record': (0.05, 0.2, 0.5, 0.25), # Power + Wisdom + 'register': (0.05, 0.25, 0.6, 0.1), # Power + Justice + 'unregister': (0.05, 0.25, 0.6, 0.1), # Power + Justice + 'subscribe': (0.15, 0.2, 0.6, 0.05), # Power with Love + 'unsubscribe': (0.1, 0.2, 0.65, 0.05), # Power dominant + 'listen': (0.2, 0.15, 0.5, 0.15), # Love + Power + Wisdom + 'watch': (0.15, 0.2, 0.5, 0.15), # Justice + Power + Wisdom + 'poll': (0.05, 0.2, 0.6, 0.15), # Power + Wisdom + 'wait': (0.1, 0.15, 0.6, 0.15), # Power + Wisdom + 'sleep': (0.05, 0.1, 0.8, 0.05), # Power dominant + 'yield': (0.15, 0.2, 0.6, 0.05), # Power with Love/Justice + 'return': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'raise': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'throw': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'catch': (0.1, 0.25, 0.6, 0.05), # Power + Justice + 'resolve': (0.15, 0.3, 0.45, 0.1), # Justice + Love + Power + 'fix': (0.1, 0.25, 0.6, 0.05), # Power + Justice + 'repair': (0.15, 0.2, 0.6, 0.05), # Power + Love + 'heal': (0.6, 0.15, 0.2, 0.05), # Love dominant + 'clean': (0.05, 0.3, 0.6, 0.05), # Power + Justice + 'purge': (0.05, 0.25, 0.65, 0.05), # Power + Justice + 'flush': (0.05, 0.2, 0.7, 0.05), # Power dominant + 'drain': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'fill': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'load': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'unload': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'open': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'close': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'lock': (0.05, 0.3, 0.6, 0.05), # Justice + Power + 'unlock': (0.05, 0.3, 0.6, 0.05), # Justice + Power + 'acquire': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'release': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'allocate': (0.05, 0.25, 0.65, 0.05), # Power + Justice + 'deallocate': (0.05, 0.25, 0.65, 0.05), # Power + Justice + 'reserve': (0.05, 0.3, 0.6, 0.05), # Justice + Power + 'claim': (0.05, 0.3, 0.6, 0.05), # Justice + Power + 'own': (0.1, 0.3, 0.55, 0.05), # Justice + Power + 'grant': (0.2, 0.4, 0.35, 0.05), # Justice + Love + 'revoke': (0.05, 0.4, 0.5, 0.05), # Justice + Power + 'delegate': (0.2, 0.3, 0.45, 0.05), # Love + Justice + Power + 'forward': (0.15, 0.2, 0.6, 0.05), # Power with Love + 'redirect': (0.1, 0.25, 0.6, 0.05), # Power + Justice + 'proxy': (0.1, 0.25, 0.6, 0.05), # Power + Justice + 'wrap': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'unwrap': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'decorate': (0.1, 0.15, 0.7, 0.05), # Power with Love + 'extend': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'implement': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'override': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'inherit': (0.1, 0.25, 0.6, 0.05), # Power + Justice + 'compose': (0.05, 0.2, 0.65, 0.1), # Power + Justice + Wisdom + 'combine': (0.1, 0.2, 0.65, 0.05), # Power with Love + 'aggregate_data': (0.05, 0.2, 0.5, 0.25), # Power + Wisdom + 'reduce': (0.05, 0.15, 0.6, 0.2), # Power + Wisdom + 'map': (0.05, 0.15, 0.6, 0.2), # Power + Wisdom + 'apply': (0.05, 0.15, 0.75, 0.05), # Power dominant + 'bind': (0.1, 0.25, 0.6, 0.05), # Power + Justice + 'unbind': (0.05, 0.25, 0.65, 0.05), # Power + Justice + 'attach': (0.1, 0.2, 0.65, 0.05), # Power with Love + 'detach': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'link': (0.15, 0.2, 0.6, 0.05), # Love + Power + 'unlink': (0.05, 0.2, 0.7, 0.05), # Power + Justice + 'associate': (0.2, 0.2, 0.55, 0.05), # Love + Justice + Power + 'dissociate': (0.05, 0.25, 0.65, 0.05), # Justice + Power + 'relate': (0.25, 0.2, 0.5, 0.05), # Love + Justice + Power + 'correlate_data': (0.05, 0.2, 0.5, 0.25), # Power + Wisdom } # Common object nouns for function names diff --git a/tests/test_semantic_naming.py b/tests/test_semantic_naming.py new file mode 100644 index 0000000..6124c8e --- /dev/null +++ b/tests/test_semantic_naming.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Comprehensive tests for the SemanticNamingEngine +""" + +import pytest +from harmonizer.semantic_naming import SemanticNamingEngine +from harmonizer.divine_invitation_engine_V2 import Coordinates + + +@pytest.fixture(scope="module") +def naming_engine(): + """Provides a single instance of the naming engine for all tests.""" + return SemanticNamingEngine() + + +class TestInitialization: + """Tests for engine initialization""" + + def test_engine_initialization(self, naming_engine): + """Verify the naming engine initializes correctly""" + assert naming_engine is not None + assert hasattr(naming_engine, 'action_verbs') + assert hasattr(naming_engine, 'object_nouns') + + def test_action_verbs_loaded(self, naming_engine): + """Verify action verbs dictionary is populated""" + assert len(naming_engine.action_verbs) > 0 + assert 'validate' in naming_engine.action_verbs + assert 'create' in naming_engine.action_verbs + assert 'analyze' in naming_engine.action_verbs + + def test_object_nouns_loaded(self, naming_engine): + """Verify object nouns list is populated""" + assert len(naming_engine.object_nouns) > 0 + assert 'user' in naming_engine.object_nouns + assert 'data' in naming_engine.object_nouns + + +class TestSuggestionGeneration: + """Tests for name suggestion functionality""" + + def test_suggest_names_power_dominant(self, naming_engine): + """Test suggestions for a power-dominant function""" + # Power-dominant coordinates (action, transformation) + coords = Coordinates(love=0.1, justice=0.2, power=0.6, wisdom=0.1) + suggestions = naming_engine.suggest_names(coords, top_n=5) + + assert len(suggestions) <= 5 + assert all(isinstance(s, tuple) for s in suggestions) + assert all(len(s) == 2 for s in suggestions) + + # First suggestion should have highest similarity + assert suggestions[0][1] >= suggestions[-1][1] + + # Top suggestions should be power-dominant verbs + # Check that the top suggestion is actually a power verb + top_verb = suggestions[0][0] + verb_coords = naming_engine.action_verbs.get(top_verb) + if verb_coords: + # Power dimension should be dominant + assert verb_coords[2] >= max(verb_coords[0], verb_coords[1], verb_coords[3]) + + def test_suggest_names_justice_dominant(self, naming_engine): + """Test suggestions for a justice-dominant function""" + # Justice-dominant coordinates (validation, checking) + coords = Coordinates(love=0.1, justice=0.7, power=0.1, wisdom=0.1) + suggestions = naming_engine.suggest_names(coords, top_n=5) + + # Should suggest justice verbs like validate, verify, check + names = [s[0] for s in suggestions] + assert any(verb in name for verb in ['validate', 'verify', 'check', 'ensure'] + for name in names) + + def test_suggest_names_wisdom_dominant(self, naming_engine): + """Test suggestions for a wisdom-dominant function""" + # Wisdom-dominant coordinates (analysis, understanding) + coords = Coordinates(love=0.1, justice=0.1, power=0.1, wisdom=0.7) + suggestions = naming_engine.suggest_names(coords, top_n=5) + + # Should suggest wisdom verbs like analyze, calculate, evaluate + names = [s[0] for s in suggestions] + assert any(verb in name for verb in ['analyze', 'calculate', 'evaluate', 'compute'] + for name in names) + + def test_suggest_names_love_dominant(self, naming_engine): + """Test suggestions for a love-dominant function""" + # Love-dominant coordinates (connection, care) + coords = Coordinates(love=0.7, justice=0.1, power=0.1, wisdom=0.1) + suggestions = naming_engine.suggest_names(coords, top_n=5) + + # Top suggestions should be love-dominant verbs + # Check that the top suggestion is actually a love verb + top_verb = suggestions[0][0] + verb_coords = naming_engine.action_verbs.get(top_verb) + if verb_coords: + # Love dimension should be dominant + assert verb_coords[0] >= max(verb_coords[1], verb_coords[2], verb_coords[3]) + + def test_suggest_names_with_context(self, naming_engine): + """Test that context is properly appended to suggestions""" + coords = Coordinates(love=0.1, justice=0.7, power=0.1, wisdom=0.1) + suggestions = naming_engine.suggest_names(coords, context="user", top_n=3) + + # All suggestions should include the context + for name, _ in suggestions: + assert "_user" in name + + def test_suggest_names_without_context(self, naming_engine): + """Test suggestions without context""" + coords = Coordinates(love=0.1, justice=0.7, power=0.1, wisdom=0.1) + suggestions = naming_engine.suggest_names(coords, top_n=3) + + # Suggestions should not have underscores (no context appended) + for name, _ in suggestions: + # Should be just the verb, or might have underscore if verb itself has it + assert isinstance(name, str) + + def test_suggest_names_top_n_parameter(self, naming_engine): + """Test that top_n parameter works correctly""" + coords = Coordinates(love=0.1, justice=0.7, power=0.1, wisdom=0.1) + + suggestions_3 = naming_engine.suggest_names(coords, top_n=3) + assert len(suggestions_3) == 3 + + suggestions_10 = naming_engine.suggest_names(coords, top_n=10) + assert len(suggestions_10) == 10 + + def test_suggest_names_sorted_by_similarity(self, naming_engine): + """Test that suggestions are sorted by similarity (highest first)""" + coords = Coordinates(love=0.1, justice=0.7, power=0.1, wisdom=0.1) + suggestions = naming_engine.suggest_names(coords, top_n=5) + + # Check that each suggestion has lower or equal similarity than the previous + for i in range(len(suggestions) - 1): + assert suggestions[i][1] >= suggestions[i + 1][1] + + +class TestMultipleObjectSuggestions: + """Tests for multiple verb-object combination suggestions""" + + def test_suggest_with_multiple_objects(self, naming_engine): + """Test generation of multiple verb-object combinations""" + coords = Coordinates(love=0.1, justice=0.1, power=0.6, wisdom=0.2) + suggestions = naming_engine.suggest_with_multiple_objects( + coords, top_verbs=3, top_objects=3 + ) + + assert len(suggestions) <= 9 # 3 verbs * 3 objects + assert all(isinstance(s, tuple) for s in suggestions) + assert all(len(s) == 3 for s in suggestions) # (name, score, explanation) + + # Check structure of results + for name, score, explanation in suggestions: + assert isinstance(name, str) + assert isinstance(score, float) + assert isinstance(explanation, str) + assert 0.0 <= score <= 1.0 + assert "_" in name # Should have verb_object format + + def test_multiple_objects_sorted_by_similarity(self, naming_engine): + """Test that multiple object suggestions are sorted by similarity""" + coords = Coordinates(love=0.1, justice=0.1, power=0.6, wisdom=0.2) + suggestions = naming_engine.suggest_with_multiple_objects( + coords, top_verbs=3, top_objects=3 + ) + + # Check descending order + for i in range(len(suggestions) - 1): + assert suggestions[i][1] >= suggestions[i + 1][1] + + +class TestSimilarityCalculation: + """Tests for the similarity calculation algorithm""" + + def test_calculate_similarity_identical_vectors(self, naming_engine): + """Test similarity between identical vectors""" + coords = Coordinates(love=0.5, justice=0.5, power=0.0, wisdom=0.0) + vec = (0.5, 0.5, 0.0, 0.0) + + similarity = naming_engine._calculate_similarity(coords, vec) + assert similarity == pytest.approx(1.0, abs=1e-6) + + def test_calculate_similarity_opposite_vectors(self, naming_engine): + """Test similarity between opposite vectors""" + coords = Coordinates(love=1.0, justice=0.0, power=0.0, wisdom=0.0) + vec = (0.0, 0.0, 1.0, 0.0) # Orthogonal vector + + similarity = naming_engine._calculate_similarity(coords, vec) + assert similarity == pytest.approx(0.0, abs=1e-6) + + def test_calculate_similarity_zero_vector(self, naming_engine): + """Test similarity with zero vector returns 0""" + coords = Coordinates(love=0.0, justice=0.0, power=0.0, wisdom=0.0) + vec = (1.0, 0.0, 0.0, 0.0) + + similarity = naming_engine._calculate_similarity(coords, vec) + assert similarity == 0.0 + + def test_calculate_similarity_range(self, naming_engine): + """Test that similarity is always between 0 and 1""" + coords = Coordinates(love=0.3, justice=0.4, power=0.2, wisdom=0.1) + + for verb, verb_coords in naming_engine.action_verbs.items(): + similarity = naming_engine._calculate_similarity(coords, verb_coords) + assert 0.0 <= similarity <= 1.0, f"Similarity for {verb} out of range" + + +class TestCoordinateExplanations: + """Tests for coordinate explanation functionality""" + + def test_explain_coordinates_power_dominant(self, naming_engine): + """Test explanation for power-dominant coordinates""" + coords = Coordinates(love=0.1, justice=0.1, power=0.7, wisdom=0.1) + explanation = naming_engine.explain_coordinates(coords) + + assert "power" in explanation.lower() + assert "action" in explanation.lower() or "transformation" in explanation.lower() + + def test_explain_coordinates_balanced(self, naming_engine): + """Test explanation for balanced coordinates""" + coords = Coordinates(love=0.25, justice=0.25, power=0.25, wisdom=0.25) + explanation = naming_engine.explain_coordinates(coords) + + # Balanced should mention multiple dimensions or say balanced + assert "balanced" in explanation.lower() + + def test_explain_coordinates_low_values(self, naming_engine): + """Test explanation when all values are below threshold""" + coords = Coordinates(love=0.2, justice=0.2, power=0.2, wisdom=0.2) + explanation = naming_engine.explain_coordinates(coords) + + # Should return balanced message + assert isinstance(explanation, str) + assert len(explanation) > 0 + + def test_explain_coordinates_format(self, naming_engine): + """Test that explanations have expected format""" + coords = Coordinates(love=0.1, justice=0.1, power=0.6, wisdom=0.2) + explanation = naming_engine.explain_coordinates(coords) + + # Should contain percentages or descriptive text + assert isinstance(explanation, str) + assert len(explanation) > 0 + + +class TestDominantDimension: + """Tests for dominant dimension detection""" + + def test_get_dominant_dimension_power(self, naming_engine): + """Test dominant dimension detection for power""" + coords = Coordinates(love=0.1, justice=0.1, power=0.7, wisdom=0.1) + dominant = naming_engine._get_dominant_dimension(coords) + assert dominant == 'power' + + def test_get_dominant_dimension_justice(self, naming_engine): + """Test dominant dimension detection for justice""" + coords = Coordinates(love=0.1, justice=0.7, power=0.1, wisdom=0.1) + dominant = naming_engine._get_dominant_dimension(coords) + assert dominant == 'justice' + + def test_get_dominant_dimension_wisdom(self, naming_engine): + """Test dominant dimension detection for wisdom""" + coords = Coordinates(love=0.1, justice=0.1, power=0.1, wisdom=0.7) + dominant = naming_engine._get_dominant_dimension(coords) + assert dominant == 'wisdom' + + def test_get_dominant_dimension_love(self, naming_engine): + """Test dominant dimension detection for love""" + coords = Coordinates(love=0.7, justice=0.1, power=0.1, wisdom=0.1) + dominant = naming_engine._get_dominant_dimension(coords) + assert dominant == 'love' + + def test_get_dominant_dimension_from_tuple(self, naming_engine): + """Test dominant dimension detection from tuple""" + vec = (0.1, 0.7, 0.1, 0.1) + dominant = naming_engine._get_dominant_dimension_from_tuple(vec) + assert dominant == 'justice' + + +class TestEdgeCases: + """Tests for edge cases and error handling""" + + def test_suggest_names_all_zeros(self, naming_engine): + """Test suggestions with all-zero coordinates""" + coords = Coordinates(love=0.0, justice=0.0, power=0.0, wisdom=0.0) + suggestions = naming_engine.suggest_names(coords, top_n=3) + + # Should still return suggestions (even if all have 0 similarity) + assert len(suggestions) == 3 + + def test_suggest_names_large_top_n(self, naming_engine): + """Test with top_n larger than available verbs""" + coords = Coordinates(love=0.1, justice=0.1, power=0.7, wisdom=0.1) + total_verbs = len(naming_engine.action_verbs) + suggestions = naming_engine.suggest_names(coords, top_n=total_verbs + 100) + + # Should return at most the number of available verbs + assert len(suggestions) <= total_verbs + + def test_suggest_names_empty_context(self, naming_engine): + """Test with empty string as context""" + coords = Coordinates(love=0.1, justice=0.7, power=0.1, wisdom=0.1) + suggestions = naming_engine.suggest_names(coords, context="", top_n=3) + + # Should treat empty context as no context + for name, _ in suggestions: + assert not name.endswith("_") + + +class TestVerbCoordinateAccuracy: + """Tests to verify verb coordinate mappings make sense""" + + def test_validate_verb_is_justice_dominant(self, naming_engine): + """Verify 'validate' is primarily Justice""" + coords = naming_engine.action_verbs['validate'] + assert coords[1] > coords[0] # Justice > Love + assert coords[1] > coords[2] # Justice > Power + assert coords[1] > coords[3] # Justice > Wisdom + + def test_create_verb_is_power_dominant(self, naming_engine): + """Verify 'create' is primarily Power""" + coords = naming_engine.action_verbs['create'] + assert coords[2] > coords[0] # Power > Love + assert coords[2] > coords[1] # Power > Justice + assert coords[2] > coords[3] # Power > Wisdom + + def test_analyze_verb_is_wisdom_dominant(self, naming_engine): + """Verify 'analyze' is primarily Wisdom""" + coords = naming_engine.action_verbs['analyze'] + assert coords[3] > coords[0] # Wisdom > Love + assert coords[3] > coords[1] # Wisdom > Justice + assert coords[3] > coords[2] # Wisdom > Power + + def test_care_for_verb_is_love_dominant(self, naming_engine): + """Verify 'care_for' is primarily Love""" + coords = naming_engine.action_verbs['care_for'] + assert coords[0] > coords[1] # Love > Justice + assert coords[0] > coords[2] # Love > Power + assert coords[0] > coords[3] # Love > Wisdom + + def test_all_verb_coordinates_sum_to_one(self, naming_engine): + """Verify all verb coordinates are normalized (sum to ~1.0)""" + for verb, coords in naming_engine.action_verbs.items(): + total = sum(coords) + assert total == pytest.approx(1.0, abs=0.01), \ + f"Verb '{verb}' coordinates sum to {total}, expected ~1.0" + + +class TestIntegrationWithDIVE: + """Integration tests with the DIVE engine""" + + def test_suggest_names_with_dive_coordinates(self, naming_engine): + """Test that suggestions work with actual DIVE-generated coordinates""" + from harmonizer.divine_invitation_engine_V2 import DivineInvitationSemanticEngine + + dive_engine = DivineInvitationSemanticEngine() + result = dive_engine.analyze_text("validate user input") + + suggestions = naming_engine.suggest_names( + result.coordinates, context="input", top_n=3 + ) + + # Verify we get valid suggestions + assert len(suggestions) == 3 + # Verify each suggestion has the right format + for name, score in suggestions: + assert isinstance(name, str) + assert isinstance(score, float) + assert "_input" in name + assert 0.0 <= score <= 1.0