Add workspace review summaries

Add workspace summary across the CLI, SDK, and MCP, and include it in the workspace-core profile so chat hosts can review one concise view of the current session.

Persist lightweight review events for syncs, file edits, patch applies, exports, service lifecycle, and snapshot activity, then synthesize them with command history, current services, snapshot state, and current diff data since the last reset.

Update the walkthroughs, use-case docs, public contract, changelog, and roadmap for 4.3.0, and make dist-check invoke the CLI module directly so local package reinstall quirks do not break the packaging gate.

Validation: uv lock; ./.venv/bin/pytest --no-cov tests/test_vm_manager.py tests/test_cli.py tests/test_api.py tests/test_server.py tests/test_public_contract.py tests/test_workspace_use_case_smokes.py; UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache make check; UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache make dist-check; real guest-backed workspace create -> patch apply -> workspace summary --json -> delete smoke.
This commit is contained in:
Thales Maciel 2026-03-13 19:21:11 -03:00
parent 899a6760c4
commit dc86d84e96
24 changed files with 994 additions and 31 deletions

View file

@ -39,6 +39,7 @@ def test_cli_help_guides_first_run() -> None:
assert "pyro host print-config opencode" in help_text
assert "If you want terminal-level visibility into the workspace model:" in help_text
assert "pyro workspace exec WORKSPACE_ID -- cat note.txt" in help_text
assert "pyro workspace summary WORKSPACE_ID" in help_text
assert "pyro workspace snapshot create WORKSPACE_ID checkpoint" in help_text
assert "pyro workspace reset WORKSPACE_ID --snapshot checkpoint" in help_text
assert "pyro workspace sync push WORKSPACE_ID ./changes" in help_text
@ -127,6 +128,7 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
assert "pyro workspace start WORKSPACE_ID" in workspace_help
assert "pyro workspace snapshot create WORKSPACE_ID checkpoint" in workspace_help
assert "pyro workspace reset WORKSPACE_ID --snapshot checkpoint" in workspace_help
assert "pyro workspace summary WORKSPACE_ID" in workspace_help
assert "pyro workspace shell open WORKSPACE_ID --id-only" in workspace_help
workspace_create_help = _subparser_choice(
@ -181,6 +183,12 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
assert "--label" in workspace_update_help
assert "--clear-label" in workspace_update_help
workspace_summary_help = _subparser_choice(
_subparser_choice(parser, "workspace"), "summary"
).format_help()
assert "Summarize the current workspace session since the last reset" in workspace_summary_help
assert "pyro workspace summary WORKSPACE_ID" in workspace_summary_help
workspace_file_help = _subparser_choice(
_subparser_choice(parser, "workspace"), "file"
).format_help()
@ -2515,6 +2523,170 @@ def test_cli_workspace_logs_prints_json(
assert payload["count"] == 0
def test_cli_workspace_summary_prints_json(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
class StubPyro:
def summarize_workspace(self, workspace_id: str) -> dict[str, Any]:
assert workspace_id == "workspace-123"
return {
"workspace_id": workspace_id,
"name": "review-eval",
"labels": {"suite": "smoke"},
"environment": "debian:12",
"state": "started",
"last_activity_at": 2.0,
"session_started_at": 1.0,
"outcome": {
"command_count": 1,
"last_command": {"command": "cat note.txt", "exit_code": 0},
"service_count": 0,
"running_service_count": 0,
"export_count": 1,
"snapshot_count": 1,
"reset_count": 0,
},
"commands": {"total": 1, "recent": []},
"edits": {"recent": []},
"changes": {"available": True, "changed": False, "summary": None, "entries": []},
"services": {"current": [], "recent": []},
"artifacts": {"exports": []},
"snapshots": {"named_count": 1, "recent": []},
}
class SummaryParser:
def parse_args(self) -> argparse.Namespace:
return argparse.Namespace(
command="workspace",
workspace_command="summary",
workspace_id="workspace-123",
json=True,
)
monkeypatch.setattr(cli, "_build_parser", lambda: SummaryParser())
monkeypatch.setattr(cli, "Pyro", StubPyro)
cli.main()
payload = json.loads(capsys.readouterr().out)
assert payload["workspace_id"] == "workspace-123"
assert payload["outcome"]["export_count"] == 1
def test_cli_workspace_summary_prints_human(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
class StubPyro:
def summarize_workspace(self, workspace_id: str) -> dict[str, Any]:
assert workspace_id == "workspace-123"
return {
"workspace_id": workspace_id,
"name": "review-eval",
"labels": {"suite": "smoke", "use_case": "review-eval"},
"environment": "debian:12",
"state": "started",
"last_activity_at": 3.0,
"session_started_at": 1.0,
"outcome": {
"command_count": 2,
"last_command": {"command": "sh review.sh", "exit_code": 0},
"service_count": 1,
"running_service_count": 0,
"export_count": 1,
"snapshot_count": 1,
"reset_count": 0,
},
"commands": {
"total": 2,
"recent": [
{
"sequence": 2,
"command": "sh review.sh",
"cwd": "/workspace",
"exit_code": 0,
"duration_ms": 12,
"execution_mode": "guest_vsock",
"recorded_at": 3.0,
}
],
},
"edits": {
"recent": [
{
"event_kind": "patch_apply",
"recorded_at": 2.0,
"path": "/workspace/note.txt",
}
]
},
"changes": {
"available": True,
"changed": True,
"summary": {
"total": 1,
"added": 0,
"modified": 1,
"deleted": 0,
"type_changed": 0,
"text_patched": 1,
"non_text": 0,
},
"entries": [
{
"path": "/workspace/note.txt",
"status": "modified",
"artifact_type": "file",
}
],
},
"services": {
"current": [{"service_name": "app", "state": "stopped"}],
"recent": [
{
"event_kind": "service_stop",
"service_name": "app",
"state": "stopped",
}
],
},
"artifacts": {
"exports": [
{
"workspace_path": "review-report.txt",
"output_path": "/tmp/review-report.txt",
}
]
},
"snapshots": {
"named_count": 1,
"recent": [
{"event_kind": "snapshot_create", "snapshot_name": "checkpoint"}
],
},
}
class SummaryParser:
def parse_args(self) -> argparse.Namespace:
return argparse.Namespace(
command="workspace",
workspace_command="summary",
workspace_id="workspace-123",
json=False,
)
monkeypatch.setattr(cli, "_build_parser", lambda: SummaryParser())
monkeypatch.setattr(cli, "Pyro", StubPyro)
cli.main()
output = capsys.readouterr().out
assert "Workspace review: workspace-123" in output
assert "Outcome: commands=2 services=0/1 exports=1 snapshots=1 resets=0" in output
assert "Recent commands:" in output
assert "Recent edits:" in output
assert "Changes: total=1 added=0 modified=1 deleted=0 type_changed=0 non_text=0" in output
assert "Recent exports:" in output
assert "Recent snapshot events:" in output
def test_cli_workspace_delete_prints_human(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
@ -3028,6 +3200,16 @@ def test_content_only_read_docs_are_aligned() -> None:
assert 'workspace patch apply "$WORKSPACE_ID" --patch-file fix.patch' in first_run
def test_workspace_summary_docs_are_aligned() -> None:
readme = Path("README.md").read_text(encoding="utf-8")
install = Path("docs/install.md").read_text(encoding="utf-8")
first_run = Path("docs/first-run.md").read_text(encoding="utf-8")
assert 'workspace summary "$WORKSPACE_ID"' in readme
assert 'workspace summary "$WORKSPACE_ID"' in install
assert 'workspace summary "$WORKSPACE_ID"' in first_run
def test_cli_workspace_shell_write_signal_close_json(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],