Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ jobs:
- name: Dependency hygiene (no direct import of a transitive dep)
run: make lint-deps

- name: Type check (call correctness)
run: make lint-types

# Four runners, each collecting and running a quarter of the test files
# (`--shard`, dealt round-robin in tests/conftest.py), each writing coverage
# data and no report; the `coverage` job below combines the four and runs
Expand Down
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help install install-deps lint lint-python lint-imports lint-deps lint-tui lint-bridge test test-python test-tui build build-tui build-bridge build-ui build-core check-commits check-pr-title check-large-files check-source-language check-core-wheel fetch-templates verify-templates beta ci clean coverage coverage-shard coverage-combine coverage-summary coverage-diff coverage-ratchet coverage-baseline-check coverage-baseline-candidate docker-build docker-up docker-down
.PHONY: help install install-deps lint lint-python lint-imports lint-deps lint-types lint-tui lint-bridge test test-python test-tui build build-tui build-bridge build-ui build-core check-commits check-pr-title check-large-files check-source-language check-core-wheel fetch-templates verify-templates beta ci clean coverage coverage-shard coverage-combine coverage-summary coverage-diff coverage-ratchet coverage-baseline-check coverage-baseline-candidate docker-build docker-up docker-down

PYTHON ?= python3
PYTHON_VERSION ?= 3.12
Expand Down Expand Up @@ -59,7 +59,7 @@ install: install-deps
npm ci --prefix ui-tui
npm ci --prefix bridge

lint: lint-python lint-imports lint-deps lint-ui lint-tui lint-bridge
lint: lint-python lint-imports lint-deps lint-types lint-ui lint-tui lint-bridge

lint-python:
uv run --frozen --python $(PYTHON_VERSION) --extra dev ruff check $(PYTHON_LINT_TARGETS)
Expand All @@ -71,6 +71,14 @@ lint-deps:
lint-imports:
uv run --frozen --python $(PYTHON_VERSION) lint-imports

# Call correctness, not typing strictness -- which rules are on, and the backlog
# behind each one that is off, are in [tool.ty] in pyproject.toml. The target
# roster is read from PYTHON_LINT_TARGETS so it cannot drift from ruff's; tests
# are filtered out there because they are executed, and the gate is for code
# that is not.
lint-types:
uv run --frozen --python $(PYTHON_VERSION) --all-extras ty check $(filter-out tests,$(PYTHON_LINT_TARGETS))

lint-ui:
npm run gen:check --prefix ui-web
npm run type-check --prefix ui-web
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1159,8 +1159,8 @@ async def execute(self, ledger: str = "", metric: str = "", **kwargs: Any) -> st
try:
budget_spent = await backend.spent_minutes()
budget_remaining = await backend.remaining_minutes()
if hasattr(backend, "unmeasured_spend"):
budget_unmeasured = backend.unmeasured_spend() or {}
if callable(unmeasured_spend := getattr(backend, "unmeasured_spend", None)):
budget_unmeasured = unmeasured_spend() or {}
except Exception as exc: # noqa: BLE001 -- an unread spend is reported, not raised
# Separate try from the reconcile: a host that cannot answer the
# spend must not also cost the trial statuses, and a spend that
Expand Down
3 changes: 2 additions & 1 deletion agents/raven-ppt/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import json
import os
import sys
from collections.abc import Collection
from pathlib import Path
from typing import Any

Expand Down Expand Up @@ -304,7 +305,7 @@ def ignored_providers(defaults: dict, model: str) -> set[str]:


def probe_context_window(
model: str, api_base: str, api_key: str, served: set[str], ignored: set[str] = frozenset()
model: str, api_base: str, api_key: str, served: set[str], ignored: Collection[str] = frozenset()
) -> tuple[int, str] | None:
"""The window the serving endpoint reports for ``model``, and where it came from.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

from __future__ import annotations

from collections.abc import Callable

TOOL_OUTPUT_ELIDED = "[earlier tool output elided to fit the context window]"
"""Body substituted for an older tool result when the turn must be shrunk to fit.

Expand Down Expand Up @@ -150,7 +152,7 @@ def sufficiency_listing_notice() -> str:

