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 observe produced; artifacts live in the store by id.

artifact_by_id(artifact_id: str) Artifact | None[source]

Return the Artifact with artifact_id or None.

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 every CaptureRun (settings_snapshot) for provenance.

classmethod from_env(**overrides: Any) OvConfig[source]

Build a config from OV_* env vars, with explicit overrides winning.

>>> isinstance(OvConfig.from_env().store_root, Path)
True
snapshot() dict[str, Any][source]

Return a JSON-serializable snapshot for CaptureRun.settings_snapshot.

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 analyzed CaptureRun`s of the same target. The :attr:`finding_deltas list is the SSOT; counts/has_drift are derived (@computed_field, so they serialize) and regressions/improvements are 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

True if 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=None is 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.

components filters which groups to check ("browser", "node", "sidecar", "clis"); None checks 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 CaptureRun or a run id.

  • baseline – a run/run-id to compare against, or None to auto-discover the latest prior run of the same target.

  • store – a CaptureStore, a path, or None.

Returns:

A RunDiff, or None if no baseline exists yet (the first review run). Sets Finding.diff_status on the run in place and persists a diff_<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 Action or 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, or None.

  • crawl_pages – if set (>1), politely crawl that many same-origin pages.

  • config – an explicit OvConfig (overrides headed).

  • 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.

ov.report(run_or_analyses: Any, *, sections: Any = 'default', out_dir: Any = None, store: Any = None) list[source]

Render Markdown reports from analyses. Returns the written paths/keys.

ov.synopsis(reports_or_dir: Any, *, out: Any = None, store: Any = None) Any[source]

Extract-and-aggregate many reports into one synopsis for downstream agents.