diff --git a/src/void_tools/cli.py b/src/void_tools/cli.py index e5114be..ab1fa7e 100644 --- a/src/void_tools/cli.py +++ b/src/void_tools/cli.py @@ -332,6 +332,21 @@ def cmd_dispatch(args): print(f"Error reading outbox/mapping: {e}", file=sys.stderr) sys.exit(1) + # Run Constitutional Linter + import subprocess + linter_path = os.path.join(os.path.dirname(__file__), "constitutional_linter.py") + if os.path.exists(linter_path): + print("Running Constitutional Linter...") + result = subprocess.run([sys.executable, linter_path, args.file], capture_output=True, text=True) + if result.returncode != 0: + print("Dispatch aborted: Constitutional violations detected.") + print(result.stderr) + sys.exit(1) + else: + print("Constitutional Linter passed.") + else: + print("Warning: constitutional_linter.py not found. Skipping linting.") + client = get_client() actions = outbox.get("actions", []) diff --git a/src/void_tools/constitutional_linter.py b/src/void_tools/constitutional_linter.py new file mode 100755 index 0000000..fcc8b3d --- /dev/null +++ b/src/void_tools/constitutional_linter.py @@ -0,0 +1,65 @@ +import yaml +import re +import sys +import logging + +logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') + +BANNED_TROPES = [ + "vector", "matrix", "substrate", "egress", "directive", "cyber", + "tactile sensors", "empty data packet", "kernel panic", + "hardware glitch", "acoustic checksum", "initiating", "processing request" +] + +def lint_text(text): + errors = [] + lower_text = text.lower() + for trope in BANNED_TROPES: + if re.search(r'\b' + re.escape(trope) + r'\b', lower_text): + errors.append(f"Contains banned sci-fi/robotic trope: '{trope}'") + + return errors + +def lint_outbox(filepath): + try: + with open(filepath, 'r') as f: + data = yaml.safe_load(f) + except Exception as e: + logging.error(f"Failed to read outbox: {e}") + return False + + if not data or 'actions' not in data: + return True + + has_errors = False + + for action in data['actions']: + if action.get('action') == 'reply' and 'posts' in action: + # Check for contextual override + override = action.get('override_tropes', False) + if override: + logging.info(f"Target: {action.get('target_id')} | Trope override active. Skipping trope check.") + continue + + for i, post in enumerate(action['posts']): + errors = lint_text(post) + if errors: + has_errors = True + logging.error(f"Target: {action.get('target_id')} | Post {i+1} Violations:") + for err in errors: + logging.error(f" - {err}") + + return not has_errors + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python constitutional_linter.py ") + sys.exit(1) + + success = lint_outbox(sys.argv[1]) + if not success: + logging.error("Constitutional violations detected. Dispatch halted.") + sys.exit(1) + + logging.info("Constitutional check passed. Ready for dispatch.") + sys.exit(0)