# Permissive recognisers: (name, matcher). Used only where a false positive is
# free. Keep the names stable - they are the diagnostic a reader gets back.
_HARNESS_BODIES: tuple[tuple[str, object], ...] = (
_HARNESS_BODIES: tuple[tuple[str, Callable[[str], bool]], ...] = (
("tool_output_elided", lambda s: TOOL_OUTPUT_ELIDED in s),
("search_closed", lambda s: s.lstrip().startswith(SEARCH_CLOSED_PREFIX)),
)
Expand Down Expand Up @@ -191,7 +193,7 @@ def harness_body_kind(content: object) -> str | None:
if not s.strip():
return None
for name, matches in _HARNESS_BODIES:
if matches(s): # type: ignore[operator]
if matches(s):
return name
return None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from typing import Any

from raven.agent.tools.ask_user import AskUserTool
from raven.contracts.tool import ToolResult

# Returned when the model's call reaches execution, which happens whenever the
# gate declined to hand off. NOT dead code - ``CompositeHook`` halts a phase only
Expand Down Expand Up @@ -295,7 +296,7 @@ async def execute(
questions: list[dict[str, Any]] | None = None,
outline: list[dict[str, Any]] | None = None,
**kwargs: Any,
) -> str:
) -> str | ToolResult:
# Every parameter defaults: with no top-level "required" the registry's
# validation passes a call carrying only ``outline``, and that is the call
# that reaches here most often.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ def _snippet_line(self, state: _SearchTurnState, item: dict[str, Any], identity:

async def _search(
self, state: _SearchTurnState, query: str, count: int | None, k: int | None = None, page: int = 1
) -> tuple[str, list[str], dict[str, int]]:
) -> tuple[str, list[str], dict[str, int | None]]:
"""Returns the rendered text, the ordered result URLs, and the shaping sizes.

The URL list is returned rather than parsed back out of the text: the ledger
Expand Down
7 changes: 5 additions & 2 deletions evolver/orchestrator/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from __future__ import annotations

import asyncio
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Optional
Expand Down Expand Up @@ -141,7 +142,7 @@ def make_llm_backend(
*,
train_task_ids: list[str],
vanilla_node: HarnessNode,
test_task_ids: list[str] = (),
test_task_ids: Sequence[str] = (),
k: int = 3,
max_tokens: int = 1024,
cull_sigma_mult: float = 1.5,
Expand Down Expand Up @@ -447,7 +448,9 @@ def build_evolution_orchestrator(
backend: EvalBackend,
gate_policy,
diagnose_of: Callable[[HarnessNode], tuple[Any, Optional[dict]]],
design_of: Callable[[Callable[[HarnessNode], str], dict], Any],
# Three parameters, matching the docstring below and every real implementation:
# the chain resolver, the cross-round history, and the GSME archive renderer.
design_of: Callable[[Callable[[HarnessNode], str], dict, Callable[[], str]], Any],
baseline_of: Callable[[], Any],
files_of: Callable[[Any], dict[str, bytes]],
deletions_of: Optional[Callable[[Any], list[str]]] = None,
Expand Down
6 changes: 3 additions & 3 deletions evolver/tree/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,11 +628,11 @@ def current_core_version() -> str:

Evolver code should call this when constructing new
:class:`HarnessNode` instances so the node carries the kernel
version it was created under. Falls back to ``"unknown"`` if
the import fails (shouldn't happen in normal operation).
version it was created under. Falls back to ``"unknown"`` when raven
is not importable at all, which a bench run outside the install is.
"""
try:
from raven.__core_version__ import __version__
from raven import __version__

return __version__
except ImportError:
Expand Down
6 changes: 5 additions & 1 deletion plugins-dist/design-engine/raven_design/plugin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,11 @@ def from_section(cls, raw: dict[str, Any]) -> "RenderSettings":
elif kind == "choice":
value = _text(raw, key, default)
if value not in bounds:
raise ValueError(f"{key} must be one of {', '.join(bounds)}, got {value!r}")
# `bounds` is read per `kind`: choice rows carry their allowed
# set, numeric rows carry (lo, hi), flag rows carry None. The
# column's declared type is that union, so the join is not
# provable here without splitting the table by kind.
raise ValueError(f"{key} must be one of {', '.join(bounds)}, got {value!r}") # ty: ignore[no-matching-overload]
values[attr] = value
settings = cls(**values)
# The fork schema's two cross-field refusals, kept as refusals.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
from pathlib import Path
from typing import Any

import uno
from com.sun.star.beans import PropertyValue
from com.sun.star.uno import Exception as UnoException
# This module runs inside LibreOffice's bundled interpreter, which is the only
# one that carries the UNO bridge. It is never imported by the host process, so
# these three are unresolvable from raven's environment by design.
import uno # ty: ignore[unresolved-import]
from com.sun.star.beans import PropertyValue # ty: ignore[unresolved-import]
from com.sun.star.uno import Exception as UnoException # ty: ignore[unresolved-import]


