adversarial-workflow 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ """Library configuration with env > file > defaults precedence."""
2
+
3
+ import os
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import yaml
9
+
10
+
11
+ @dataclass
12
+ class LibraryConfig:
13
+ """Configuration for the evaluator library.
14
+
15
+ Precedence: environment variables > config file > defaults.
16
+ """
17
+
18
+ url: str = "https://raw.githubusercontent.com/movito/adversarial-evaluator-library/main"
19
+ ref: str = "main"
20
+ cache_ttl: int = 3600 # 1 hour
21
+ cache_dir: Path = field(default_factory=lambda: Path.home() / ".cache" / "adversarial-workflow")
22
+ enabled: bool = True
23
+
24
+
25
+ def get_library_config(config_path: Optional[Path] = None) -> LibraryConfig:
26
+ """
27
+ Load library configuration with precedence: env > file > defaults.
28
+
29
+ Args:
30
+ config_path: Optional path to config file. Defaults to .adversarial/config.yml
31
+
32
+ Returns:
33
+ LibraryConfig with merged settings.
34
+ """
35
+ config = LibraryConfig()
36
+
37
+ # Load from config file if exists
38
+ config_file = config_path or Path(".adversarial/config.yml")
39
+ if config_file.exists():
40
+ try:
41
+ with open(config_file, "r", encoding="utf-8") as f:
42
+ data = yaml.safe_load(f) or {}
43
+ # Handle non-dict YAML (list, scalar, etc.) gracefully
44
+ if not isinstance(data, dict):
45
+ data = {}
46
+ lib_config = data.get("library", {})
47
+
48
+ if "url" in lib_config:
49
+ config.url = lib_config["url"]
50
+ if "ref" in lib_config:
51
+ config.ref = lib_config["ref"]
52
+ if "cache_ttl" in lib_config:
53
+ config.cache_ttl = int(lib_config["cache_ttl"])
54
+ if "cache_dir" in lib_config:
55
+ # Expand ~ in path
56
+ config.cache_dir = Path(lib_config["cache_dir"]).expanduser()
57
+ if "enabled" in lib_config:
58
+ config.enabled = bool(lib_config["enabled"])
59
+ except (yaml.YAMLError, OSError, ValueError):
60
+ # Config file is invalid, use defaults
61
+ pass
62
+
63
+ # Apply environment variable overrides (highest precedence)
64
+ if url := os.environ.get("ADVERSARIAL_LIBRARY_URL"):
65
+ config.url = url
66
+
67
+ # Process TTL first, then NO_CACHE (so NO_CACHE always wins)
68
+ if ttl := os.environ.get("ADVERSARIAL_LIBRARY_CACHE_TTL"):
69
+ try:
70
+ config.cache_ttl = int(ttl)
71
+ except ValueError:
72
+ pass # Invalid TTL, keep current value
73
+
74
+ # NO_CACHE takes precedence over CACHE_TTL - check it last
75
+ if os.environ.get("ADVERSARIAL_LIBRARY_NO_CACHE"):
76
+ config.cache_ttl = 0
77
+
78
+ if ref := os.environ.get("ADVERSARIAL_LIBRARY_REF"):
79
+ config.ref = ref
80
+
81
+ return config
@@ -0,0 +1,129 @@
1
+ """Data models for the evaluator library client."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime, timezone
5
+ from typing import Dict, List, Optional
6
+
7
+
8
+ @dataclass
9
+ class EvaluatorEntry:
10
+ """An evaluator entry from the library index."""
11
+
12
+ name: str
13
+ provider: str
14
+ path: str
15
+ model: str
16
+ category: str
17
+ description: str
18
+
19
+ @classmethod
20
+ def from_dict(cls, data: Dict) -> "EvaluatorEntry":
21
+ """Create an EvaluatorEntry from a dictionary."""
22
+ return cls(
23
+ name=data["name"],
24
+ provider=data["provider"],
25
+ path=data["path"],
26
+ model=data["model"],
27
+ category=data["category"],
28
+ description=data["description"],
29
+ )
30
+
31
+ @property
32
+ def full_name(self) -> str:
33
+ """Return provider/name format."""
34
+ return f"{self.provider}/{self.name}"
35
+
36
+
37
+ @dataclass
38
+ class IndexData:
39
+ """Parsed library index data."""
40
+
41
+ version: str
42
+ evaluators: List[EvaluatorEntry]
43
+ categories: Dict[str, str]
44
+ fetched_at: Optional[datetime] = None
45
+
46
+ @classmethod
47
+ def from_dict(cls, data: Dict) -> "IndexData":
48
+ """Create an IndexData from a dictionary."""
49
+ evaluators = [EvaluatorEntry.from_dict(e) for e in data.get("evaluators", [])]
50
+ return cls(
51
+ version=data.get("version", "unknown"),
52
+ evaluators=evaluators,
53
+ categories=data.get("categories", {}),
54
+ fetched_at=datetime.now(timezone.utc),
55
+ )
56
+
57
+ def get_evaluator(self, provider: str, name: str) -> Optional[EvaluatorEntry]:
58
+ """Find an evaluator by provider and name."""
59
+ for e in self.evaluators:
60
+ if e.provider == provider and e.name == name:
61
+ return e
62
+ return None
63
+
64
+ def filter_by_provider(self, provider: str) -> List[EvaluatorEntry]:
65
+ """Filter evaluators by provider."""
66
+ return [e for e in self.evaluators if e.provider == provider]
67
+
68
+ def filter_by_category(self, category: str) -> List[EvaluatorEntry]:
69
+ """Filter evaluators by category."""
70
+ return [e for e in self.evaluators if e.category == category]
71
+
72
+
73
+ @dataclass
74
+ class InstalledEvaluatorMeta:
75
+ """Metadata for an installed evaluator (from _meta block)."""
76
+
77
+ source: str
78
+ source_path: str
79
+ version: str
80
+ installed: str
81
+ file_path: Optional[str] = None # Path to the installed file
82
+
83
+ @classmethod
84
+ def from_dict(cls, data: Dict) -> Optional["InstalledEvaluatorMeta"]:
85
+ """Create from _meta dictionary, returns None if invalid."""
86
+ if not data:
87
+ return None
88
+ try:
89
+ return cls(
90
+ source=data.get("source", ""),
91
+ source_path=data.get("source_path", ""),
92
+ version=data.get("version", ""),
93
+ installed=data.get("installed", ""),
94
+ )
95
+ except (KeyError, TypeError):
96
+ return None
97
+
98
+ @property
99
+ def provider(self) -> str:
100
+ """Extract provider from source_path."""
101
+ parts = self.source_path.split("/")
102
+ return parts[0] if parts else ""
103
+
104
+ @property
105
+ def name(self) -> str:
106
+ """Extract name from source_path."""
107
+ parts = self.source_path.split("/")
108
+ return parts[1] if len(parts) > 1 else ""
109
+
110
+
111
+ @dataclass
112
+ class UpdateInfo:
113
+ """Information about an available update."""
114
+
115
+ name: str
116
+ installed_version: str
117
+ available_version: str
118
+ is_outdated: bool
119
+ is_local_only: bool = False
120
+
121
+ @property
122
+ def status(self) -> str:
123
+ """Human-readable status."""
124
+ if self.is_local_only:
125
+ return "Local only"
126
+ elif self.is_outdated:
127
+ return "Update available"
128
+ else:
129
+ return "Up to date"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adversarial-workflow
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: Multi-stage AI evaluation system for task plans, code review, and test validation
5
5
  Author: Fredrik Matheson
6
6
  License: MIT
@@ -57,7 +57,7 @@ Evaluate proposals, sort out ideas, and prevent "phantom work" (AI claiming to i
57
57
  - 🎯 **Tool-agnostic**: Use with Claude Code, Cursor, Aider, manual coding, or any workflow
58
58
  - ✨ **Interactive onboarding**: Guided setup wizard gets you started in <5 minutes
59
59
 
60
- ## What's New in v0.6.3
60
+ ## What's New in v0.9.0
61
61
 
62
62
  ### Upgrade
63
63
 
@@ -65,6 +65,67 @@ Evaluate proposals, sort out ideas, and prevent "phantom work" (AI claiming to i
65
65
  pip install --upgrade adversarial-workflow
66
66
  ```
67
67
 
68
+ ### v0.9.0 - Run Library Evaluators
69
+
70
+ **Finally run your installed evaluators!** Use the new `--evaluator` flag:
71
+
72
+ ```bash
73
+ # Install an evaluator from the library
74
+ adversarial library install google/gemini-flash
75
+
76
+ # Run it with --evaluator flag
77
+ adversarial evaluate --evaluator gemini-flash task.md
78
+ adversarial evaluate -e gemini-flash task.md # short form
79
+
80
+ # Works with model_requirement for portable evaluators
81
+ # Automatically resolves to best available model
82
+ ```
83
+
84
+ **Key Features:**
85
+ - Run any installed evaluator by name
86
+ - Supports evaluator aliases
87
+ - Automatic model resolution via `model_requirement`
88
+ - Falls back to legacy `model` field if resolution fails
89
+ - Full backward compatibility - no flag uses existing behavior
90
+
91
+ See [Evaluator Library](#evaluator-library) for full documentation.
92
+
93
+ ### v0.8.1 - BugBot Fixes
94
+
95
+ - **CI/CD compatibility**: `--category --dry-run` no longer hangs in non-TTY environments
96
+ - **Proper exit codes**: Dry-run returns 1 when all previews fail
97
+ - **Config robustness**: Non-dict YAML configs no longer crash
98
+
99
+ ### v0.7.0 - Evaluator Library
100
+
101
+ Browse, install, and update evaluators from the community [adversarial-evaluator-library](https://github.com/movito/adversarial-evaluator-library):
102
+
103
+ ```bash
104
+ # Browse available evaluators
105
+ adversarial library list
106
+
107
+ # Filter by provider or category
108
+ adversarial library list --provider google
109
+ adversarial library list --category quick-check
110
+
111
+ # Install evaluators
112
+ adversarial library install google/gemini-flash openai/fast-check
113
+
114
+ # Check for updates
115
+ adversarial library check-updates
116
+
117
+ # Update installed evaluators
118
+ adversarial library update --all
119
+ ```
120
+
121
+ **Key Features:**
122
+ - Index caching with 1-hour TTL for faster lookups
123
+ - Offline support with stale cache fallback
124
+ - Provenance tracking via `_meta` block in installed files
125
+ - Diff preview before applying updates
126
+
127
+ See [Evaluator Library](#evaluator-library) for full documentation.
128
+
68
129
  ### v0.6.3 - Configurable Timeouts
69
130
 
70
131
  - **Per-evaluator timeout**: Add `timeout: 300` to evaluator YAML for slow models like Mistral Large
@@ -429,7 +490,8 @@ adversarial health # Comprehensive system health check
429
490
  adversarial agent onboard # Set up agent coordination system
430
491
 
431
492
  # Workflow
432
- adversarial evaluate task.md # Phase 1: Evaluate plan
493
+ adversarial evaluate task.md # Phase 1: Evaluate plan (uses config.yml)
494
+ adversarial evaluate -e <name> task.md # Phase 1: Evaluate with installed evaluator
433
495
  adversarial split task.md # Split large files into smaller parts
434
496
  adversarial split task.md --dry-run # Preview split without creating files
435
497
  adversarial review # Phase 3: Review implementation
@@ -437,6 +499,99 @@ adversarial validate "pytest" # Phase 4: Validate with tests
437
499
  adversarial list-evaluators # List all available evaluators
438
500
  ```
439
501
 
502
+ ## Evaluator Library
503
+
504
+ Browse and install pre-configured evaluators from the community [adversarial-evaluator-library](https://github.com/movito/adversarial-evaluator-library).
505
+
506
+ ### Quick Start
507
+
508
+ ```bash
509
+ # Browse available evaluators
510
+ adversarial library list
511
+
512
+ # Filter by provider or category
513
+ adversarial library list --provider google
514
+ adversarial library list --category quick-check
515
+
516
+ # Install an evaluator
517
+ adversarial library install google/gemini-flash
518
+
519
+ # Run it with --evaluator flag
520
+ adversarial evaluate --evaluator gemini-flash task.md
521
+ adversarial evaluate -e gemini-flash task.md # short form
522
+ ```
523
+
524
+ ### Available Commands
525
+
526
+ | Command | Description |
527
+ |---------|-------------|
528
+ | `adversarial library list` | Browse available evaluators |
529
+ | `adversarial library install <provider>/<name>` | Install evaluator to project |
530
+ | `adversarial library check-updates` | Check for updates to installed evaluators |
531
+ | `adversarial library update <name>` | Update an evaluator (with diff preview) |
532
+
533
+ ### Running Installed Evaluators
534
+
535
+ Use the `--evaluator` flag to run any installed evaluator:
536
+
537
+ ```bash
538
+ # Run by name
539
+ adversarial evaluate --evaluator plan-evaluator task.md
540
+
541
+ # Short form
542
+ adversarial evaluate -e security-reviewer task.md
543
+
544
+ # Evaluators with model_requirement auto-resolve to best available model
545
+ adversarial evaluate -e gemini-flash task.md
546
+ ```
547
+
548
+ **How it works:**
549
+ - Looks up evaluator in `.adversarial/evaluators/*.yml`
550
+ - Uses the evaluator's model, prompt, and output settings
551
+ - Supports evaluator aliases
552
+ - If evaluator has `model_requirement`, resolves to best available model
553
+ - Falls back to legacy `model` field if resolution fails
554
+
555
+ **Without --evaluator flag**: Uses existing shell script behavior (backward compatible)
556
+
557
+ ### Philosophy: Copy, Don't Link
558
+
559
+ Installed evaluators are **copied** to your project, not referenced at runtime:
560
+ - Projects remain self-contained and work offline
561
+ - You can customize your local copies freely
562
+ - Updates are explicit and user-controlled
563
+
564
+ ### Provenance Tracking
565
+
566
+ Installed evaluators include metadata for tracking updates:
567
+
568
+ ```yaml
569
+ _meta:
570
+ source: adversarial-evaluator-library
571
+ source_path: google/gemini-flash
572
+ version: "1.2.0"
573
+ installed: "2026-02-03T10:00:00Z"
574
+
575
+ name: gemini-flash
576
+ # ... rest of evaluator config
577
+ ```
578
+
579
+ ### Options
580
+
581
+ ```bash
582
+ # Bypass cache (1-hour TTL by default)
583
+ adversarial library list --no-cache
584
+
585
+ # Force overwrite existing files
586
+ adversarial library install google/gemini-flash --force
587
+
588
+ # Update all outdated evaluators
589
+ adversarial library update --all
590
+
591
+ # Preview changes without applying
592
+ adversarial library update gemini-flash --diff-only
593
+ ```
594
+
440
595
  ## Custom Evaluators
441
596
 
442
597
  Starting with v0.6.0, you can define project-specific evaluators without modifying the package.
@@ -1,11 +1,18 @@
1
- adversarial_workflow/__init__.py,sha256=Aj_FdCEOJYpaeOe9SMO1vWqlpzoR9AONRz2SZ8RKgVg,596
1
+ adversarial_workflow/__init__.py,sha256=OQ1JUc1P3VTc-4mCNKIP7NYmEZ1XVzxL6DdAIhCk6zM,596
2
2
  adversarial_workflow/__main__.py,sha256=iM2jmO5YCFpGxfWiEhIYi_SsxVa0hRIE-MB7J0EcN7Y,120
3
- adversarial_workflow/cli.py,sha256=mKvOe3Q-afCfzXALNCQ47GBOSwccE17kqYpxOHbOC6k,115541
4
- adversarial_workflow/evaluators/__init__.py,sha256=A9ZKUmjSMfyvEu6jDzYAFLxfkt_OQ4RGA10Bv_eO2i4,1267
3
+ adversarial_workflow/cli.py,sha256=DbDPxl5tKgFa44lRW8fzIx7vPO4Eyv5PQKF6NsUHO1A,123004
4
+ adversarial_workflow/evaluators/__init__.py,sha256=wP1mLvw6VgloQgtvlxoiqy8DvwNMUn5EWWxPDoigtZc,1580
5
5
  adversarial_workflow/evaluators/builtins.py,sha256=u5LokYLe8ruEW2tunhOQaNSkpcZ9Ee2IeTkaC0dZDSY,1102
6
- adversarial_workflow/evaluators/config.py,sha256=H_4vkto07rAqnz0qEYdzN_DH6WbvRPMIEdkEOFE58UI,1651
7
- adversarial_workflow/evaluators/discovery.py,sha256=Fe_mj7S4aIzXuib28b_uLu3vH8AMAy1B3MDyssi3QU0,7953
8
- adversarial_workflow/evaluators/runner.py,sha256=7zui3ec190pBC4VB3HRxVgexJJff5F2V6oxgJiMv2tc,9312
6
+ adversarial_workflow/evaluators/config.py,sha256=5aCxhUIw-w34XNZmsxZ-TCXznxLMAlTzgxFCBjVGTuo,3206
7
+ adversarial_workflow/evaluators/discovery.py,sha256=VAD16fDaBuVbtqYxR4_Bj95yP1vyB4oaeV3QVULQ2qk,12087
8
+ adversarial_workflow/evaluators/resolver.py,sha256=8RJYNyaTIl8BNk5r3-LTjfM5hRivSvmm4ZtSZpFE4Jo,7075
9
+ adversarial_workflow/evaluators/runner.py,sha256=VPpQpd0lTDA3Z8zGlBqbKM5BIUqyTDYJHgBVVO_gzxY,10127
10
+ adversarial_workflow/library/__init__.py,sha256=9s63oT4Hi1g8ZVMyIVG8Bq91L1kP70UVvE7YTFDQILs,1410
11
+ adversarial_workflow/library/cache.py,sha256=6X0MoBjpMMjEjeoA3Inevb4dMmzHamwlYomEuPa6T54,4861
12
+ adversarial_workflow/library/client.py,sha256=_PewGNdKG8dY73rwCNQKjTfazjAF-qIlCa7UuIG-mUM,7319
13
+ adversarial_workflow/library/commands.py,sha256=-kzRqzT4duZnIKQSzlWHbNRABe879UXlsdKWVmpmMyU,27548
14
+ adversarial_workflow/library/config.py,sha256=SFCw3ZzVmzT-DGuPZVmi-tCO7OHTrqnZlKQ9u_Vk0Nw,2720
15
+ adversarial_workflow/library/models.py,sha256=NQCFp8SG_s_mom6daao2T0rgA3mdKihl7ne8kpdNytI,3752
9
16
  adversarial_workflow/templates/.aider.conf.yml.template,sha256=jT2jWIgsnmS3HLhoQWMTO3GV07bUcsT2keYw60jqiDw,183
10
17
  adversarial_workflow/templates/.env.example.template,sha256=TmTlcgz44uZqIbqgXqdfHMl-0vVn96F_EGNohClFkb8,1821
11
18
  adversarial_workflow/templates/README.template,sha256=FQAMPO99eIt_kgQfwhGHcrK736rm_MEvWSbPnqBSjAE,1349
@@ -26,9 +33,9 @@ adversarial_workflow/utils/colors.py,sha256=uRrG6KfIDBLo0F5_vPwms9NCm9-x8YXBiyZ4
26
33
  adversarial_workflow/utils/config.py,sha256=3VmF65ItUbFzbyAZ1RUoOtpS_t6n1wqIhKft8eSNsdw,1303
27
34
  adversarial_workflow/utils/file_splitter.py,sha256=kvWh0xVjd08fsEXgysoHd5zFwJHqs-JRKottO8scYCA,12381
28
35
  adversarial_workflow/utils/validation.py,sha256=0QfuRd-kurcadUCd9XQvO-N8RsmLp6ONQnc0vaQTUBA,2188
29
- adversarial_workflow-0.7.0.dist-info/licenses/LICENSE,sha256=M-dOQlre-NmicyPa55hYOJUW8roGpCKEgtq-z0z1KCA,1073
30
- adversarial_workflow-0.7.0.dist-info/METADATA,sha256=s0lrhtLRaXy6HE-QeCAcmdH8mswaeyxTvQ1H3khhG1k,30916
31
- adversarial_workflow-0.7.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
32
- adversarial_workflow-0.7.0.dist-info/entry_points.txt,sha256=9H-iZ-yF1uKZ8P0G1suc6kWR0NvK7uPZJbhN7nvt1sE,62
33
- adversarial_workflow-0.7.0.dist-info/top_level.txt,sha256=8irutNxLRjUbTlzfAibIpz7_ovkkF2h8ES69NQpv24c,21
34
- adversarial_workflow-0.7.0.dist-info/RECORD,,
36
+ adversarial_workflow-0.9.0.dist-info/licenses/LICENSE,sha256=M-dOQlre-NmicyPa55hYOJUW8roGpCKEgtq-z0z1KCA,1073
37
+ adversarial_workflow-0.9.0.dist-info/METADATA,sha256=CqcZkNusUA4JMhuLW77vWsDPznKLx_uH4Gkqav-f4Zc,35538
38
+ adversarial_workflow-0.9.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
39
+ adversarial_workflow-0.9.0.dist-info/entry_points.txt,sha256=9H-iZ-yF1uKZ8P0G1suc6kWR0NvK7uPZJbhN7nvt1sE,62
40
+ adversarial_workflow-0.9.0.dist-info/top_level.txt,sha256=8irutNxLRjUbTlzfAibIpz7_ovkkF2h8ES69NQpv24c,21
41
+ adversarial_workflow-0.9.0.dist-info/RECORD,,