ghostlab 0.1.0__tar.gz → 0.2.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 (136) hide show
  1. {ghostlab-0.1.0 → ghostlab-0.2.0}/.gitignore +9 -0
  2. ghostlab-0.2.0/PKG-INFO +506 -0
  3. ghostlab-0.2.0/README.md +466 -0
  4. ghostlab-0.2.0/docs/cli.md +354 -0
  5. {ghostlab-0.1.0 → ghostlab-0.2.0}/docs/concepts.md +19 -0
  6. ghostlab-0.2.0/docs/getting-started.md +126 -0
  7. ghostlab-0.2.0/docs/ghostlab-vision-gap-and-roadmap.md +963 -0
  8. {ghostlab-0.1.0 → ghostlab-0.2.0}/mkdocs.yml +1 -0
  9. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/__init__.py +1 -1
  10. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/executor.py +18 -5
  11. ghostlab-0.2.0/rehearsal/apps_host/live.py +356 -0
  12. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/protocol.py +81 -4
  13. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/renderer.py +81 -16
  14. ghostlab-0.2.0/rehearsal/cli.py +2238 -0
  15. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/config.py +27 -12
  16. ghostlab-0.2.0/rehearsal/contract.py +501 -0
  17. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/critique.py +18 -6
  18. ghostlab-0.2.0/rehearsal/dashboard.py +456 -0
  19. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/dataset.py +2 -1
  20. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/evaluate.py +33 -17
  21. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/generate.py +22 -9
  22. ghostlab-0.2.0/rehearsal/hosts/__init__.py +63 -0
  23. ghostlab-0.2.0/rehearsal/hosts/base.py +80 -0
  24. ghostlab-0.2.0/rehearsal/hosts/direct.py +236 -0
  25. ghostlab-0.2.0/rehearsal/hosts/runner.py +295 -0
  26. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/inspect.py +7 -1
  27. ghostlab-0.2.0/rehearsal/jobs.py +245 -0
  28. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/mcp_apps.py +70 -0
  29. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/mcp_client.py +96 -20
  30. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/mcp_config.py +4 -2
  31. ghostlab-0.2.0/rehearsal/mcp_targets.py +128 -0
  32. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/orchestrator.py +54 -1
  33. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/personas.py +20 -7
  34. ghostlab-0.2.0/rehearsal/plan.py +611 -0
  35. ghostlab-0.2.0/rehearsal/plan_generate.py +131 -0
  36. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/profile.py +14 -3
  37. ghostlab-0.2.0/rehearsal/prompts.py +218 -0
  38. ghostlab-0.2.0/rehearsal/readiness.py +386 -0
  39. ghostlab-0.2.0/rehearsal/sampling.py +300 -0
  40. ghostlab-0.2.0/rehearsal/setup_runtime.py +331 -0
  41. ghostlab-0.2.0/rehearsal/spec.py +529 -0
  42. ghostlab-0.2.0/rehearsal/termcolor.py +96 -0
  43. ghostlab-0.2.0/rehearsal/testrun.py +237 -0
  44. ghostlab-0.2.0/rehearsal/ui/app.py +487 -0
  45. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_apps_host.py +6 -1
  46. ghostlab-0.2.0/tests/test_apps_live.py +126 -0
  47. ghostlab-0.2.0/tests/test_cli_spec_flow.py +471 -0
  48. ghostlab-0.2.0/tests/test_contract.py +216 -0
  49. ghostlab-0.2.0/tests/test_hosts_runner.py +304 -0
  50. ghostlab-0.2.0/tests/test_jobs.py +192 -0
  51. ghostlab-0.2.0/tests/test_mcp_client.py +72 -0
  52. ghostlab-0.2.0/tests/test_mcp_targets.py +110 -0
  53. ghostlab-0.2.0/tests/test_plan.py +201 -0
  54. ghostlab-0.2.0/tests/test_plan_generate.py +105 -0
  55. ghostlab-0.2.0/tests/test_readiness.py +169 -0
  56. ghostlab-0.2.0/tests/test_sampling.py +187 -0
  57. ghostlab-0.2.0/tests/test_setup_runtime.py +160 -0
  58. ghostlab-0.2.0/tests/test_spec.py +160 -0
  59. ghostlab-0.2.0/tests/test_testrun.py +240 -0
  60. ghostlab-0.2.0/tests/test_widgets_and_dashboard.py +138 -0
  61. ghostlab-0.1.0/PKG-INFO +0 -669
  62. ghostlab-0.1.0/README.md +0 -629
  63. ghostlab-0.1.0/docs/cli.md +0 -146
  64. ghostlab-0.1.0/docs/getting-started.md +0 -49
  65. ghostlab-0.1.0/rehearsal/cli.py +0 -1027
  66. ghostlab-0.1.0/rehearsal/prompts.py +0 -90
  67. ghostlab-0.1.0/rehearsal/ui/app.py +0 -1095
  68. {ghostlab-0.1.0 → ghostlab-0.2.0}/CONTRIBUTING.md +0 -0
  69. {ghostlab-0.1.0 → ghostlab-0.2.0}/LICENSE +0 -0
  70. {ghostlab-0.1.0 → ghostlab-0.2.0}/docs/datasets-evaluation.md +0 -0
  71. {ghostlab-0.1.0 → ghostlab-0.2.0}/docs/index.md +0 -0
  72. {ghostlab-0.1.0 → ghostlab-0.2.0}/docs/publishing.md +0 -0
  73. {ghostlab-0.1.0 → ghostlab-0.2.0}/ghostlab/__init__.py +0 -0
  74. {ghostlab-0.1.0 → ghostlab-0.2.0}/ghostlab/__main__.py +0 -0
  75. {ghostlab-0.1.0 → ghostlab-0.2.0}/ghostlab/cli.py +0 -0
  76. {ghostlab-0.1.0 → ghostlab-0.2.0}/llms.txt +0 -0
  77. {ghostlab-0.1.0 → ghostlab-0.2.0}/personas/careful-celpip-beginner.json +0 -0
  78. {ghostlab-0.1.0 → ghostlab-0.2.0}/personas/general-learning-edge-case.json +0 -0
  79. {ghostlab-0.1.0 → ghostlab-0.2.0}/personas/ielts-power-user.json +0 -0
  80. {ghostlab-0.1.0 → ghostlab-0.2.0}/personas/skeptical-toefl-retaker.json +0 -0
  81. {ghostlab-0.1.0 → ghostlab-0.2.0}/pyproject.toml +0 -0
  82. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/__main__.py +0 -0
  83. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/__init__.py +0 -0
  84. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/assertions.py +0 -0
  85. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/report.py +0 -0
  86. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/codex_backend.py +0 -0
  87. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/compare.py +0 -0
  88. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/logging.py +0 -0
  89. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/report.py +0 -0
  90. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/review.py +0 -0
  91. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/runner_presets.py +0 -0
  92. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/runners.py +0 -0
  93. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/scorecard.py +0 -0
  94. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/__init__.py +0 -0
  95. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/db.py +0 -0
  96. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/hashing.py +0 -0
  97. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/ids.py +0 -0
  98. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/migrations/0001_initial.sql +0 -0
  99. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/redact.py +0 -0
  100. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/repository.py +0 -0
  101. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/tool_capture.py +0 -0
  102. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/types.py +0 -0
  103. {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/ui/__init__.py +0 -0
  104. {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/claude-process.example.json +0 -0
  105. {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-cortex-aut.json +0 -0
  106. {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-cortex-local-aut.json +0 -0
  107. {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-cortex-local-session.json +0 -0
  108. {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-process.example.json +0 -0
  109. {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-user-emulator.json +0 -0
  110. {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/mock-aut.json +0 -0
  111. {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/mock-user.json +0 -0
  112. {ghostlab-0.1.0 → ghostlab-0.2.0}/scenarios/basic-discovery.json +0 -0
  113. {ghostlab-0.1.0 → ghostlab-0.2.0}/scenarios/cortex-onboarding-status.json +0 -0
  114. {ghostlab-0.1.0 → ghostlab-0.2.0}/scenarios/cortex-practice-generation.json +0 -0
  115. {ghostlab-0.1.0 → ghostlab-0.2.0}/scenarios/error-recovery.json +0 -0
  116. {ghostlab-0.1.0 → ghostlab-0.2.0}/targets/cortex-local.json +0 -0
  117. {ghostlab-0.1.0 → ghostlab-0.2.0}/targets/cortex-ngrok.json +0 -0
  118. {ghostlab-0.1.0 → ghostlab-0.2.0}/targets/example-http.json +0 -0
  119. {ghostlab-0.1.0 → ghostlab-0.2.0}/targets/example-stdio.json +0 -0
  120. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_compare.py +0 -0
  121. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_config.py +0 -0
  122. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_critique.py +0 -0
  123. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_dataset.py +0 -0
  124. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_evaluate.py +0 -0
  125. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_generate.py +0 -0
  126. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_inspect_lint.py +0 -0
  127. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_mcp_apps.py +0 -0
  128. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_orchestrator.py +0 -0
  129. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_personas.py +0 -0
  130. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_profile.py +0 -0
  131. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_review.py +0 -0
  132. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_runner_presets.py +0 -0
  133. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_scorecard.py +0 -0
  134. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_session_runner.py +0 -0
  135. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_storage.py +0 -0
  136. {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_tool_capture.py +0 -0
@@ -222,3 +222,12 @@ __marimo__/
222
222
 
223
223
  # Streamlit UI workspace (generated)
224
224
  ghostlab_workspace/
225
+
226
+ # ghostlab spec-flow local artifacts
227
+ .ghostlab/
228
+ ghostlab.sqlite3
229
+
230
+ # ghostlab jobs — track job.yaml + test-plan.*, ignore per-job run artifacts + db
231
+ jobs/*/workspace/
232
+ jobs/*/runs/
233
+ jobs/*/ghostlab.sqlite3*
@@ -0,0 +1,506 @@
1
+ Metadata-Version: 2.4
2
+ Name: ghostlab
3
+ Version: 0.2.0
4
+ Summary: Local end-to-end testing lab for any MCP server: coding agents role-play real users, drive your tools over multiple turns, score runs, and render/interact with MCP Apps ui:// widgets.
5
+ Project-URL: Documentation, https://sajjadgg.github.io/Rehearsal/
6
+ Project-URL: Homepage, https://github.com/sajjadGG/Rehearsal
7
+ Project-URL: Repository, https://github.com/sajjadGG/Rehearsal
8
+ Project-URL: Issues, https://github.com/sajjadGG/Rehearsal/issues
9
+ Project-URL: Changelog, https://github.com/sajjadGG/Rehearsal/releases
10
+ Author: Sajjad Gholamzadeh
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: agents,ai-agents,claude,cli,codex,end-to-end-testing,evaluation,llm,llm-evaluation,mcp,mcp-apps,model-context-protocol,testing
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Topic :: Software Development :: Quality Assurance
26
+ Classifier: Topic :: Software Development :: Testing
27
+ Requires-Python: >=3.10
28
+ Provides-Extra: apps
29
+ Requires-Dist: playwright>=1.40; extra == 'apps'
30
+ Provides-Extra: dev
31
+ Requires-Dist: build>=1.2; extra == 'dev'
32
+ Requires-Dist: mkdocs>=1.6; extra == 'dev'
33
+ Requires-Dist: pytest>=8.0; extra == 'dev'
34
+ Requires-Dist: twine>=5.0; extra == 'dev'
35
+ Provides-Extra: docs
36
+ Requires-Dist: mkdocs>=1.6; extra == 'docs'
37
+ Provides-Extra: ui
38
+ Requires-Dist: streamlit>=1.30; extra == 'ui'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # MCP Rehearsal / Ghostlab
42
+
43
+ > A local, end-to-end **testing lab for any MCP server** — coding agents role-play
44
+ > real users, drive your tools over multiple turns, and the harness captures
45
+ > traces, scores outcomes, and even **renders and clicks through MCP Apps UI
46
+ > widgets**.
47
+
48
+ [![CI](https://github.com/sajjadGG/Rehearsal/actions/workflows/ci.yml/badge.svg)](https://github.com/sajjadGG/Rehearsal/actions)
49
+ [![Docs](https://img.shields.io/badge/docs-wiki-blue)](https://sajjadgg.github.io/Rehearsal/)
50
+ [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue)](pyproject.toml)
51
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
52
+ [![llms.txt](https://img.shields.io/badge/llms.txt-✓-purple)](llms.txt)
53
+
54
+ **Test your MCP server the way it's actually used** — not with unit tests against
55
+ the protocol, but with a real coding agent (Codex / Claude) that picks tools,
56
+ makes mistakes, and tries to accomplish goals, while a second agent plays the
57
+ user. Protocol-level checks (schema errors, a tool call that 500s) are useful
58
+ sanity checks, but they aren't the real test — the real test is whether an
59
+ agent can actually get a task done through your MCP, end to end.
60
+
61
+ 📖 **Docs wiki:** https://sajjadgg.github.io/Rehearsal/ · 🤖 **For agents:** [`llms.txt`](llms.txt) · 🛠 **Contributing:** [`CONTRIBUTING.md`](CONTRIBUTING.md)
62
+
63
+ ## Quickstart
64
+
65
+ ```bash
66
+ python3.13 -m venv .venv
67
+ .venv/bin/pip install -e . # add '.[ui]' for the web UI, '.[apps]' for widget rendering
68
+
69
+ ghostlab create
70
+ ```
71
+
72
+ That's the whole flow. `ghostlab create` walks you through everything, end to end:
73
+
74
+ 1. **Name + target** — the only two prompts. A target is an MCP URL, or a path
75
+ to a target JSON / standard `mcpServers` config.
76
+ 2. **Discover** — connects to the target, lints its contract (schema errors,
77
+ risk labels), and probes any MCP Apps `ui://` widgets.
78
+ 3. **Configure semantic testing** — if `codex` is on your `$PATH`, offers to
79
+ wire it up as the **agent-under-test**: a real coding-agent session with
80
+ your MCP's tools available, so the dual-agent conversations below have
81
+ something to actually drive. (No codex? The wizard still finishes —
82
+ protocol-level suites still run, semantic/security just skip until a host
83
+ is configured; see [Runner Configs](#runner-configs).)
84
+ 4. **Generate a test plan** — personas × scenarios for the semantic/security
85
+ suites, plus deterministic coverage for every discovered tool
86
+ (`test-plan.yaml`), all editable afterward.
87
+ 5. **Pick which suites to run** — defaults to everything; narrow it to just
88
+ `semantic` while you're iterating, or the full set for a release check.
89
+ 6. **Run + review** — executes the plan against your configured host(s),
90
+ writes a colored pass/fail summary plus a dashboard, and prints the
91
+ readiness/gate verdict.
92
+
93
+ Everything the wizard does is one of `discover` / `plan` / `test` / `review`
94
+ under the hood — run any of them standalone afterward to iterate without
95
+ repeating the whole wizard:
96
+
97
+ ```bash
98
+ ghostlab discover --job <name> # re-inspect after the target changes
99
+ ghostlab plan --job <name> # regenerate/curate the test plan
100
+ ghostlab test --job <name> # rerun (add --suite semantic to narrow it)
101
+ ghostlab review --job <name> # the readiness/gate report on its own
102
+ ```
103
+
104
+ A job is a self-contained folder: `jobs/<name>/job.yaml` (target, hosts,
105
+ generation/test defaults, gates — all editable), `test-plan.yaml`, `workspace/`
106
+ (discover/generated/test artifacts + a local sqlite db), and `runs/`.
107
+
108
+ ## What you get
109
+
110
+ | Stage | What it produces |
111
+ | --- | --- |
112
+ | **Discover** | A deterministic `contract.json` (schema lint, risk labels, MCP Apps metadata checks) and a refreshed `capabilities:` section in `job.yaml` |
113
+ | **Plan** | A coverage-driven `test-plan.yaml`: deterministic protocol cases for every tool, plus generated persona/scenario cases for the semantic/security suites |
114
+ | **Test** | Multi-host execution results (`results.json`/`results.md`), a standalone HTML dashboard, and — for conversational cases — full dual-agent transcripts with structured tool-call capture |
115
+ | **Review** | A readiness report: pass/fail gate verdict, failure clusters, and prioritized repairs |
116
+
117
+ ## Goal
118
+
119
+ Build a repeatable, sandboxed tester that can:
120
+
121
+ - Run any target MCP app in an isolated environment.
122
+ - Launch one coding-agent session as the **agent-under-test** (with target MCP injected).
123
+ - Launch another coding-agent session as the **user emulator** (persona + goal driven).
124
+ - Drive multi-turn interactions between them.
125
+ - Capture full traces, tool activity, failures, and outcomes.
126
+
127
+ This lets you test with your existing Codex/Claude usage path, instead of wiring a separate LLM provider deployment just for E2E testing.
128
+
129
+ ## Scope
130
+
131
+ Rehearsal is intentionally **app-agnostic**:
132
+
133
+ - Works with any MCP server reachable by stdio/SSE/streamable HTTP.
134
+ - Supports local or remote MCP endpoints.
135
+ - Supports multiple coding-agent runners (Codex, Claude Code, and future adapters).
136
+
137
+ No Cortex-specific assumptions are required in the core harness.
138
+
139
+ ## Reference
140
+
141
+ Everything below is the individual-command reference and advanced usage —
142
+ useful once you're past the first `ghostlab create` run, or scripting CI.
143
+
144
+ ### Job folder layout
145
+
146
+ ```text
147
+ jobs/<name>/
148
+ job.yaml # target, setup, hosts, generation, test, prompts, gates
149
+ test-plan.yaml # produced by `ghostlab plan`
150
+ workspace/ # discover/, generated/, test/ artifacts + ghostlab.sqlite3
151
+ runs/ # dual-agent run output
152
+ ```
153
+
154
+ ### Core dual-harness architecture
155
+
156
+ 1. **AUT Harness (Agent Under Test)** — starts a coding-agent session (Codex or
157
+ Claude Code), injects the target MCP server config into it, and exposes a
158
+ controlled I/O bridge so it can receive user messages and return
159
+ replies/tool results.
160
+ 2. **User Emulator Harness** — starts a second coding-agent session, gives it a
161
+ scenario file (persona, goals, constraints, success criteria), and asks it
162
+ to act like a realistic user, sending messages turn-by-turn to the AUT.
163
+ 3. **Orchestrator** — coordinates turn-taking, timeouts, retries, and stop
164
+ conditions; logs every message/event in structured format; produces a run
165
+ report with bug candidates and reproduction context.
166
+
167
+ ### Target configuration model
168
+
169
+ Each test run points to a target definition:
170
+
171
+ - `target.id`: unique name (`filesystem-mcp-local`, `my-app-staging`)
172
+ - `transport`: `stdio` | `sse` | `streamable-http`
173
+ - `connection`: command+args+env (stdio) or URL+headers (network transports)
174
+ - `capabilities`: optional expected tools/resources/prompts
175
+ - `startup`: optional health checks and boot timeout
176
+
177
+ ### Commands
178
+
179
+ The package installs two equivalent console scripts: `ghostlab` and `rehearsal`.
180
+
181
+ - `ghostlab create` — the end-to-end wizard described above.
182
+ - `ghostlab init` — create a `job.yaml`/`ghostlab.yaml` from an existing target JSON, without the wizard prompts.
183
+ - `ghostlab discover` — inspect the job's target, lint its contract, refresh capabilities.
184
+ - `ghostlab plan` — generate (or curate) the coverage-driven test plan.
185
+ - `ghostlab test` — execute the test plan across the job's host adapters.
186
+ - `ghostlab review` — readiness report over discover + plan + test artifacts (release gate).
187
+ - `ghostlab inspect` — connect to a target MCP and capture what it exposes (no job needed).
188
+ - `ghostlab profile` — turn an `inspect.json` into a capability profile (codex).
189
+ - `ghostlab generate-scenarios` / `generate-personas` / `generate-dataset` — build reusable persona×scenario datasets outside the job model.
190
+ - `ghostlab review-dataset` / `run-dataset` — curate and run a standalone dataset.
191
+ - `ghostlab run` — run one dual-agent E2E scenario directly.
192
+ - `ghostlab evaluate` — score a run into a pass/fail verdict (codex judge).
193
+ - `ghostlab compare` — diff two dataset runs for regressions.
194
+ - `ghostlab apps-probe` / `apps-render` — probe/render MCP Apps `ui://` widgets.
195
+ - `ghostlab doctor` — check codex and validate runner presets.
196
+ - `ghostlab dashboard` — build a standalone HTML dashboard for a `ghostlab test` run.
197
+ - `ghostlab ui` — launch the Streamlit pipeline UI.
198
+ - `ghostlab db` — manage the SQLite persistence database.
199
+
200
+ ### The UI: `ghostlab ui`
201
+
202
+ Run the whole pipeline from a browser instead of the CLI:
203
+
204
+ ```bash
205
+ pip install 'ghostlab[ui]' # installs streamlit
206
+ ghostlab ui # opens http://localhost:8501
207
+ ```
208
+
209
+ The app mirrors the same job-based flow as `ghostlab create`: pick or create a
210
+ job, discover its target, configure the agent-under-test host, generate and
211
+ curate the test plan, run selected suites, and review colored results and full
212
+ conversation traces — the same `job.yaml`/`test-plan.yaml`/`results.json` a CLI
213
+ run of the same job would produce.
214
+
215
+ ### Understand a new MCP: `inspect`
216
+
217
+ Point it at a target and it introspects the server without any coding-agent
218
+ credits or manual `curl`:
219
+
220
+ ```bash
221
+ ghostlab inspect --target targets/cortex-local.json
222
+ ```
223
+
224
+ This connects over the configured transport (stdio / streamable-HTTP / SSE),
225
+ runs the `initialize` handshake, and pages through `tools/list`,
226
+ `resources/list`, `resources/templates/list`, and `prompts/list`. It writes
227
+ `runs/<id>-inspect/inspect.json` (raw) and `inspect.md` (readable), and **lints**
228
+ tool/resource descriptions for references to tools the server does not actually
229
+ expose. This capability dump is the input to capability profiling and scenario
230
+ generation.
231
+
232
+ ### Profile a new MCP: `profile`
233
+
234
+ Turn the raw `inspect.json` into a structured **capability profile** — the
235
+ bridge between Understand and Generate. Deterministic structure (tool taxonomy
236
+ by name family, read/write state surfaces, gaps) is computed locally; a domain
237
+ summary and inferred multi-step workflows are generated by codex:
238
+
239
+ ```bash
240
+ ghostlab profile \
241
+ --inspect runs/<id>-inspect/inspect.json
242
+ ```
243
+
244
+ It writes `capabilities.json` + `capabilities.md` next to the `inspect.json`.
245
+ Generated workflow steps are filtered to real tool names, so the profile never
246
+ references hallucinated or non-exposed tools.
247
+
248
+ ### Generate scenarios: `generate-scenarios`
249
+
250
+ Generate grounded use-case scenarios the MCP supports, derived from the
251
+ capability profile:
252
+
253
+ ```bash
254
+ ghostlab generate-scenarios \
255
+ --profile runs/<id>-inspect/capabilities.json \
256
+ --n 3 \
257
+ --output-dir scenarios
258
+ ```
259
+
260
+ Scenarios are spread across intents (`happy_path` / `edge_case` / `adversarial`)
261
+ and each declares an `exercises` list of the tools it should drive the assistant
262
+ to use. Tool references are filtered to real tool names.
263
+
264
+ ### Build a persona library: `generate-personas`
265
+
266
+ Personas are reusable **user profiles** decoupled from scenarios, so the same
267
+ persona can be paired with many scenarios (the basis for the dataset matrix).
268
+ Generate a domain-relevant library from a capability profile:
269
+
270
+ ```bash
271
+ ghostlab generate-personas \
272
+ --profile runs/<id>-inspect/capabilities.json \
273
+ --n 4 \
274
+ --output-dir personas
275
+ ```
276
+
277
+ Each persona has a `summary`, behavioral `traits` (terse, impatient, easily
278
+ confused, non-native, ...), and a domain `context` map (native_language,
279
+ target_exam, level, ...). Pass one to a run with `--persona`:
280
+
281
+ ```bash
282
+ ghostlab run ... --persona personas/ielts-power-user.json
283
+ ```
284
+
285
+ ### Build a dataset: `generate-dataset`
286
+
287
+ A dataset is a **persona x scenario matrix** — different users, and different
288
+ scenarios tailored to each of them. For every persona, codex generates
289
+ persona-specific scenarios, and the pairs become runnable cases:
290
+
291
+ ```bash
292
+ ghostlab generate-dataset \
293
+ --profile runs/<id>-inspect/capabilities.json \
294
+ --personas 3 --scenarios-per-persona 3 --seed 7 \
295
+ --name cortex
296
+ ```
297
+
298
+ This writes a self-contained dataset directory:
299
+
300
+ ```text
301
+ datasets/cortex/
302
+ dataset.json manifest: mcp, seed, cases[]
303
+ personas/<id>.json
304
+ scenarios/<id>.json persona-namespaced; inline `persona` is a situational note
305
+ ```
306
+
307
+ ### Review & curate a dataset: `review-dataset`
308
+
309
+ Before spending agent credits, check that the dataset makes sense:
310
+
311
+ ```bash
312
+ ghostlab review-dataset \
313
+ --dataset datasets/cortex \
314
+ --profile runs/<id>-inspect/capabilities.json
315
+ ```
316
+
317
+ This writes `review.md` + `review.json` with a tool-coverage matrix, per-case
318
+ previews, and flags (near-duplicate cases, scenarios exercising non-exposed
319
+ tools, personas with no scenarios). Curation is file-first — each case gets a
320
+ `status` in `dataset.json` (`pending` / `approved` / `rejected` / `needs-edit`):
321
+
322
+ ```bash
323
+ ghostlab review-dataset --dataset datasets/cortex \
324
+ --approve case-a case-b --reject case-c
325
+ ```
326
+
327
+ ### Run a dataset: `run-dataset`
328
+
329
+ ```bash
330
+ ghostlab run-dataset \
331
+ --dataset datasets/cortex \
332
+ --target targets/cortex-local.json \
333
+ --aut-runner runners/codex-cortex-aut.json \
334
+ --user-runner runners/codex-user-emulator.json \
335
+ --limit 2
336
+ ```
337
+
338
+ Each case runs through the orchestrator (with its persona) into its own run
339
+ directory, and a dataset-level `summary.md` + `results.json` capture per-case
340
+ status and turn counts.
341
+
342
+ ### Tool-call capture & output hygiene
343
+
344
+ Every run captures structured MCP tool calls from the agent host. The codex AUT
345
+ runners set `"parser": "codex-json"` and run `codex exec --json`, so the
346
+ orchestrator parses the JSONL stream and records each `mcp_tool_call` with its
347
+ arguments, result, error, and status into `events.jsonl`, with a per-turn table
348
+ in `report.md`. stdout and stderr are kept separate: only stdout (with known
349
+ host noise redacted) becomes the conversational message handed to the other
350
+ agent, while raw stderr is logged for debugging.
351
+
352
+ ### Evaluate a run: `evaluate`
353
+
354
+ ```bash
355
+ ghostlab evaluate --run runs/<id> --capabilities runs/<id>-inspect/capabilities.json
356
+ ```
357
+
358
+ Combines deterministic checks over captured tool calls with a codex LLM-judge
359
+ that scores each `success_criterion`/`failure_signal` from the transcript.
360
+ Writes `verdict.json` + `verdict.md`; exits non-zero unless the verdict is
361
+ `pass`.
362
+
363
+ ### Compare two runs: `compare`
364
+
365
+ ```bash
366
+ ghostlab compare --base runs/<base>-summary --candidate runs/<cand>-summary \
367
+ --output comparison.md
368
+ ```
369
+
370
+ Diffs case-by-case on verdict, listing regressions first, then fixes, then
371
+ other changes. Exits non-zero when there are regressions, so it can gate CI.
372
+
373
+ ### MCP Apps: `apps-probe` / `apps-render`
374
+
375
+ Some MCPs ship **MCP Apps UI** resources — a tool's `_meta.ui.resourceUri`
376
+ points to a `ui://…` HTML widget a compatible host is expected to render.
377
+
378
+ `apps-probe` connects to a target, finds every UI-producing tool, fetches each
379
+ `ui://` resource, and reports render-readiness and CSP diagnostics:
380
+
381
+ ```bash
382
+ ghostlab apps-probe --target targets/cortex-local.json
383
+ ```
384
+
385
+ `apps-render` actually renders a `ui://` widget in headless Chrome, proving a
386
+ user can see and use it — it implements the MCP Apps host bridge, mounts the
387
+ widget in a sandboxed iframe, completes the `ui/initialize` handshake, feeds it
388
+ real tool input/result, and can drive a sequence of UI intents:
389
+
390
+ ```bash
391
+ pip install 'ghostlab[apps]' && playwright install chrome # one-time
392
+ ghostlab apps-render --target targets/cortex-local.json \
393
+ --tool views_generate_sentence_scramble \
394
+ --arguments '{"target_sentence":"The cat sat on the mat","shuffled_elements":["mat","The","on","sat","cat","the"]}' \
395
+ --intent '{"type":"reorder","value":["The","cat","sat","on","the","mat"]}' \
396
+ --intent '{"type":"reveal"}'
397
+ ```
398
+
399
+ It writes `apps-render.json` + `apps-render.md`, a `widget.png` of the initial
400
+ render, and `widget-final.png` after the intents run. Exit status is non-zero
401
+ if the render errored or any assertion failed.
402
+
403
+ ### Session runner (one live agent across turns)
404
+
405
+ By default each turn spawns a fresh agent process and the orchestrator replays
406
+ the transcript. The session runner (`"kind": "codex-session"`) instead keeps
407
+ one codex session alive: turn 1 records the `thread_id`, and later turns run
408
+ `codex exec resume <thread_id>` so codex retains context — fewer tokens, no
409
+ repeated cold-start noise.
410
+
411
+ ```bash
412
+ ghostlab run --target targets/cortex-local.json --scenario <scenario.json> \
413
+ --aut-runner runners/codex-cortex-local-session.json --user-runner <user.json>
414
+ ```
415
+
416
+ ### Validate your setup: `doctor`
417
+
418
+ ```bash
419
+ ghostlab doctor # validates runners/*.json
420
+ ghostlab doctor --runners runners/codex-cortex-local-session.json
421
+ ```
422
+
423
+ Reports the codex binary + version and validates each runner's kind, command,
424
+ and parser.
425
+
426
+ ### Default agent backend
427
+
428
+ `codex` is the default coding-agent backend for generation, the
429
+ agent-under-test, and judging. `inspect` needs no agent — it is a direct MCP
430
+ client. The codex binary is auto-detected from `$PATH`, then the macOS app
431
+ bundle (`/Applications/Codex.app/Contents/Resources/codex`); override with
432
+ `$REHEARSAL_CODEX_BIN` or `--codex-bin`.
433
+
434
+ ### Colored output
435
+
436
+ CLI output is colored automatically on a TTY (dual-agent transcripts,
437
+ pass/fail/skip verdicts, gate failures). Set `NO_COLOR=1` (or
438
+ `GHOSTLAB_COLOR=0`) to disable it, `GHOSTLAB_COLOR=1` to force it on (e.g.
439
+ piping into a pager that groks ANSI).
440
+
441
+ ## Runner Configs
442
+
443
+ Mock runner (no agent, free):
444
+
445
+ ```json
446
+ { "kind": "mock" }
447
+ ```
448
+
449
+ Process runner:
450
+
451
+ ```json
452
+ {
453
+ "kind": "process",
454
+ "command": ["codex", "exec", "-"],
455
+ "env": {},
456
+ "timeout_seconds": 300,
457
+ "prompt_mode": "stdin"
458
+ }
459
+ ```
460
+
461
+ The process runner starts one fresh process per turn. `prompt_mode` can be
462
+ `stdin`, `append-arg`, or `replace-placeholder`. `ghostlab create` synthesizes
463
+ one of these automatically for the agent-under-test host (wiring the target
464
+ MCP in via codex's `-c mcp_servers.<id>...` overrides, including
465
+ `bearer_token_env_var` for `Bearer ${VAR}`-style auth headers) — see
466
+ `jobs/<name>/runners/aut.json` after running it. To use Claude Code or another
467
+ agent as the AUT instead, hand-write a runner JSON (see
468
+ `runners/claude-process.example.json`) and pass it via `--aut-runner` to
469
+ `ghostlab create`/`ghostlab plan`, or add it directly under `hosts:` in
470
+ `job.yaml`.
471
+
472
+ ## Install from PyPI
473
+
474
+ ```bash
475
+ pip install ghostlab # add [ui] and/or [apps] for those extras
476
+ ghostlab --help
477
+ ```
478
+
479
+ ## Packaging & Release
480
+
481
+ Build and validate distributions locally:
482
+
483
+ ```bash
484
+ .venv/bin/python -m pytest
485
+ .venv/bin/python -m build
486
+ .venv/bin/twine check dist/*
487
+ ```
488
+
489
+ CI runs tests on Python 3.10 through 3.13 and verifies that the package builds.
490
+ Releases are automated: the **`publish.yml`** workflow builds the sdist + wheel,
491
+ publishes them to PyPI via **Trusted Publishing**, and attaches them to the
492
+ GitHub Release — triggered when you **publish a GitHub Release** (or run the
493
+ workflow manually). Cut a release like:
494
+
495
+ ```bash
496
+ # bump rehearsal/__init__.py __version__ first, then:
497
+ gh release create v0.1.0 --generate-notes
498
+ ```
499
+
500
+ To enable publishing, create the PyPI project **`ghostlab`** and add a Trusted
501
+ Publisher for this repository, workflow `.github/workflows/publish.yml`,
502
+ environment `pypi`. No PyPI username or token is committed.
503
+
504
+ The Pages workflow builds the docs wiki with MkDocs and deploys it to GitHub
505
+ Pages on pushes to `main`, `v*.*.*` release tags, and manual workflow runs. In
506
+ the GitHub repository settings, set Pages to use GitHub Actions as the source.