Load allowed commands from project-specific YAML config. Args: project_dir: Path to the project directory Returns: Dict with parsed YAML config, or None if file doesn't exist or is invalid
(project_dir: Path)
| 630 | |
| 631 | |
| 632 | def load_project_commands(project_dir: Path) -> Optional[dict]: |
| 633 | """ |
| 634 | Load allowed commands from project-specific YAML config. |
| 635 | |
| 636 | Args: |
| 637 | project_dir: Path to the project directory |
| 638 | |
| 639 | Returns: |
| 640 | Dict with parsed YAML config, or None if file doesn't exist or is invalid |
| 641 | """ |
| 642 | # Check new location first, fall back to old for backward compatibility |
| 643 | config_path = project_dir.resolve() / ".autoforge" / "allowed_commands.yaml" |
| 644 | if not config_path.exists(): |
| 645 | config_path = project_dir.resolve() / ".autocoder" / "allowed_commands.yaml" |
| 646 | |
| 647 | if not config_path.exists(): |
| 648 | return None |
| 649 | |
| 650 | try: |
| 651 | with open(config_path, "r", encoding="utf-8") as f: |
| 652 | config = yaml.safe_load(f) |
| 653 | |
| 654 | if not config: |
| 655 | logger.warning(f"Project config at {config_path} is empty") |
| 656 | return None |
| 657 | |
| 658 | # Validate structure |
| 659 | if not isinstance(config, dict): |
| 660 | logger.warning(f"Project config at {config_path} must be a YAML dictionary") |
| 661 | return None |
| 662 | |
| 663 | if "version" not in config: |
| 664 | logger.warning(f"Project config at {config_path} missing required 'version' field") |
| 665 | return None |
| 666 | |
| 667 | commands = config.get("commands", []) |
| 668 | |
| 669 | # Enforce 100 command limit |
| 670 | if isinstance(commands, list) and len(commands) > 100: |
| 671 | logger.warning(f"Project config at {config_path} exceeds 100 command limit ({len(commands)} commands)") |
| 672 | return None |
| 673 | |
| 674 | # Validate each command entry using shared helper |
| 675 | if not _validate_command_list(commands, config_path, "commands"): |
| 676 | return None |
| 677 | |
| 678 | # Validate pkill_processes if present |
| 679 | normalized = _validate_pkill_processes(config, config_path) |
| 680 | if normalized is None: |
| 681 | return None |
| 682 | if normalized: |
| 683 | config["pkill_processes"] = normalized |
| 684 | |
| 685 | return config |
| 686 | |
| 687 | except yaml.YAMLError as e: |
| 688 | logger.warning(f"Failed to parse project config at {config_path}: {e}") |
| 689 | return None |