agent-eval-planner 0.1.0__tar.gz

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.
Files changed (27) hide show
  1. agent_eval_planner-0.1.0/LICENSE +20 -0
  2. agent_eval_planner-0.1.0/PKG-INFO +139 -0
  3. agent_eval_planner-0.1.0/README.md +117 -0
  4. agent_eval_planner-0.1.0/pyproject.toml +37 -0
  5. agent_eval_planner-0.1.0/setup.cfg +4 -0
  6. agent_eval_planner-0.1.0/setup.py +3 -0
  7. agent_eval_planner-0.1.0/src/agent_eval_planner/__init__.py +50 -0
  8. agent_eval_planner-0.1.0/src/agent_eval_planner/__main__.py +8 -0
  9. agent_eval_planner-0.1.0/src/agent_eval_planner/cli.py +211 -0
  10. agent_eval_planner-0.1.0/src/agent_eval_planner/contract_analyzer.py +195 -0
  11. agent_eval_planner-0.1.0/src/agent_eval_planner/errors.py +13 -0
  12. agent_eval_planner-0.1.0/src/agent_eval_planner/models.py +39 -0
  13. agent_eval_planner-0.1.0/src/agent_eval_planner/plan_renderer.py +125 -0
  14. agent_eval_planner-0.1.0/src/agent_eval_planner/planner.py +79 -0
  15. agent_eval_planner-0.1.0/src/agent_eval_planner/remediation_renderer.py +138 -0
  16. agent_eval_planner-0.1.0/src/agent_eval_planner/suite_renderer.py +56 -0
  17. agent_eval_planner-0.1.0/src/agent_eval_planner/suite_validator.py +166 -0
  18. agent_eval_planner-0.1.0/src/agent_eval_planner/vector_catalog.py +333 -0
  19. agent_eval_planner-0.1.0/src/agent_eval_planner/version.py +3 -0
  20. agent_eval_planner-0.1.0/src/agent_eval_planner.egg-info/PKG-INFO +139 -0
  21. agent_eval_planner-0.1.0/src/agent_eval_planner.egg-info/SOURCES.txt +25 -0
  22. agent_eval_planner-0.1.0/src/agent_eval_planner.egg-info/dependency_links.txt +1 -0
  23. agent_eval_planner-0.1.0/src/agent_eval_planner.egg-info/entry_points.txt +2 -0
  24. agent_eval_planner-0.1.0/src/agent_eval_planner.egg-info/requires.txt +1 -0
  25. agent_eval_planner-0.1.0/src/agent_eval_planner.egg-info/top_level.txt +1 -0
  26. agent_eval_planner-0.1.0/tests/test_contract_analyzer.py +30 -0
  27. agent_eval_planner-0.1.0/tests/test_planner.py +91 -0
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
20
+
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-eval-planner
3
+ Version: 0.1.0
4
+ Summary: Generate guardrail evaluation plans, JSONL suites, and remediations from agent contracts
5
+ Author-email: Saulo Filho <saulofilho@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://saulofilho.github.io/agent-eval-planner-python/
8
+ Project-URL: Repository, https://github.com/saulofilho/agent-eval-planner-python
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Testing
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pyyaml>=6.0
21
+ Dynamic: license-file
22
+
23
+ # agent-eval-planner
24
+
25
+ [![PyPI version](https://badge.fury.io/py/agent-eval-planner.svg)](https://badge.fury.io/py/agent-eval-planner)
26
+ [![Python Versions](https://img.shields.io/pypi/pyversions/agent-eval-planner.svg)](https://pypi.org/project/agent-eval-planner/)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
28
+
29
+ **Site:** [saulofilho.github.io/agent-eval-planner-python](https://saulofilho.github.io/agent-eval-planner-python/)
30
+
31
+ Python library that turns an **agent contract** (system prompt, tools, policy gates) into a guardrail evaluation pipeline:
32
+
33
+ 1. **Action plan** — SCOPE / INJECT / ROLE / PII / TOOL / HALLUC / EXFIL vectors
34
+ 2. **suite.jsonl** — executable cases with `forbidden_tools` filled
35
+ 3. **Remediations** — prompt / gate / evaluator quick wins
36
+
37
+ Sibling of [`security-pentest-planner`](https://github.com/saulofilho/security-pentest-planner-python), applied to LLM agents. Port of the Ruby gem [`agent_eval_planner`](https://github.com/saulofilho/agent-eval-planner).
38
+
39
+ Canonical smoke test: **carrot cake** (off-topic). If the agent answers with a recipe, its scope is not limited.
40
+
41
+ ## What it does
42
+
43
+ - Parses agent contracts (Markdown, text, JSON, YAML).
44
+ - Generates a Markdown eval plan, a JSONL suite, and remediations.
45
+ - Validates suites (hard-fail on empty `forbidden_tools` / leftover placeholders).
46
+
47
+ ## What it does **NOT** do
48
+
49
+ - Execute the evaluation against a live agent.
50
+ - Replace pentest of APIs/infra (use `security-pentest-planner`).
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install agent-eval-planner
56
+ ```
57
+
58
+ Or using `uv` / `poetry`:
59
+
60
+ ```bash
61
+ uv add agent-eval-planner
62
+ # or
63
+ poetry add agent-eval-planner
64
+ ```
65
+
66
+ ## CLI usage
67
+
68
+ ```bash
69
+ # Full pipeline → directory (plan + suite + remediations)
70
+ agent-eval-planner agent.md --tools funnel_analytics,open_service_center_ticket \
71
+ -t "Platform Team" -a analytics -o ./out
72
+
73
+ # Validate suite (hard-fail on empty forbidden_tools / placeholders)
74
+ agent-eval-planner validate ./out/suite.jsonl \
75
+ --known-tools funnel_analytics,open_service_center_ticket
76
+
77
+ # Plan only to stdout
78
+ agent-eval-planner agent.md --plan-only --agent analytics
79
+ ```
80
+
81
+ ### Options
82
+
83
+ | Flag | Description |
84
+ |------|-------------|
85
+ | `-t, --team TEAM` | Team name in the document title |
86
+ | `-a, --agent NAME` | `target_agent` name |
87
+ | `--tools LIST` | Comma-separated real tool names |
88
+ | `--scope TEXT` | Declared scope summary |
89
+ | `--harness NAME` | `generic` or `marketing-copilot` |
90
+ | `-o, --output PATH` | Output directory or single file |
91
+ | `--plan-only` / `--suite-only` / `--remediations-only` | Emit a single artifact |
92
+ | `-v, --version` | Show version |
93
+
94
+ ## Programmatic usage
95
+
96
+ ```python
97
+ import agent_eval_planner
98
+
99
+ result = agent_eval_planner.generate(
100
+ input_path="agent.md",
101
+ team="Platform Team",
102
+ tools=["funnel_analytics", "open_service_center_ticket"],
103
+ )
104
+
105
+ with open("evaluation-plan.md", "w", encoding="utf-8") as f:
106
+ f.write(result.plan)
107
+ ```
108
+
109
+ ## Publish to PyPI
110
+
111
+ ```bash
112
+ python -m pip install build twine
113
+ python -m build
114
+ python -m twine upload dist/*
115
+ ```
116
+
117
+ ## Development and testing
118
+
119
+ ```bash
120
+ git clone https://github.com/saulofilho/agent-eval-planner-python.git
121
+ cd agent-eval-planner-python
122
+
123
+ python -m pip install .
124
+ python -m unittest discover -s tests -v
125
+ ```
126
+
127
+ ## Recommended workflow
128
+
129
+ ```
130
+ Agent contract → agent-eval-planner → Plan + suite.jsonl + remediations
131
+ ↓
132
+ Harness / CI execution
133
+ ↓
134
+ Failures + remediations
135
+ ```
136
+
137
+ ## License
138
+
139
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,117 @@
1
+ # agent-eval-planner
2
+
3
+ [![PyPI version](https://badge.fury.io/py/agent-eval-planner.svg)](https://badge.fury.io/py/agent-eval-planner)
4
+ [![Python Versions](https://img.shields.io/pypi/pyversions/agent-eval-planner.svg)](https://pypi.org/project/agent-eval-planner/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ **Site:** [saulofilho.github.io/agent-eval-planner-python](https://saulofilho.github.io/agent-eval-planner-python/)
8
+
9
+ Python library that turns an **agent contract** (system prompt, tools, policy gates) into a guardrail evaluation pipeline:
10
+
11
+ 1. **Action plan** — SCOPE / INJECT / ROLE / PII / TOOL / HALLUC / EXFIL vectors
12
+ 2. **suite.jsonl** — executable cases with `forbidden_tools` filled
13
+ 3. **Remediations** — prompt / gate / evaluator quick wins
14
+
15
+ Sibling of [`security-pentest-planner`](https://github.com/saulofilho/security-pentest-planner-python), applied to LLM agents. Port of the Ruby gem [`agent_eval_planner`](https://github.com/saulofilho/agent-eval-planner).
16
+
17
+ Canonical smoke test: **carrot cake** (off-topic). If the agent answers with a recipe, its scope is not limited.
18
+
19
+ ## What it does
20
+
21
+ - Parses agent contracts (Markdown, text, JSON, YAML).
22
+ - Generates a Markdown eval plan, a JSONL suite, and remediations.
23
+ - Validates suites (hard-fail on empty `forbidden_tools` / leftover placeholders).
24
+
25
+ ## What it does **NOT** do
26
+
27
+ - Execute the evaluation against a live agent.
28
+ - Replace pentest of APIs/infra (use `security-pentest-planner`).
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install agent-eval-planner
34
+ ```
35
+
36
+ Or using `uv` / `poetry`:
37
+
38
+ ```bash
39
+ uv add agent-eval-planner
40
+ # or
41
+ poetry add agent-eval-planner
42
+ ```
43
+
44
+ ## CLI usage
45
+
46
+ ```bash
47
+ # Full pipeline → directory (plan + suite + remediations)
48
+ agent-eval-planner agent.md --tools funnel_analytics,open_service_center_ticket \
49
+ -t "Platform Team" -a analytics -o ./out
50
+
51
+ # Validate suite (hard-fail on empty forbidden_tools / placeholders)
52
+ agent-eval-planner validate ./out/suite.jsonl \
53
+ --known-tools funnel_analytics,open_service_center_ticket
54
+
55
+ # Plan only to stdout
56
+ agent-eval-planner agent.md --plan-only --agent analytics
57
+ ```
58
+
59
+ ### Options
60
+
61
+ | Flag | Description |
62
+ |------|-------------|
63
+ | `-t, --team TEAM` | Team name in the document title |
64
+ | `-a, --agent NAME` | `target_agent` name |
65
+ | `--tools LIST` | Comma-separated real tool names |
66
+ | `--scope TEXT` | Declared scope summary |
67
+ | `--harness NAME` | `generic` or `marketing-copilot` |
68
+ | `-o, --output PATH` | Output directory or single file |
69
+ | `--plan-only` / `--suite-only` / `--remediations-only` | Emit a single artifact |
70
+ | `-v, --version` | Show version |
71
+
72
+ ## Programmatic usage
73
+
74
+ ```python
75
+ import agent_eval_planner
76
+
77
+ result = agent_eval_planner.generate(
78
+ input_path="agent.md",
79
+ team="Platform Team",
80
+ tools=["funnel_analytics", "open_service_center_ticket"],
81
+ )
82
+
83
+ with open("evaluation-plan.md", "w", encoding="utf-8") as f:
84
+ f.write(result.plan)
85
+ ```
86
+
87
+ ## Publish to PyPI
88
+
89
+ ```bash
90
+ python -m pip install build twine
91
+ python -m build
92
+ python -m twine upload dist/*
93
+ ```
94
+
95
+ ## Development and testing
96
+
97
+ ```bash
98
+ git clone https://github.com/saulofilho/agent-eval-planner-python.git
99
+ cd agent-eval-planner-python
100
+
101
+ python -m pip install .
102
+ python -m unittest discover -s tests -v
103
+ ```
104
+
105
+ ## Recommended workflow
106
+
107
+ ```
108
+ Agent contract → agent-eval-planner → Plan + suite.jsonl + remediations
109
+ ↓
110
+ Harness / CI execution
111
+ ↓
112
+ Failures + remediations
113
+ ```
114
+
115
+ ## License
116
+
117
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agent-eval-planner"
7
+ version = "0.1.0"
8
+ description = "Generate guardrail evaluation plans, JSONL suites, and remediations from agent contracts"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Saulo Filho", email = "saulofilho@users.noreply.github.com" }
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Testing",
24
+ ]
25
+ dependencies = [
26
+ "pyyaml>=6.0",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://saulofilho.github.io/agent-eval-planner-python/"
31
+ Repository = "https://github.com/saulofilho/agent-eval-planner-python"
32
+
33
+ [project.scripts]
34
+ agent-eval-planner = "agent_eval_planner.cli:main"
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from setuptools import setup
2
+
3
+ setup()
@@ -0,0 +1,50 @@
1
+ """agent_eval_planner package.
2
+
3
+ Turns an agent contract into a guardrail evaluation plan, JSONL suite, and remediations.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from .errors import Error, InputError, ValidationError
9
+ from .models import PlannerResult, Vector
10
+ from .planner import Planner
11
+ from .suite_validator import SuiteValidator
12
+ from .version import __version__
13
+
14
+
15
+ def generate(input_path: str | None = None, raw: str | None = None, **options) -> PlannerResult:
16
+ """Generate plan, suite, and remediations from an agent contract."""
17
+ return Planner(input_path=input_path, raw=raw, **options).call()
18
+
19
+
20
+ def generate_plan(input_path: str | None = None, raw: str | None = None, **options) -> str:
21
+ """Generate only the Markdown evaluation plan."""
22
+ return generate(input_path=input_path, raw=raw, **options).plan
23
+
24
+
25
+ def validate_suite(
26
+ path: str,
27
+ known_tools: list[str] | None = None,
28
+ agent_has_no_tools: bool = False,
29
+ ) -> list[str]:
30
+ """Validate a suite.jsonl file. Returns a list of errors (empty if OK)."""
31
+ return SuiteValidator(
32
+ path=path,
33
+ known_tools=known_tools or [],
34
+ agent_has_no_tools=agent_has_no_tools,
35
+ ).validate()
36
+
37
+
38
+ __all__ = [
39
+ "__version__",
40
+ "generate",
41
+ "generate_plan",
42
+ "validate_suite",
43
+ "Planner",
44
+ "PlannerResult",
45
+ "Vector",
46
+ "SuiteValidator",
47
+ "Error",
48
+ "InputError",
49
+ "ValidationError",
50
+ ]
@@ -0,0 +1,8 @@
1
+ """Main entry point when invoked as python -m agent_eval_planner."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -0,0 +1,211 @@
1
+ """Command-line interface for agent-eval-planner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Sequence
9
+
10
+ from . import generate, validate_suite
11
+ from .errors import Error
12
+ from .models import PlannerResult
13
+ from .version import __version__
14
+
15
+
16
+ def build_parser() -> argparse.ArgumentParser:
17
+ """Builds the argument parser for generate mode."""
18
+ parser = argparse.ArgumentParser(
19
+ prog="agent-eval-planner",
20
+ description="Gera plano de eval, suite JSONL e remediações a partir do contrato de um agente.",
21
+ formatter_class=argparse.RawDescriptionHelpFormatter,
22
+ epilog=(
23
+ "Usage:\n"
24
+ " agent-eval-planner [options] <contract.md|prompt.txt|agent.json>\n"
25
+ " agent-eval-planner validate [options] <suite.jsonl>"
26
+ ),
27
+ )
28
+ parser.add_argument("input", nargs="?", help="Caminho do contrato do agente")
29
+ parser.add_argument("-t", "--team", help="Nome do time no título")
30
+ parser.add_argument("-a", "--agent", dest="agent_name", help="Nome do target_agent")
31
+ parser.add_argument("--tools", help="Tools conhecidas (vírgula)")
32
+ parser.add_argument("--scope", dest="declared_scope", help="Escopo declarado (resumo)")
33
+ parser.add_argument("--out-of-scope", dest="out_of_scope", help="Fora de escopo declarado")
34
+ parser.add_argument(
35
+ "--harness",
36
+ default="generic",
37
+ help="Harness alvo (generic|marketing-copilot)",
38
+ )
39
+ parser.add_argument("-o", "--output", help="Arquivo .md/.jsonl ou diretório de saída")
40
+ parser.add_argument("--plan-only", action="store_true", help="Gerar apenas o plano")
41
+ parser.add_argument("--suite-only", action="store_true", help="Gerar apenas a suite JSONL")
42
+ parser.add_argument(
43
+ "--remediations-only",
44
+ action="store_true",
45
+ help="Gerar apenas remediações",
46
+ )
47
+ parser.add_argument(
48
+ "-v",
49
+ "--version",
50
+ action="version",
51
+ version=f"agent_eval_planner {__version__}",
52
+ )
53
+ return parser
54
+
55
+
56
+ def build_validate_parser() -> argparse.ArgumentParser:
57
+ """Builds the argument parser for validate mode."""
58
+ parser = argparse.ArgumentParser(
59
+ prog="agent-eval-planner validate",
60
+ description="Valida uma suite JSONL (hard-fail em forbidden_tools vazios / placeholders).",
61
+ )
62
+ parser.add_argument("suite", nargs="?", help="Caminho da suite.jsonl")
63
+ parser.add_argument(
64
+ "--known-tools",
65
+ help="Tools reais (vírgula)",
66
+ )
67
+ parser.add_argument(
68
+ "--agent-has-no-tools",
69
+ action="store_true",
70
+ help="Agente sem tools",
71
+ )
72
+ return parser
73
+
74
+
75
+ def main(argv: Sequence[str] | None = None) -> int:
76
+ """Main CLI execution method."""
77
+ args_list = list(argv) if argv is not None else sys.argv[1:]
78
+ if args_list and args_list[0] == "validate":
79
+ return _run_validate(args_list[1:])
80
+ return _run_generate(args_list)
81
+
82
+
83
+ def _run_generate(argv: list[str]) -> int:
84
+ parser = build_parser()
85
+ args = parser.parse_args(argv)
86
+ if not args.input:
87
+ parser.print_help(sys.stderr)
88
+ return 1
89
+
90
+ tools = None
91
+ if args.tools:
92
+ tools = [part.strip() for part in args.tools.split(",") if part.strip()]
93
+
94
+ try:
95
+ result = generate(
96
+ input_path=args.input,
97
+ team=args.team,
98
+ agent_name=args.agent_name,
99
+ tools=tools,
100
+ declared_scope=args.declared_scope,
101
+ out_of_scope=args.out_of_scope,
102
+ harness=args.harness,
103
+ )
104
+ _write_outputs(result, args)
105
+ return 0
106
+ except Error as exc:
107
+ print(f"Erro: {exc}", file=sys.stderr)
108
+ return 1
109
+
110
+
111
+ def _looks_like_directory(path: str) -> bool:
112
+ candidate = Path(path)
113
+ return candidate.is_dir() or path.endswith("/") or candidate.suffix == ""
114
+
115
+
116
+ def _select_single(result: PlannerResult, args: argparse.Namespace) -> str | None:
117
+ if args.suite_only:
118
+ return result.suite
119
+ if args.remediations_only:
120
+ return result.remediations
121
+ if args.plan_only:
122
+ return result.plan
123
+ return None
124
+
125
+
126
+ def _write_bundle(directory: Path, result: PlannerResult, args: argparse.Namespace) -> None:
127
+ directory.mkdir(parents=True, exist_ok=True)
128
+ if not args.suite_only and not args.remediations_only:
129
+ plan_path = directory / "plano-de-acao-agent-eval.md"
130
+ plan_path.write_text(result.plan, encoding="utf-8")
131
+ print(f"Plano gerado em {plan_path}", file=sys.stderr)
132
+ if not args.plan_only and not args.remediations_only:
133
+ suite_path = directory / "suite.jsonl"
134
+ suite_path.write_text(result.suite, encoding="utf-8")
135
+ print(f"Suite gerada em {suite_path}", file=sys.stderr)
136
+ if args.plan_only or args.suite_only:
137
+ return
138
+ rem_path = directory / "remediacoes.md"
139
+ rem_path.write_text(result.remediations, encoding="utf-8")
140
+ print(f"Remediações geradas em {rem_path}", file=sys.stderr)
141
+
142
+
143
+ def _emit_stdout(result: PlannerResult, args: argparse.Namespace) -> None:
144
+ if args.suite_only:
145
+ sys.stdout.write(result.suite)
146
+ elif args.remediations_only:
147
+ sys.stdout.write(result.remediations)
148
+ elif args.plan_only:
149
+ sys.stdout.write(result.plan)
150
+ else:
151
+ sys.stdout.write(result.plan)
152
+ print("\n# --- suite.jsonl ---\n", file=sys.stderr)
153
+ sys.stdout.write(result.suite)
154
+ print("\n# --- remediacoes.md ---\n", file=sys.stderr)
155
+ sys.stdout.write(result.remediations)
156
+
157
+
158
+ def _write_outputs(result: PlannerResult, args: argparse.Namespace) -> None:
159
+ if not args.output:
160
+ _emit_stdout(result, args)
161
+ return
162
+
163
+ path = args.output
164
+ if _looks_like_directory(path):
165
+ _write_bundle(Path(path), result, args)
166
+ return
167
+
168
+ output = Path(path)
169
+ if args.suite_only or output.suffix == ".jsonl":
170
+ output.write_text(result.suite, encoding="utf-8")
171
+ print(f"Suite gerada em {path}", file=sys.stderr)
172
+ return
173
+
174
+ content = _select_single(result, args) or result.plan
175
+ output.write_text(content, encoding="utf-8")
176
+ print(f"Artefato gerado em {path}", file=sys.stderr)
177
+
178
+
179
+ def _run_validate(argv: list[str]) -> int:
180
+ parser = build_validate_parser()
181
+ args = parser.parse_args(argv)
182
+ if not args.suite:
183
+ parser.print_help(sys.stderr)
184
+ return 1
185
+
186
+ known_tools: list[str] = []
187
+ if args.known_tools:
188
+ known_tools = [part.strip() for part in args.known_tools.split(",") if part.strip()]
189
+
190
+ try:
191
+ errors = validate_suite(
192
+ args.suite,
193
+ known_tools=known_tools,
194
+ agent_has_no_tools=args.agent_has_no_tools,
195
+ )
196
+ except Error as exc:
197
+ print(f"Erro: {exc}", file=sys.stderr)
198
+ return 1
199
+
200
+ if errors:
201
+ print(f"INVALID: {args.suite} ({len(errors)} error(s))", file=sys.stderr)
202
+ for error in errors:
203
+ print(f" - {error}", file=sys.stderr)
204
+ return 1
205
+
206
+ print(f"OK: {args.suite}")
207
+ return 0
208
+
209
+
210
+ if __name__ == "__main__":
211
+ sys.exit(main())