diff --git a/CLAUDE.md b/CLAUDE.md index 02308bc..08386ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -233,7 +233,7 @@ blocked_commands: - Scripts: `./scripts/build.sh` matches the script by name from any directory **Limits:** -- Maximum 50 commands per project config +- Maximum 100 commands per project config - Blocklisted commands (sudo, dd, shutdown, etc.) can NEVER be allowed - Org-level blocked commands cannot be overridden by project configs diff --git a/examples/OPTIMIZE_CONFIG.md b/examples/OPTIMIZE_CONFIG.md new file mode 100644 index 0000000..2fea54a --- /dev/null +++ b/examples/OPTIMIZE_CONFIG.md @@ -0,0 +1,230 @@ +# How to Optimize Your allowed_commands.yaml + +## The Problem + +Your config might have redundant commands like this: + +```yaml +commands: + - name: flutter + - name: flutter* # ← This already covers EVERYTHING below! + - name: flutter test # ← Redundant + - name: flutter test --coverage # ← Redundant + - name: flutter build apk # ← Redundant + - name: flutter build ios # ← Redundant + # ... 20+ more flutter commands +``` + +**Result:** 65 commands when you only need ~10-15 + +## How Wildcards Work + +When you have `flutter*`, it matches: +- ✅ `flutter` (the base command) +- ✅ `flutter test` +- ✅ `flutter test --coverage` +- ✅ `flutter build apk` +- ✅ `flutter build ios` +- ✅ `flutter run` +- ✅ **ANY command starting with "flutter"** + +**You don't need to list every subcommand separately!** + +## Example: GOD-APP Optimization + +### Before (65 commands) +```yaml +commands: + # Flutter + - name: flutter + - name: flutter* + - name: flutter test + - name: flutter test --coverage + - name: flutter test --exclude-tags=golden,integration + - name: flutter test --tags=golden + - name: flutter test --tags=golden --update-goldens + - name: flutter test --verbose + - name: flutter drive + - name: flutter test integration_test/ + - name: flutter build apk + - name: flutter build apk --debug + - name: flutter build apk --release + - name: flutter build appbundle + - name: flutter build ios + - name: flutter build ios --debug + - name: flutter build ipa + - name: flutter build web + - name: flutter pub get + - name: flutter pub upgrade + - name: flutter doctor + - name: flutter clean + # ... and more + + # Dart + - name: dart + - name: dartfmt + - name: dartanalyzer + - name: dart format + - name: dart analyze + - name: dart fix + - name: dart pub +``` + +### After (15 commands) ✨ +```yaml +commands: + # Flutter & Dart (wildcards cover all subcommands) + - name: flutter* + description: All Flutter SDK commands + + - name: dart* + description: All Dart language tools + + # Testing tools + - name: patrol + description: Patrol integration testing (if needed separately) + + # Coverage tools + - name: lcov + description: Code coverage tool + + - name: genhtml + description: Generate HTML coverage reports + + # Android tools + - name: adb* + description: Android Debug Bridge commands + + - name: gradle* + description: Gradle build system + + # iOS tools (macOS only) + - name: xcrun* + description: Xcode developer tools + + - name: xcodebuild + description: Xcode build system + + - name: simctl + description: iOS Simulator control + + - name: ios-deploy + description: Deploy to iOS devices + + # Project scripts + - name: ./scripts/*.sh + description: All project build/test scripts +``` + +**Reduced from 65 → 15 commands (77% reduction!)** + +## Optimization Checklist + +For each group of commands, ask: + +### ❓ "Do I have the base command AND a wildcard?" +```yaml +# Bad (redundant) +- name: flutter +- name: flutter* + +# Good (just the wildcard) +- name: flutter* +``` + +The wildcard already matches the base command! + +### ❓ "Am I listing subcommands individually?" +```yaml +# Bad (verbose) +- name: flutter test +- name: flutter test --coverage +- name: flutter build apk +- name: flutter run + +# Good (one wildcard) +- name: flutter* +``` + +### ❓ "Can I group multiple scripts?" +```yaml +# If you can't use wildcards for scripts, at least group them logically +- name: ./scripts/test.sh +- name: ./scripts/build.sh +- name: ./scripts/integration_test.sh +``` + +These are fine - scripts need explicit paths. But if you have 20+ scripts, consider if they're all necessary. + +## Common Wildcards + +| Instead of... | Use... | Covers | +|---------------|--------|--------| +| flutter, flutter test, flutter build, flutter run | `flutter*` | All flutter commands | +| dart, dart format, dart analyze, dart pub | `dart*` | All dart commands | +| npm, npm install, npm run, npm test | `npm*` | All npm commands | +| cargo, cargo build, cargo test, cargo run | `cargo*` | All cargo commands | +| git, git status, git commit, git push | Just `git` | Git is in global defaults | + +## When NOT to Optimize + +Keep separate entries when: +1. **Different base commands:** `swift` and `swiftc` are different (though `swift*` covers both) +2. **Documentation clarity:** Sometimes listing helps future developers understand what's needed +3. **Argument restrictions (Phase 3):** If you'll add argument validation later + +## Quick Optimization Script + +To see what you can reduce: + +```bash +# Count commands by prefix +grep "^ - name:" .autocoder/allowed_commands.yaml | \ + sed 's/^ - name: //' | \ + cut -d' ' -f1 | \ + sort | uniq -c | sort -rn +``` + +If you see multiple commands with the same prefix, use a wildcard! + +## UI Feedback (Future Enhancement) + +Great suggestion! Here's what a UI could show: + +``` +⚠️ Config Optimization Available + +Your config has 65 commands. We detected opportunities to reduce it: + +• 25 flutter commands → Use flutter* (saves 24 entries) +• 8 dart commands → Use dart* (saves 7 entries) +• 12 adb commands → Use adb* (saves 11 entries) + +[Optimize Automatically] [Keep As-Is] + +Potential reduction: 65 → 23 commands +``` + +Or during config editing: + +``` +📊 Command Usage Stats + +flutter* : 25 subcommands detected +dart* : 8 subcommands detected +./scripts/*.sh : 7 scripts detected + +💡 Tip: Using wildcards covers all subcommands automatically +``` + +**This would be a great addition to Phase 3 or beyond!** + +## Your GOD-APP Optimization + +Here's what you could do: + +**Current:** 65 commands (exceeds 50 limit, config rejected!) + +**Optimized:** ~15 commands using wildcards + +Would you like me to create an optimized version of your GOD-APP config? diff --git a/examples/README.md b/examples/README.md index b995f5e..4f228db 100644 --- a/examples/README.md +++ b/examples/README.md @@ -72,7 +72,7 @@ commands: - ✅ Temporary tools needed during development **Limits:** -- Maximum 50 commands per project +- Maximum 100 commands per project - Cannot override org-level blocked commands - Cannot allow hardcoded blocklist commands (sudo, dd, etc.) @@ -321,13 +321,13 @@ blocked_commands: [] # Rely on hardcoded blocklist only - Bad: Adding `xcodebuild` to org config when only one project uses it - Good: Add `xcodebuild` to that project's config -4. **Don't exceed the 50 command limit per project** - - If you need more, you're probably being too specific - - Use wildcards instead: `npm-*` covers many npm tools +4. **Don't exceed the 100 command limit per project** + - If you need more, you're probably listing subcommands unnecessarily + - Use wildcards instead: `flutter*` covers all flutter commands, not just the base 5. **Don't ignore validation errors** - If your YAML is rejected, fix the structure - - Common issues: missing `version`, malformed lists, over 50 commands + - Common issues: missing `version`, malformed lists, over 100 commands --- diff --git a/examples/project_allowed_commands.yaml b/examples/project_allowed_commands.yaml index 48b8bd6..2a3bdf5 100644 --- a/examples/project_allowed_commands.yaml +++ b/examples/project_allowed_commands.yaml @@ -113,7 +113,7 @@ commands: [] # - Scripts: "./scripts/build.sh" matches the script by name # # Limits: -# - Maximum 50 commands per project +# - Maximum 100 commands per project # - Commands in the blocklist (sudo, dd, shutdown, etc.) can NEVER be allowed # - Org-level blocked commands (see ~/.autocoder/config.yaml) cannot be overridden # diff --git a/security.py b/security.py index a73a2e9..17f9ee2 100644 --- a/security.py +++ b/security.py @@ -491,8 +491,8 @@ def load_project_commands(project_dir: Path) -> Optional[dict]: if not isinstance(commands, list): return None - # Enforce 50 command limit - if len(commands) > 50: + # Enforce 100 command limit + if len(commands) > 100: return None # Validate each command entry diff --git a/test_security.py b/test_security.py index 3af10cf..8f1f757 100644 --- a/test_security.py +++ b/test_security.py @@ -263,8 +263,8 @@ commands: print(f" Got: {config}") failed += 1 - # Test 4: Over limit (50 commands) - commands = [f" - name: cmd{i}\n description: Command {i}" for i in range(51)] + # Test 4: Over limit (100 commands) + commands = [f" - name: cmd{i}\n description: Command {i}" for i in range(101)] config_path.write_text("version: 1\ncommands:\n" + "\n".join(commands)) config = load_project_commands(project_dir) if config is None: diff --git a/test_security_integration.py b/test_security_integration.py index 4a13329..f4ded43 100644 --- a/test_security_integration.py +++ b/test_security_integration.py @@ -326,21 +326,21 @@ def test_invalid_yaml_ignored(): return False -def test_50_command_limit(): - """Test that configs with >50 commands are rejected.""" +def test_100_command_limit(): + """Test that configs with >100 commands are rejected.""" print("\n" + "=" * 70) - print("TEST 9: 50 command limit enforced") + print("TEST 9: 100 command limit enforced") print("=" * 70) with tempfile.TemporaryDirectory() as tmpdir: project_dir = Path(tmpdir) - # Create config with 51 commands + # Create config with 101 commands autocoder_dir = project_dir / ".autocoder" autocoder_dir.mkdir() commands = [ - f" - name: cmd{i}\n description: Command {i}" for i in range(51) + f" - name: cmd{i}\n description: Command {i}" for i in range(101) ] (autocoder_dir / "allowed_commands.yaml").write_text( "version: 1\ncommands:\n" + "\n".join(commands) @@ -353,10 +353,10 @@ def test_50_command_limit(): result = asyncio.run(bash_security_hook(input_data, context=context)) if result.get("decision") == "block": - print("✅ PASS: Config with >50 commands rejected") + print("✅ PASS: Config with >100 commands rejected") return True else: - print("❌ FAIL: Config with >50 commands should be rejected") + print("❌ FAIL: Config with >100 commands should be rejected") return False @@ -376,7 +376,7 @@ def main(): test_org_blocklist_enforcement, test_org_allowlist_inheritance, test_invalid_yaml_ignored, - test_50_command_limit, + test_100_command_limit, ] passed = 0