diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be06bd4..a837d3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -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: diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index d3036ff..8029afd 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -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 diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml new file mode 100644 index 0000000..4462432 --- /dev/null +++ b/.github/workflows/reusable-test.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index f41c3fe..dd295e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)). @@ -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`). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d449201..c19613f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 @@ -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 | diff --git a/README.md b/README.md index a49cb9b..9c4a7e9 100644 --- a/README.md +++ b/README.md @@ -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/) diff --git a/aura/cli/commands.py b/aura/cli/commands.py index 9f3d6f7..1065778 100644 --- a/aura/cli/commands.py +++ b/aura/cli/commands.py @@ -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 diff --git a/aura/cli/help_text.py b/aura/cli/help_text.py index 5d993c8..8d5bcea 100644 --- a/aura/cli/help_text.py +++ b/aura/cli/help_text.py @@ -25,6 +25,8 @@ ("aura run ", "run script under audited session"), ("aura logs ", "print session JSONL to stdout"), ("aura export ", "print session summary JSON"), + ("aura report show ", "show a human-readable audit report"), + ("aura report show --json", "print the audit report for CI"), ("aura export-otel ", "write OTel-style JSONL beside session"), ("aura compare ", "diff two session summaries"), ("aura verify chain ", "validate an exported JSONL hash chain"), @@ -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"), @@ -72,6 +74,7 @@ "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", @@ -79,7 +82,7 @@ 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"), diff --git a/aura/cli/interactive.py b/aura/cli/interactive.py index cad06a2..7d8d90e 100644 --- a/aura/cli/interactive.py +++ b/aura/cli/interactive.py @@ -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) @@ -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: diff --git a/aura/cli/main.py b/aura/cli/main.py index 16ddecd..f36c885 100644 --- a/aura/cli/main.py +++ b/aura/cli/main.py @@ -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") @@ -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 [--json]", file=sys.stderr) + return 1 if args.command == "export-otel": return commands.cmd_export_otel(args.session_id) if args.command == "compare": diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md index 4f653dd..fb560c6 100644 --- a/docs/PUBLISHING.md +++ b/docs/PUBLISHING.md @@ -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 diff --git a/docs/TESTING.md b/docs/TESTING.md index dc3de29..3059829 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -26,7 +26,15 @@ CI expectation: **pytest**, **black**, and **flake8** all pass on `aura/` and `t ## Continuous integration -GitHub Actions workflow **[`.github/workflows/ci.yml`](../.github/workflows/ci.yml)** runs on: +GitHub Actions: + +| Workflow | Role | +|---|---| +| **[`ci.yml`](../.github/workflows/ci.yml)** | PR + push to `main` — Python matrix, Skillware job, gate `lint-test` | +| **[`reusable-test.yml`](../.github/workflows/reusable-test.yml)** | **Single source** for install / black / flake8 / pytest (issue [#17](https://github.com/ARPAHLS/aura/issues/17)) | +| **[`publish-pypi.yml`](../.github/workflows/publish-pypi.yml)** | Release gate — calls `reusable-test.yml` on Python 3.12 before upload | + +**[`ci.yml`](../.github/workflows/ci.yml)** runs on: - every **pull request** targeting `main` - every **push** to `main` (post-merge sanity) @@ -37,15 +45,15 @@ Python matrix on **ubuntu-latest** (`fail-fast: true` — one version failure ca python-version: ["3.10", "3.11", "3.12", "3.13"] ``` -Each cell runs the same steps: +Each matrix cell invokes **`reusable-test.yml`** with the same steps: ```bash pip install -e ".[dev]" pip install pip-audit pip-audit # warn-only; does not block the CI gate -black --check aura tests -flake8 aura tests -pytest --cov=aura --cov-report=term-missing +black --check aura tests integrations examples +flake8 aura tests integrations examples +pytest --cov=aura --cov-report=term-missing --ignore=tests/integration ``` The **Dependency audit (warn-only)** step runs `pip-audit` with @@ -59,7 +67,9 @@ The workflow also emits a gate job named **`lint-test`** that succeeds only when **Fork PRs:** the workflow uses `permissions: contents: read` only — no repository secrets, no PyPI OIDC, no deploy environment. -**Publish workflow:** [`.github/workflows/publish-pypi.yml`](../.github/workflows/publish-pypi.yml) runs the same install/lint/test commands on a single Python 3.12 before release upload. The advisory `pip-audit` step belongs to PR CI and is not a release-blocking check. Full 3.10–3.13 coverage is the PR CI matrix; keep the *commands* in sync until a reusable workflow lands (separate CI follow-up issue). +**Publish workflow:** [`.github/workflows/publish-pypi.yml`](../.github/workflows/publish-pypi.yml) calls **`reusable-test.yml`** on Python 3.12 before release upload (no `pip-audit`; same black/flake8/pytest scope as PR CI). Full 3.10–3.13 coverage is the PR CI matrix. + +**Skillware job:** **`skillware-live`** in `ci.yml` calls **`reusable-test.yml`** with `skillware: true` — `pip install -e ".[dev,skillware]"` and `pytest tests/test_skillware_integration.py`. **Maintainers:** after the first green `lint-test` run on `main`, enable **branch protection** → required status check **`lint-test`**. @@ -107,9 +117,9 @@ Integration tests **fail** (not skip) if Ollama or Skillware is missing — that ## Pre-PR checklist -1. `pytest` -2. `black aura tests` (no diff) -3. `flake8 aura tests` +1. `pytest --ignore=tests/integration` +2. `black aura tests integrations examples` (no diff) +3. `flake8 aura tests integrations examples` 4. CHANGELOG entry under `[Unreleased]` or release section 5. Docs updated if behavior or CLI changed diff --git a/docs/contributing/ai_native_workflow.md b/docs/contributing/ai_native_workflow.md index 6eeb921..1300e00 100644 --- a/docs/contributing/ai_native_workflow.md +++ b/docs/contributing/ai_native_workflow.md @@ -45,7 +45,7 @@ Written for **autonomous and semi-autonomous agents** working on AURA Harness. H - **Session export:** `.jsonl`, `.summary.json` (with `audit_report`), `.otel.jsonl` - **CLI:** `aura agent create/set`, `config show`, `paths`, `run`, `logs`, `export`, `export-otel`, `compare` - **SDK helpers:** `AuditSpine.from_jsonl()` for disk verify; `compare_sessions()` includes `agent_ref` and `hash_chain_valid` diffs -- **CI:** Python 3.10–3.13 matrix on every PR; gate job `lint-test` ([`ci.yml`](../../.github/workflows/ci.yml)) +- **CI:** Python 3.10–3.13 matrix on every PR; gate job `lint-test` ([`ci.yml`](../../.github/workflows/ci.yml) → [`reusable-test.yml`](../../.github/workflows/reusable-test.yml)) --- diff --git a/docs/getting-started.md b/docs/getting-started.md index a54886d..2ed8633 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -80,6 +80,8 @@ aura agent list aura run my-bot path/to/script.py aura logs aura_sess_xxxxxxxxxxxx aura export aura_sess_xxxxxxxxxxxx +aura report show aura_sess_xxxxxxxxxxxx +aura report show aura_sess_xxxxxxxxxxxx --json aura verify chain ~/.aura/sessions/aura_sess_xxxxxxxxxxxx.jsonl ``` diff --git a/docs/outputs.md b/docs/outputs.md index 248ba62..7c90c1b 100644 --- a/docs/outputs.md +++ b/docs/outputs.md @@ -10,9 +10,9 @@ What a session produces on close (v0.3). |---|---|---| | **Audit trail** | `{session_id}.jsonl` | Append-only AuraEvents with causal ids + hash chain | | **Summary** | `{session_id}.summary.json` | Metadata, conformance, audit report | -| **OTel JSONL** | `{session_id}.otel.jsonl` | Span-style records mapped from events | +| **OTel JSONL** | `{session_id}.otel.jsonl` | Span-style records mapped from events; written on close by default and refreshed by `aura export-otel` | -CLI: `aura export`, `aura export-otel`, `aura compare`, `aura verify chain `. +CLI: `aura report show `, `aura report show --json`, `aura export`, `aura export-otel`, `aura compare`, `aura verify chain `. --- @@ -30,6 +30,8 @@ CLI: `aura export`, `aura export-otel`, `aura compare`, `aura verify chain