feat(cli): add suite runner for scenario and trace directories (#85) - #154
Conversation
Add an `agent-harness suite` subcommand that runs a directory of scenarios against trace files and emits one aggregate summary plus optional per-scenario result JSON. Single-scenario `run` is unchanged. - Map each scenario to `<trace-dir>/<scenario_id>.json` by id. - Constrain scenario ids to a filename-safe charset and add a path containment check so a trace lookup can never escape `--trace-dir`. - Detect duplicate scenario ids; record per-scenario errors (missing trace, malformed trace, invalid scenario, duplicate id) without aborting the suite. - Fail an empty match / missing trace dir rather than passing vacuously. - Emit per-status counts and provenance (trace path, severity, category) in the summary, validated against schemas/suite_result.schema.json. - `--exit-on-fail` gates on any fail/error, composing with `run`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # tests/test_cli.py
mertsatilmaz
left a comment
There was a problem hiding this comment.
Reviewed in depth — this is excellent work, @nidheesh-p, and thank you for your patience while it sat in my queue.
The design decisions are the ones I'd have asked for: a separate suite subcommand so single-scenario run is untouched, discovery reusing the same files/dirs/globs logic as validate, per-scenario error entries (missing_trace, invalid_scenario, malformed_trace, duplicate id) that record the failure without aborting the rest of the suite, --exit-on-fail composing exactly like run, and a new suite_result.schema.json that correctly carries "version": 1 per the policy from #143. I especially liked that empty matches and a missing --trace-dir exit 1 rather than passing vacuously — that's the failure mode that makes suite runners dangerous in CI.
I pushed three changes to your branch rather than sending it back for a round-trip:
- Merged
mainand resolved conflicts with #153/#157/#158 (CHANGELOG bullets and an interleavedtests/test_cli.pyappend). - Bumped
schemas/scenario.schema.jsonto"version": 2. Constrainingidto[A-Za-z0-9._-]narrows a previously-valid input, whichdocs/schema-versioning.mdclassifies as a breaking schema change requiring a version bump. Your### Changedentry already described the change well — I extended it to record the bump and to note that the pre-1.0 rule means the package major line doesn't move. The constraint itself is right, and mirroring it into both the schema and the Python validator is required by our schema/validator sync tests. - Applied the containment check to the write path.
resolve_withinguarded the trace read (<trace-dir>/<id>.json) but--out-dirwrites used a bareout_dir / f"{id}.json", even though the helper's own docstring promises the suite "must never read or write outside the configured directory". Now both paths go through it. Since it's used across modules I promoted it from_resolve_withintoresolve_within.
Verified locally: ruff, mypy, and 378 passing tests; a full 20-scenario suite run against the bundled fixtures (18 pass / 2 not_run / 0 error, summary.json + 20 per-scenario files); the missing-trace path recording 19 missing_trace errors without aborting; --exit-on-fail returning 1 while the flagless run returns 0; and the traversal guard rejecting ../escape.json, ../../etc/passwd, and sub/../../out2.json. CI is green.
Approving and squash-merging. Closes #85 — and with it the last open item on the v0.2.0 milestone.
Closes #85.
Summary
Adds an
agent-harness suitesubcommand that runs a directory of scenarios against a directory of trace files and produces one aggregate result. Single-scenariorunbehavior is unchanged.validate.<trace-dir>/<scenario_id>.jsonby id.--out-dirplus an aggregatesummary.json, and the summary is always printed to stdout. Validated against the newschemas/suite_result.schema.json.--exit-on-failexits 1 on anyfail/error, composing the same way asrun. An empty match or missing--trace-direxits 1 rather than passing vacuously.Acceptance criteria (#85)
suitesubcommand;rununtouched)--exit-on-faildocs/ci-github-actions.md)Security / robustness hardening
This went through a security/governance review before implementation:
[A-Za-z0-9._-](Python validator +scenario.schema.json), with a path-containment check in the runner as defense-in-depth. A trace lookup can never escape--trace-dir.error(with anerror_reason) and the suite continues — one broken input never hides the rest.not_runcounts are surfaced so a green suite cannot hide a suite that tested nothing.Testing
suite_result.schema.jsoncontract test).ruff checkandmypyclean.--exit-on-failexit codes, out-dir writes, and missing-trace resilience.AI-assisted contribution disclosure
suitecommand, result models, schema, tests, and docs.🤖 Generated with Claude Code