Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 10 additions & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# PR and main-branch CI — lint and test only (no secrets, no PyPI OIDC).
# Keep install/lint/test steps in sync with publish-pypi.yml test job until issue #7
# (reusable workflow) lands. PR CI also matrices Python versions; publish stays
# single-version as a release gate (see docs/TESTING.md).
# Lint/test steps live in reusable-test.yml (issue #17). PR CI matrices Python
# versions; publish stays single-version as a release gate (see docs/TESTING.md).

name: CI

Expand All @@ -21,46 +20,21 @@ concurrency:
jobs:
lint-test:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dev dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Dependency audit (warn-only)
continue-on-error: true
run: |
pip install pip-audit
pip-audit
- name: black
run: black --check aura tests integrations examples
- name: flake8
run: flake8 aura tests integrations examples
- name: pytest
run: pytest --cov=aura --cov-report=term-missing --ignore=tests/integration
uses: ./.github/workflows/reusable-test.yml
with:
python-version: ${{ matrix.python-version }}
pip-audit: true

skillware-live:
name: Skillware registry (live)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install with Skillware extra
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,skillware]"
- name: pytest skillware
run: pytest tests/test_skillware_integration.py -v
uses: ./.github/workflows/reusable-test.yml
with:
python-version: "3.12"
skillware: true

# Stable check name for branch protection (matrix cells are "Python 3.xx").
ci-ok:
Expand Down
19 changes: 4 additions & 15 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,11 @@ permissions:
contents: read

jobs:
# Commands must match .github/workflows/ci.yml (see docs/TESTING.md).
# PR CI matrices 3.10–3.13; this job stays on 3.12 as a single-version release gate.
# Same lint/test as PR CI via reusable-test.yml (Python 3.12 release gate).
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install and test
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
black --check aura tests integrations examples
flake8 aura tests
pytest --cov=aura --cov-report=term-missing
uses: ./.github/workflows/reusable-test.yml
with:
python-version: "3.12"

publish:
needs: test
Expand Down
65 changes: 65 additions & 0 deletions .github/workflows/reusable-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Callable lint + test job — single source for PR CI and PyPI publish (issue #17).
# Callers: ci.yml (matrix + skillware-live), publish-pypi.yml (release gate).

name: Reusable lint and test

on:
workflow_call:
inputs:
python-version:
required: true
type: string
pip-audit:
description: Run pip-audit (warn-only; does not fail the job)
required: false
type: boolean
default: false
skillware:
description: Install [skillware] and run registry integration tests only
required: false
type: boolean
default: false

permissions:
contents: read

jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ "${{ inputs.skillware }}" = "true" ]; then
pip install -e ".[dev,skillware]"
else
pip install -e ".[dev]"
fi

- name: Dependency audit (warn-only)
if: inputs.pip-audit == true
continue-on-error: true
run: |
pip install pip-audit
pip-audit

- name: black
if: inputs.skillware != true
run: black --check aura tests integrations examples

- name: flake8
if: inputs.skillware != true
run: flake8 aura tests integrations examples

- name: pytest (gate suite)
if: inputs.skillware != true
run: pytest --cov=aura --cov-report=term-missing --ignore=tests/integration