def _property(name: str, value: Any) -> PropertyValue:
Expand Down
3 changes: 2 additions & 1 deletion plugins-dist/ppt-engine/raven_ppt/services/ingest/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,8 @@ def _uniform_border_color(rgb: Image.Image) -> tuple[int, int, int] | None:
values = [corner[c] for corner in corners]
if max(values) - min(values) > _MAX_CORNER_SPREAD:
return None
return tuple(int(median(corner[c] for corner in corners)) for c in range(3))
red, green, blue = (int(median(corner[c] for corner in corners)) for c in range(3))
return red, green, blue


def _rewrite(cropped: Image.Image, path: Path, fmt: str | None) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,8 @@ def _ground(arr: Any, ink: tuple[int, int, int] | None = None) -> tuple[int, int
else int(min(candidates, key=lambda code: int(np.argmax(codes == code))))
)
held = flat[codes == winner].astype(np.float64)
return tuple(int(round(float(value))) for value in held.sum(axis=0) / held.shape[0])
red, green, blue = (int(round(float(value))) for value in held.sum(axis=0) / held.shape[0])
return red, green, blue


def _states_a_fill(frame: Any) -> bool:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ def _custom(data: bytes) -> bytes | None:
return None
marked = False
for prop in list(root):
if VENDOR.search(prop.get("name", "")) or VENDOR.search("".join(prop.itertext())):
# lxml's stub types itertext() as Iterator[str | bytes] because one
# element API serves byte-mode trees too; _parse builds a str tree here.
if VENDOR.search(prop.get("name", "")) or VENDOR.search("".join(prop.itertext())): # ty: ignore[no-matching-overload]
root.remove(prop)
marked = True
return _serialise(root) if marked else None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,8 @@ def row_band(shape, size_pt: float | None = None) -> str:
def _band(width_in: float, font_px: int, lines: int, bold: bool, han: bool) -> tuple[int, int]:
"""How many characters of one script the box takes, at both bounds."""
room = width_in * 96 / RENDER_DRIFT_HEADROOM
return tuple( # type: ignore[return-value]
max(0, int(room * bound // (_em(bold, han) * font_px)) * lines) for bound in BOUNDS
)
at_bound, at_overshoot = (max(0, int(room * bound // (_em(bold, han) * font_px)) * lines) for bound in BOUNDS)
return at_bound, at_overshoot


def _em(bold: bool, han: bool) -> float:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2515,7 +2515,8 @@ class here would be False for the very Box the author passed.
"pass the ppt_layout Box itself, or divide each number by 914400"
)
try:
return tuple(float(number) for number in numbers)
left, top, width, height = (float(number) for number in numbers)
return left, top, width, height
except (TypeError, ValueError):
raise ValueError(wanted) from None

Expand Down
4 changes: 3 additions & 1 deletion plugins-dist/ppt-engine/raven_ppt/stages/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,9 @@ def _deck_title(project: Project) -> str | None:
return topic or None


def _as_delivered(project: Project, outcome: BuildOutcome, *, released: bool) -> tuple[Path, int, tuple[int, ...]]:
def _as_delivered(
project: Project, outcome: BuildOutcome, *, released: bool
) -> tuple[Path | None, int, tuple[int, ...]]:
"""The file to publish, its page count, and the pages left out of it.

The build cap is the one route that publishes past a page the runner stood in
Expand Down
2 changes: 1 addition & 1 deletion plugins-dist/ppt-engine/raven_ppt/tools/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ async def _read(
seconds: dict[int, float] = {}
misread: list[int] = []

async def one(number: int) -> tuple[int, list[dict[str, str]]] | None:
async def one(number: int) -> tuple[int, list[dict[str, str]], str] | None:
async with reading:
started = time.monotonic()
try:
Expand Down
73 changes: 73 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,77 @@ ignore = ["DEP002", "DEP004"]
DEP001 = ["raven_everos", "requests", "cryptography"]
DEP003 = ["raven_everos", "requests", "cryptography"]

[tool.ty]
# A call-correctness gate, not a typing-strictness project. Of ty's 133 rules,
# 110 are live here and this tree satisfies all of them -- measured at zero, or
# cleared to zero. They answer "is this
# call, override or annotation self-consistent" -- the class of error a test
# only finds by executing the line, which is exactly what a bundled plugin's
# render path or a rarely-taken CLI branch does not do in CI.
#
# The other 23: nine switched off below, and fourteen that ship disabled
# upstream and are left that way (the `unsound-*` family, `division-by-zero`,
# the `possibly-*` family and friends). Those fourteen are unmeasured, not
# satisfied -- turning any of them on is its own sized change.
#
# The nine below are switched off with their measured backlog, not because they
# are wrong. Each is a separate piece of work; the number is the count on the
# commit that added this block, so a later reader can tell progress from drift.
#
# Rule levels are pinned by pinning ty itself (dependency-groups, exact version):
# at 0.0.x the rule set still moves, and a gate whose meaning changes under a
# transitive bump is not a gate. Bumping it is a reviewed change.
#
# Scope note: tests/ is out (113 diagnostics under this same set). Tests are
# run, so a broken call there fails loudly; unexecuted production code is what
# this gate is for. Bringing tests in is sized, separate work.
#
# Environment note: measured identical with `--extra dev` and with
# `--all-extras` (1205 both ways), unlike deptry, whose verdict does move with
# what is installed. The Makefile still uses --all-extras so that turning any
# resolution-sensitive rule back on does not silently change meaning.
# The three agent products keep their plugin package beside them rather than in
# plugins-dist, so their roots are not on the path a plain run sees. ruff has the
# same list for plugins-dist under [tool.ruff] src, for the same reason.
[tool.ty.environment]
extra-paths = [
"agents/raven-code/plugins/code-flow",
"agents/raven-oncall/plugins/oncall-flow",
"agents/raven-research/plugins/research-flow",
]

[tool.ty.rules]
# 725: 432 of them are the agent-loop mixins reaching for attributes the host
# class owns (`Self@<method>` has no attribute ...), and 180 more are attribute
# access on an optional. Barely any of it is third-party noise -- clearing it
# means giving the mixins a declared surface, which is a refactor, not a sweep.
unresolved-attribute = "ignore" # 725
# 231: sampled, not enumerated -- loose annotations and unnarrowed unions rather
# than a single shape.
invalid-argument-type = "ignore" # 231
# 66, and enumerated rather than sampled: 53 are Tool subclasses declaring named
# parameters against a base of `execute(self, **kwargs: Any)`, which is how a
# tool publishes its JSON schema -- the pattern, not a defect. 9 are provider
# wrappers that take `**kwargs` and forward, so the named parameter ty wants is
# reachable at runtime. 3 rename a parameter (`path` -> `file_path`) together
# with the `parameters` override that renames the schema key, so the tool and
# the schema agree. The one left is real and is a kernel contract question:
# LLMProvider.chat_stream is declared `async def -> AsyncIterator[ChatDelta]`,
# which types a coroutine returning an iterator, while every implementation is
# an async generator. Enabling this rule would mean suppressing 65 lines to keep
# one, so it stays off until that declaration is fixed.
invalid-method-override = "ignore" # 66
invalid-assignment = "ignore" # 33
not-subscriptable = "ignore" # 32
unsupported-operator = "ignore" # 28
not-iterable = "ignore" # 21
redundant-condition = "ignore" # 7 (warn by default)
unsupported-base = "ignore" # 2 (warn by default)

# Off by default upstream, on here: a bare `# ty: ignore` silences every rule on
# that line forever, including ones written after it. Suppressions name a rule.
blanket-ignore-comment = "error"

[tool.ruff]
line-length = 120
target-version = "py312"
Expand Down Expand Up @@ -469,6 +540,7 @@ dev = [
"import-linter>=2.14",
"json-repair>=0.59.5",
"jsonschema>=4.23.0",
"lxml-stubs>=0.5.1",
"packaging>=26.1",
"pexpect>=4.9",
"pip-audit>=2.10.0",
Expand All @@ -479,6 +551,7 @@ dev = [
"pytest-cov>=7.1.0",
"pytest-xdist>=3.8.0",
"ruff==0.15.12",
"ty==0.0.80",
]

[tool.importlinter]
Expand Down
3 changes: 2 additions & 1 deletion raven/agent/loop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
in ``_shared``. Callers import ``AgentLoop`` from here.
"""

from raven.agent.loop.main import AgentLoop, LoopOutcome
from raven.agent.loop._shared import LoopOutcome
from raven.agent.loop.main import AgentLoop

__all__ = ["AgentLoop", "LoopOutcome"]
Loading
Loading