ov
ov – OverView: agent-driven web reconnaissance & analysis.
Point ov at a URL and it drives the target like a user, recording two
parallel streams – the behavioral stream (what the app does) and the static
stream (what it is made of) – then runs two analysis lenses (UX and
software-design) and renders Markdown reports plus a single synopsis.
This module is the progressive-disclosure facade: the five top-level functions below are all most callers need, and each works with sensible defaults. Everything underneath (capture probes, operate primitives, analyzers, report sections) is reachable but never required.
>>> import ov
>>> callable(ov.observe) and callable(ov.overview)
True
The primary consumption model is a host agent (Claude Code) wielding these
functions via the skills in .claude/skills/ – the deterministic core here
runs with no model and no host.
- class ov.CaptureRun(*, run_id: str = <factory>, target_url: str = '', mode: Literal['reconstruct', 'review']='reconstruct', started_at: datetime = <factory>, finished_at: datetime | None = None, steps: list[JourneyStep] = <factory>, artifacts: list[Artifact] = <factory>, fingerprint: list[TechFinding] = <factory>, rendering_model: str | None = None, source_maps_present: bool | None = None, api_surface: list[Endpoint] = <factory>, findings: list[Finding] = <factory>, settings_snapshot: dict[str, ~typing.Any]=<factory>, notes: list[str] = <factory>)[source]
Everything one
observeproduced; artifacts live in the store by id.- artifact_by_id(artifact_id: str) Artifact | None[source]
Return the
Artifactwithartifact_idorNone.
- model_config = {'validate_assignment': False}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class ov.OvConfig(*, store_root: Path = <factory>, headed: bool = False, browser: str = 'chromium', nav_timeout_ms: int = 30000, default_probes: tuple[str, ...]=('navigation', 'network', 'dom', 'screenshot', 'console', 'fingerprint', 'assets', 'a11y'), max_body_bytes: int = 2000000, console_text_cap: int = 2000, ws_frame_cap: int = 4096, capture_body_content_types: tuple[str, ...]=('application/json', 'application/javascript', 'text/javascript', 'text/html', 'text/css', 'text/event-stream', 'application/graphql', 'application/xml', 'text/plain'), max_steps: int = 40, max_failures: int = 6, wall_clock_s: float = 300.0, no_progress_steps: int = 3, redact_values: bool = True, capture_secrets: bool = False, respect_robots: bool = True, polite_rate_s: float = 0.3, authorized: bool = False, use_proxy: bool = False, stealth_profile: str | None = None)[source]
Resolved settings for a capture/analysis run (keyword-only, env-overridable).
Construct with
from_env()for the env-aware default, or pass explicit fields to override. The instance is snapshotted into everyCaptureRun(settings_snapshot) for provenance.
- class ov.RunDiff(*, run_id: str, baseline_run_id: str, target_url: str = '', created_at: datetime = <factory>, finding_deltas: list[FindingDelta] = <factory>, tech_added: list[str] = <factory>, tech_removed: list[str] = <factory>, endpoints_added: list[str] = <factory>, endpoints_removed: list[str] = <factory>, rendering_model_change: dict[str, ~typing.Any] | None=None, source_maps_change: dict[str, ~typing.Any] | None=None, notes: list[str] = <factory>)[source]
Own-target regression: a run vs a stored prior baseline run (review mode, §10).
Produced by
ov.analysis.diff.diff_runs()from two analyzedCaptureRun`s of the same target. The :attr:`finding_deltaslist is the SSOT;counts/has_driftare derived (@computed_field, so they serialize) andregressions/improvementsare views over the deltas – nothing is stored twice.>>> d = RunDiff(run_id="run_b", baseline_run_id="run_a", finding_deltas=[ ... FindingDelta(key="k1", status="new", direction="regression"), ... FindingDelta(key="k2", status="resolved", direction="improvement"), ... ]) >>> d.counts["new"], d.counts["resolved"], d.has_drift (1, 1, True) >>> [r.key for r in d.regressions], [i.key for i in d.improvements] (['k1'], ['k2'])
- property counts: dict[str, int]
Per-status tallies derived from the deltas (the headline of a diff).
- property has_drift: bool
Trueif anything changed across runs (findings, stack, API, or model).
- property improvements: list[FindingDelta]
Deltas that were resolved or got better since the baseline.
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- property regressions: list[FindingDelta]
Deltas that got worse or are newly present (a downstream fix list).
- ov.analyze(run: Any, *, lenses: Iterable[str] = ('ux', 'arch'), llm: Any = None, store: Any = None) dict[source]
Run the deterministic UX + architecture analyzers over a captured run.
Runs fully without a model (
llm=Noneis the default and the only path needed in Phases 1-2). The host agent reasons over the returned analyses + evidence bundle to add narrative judgment.
- ov.check_requirements(*, components: Iterable[str] | None = None, verbose: bool = True) RequirementReport[source]
Detect missing system dependencies and print exact install commands.
componentsfilters which groups to check ("browser","node","sidecar","clis");Nonechecks all. The deterministic analysis core needs none of these – they gate capture and the arch sidecar, so every requirement here is reported as optional unless capture is requested.>>> rep = check_requirements(components=["clis"], verbose=False) >>> isinstance(rep, RequirementReport) True
- ov.diff(run: Any, *, baseline: Any = None, store: Any = None) RunDiff | None[source]
Diff an analyzed run against a prior baseline run (own-target review mode).
The distinguishing capability of
mode="review": report what is new, changed, or resolved versus a stored prior run of the same target (regression/drift detection, §10). Fully deterministic – no browser, no model.- Parameters:
run – an analyzed
CaptureRunor a run id.baseline – a run/run-id to compare against, or
Noneto auto-discover the latest prior run of the same target.store – a
CaptureStore, a path, orNone.
- Returns:
A
RunDiff, orNoneif no baseline exists yet (the first review run). SetsFinding.diff_statuson the run in place and persists adiff_<run_id>blob that the review report + synopsis read.
- ov.observe(url: str, *, goal: str | None = None, journey: list | None = None, mode: str = 'reconstruct', probes: Any = 'default', headed: bool = False, store: Any = None, crawl_pages: int | None = None, config: OvConfig | None = None, authorized: bool | None = None) CaptureRun[source]
Drive the target and capture everything. Zero-config default works.
- Parameters:
url – the target to study.
goal – a natural-language objective (goal-pursuit is host policy; the deterministic core records it and does the baseline journey).
journey – an optional scripted action list (guided-replay), each item an
Actionor a dict like{"type": "click", "ref": "e3"}.mode –
"reconstruct"(foreign target) or"review"(own target).probes –
"default","all", or an iterable of probe names.headed – run the browser headed (default headless).
store – a
CaptureStore, a path, orNone.crawl_pages – if set (>1), politely crawl that many same-origin pages.
config – an explicit
OvConfig(overridesheaded).authorized – acknowledge authorization to study a foreign target.
- Returns:
The populated
CaptureRun(also persisted to the store).
- ov.overview(url: str, **kw: Any) Any[source]
observe -> analyze -> report -> synopsis, the one-liner.Returns the synopsis path/handle. This is the pit-of-success entry point.