- name: pytest skillware
if: inputs.skillware == true
run: pytest tests/test_skillware_integration.py -v
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **`aura report show`** — print a human-readable audit report, or use `--json` for CI output ([#25](https://github.com/ARPAHLS/aura/issues/25)).
- **Session close and export guide** — `using-aura.md` now covers audit reports, export commands, and receipt review ([#37](https://github.com/ARPAHLS/aura/issues/37)).
- **Break observer preset** — `observer.alert` on repeated tool intents ([#34](https://github.com/ARPAHLS/aura/issues/34)).
- **Sequencer `when`** — conditional step skip with `sequencer.step.skipped` on the spine.
- **Ingress bind enrichment** — `host.bind`, `bound_skill_ids`, `session_snapshot_hash` on `skill.registered` ([#33](https://github.com/ARPAHLS/aura/issues/33)).
Expand Down Expand Up @@ -38,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **Reusable CI workflow** — `.github/workflows/reusable-test.yml` shared by PR CI and PyPI publish; fixes publish drift (flake8 scope, `--ignore=tests/integration`) ([#17](https://github.com/ARPAHLS/aura/issues/17)).

- **Docs sync (post–#12)** — INDEX, ROADMAP, integration guides, follow-ups backlog, OTel/observer sections aligned with PR #43 closure ([#41](https://github.com/ARPAHLS/aura/issues/41), [#22](https://github.com/ARPAHLS/aura/issues/22)).

- **Example 06** — compress step skips when scan `is_safe` is false (sequencer `when`).
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Follow the [Code of Conduct](CODE_OF_CONDUCT.md). We welcome autonomous logical

- Shared fixtures: **`tests/conftest.py`** (`aura_home`, `run_aura` for CLI subprocess tests).
- Full suite is **64+ tests** across `test_core.py`, `test_core_gaps.py`, `test_v02.py`, `test_v03.py`, `test_cli.py`, `test_examples_smoke.py` — see [TESTING.md](docs/TESTING.md).
- CI runs on PRs via [`.github/workflows/ci.yml`](.github/workflows/ci.yml) (Python **3.10–3.13** matrix; gate job **`lint-test`**: pytest with coverage report, black, flake8). Each matrix cell also runs `pip-audit` as a warn-only dependency check; its findings or audit errors do not fail the gate or block a PR. See [TESTING.md](docs/TESTING.md) for the exact commands.
- CI runs on PRs via [`.github/workflows/ci.yml`](.github/workflows/ci.yml) (Python **3.10–3.13** matrix; gate job **`lint-test`**: pytest with coverage report, black, flake8). Steps are defined once in [`.github/workflows/reusable-test.yml`](.github/workflows/reusable-test.yml). Each matrix cell also runs `pip-audit` as a warn-only dependency check; its findings or audit errors do not fail the gate or block a PR. See [TESTING.md](docs/TESTING.md) for the exact commands.
- Wait for green checks before requesting review.

### CHANGELOG
Expand Down Expand Up @@ -161,7 +161,7 @@ Pure internal refactors with no user-visible effect may omit CHANGELOG; ask on t
| New core example | `examples/README.md`, optional link from `docs/getting-started.md` |
| Architecture terminology | `docs/architecture.md`, `README.md` diagrams (keep in sync) |
| Release / PyPI behavior | `docs/PUBLISHING.md`, `.github/workflows/publish-pypi.yml`, CHANGELOG |
| PR CI workflow | `.github/workflows/ci.yml`, `docs/TESTING.md`, `CONTRIBUTING.md`; keep publish test job in sync |
| PR CI workflow | `.github/workflows/ci.yml`, `.github/workflows/reusable-test.yml`, `docs/TESTING.md`, `CONTRIBUTING.md` |
| Issue template fields | `.github/labels.json` if new label needed; run label sync |
| Version (maintainer only) | `pyproject.toml`, `aura/__init__.py`, `CITATION.cff`, CHANGELOG release section, Zenodo if archived |

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ with ag.session() as run:
print(run.exports)
```

CLI: `aura agent create`, `aura run`, `aura export`, `aura compare`, `aura export-otel`, `aura verify chain`.
CLI: `aura agent create`, `aura run`, `aura export`, `aura report show`, `aura compare`, `aura export-otel`, `aura verify chain`.

→ [getting-started.md](docs/getting-started.md) · [examples/](examples/)

Expand Down
68 changes: 68 additions & 0 deletions aura/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,74 @@ def cmd_export(session_id: str, *, console: Console | None = None) -> int:
return 0


def cmd_report_show(
session_id: str,
*,
json_output: bool = False,
console: Console | None = None,
) -> int:
from aura.config import get_config

path = get_config().sessions_dir() / f"{session_id}.summary.json"
if not path.is_file():
message = f"not found: {path}"
if console is None:
print(message, file=sys.stderr)
else:
console.print(message, style="bold #FF9AA2")
return 1

summary = json.loads(path.read_text(encoding="utf-8"))
report = summary.get("audit_report")
if not isinstance(report, dict):
message = f"audit report missing: {path}"
if console is None:
print(message, file=sys.stderr)
else:
console.print(message, style="bold #FF9AA2")
return 1

if json_output:
payload = json.dumps(report, indent=2)
if console is None:
print(payload)
else:
console.print(payload, style="dim")
return 0

verdict = str(report.get("verdict", "unknown")).upper()
print(f"Verdict: {verdict}")
print(f"Hash chain valid: {report.get('hash_chain_valid')}")
print("Scorecard:")
scorecard = report.get("scorecard") or {}
for section, values in scorecard.items():
if isinstance(values, dict):
details = ", ".join(f"{key}={value}" for key, value in values.items())
print(f" {section}: {details}")
else:
print(f" {section}: {values}")

findings = report.get("findings") or []
print("Findings:")
if findings:
for finding in findings:
severity = str(finding.get("severity", "info")).upper()
code = finding.get("code", "UNKNOWN")
message = finding.get("message", "")
print(f" [{severity}] {code}: {message}")
else:
print(" none")

recommendations = report.get("recommendations") or []
print("Recommendations:")
if recommendations:
for recommendation in recommendations:
print(f" - {recommendation}")
else:
print(" none")
return 0


def cmd_export_otel(session_id: str, *, console: Console | None = None) -> int:
from aura.config import get_config

Expand Down
7 changes: 5 additions & 2 deletions aura/cli/help_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
("aura run <agent> <script.py>", "run script under audited session"),
("aura logs <session_id>", "print session JSONL to stdout"),
("aura export <session_id>", "print session summary JSON"),
("aura report show <session_id>", "show a human-readable audit report"),
("aura report show <id> --json", "print the audit report for CI"),
("aura export-otel <session_id>", "write OTel-style JSONL beside session"),
("aura compare <a> <b>", "diff two session summaries"),
("aura verify chain <path>", "validate an exported JSONL hash chain"),
Expand Down Expand Up @@ -56,7 +58,7 @@

_HELP_MENU: List[Tuple[str, str, str, Union[int, str]]] = [
("1", "agents", "create, list, show, set", 0),
("2", "sessions", "run, logs, export, compare, verify", 1),
("2", "sessions", "run, logs, report, export, compare, verify", 1),
("3", "paths", "AURA_HOME and project storage", 2),
("4", "general", "menu, help, version", 3),
("5", "install", "pip install aura-harness", "install"),
Expand All @@ -72,14 +74,15 @@
"aura paths set-project .",
"aura run cli-runner path/to/script.py",
"aura export aura_sess_01H...",
"aura report show aura_sess_01H...",
"aura compare sess_a sess_b",
"aura export-otel aura_sess_01H...",
"aura verify chain path/to/session.jsonl",
)

MAIN_MENU: List[Tuple[str, str, str]] = [
("1", "agents", "list, show, create, or edit profiles"),
("2", "sessions", "logs, export, compare, verify, or export-otel"),
("2", "sessions", "logs, report, export, compare, verify, or export-otel"),
("3", "run", "run a Python script under an agent session"),
("4", "paths", "view/edit AURA_HOME, project, and config"),
("5", "help", "grouped CLI reference and doc links"),
Expand Down
19 changes: 13 additions & 6 deletions aura/cli/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,20 +133,23 @@ def _sessions_submenu(
"logs": "logs",
"2": "export",
"export": "export",
"3": "export-otel",
"3": "report",
"report": "report",
"4": "export-otel",
"otel": "export-otel",
"4": "compare",
"5": "compare",
"compare": "compare",
"5": "verify",
"6": "verify",
"verify": "verify",
}
while True:
console.print(Text("Sessions", style=f"bold {TABLE_STYLE}"))
console.print(" [1] logs — print session JSONL", style=MENU_STYLE)
console.print(" [2] export — session summary JSON", style=MENU_STYLE)
console.print(" [3] export-otel — OTel-style JSONL export", style=MENU_STYLE)
console.print(" [4] compare — diff two session summaries", style=MENU_STYLE)
console.print(" [5] verify — validate an exported hash chain", style=MENU_STYLE)
console.print(" [3] report — show audit report", style=MENU_STYLE)
console.print(" [4] export-otel — OTel-style JSONL export", style=MENU_STYLE)
console.print(" [5] compare — diff two session summaries", style=MENU_STYLE)
console.print(" [6] verify — validate an exported hash chain", style=MENU_STYLE)
_print_nav_footer(console, show_back=True)

raw = _read_line(" sessions> ", input_fn)
Expand All @@ -171,6 +174,10 @@ def _sessions_submenu(
session_id = _read_line(" session_id> ", input_fn)
if session_id and session_id.strip():
commands.cmd_export_otel(session_id.strip(), console=console)
elif command == "report":
session_id = _read_line(" session_id> ", input_fn)
if session_id and session_id.strip():
commands.cmd_report_show(session_id.strip(), console=console)
elif command == "compare":
session_a = _read_line(" session_a> ", input_fn)
if session_a is None:
Expand Down
11 changes: 11 additions & 0 deletions aura/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ def build_parser() -> argparse.ArgumentParser:
export_p = sub.add_parser("export", help="Print session summary JSON")
export_p.add_argument("session_id", help="Session id")

report_p = sub.add_parser("report", help="Inspect session audit reports")
report_sub = report_p.add_subparsers(dest="report_command")
report_show_p = report_sub.add_parser("show", help="Show a session audit report")
report_show_p.add_argument("session_id", help="Session id")
report_show_p.add_argument("--json", action="store_true", help="Print report JSON")

otel_p = sub.add_parser("export-otel", help="Export session as OTel-style JSONL")
otel_p.add_argument("session_id", help="Session id")

Expand Down Expand Up @@ -145,6 +151,11 @@ def dispatch(args: argparse.Namespace) -> int:
return commands.cmd_logs(args.session_id)
if args.command == "export":
return commands.cmd_export(args.session_id)
if args.command == "report":
if args.report_command == "show":
return commands.cmd_report_show(args.session_id, json_output=args.json)
print("usage: aura report show <session_id> [--json]", file=sys.stderr)
return 1
if args.command == "export-otel":
return commands.cmd_export_otel(args.session_id)
if args.command == "compare":
Expand Down
2 changes: 1 addition & 1 deletion docs/PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Releases are published automatically by [`.github/workflows/publish-pypi.yml`](.

Pushing a `v*` tag alone does **not** publish; create and publish the GitHub Release from that tag.

The workflow runs tests (Python 3.12), builds sdist/wheel, then uploads to PyPI. **Pull requests** are gated separately by [`.github/workflows/ci.yml`](.github/workflows/ci.yml) (Python 3.10–3.13 matrix; `lint-test` on every PR to `main`).
The workflow calls **`reusable-test.yml`** (Python 3.12), builds sdist/wheel, then uploads to PyPI. **Pull requests** are gated separately by [`.github/workflows/ci.yml`](.github/workflows/ci.yml) (Python 3.10–3.13 matrix; `lint-test` on every PR to `main`).

## One-time PyPI setup

Expand Down
Loading
Loading