Skip to content

Releases: Cloto-dev/CPersona

v2.5.2b2 — beta: recall ranking correction (bug-155) and a storage policy the maintenance layer respects (bug-182)

Choose a tag to compare

@Cloto-dev Cloto-dev released this 25 Jul 10:10

Pre-release (second beta of the 2.5.2 stabilization ladder; the latest final release remains v2.5.1, which pip installs by default — pre-releases require explicit opt-in).

b1 changed what callers see. This rung changes what recall returns and what the maintenance layer does about a configuration it had been misreading. No contract break — the tool responses and the DB schema are the ones b1 published, so upgrade and rollback across this cut need no migration and no caller change.

Ranking correction

  • Vector-less rows no longer out-score real vector hits (bug-155, HIGH). Under CONFIDENCE_ENABLED, rows admitted through the FTS or keyword channel reached _apply_recall_scoring with _cosine=None — even when their embedding was sitting in the DB — and _compute_confidence's None branch (sqrt(time_decay) * …) scored at least as high as its cosine branch (sqrt(norm_cos * time_decay) * …). The keyword channel, documented as a way to down-rank lexical contaminants, was structurally promoting them instead. The fix backfills the cosine for those rows before scoring.

    Measured on LMEB: +14.27 NDCG@10 on rsf, 16 of 16 tasks improved, cascade undisturbed (it does not route through this seam). This is a default-behaviour change: the same query against the same database can come back in a different order — that is the point of the fix, and the reason it ships as a beta rung rather than inside a patch.

Storage policy, respected

  • CPERSONA_STORE_BLOB=false is a policy, not a broken pipeline (bug-182, HIGH). The maintenance layer did not know that. On a correctly configured remote deployment — where do_store deliberately writes no local embedding — check_null_embedding read every NULL row as a dead embedding pipeline: severity=critical permanently (>50% NULL by construction), and every check_health(fix=true) re-embedded up to 500 memories over HTTP and wrote back the very BLOBs the operator had configured away, which the next store made NULL again. The rule now has one home, config.local_blobs_stored, and the checks read it. Episodes are deliberately out of scope: they have no storage gate and always write a blob.
  • The empty safety net now says so (bug-180, LOW). Under remote mode with that same setting, the local cosine scan — documented as the fallback for a /search outage — matches nothing, because embedding IS NOT NULL is false for every memory by construction. A /search fault with a working /embed therefore dropped recall to FTS/keyword silently; the degraded advisory watched only the embed boundary.

Supply-chain surface

  • The wheel stops shipping dead vendored code (bug-181, LOW). Every wheel carried ~2,479 LOC of vendored modules with no import path from any cpersona module (llm_provider.py, mcp_stream_interceptor.py, mgp_utils.py, search.py, semantic_cache.py) — unreachable by construction, yet on every user's disk, enlarging the audited and supply-chain surface (llm_provider alone carries outbound third-party HTTP). They are excluded at the wheel boundary only: the in-tree vendored snapshot stays byte-faithful to upstream, so the sync gate still compares like for like. An AST import-graph gate and a wheel-smoke CI job guard the two ends.

Verification

  • Full suite: 683 passed, 5 skipped; ruff clean; verify-issues.sh 182 entries, 0 errors, 0 stale; waiver registry clean; CI green on the bump commit (lint, test (3.11), test (3.13), wheel-smoke, verify-issues, mutation-proof).
  • The cosine backfill carries hand-applied mutation evidence, re-proven after the fix was rebased onto master.
  • PyPI artifacts (wheel + sdist) published via OIDC Trusted Publishing with provenance.

Known gap (unchanged from b1)

The automated mutation-diff lane is advisory and still skips everything when the diff exceeds its cap rather than sampling a subset. Of the two changes in this cut, bug-155 (112 in-scope mutants, cap 60) was not executed for that reason; the bug-182/bug-180/bug-181 change (55 in-scope) ran and returned 43 killed / 12 survived. A capped run and a passing run currently render the same colour in the checks list. Tracked — the fix makes large diffs sample instead of opt out.

v2.5.2b1 — beta: the contract breaks (store result / check_health status / source-type enum)

Choose a tag to compare

@Cloto-dev Cloto-dev released this 25 Jul 08:46

Pre-release (first beta of the 2.5.2 stabilization ladder; the latest final release remains v2.5.1, which pip installs by default — pre-releases require explicit opt-in).

The alpha rungs stayed inside the implementation. This one changes what callers see: it is the first cut of the 2.5.x line to break the public MCP tool contract, which is why it goes through the ladder (RELEASE_LIFECYCLE_STANDARD §2.1). The DB schema is unchanged — upgrade and rollback across this cut need no migration.

Contract breaks (3)

  • store answers whether it stored. Every response now carries result: stored (a row was written), skipped (nothing written, nothing wrong — dedup, or persistence paused), rejected (the request was refused). ok tracks that verdict instead of being unconditionally true, and the old skipped: true flag is gone. Previously a store that wrote nothing — empty content, content that sanitizes to empty — answered {ok: true}, so a caller checking the obvious field read success for a write that never happened.
  • check_health has one verdict. The healthy boolean is gone; status (healthy / degraded / unhealthy) is the verdict. The two fields disagreed by construction: healthy was len(issues) == 0, so a database whose only findings were informational reported healthy: false while status in the same response said healthy. The counts are still in issues and severity_summary.
  • One definition of the source-type enum. The canonical set was stated in three places and owned by none of them (a SQL literal in the health check, a tuple in the write seam, prose in the published schema). utils.CANONICAL_SOURCE_TYPES is now the single definition; the store JSON Schema publishes it as an enum and generates its alias sentence from the alias table, so a caller no longer learns the contract by reading a health check.

Also in this cut

  • Write-path bounds (bug-156): source and metadata reach the row as JSON with no size limit, and an episode's summary/keywords never had one — content has been capped since 2.1. Oversized sidecars are now refused, episode prose is capped in the shared prepare seam (so the queue drain obeys the same rule), and update_memory runs the same sanitizer as store instead of only .strip().
  • A silent filter, disclosed (bug-157): every external_context entry filtered recall while only user/assistant entries were merged into the response, so a system entry could make a memory disappear with nothing saying why. The response now carries context_filter_only.
  • Remote-recall hydration batched (bug-158): remote mode re-hydrated every hit with its own awaited SELECT ... WHERE id = ?. A limit=100 recall paid 100 sequential round trips on the read hot path — measured 102 for 102 hits, now 2.
  • Bounded /index push (bug-159): the write-path POST inherited the embed client's 30s default while the health probe (3s) and remote search (5s) each state their own. It states 10s now.
  • Descriptions rendered from the code they describe (bug-160): the check-registry size (claimed 20, was 23), which deep checks honour fix=true, the tools a pause silences, the @auto sentinel. Structural CI gates fail when any of them falls behind the implementation again. The shipped cpersona-memory skill had the same drift and is gated too.
  • 18 findings from the independent pre-ship review (bug-162..bug-179), and the outward claims the breaks falsified — README, SUPPORT.md and the standard saying the tool contract is preserved across line transitions — corrected in the same batch.

Verification

  • Behaviour-equivalence goldens were extended to the three layers these breaks touch (handler seam, MCP boundary, health/deep-check) before the breaks landed, with a frozen clock so wall-clock never reaches the golden; pin effectiveness was proven by hand-applied mutation. Each golden line that moved in this cut is one of the documented contract changes.
  • Full suite: 664 passed, 5 skipped; verify-issues.sh 178 entries, 0 errors; ruff clean; mutation-proof CI job green; waiver registry clean.
  • PyPI artifacts (wheel + sdist) published via OIDC Trusted Publishing with provenance.

Known gap

The automated mutation-diff lane was skipped on this cut: the diff exceeded the lane's cap, and the cap currently skips everything rather than selecting a subset (tracked; the fix makes large diffs sample instead of opt out). The contract changes carry hand-applied mutation evidence instead, recorded per test.

v2.5.2a2 — alpha: promotion-gate audit remediation (M-142..L-154)

Choose a tag to compare

@Cloto-dev Cloto-dev released this 23 Jul 09:38

Pre-release (second alpha of the 2.5.2 stabilization ladder; latest final release remains v2.5.1, which pip installs by default — pre-releases require explicit opt-in).

This cut ships the remediation of the alpha→beta promotion-gate audit (#61): a 78-agent workflow (4 equivalence seams + 16 finder dimensions, every finding adversarially verified through two independent lenses) confirmed the a1 refactor seams behavior-preserving and surfaced 13 defects, all fixed here as registry entries bug-142..bug-154. DB schema unchanged.

Highlights

  • Silent-failure fixes: store no longer reports embedded: true when the remote index POST returns 4xx/5xx (M-146); one malformed-JSON source row no longer blanks check_invalid_source_type / check_anonymous_source for the whole agent (M-144); delete_episode now purges the episode from the remote vector index, symmetric with delete_memory (M-148).
  • Isolation correctness: the episode-boundary recall penalty is scoped to the recall's project_id/channel axes instead of the whole agent (M-147).
  • Legacy-data remediation path: pre-v12 msg_id collisions that permanently blocked the dedup UNIQUE index are now resolved non-destructively (keep newest, blank older unlocked msg_ids — no row deleted) in both the migration and a new dedup_msg_id_index health check (L-145).
  • Operational hardening: checkup CLI exits with a readable error instead of hanging forever on a corrupt/foreign DB (M-143) and no longer advertises a phantom default DB path (M-142); HTTP port and embedding timeout env vars degrade to defaults instead of crashing startup (M-150); pause_persistence/resume_persistence/persistence_status responses carry scope: "process" and their descriptions state the HTTP-deployment blast radius (M-151); concurrent set_recall_precision rollback can no longer clobber another writer (L-149).
  • Gate integrity: verify-issues.sh switches to unit-separator serialization with an emitter assertion and a transport count check — a literal | in a registry field can no longer silently drop a check while the gate exits 0 (M-152); the vendored-common sync script gains a post-sync reconciliation gate for the not-yet-upstreamed patches (L-153); scripts/mutation-proof.py now runs as a blocking CI job (L-154).
  • Standard v1.3 (#62): the Release Lifecycle Standard gains §2.8 (defect identifiers — one number space, severity-prefixed human-facing references like M-144, bare bug-NNN only in the tree).

Verification

  • Every behavioral fix ships a fail-first regression test (captured failing on the unfixed code for the intended assertion); one fix was independently re-proven by hand-applied mutation during review.
  • Full suite: 556 passed, 5 skipped; verify-issues.sh 154 entries clean; ruff green; mutation-proof CI job green (first runs on the merged tree).
  • The branch code was validated read-only against a production-corpus copy (1,631 memories) before merge: clean run, expected findings only, and the new health check confirmed no legacy msg_id collisions in production.
  • PyPI artifacts (wheel + sdist) published via OIDC Trusted Publishing with provenance.

v2.5.2a1 — alpha: refactor seams under mutation proof + agent-facing API polish

Choose a tag to compare

@Cloto-dev Cloto-dev released this 22 Jul 15:58
1f1f552

Pre-release (opens the 2.5.2 stabilization-patch ladder; latest final release remains v2.5.1, which pip installs by default — pre-releases require explicit opt-in).

First alpha of the 2.5.2 line ("stabilization / bug-fix patch, no new features" — the ladder runs a1 → beta → final). This cut carries the merged alpha-stage work (#59) plus the pre-release review follow-up (#60). DB schema unchanged; tool-contract changes are deliberate, additive, and documented in the tool descriptions (2.5.x charter).

Highlights

  • Refactor seams under mutation proof (#59): admin_handlers per-record decomposition (_ImportTally / _MergeTally), vector._search_vector split (_search_vector_remote / local scans), _calibrate_fused_gate extraction — all pinned equivalent to v2.5.1 by a machine-generated golden behaviour harness (tests/behaviour_252.py, 43 equivalence tests) and a mutation-proof script.
  • Source contract normalization at the write seam (Task #282): known legacy source shapes (lowercase type words, Rust serde externally-tagged dicts, bare 'user'/'assistant' strings) are normalized to the canonical {type, id, name} on store; unknown shapes stay verbatim and keep surfacing via check_health(invalid_source_type). The fixer is now mapping-based (preserves id/name) instead of the pre-2.5.2 blanket-User overwrite.
  • Agent-facing API polish: store responses expose embedded; recall rows expose match_reason; check_health gains a 3-tier status surface.
  • Pre-release review follow-up (#60, bug-139..141 in qa/issue-registry.json): check_invalid_source_type(fix=True) reports an additive locked counter so mapped + unmapped + locked reconciles with count; the deliberate null-source → {} write-seam convergence is documented in the store tool description; the no-persist branch of store carries embedded:false (branch on persisted).

Verification

  • Full suite: 523 passed, 5 skipped; lint + issue-registry verification green (CI on the tagged commit).
  • Independent read-only code review of the full v2.5.1..master diff: APPROVE-WITH-NITS, no release blockers; all three nits fixed in #60.
  • PyPI artifacts (wheel + sdist) published via OIDC Trusted Publishing with provenance.

v2.5.1 — server-served operating context

Choose a tag to compare

@Cloto-dev Cloto-dev released this 19 Jul 05:16
768bd9c

Server-served operating context: CPersona can now carry the operating doctrine itself (project_id registry, agent_id conventions, recall discipline) instead of every client re-deriving it from local prose.

Added

  • Operator-owned TOML sidecar (cpersona/operating_context.py) at ~/.cpersona/operating-context.toml, with a CPERSONA_OPERATING_CONTEXT_PATH override and a CPERSONA_OPERATING_CONTEXT=off kill switch. Lazy mtime-based reload. An absent or invalid file degrades to fully dormant — a config typo can never take memory down.
  • Soft layer: instructions.summary rides the MCP initialize response through a new ToolRegistry(instructions=...) kwarg, so a connecting client receives the doctrine as part of the handshake.
  • Hard layer: a boundary gate on the six project_id-accepting tools — registry validation (off / warn / reject) plus an opt-in @auto sentinel resolved from [defaults], echoed back as resolved_project_id. Reads warn even under reject (damage asymmetry: a bad read is recoverable, a misfiled write is not). Explicit values are never rewritten, omitted / "" semantics are untouched, and library callers bypass the gate by design.
  • get_operating_context — a new read-only tool (28 → 29). The write path stays the filesystem: there is deliberately no MCP write tool for operator config.
  • check_health: additive operating_context_parse (warn) and operating_context_size (info, 3,000-char budget) registry entries.

Contract

No DB schema change. No breaking change: the tool surface is additive (one new tool, additive response fields), consistent with the 2.5.x line declaration in SUPPORT.md. The feature is entirely dormant until an operator creates the sidecar.

Verification

430 passed / 5 skipped, ruff clean, issue registry 110 verified / 0 stale / 0 errors. Full CI green on master before tagging.

Design: docs/OPERATING_CONTEXT_DESIGN.md.

v2.5.0 — final: remote-mode + export cluster closed; 2.5.x enters Current

Choose a tag to compare

@Cloto-dev Cloto-dev released this 18 Jul 18:27
8540cbe

First final of the 2.5.x line. The 2.5.0 pre-release series (a1→b3) graduates to 2.5.0, which enters the Current tier (passed the full release gate; awaiting production-soak certification to Stable). 2.4.x remains Stable and the marketplace default.

Fixes — the last five open issues (all deferred remote-mode / export)

  • bug-046 + bug-075: the remote (VECTOR_SEARCH_MODE=remote) by-id episode fetch omitted the channel predicate → a channel-scoped recall could return an episode from another channel. The remote fetch now carries the channel axis, symmetric with the local branch.
  • bug-049: do_archive_episode now pushes ep:{id} to the remote vector index after commit — archived episodes are no longer absent from remote-mode semantic recall.
  • bug-050: import and merge now sync their inserted episodes to the remote index.
  • bug-073: do_export_memories streams each table (fetchmany) instead of materialising the whole corpus, removing the OOM risk on large exports. A new read_snapshot() seam gives the header and body one consistent private WAL snapshot.

Zero open bugs remain in the issue registry. Full suite: 386 passed; verify-issues green; ruff clean. No DB schema or MCP tool-contract changes.

v2.5.0b3 — beta: post-b2 CI-incident fix bundle (bug-123, bug-124)

Choose a tag to compare

@Cloto-dev Cloto-dev released this 17 Jul 05:17

Pre-release (2.5 Experimental line — the 2.4 series remains the Stable line, latest v2.4.40).

Fix-only beta carrying the two defects uncovered by the post-b2 CI-incident investigation — bug-123, bug-124 in qa/issue-registry.json — plus the upstream doc-drift corrections surfaced by the Zenn book fact-check audit (PRs #48, #50). No new features; DB schema and MCP tool contract unchanged.

Highlights

  • Same-boot FTS recovery rebuild (bug-123, MEDIUM): the bug-118 recovery path recreated stale FTS triggers and flagged an index rebuild, but the rebuild condition never checked that flag — the actual rebuild was silently deferred to the next boot, leaving FTS search on a stale index for the whole recovered session. The rebuild now fires in the same boot that performs the recovery.
  • Orphaned-connection interpreter hang (bug-124, HIGH, test-infra): every aiosqlite connection owns a non-daemon worker thread, so a test that re-pointed database._db = None without close() left a thread that blocked interpreter exit forever — the suite passed in seconds, then the process hung until GitHub's 6-hour job limit. This is how b2 was merged with its test jobs still pending. Fixes: the offending test now goes through the existing close_db() helper, the helper's docstring carries the doctrine, and ci.yml gained a timeout-minutes: 15 backstop so any future hang fails fast instead of burning 6 runner-hours.
  • Doc drift (PR #48): LMEB expanded to its correct name (Long-horizon Memory Embedding Benchmark), archive_episode's history description now states what the field is actually used for (start/end timestamp extraction; the episode embedding comes from summary), and calibrate_threshold's description reflects the separation-based default. README Quick Start now leads with the bundled cpersona-memory skill install path.

Deliberate non-change: version stays a fix-only beta — the structural gate for the _db = None orphan pattern (class prevention for bug-124) is tracked separately and will ride a later cut.

Verification before this cut: 352 passed / 5 skipped with clean interpreter exit, ruff clean, verify-issues.sh 124 issues / 0 stale / 0 errors; CI green on the bump commit (all four jobs individually confirmed).

Graduation line: 2.5.0b1 → 2.5.0b2 → 2.5.0b3 → (rc) → 2.5.0 final — the line then enters Current; Stable requires a later production-soak certification event (see SUPPORT.md).

🤖 Generated with Claude Code

v2.5.0b2 — beta: post-b1 audit fix bundle (bug-114..122)

Choose a tag to compare

@Cloto-dev Cloto-dev released this 16 Jul 09:18
fca25e5

Pre-release (2.5 Experimental line — the 2.4 series remains the Stable line, latest v2.4.40).

Fix-only beta: all 10 confirmed defects from the post-b1 comprehensive audit (cross-provider: Gemini finders + Anthropic finders, refute-framed verification, final adjudication) — bug-114..122 in qa/issue-registry.json. No new features; DB schema and MCP tool contract unchanged.

Highlights

  • Naive-timestamp UTC anchoring (bug-114, HIGH): _parse_timestamp_utc ran astimezone() on naive datetime('now') values, assuming system-local time — on any non-UTC host, recall-boost decay and the episode-boundary factor were silently shifted by the local UTC offset (repro-confirmed: JST, 9 hours). Naive DB timestamps are now anchored as UTC at the parse choke points, and ruff DTZ (flake8-datetimez) joins the lint gate so new naive datetime construction is a CI error.
  • Episode penalty actually ranks (bug-115, HIGH): under the default configuration (rrf fusion + confidence off) the episode-boundary penalty scaled scores but nothing re-sorted — a ranking no-op. Penalised fusion lists now re-sort; cascade stage order is preserved by design.
  • Structural gate hardening (bug-120, HIGH): the isolation gate no longer accepts substring tricks (OR agent_id IS NULL, commented mentions) and now sees interpolated DML targets (f"DELETE FROM {table}"), closing a class of unscoped-DML invisibility. Five new teeth tests.
  • verify-issues as a CI gate (bug-122): the 122-entry regression registry is now enforced on every PR/master push (it was hand-run only).
  • FTS migration recovery (bug-118): FTS-gated migrations skipped while CPERSONA_FTS_ENABLED=false (stale trigger bodies, missed contamination rebuild) are detected by a trigger-DDL probe and recovered on boot.
  • Also: mixed-format orphan-episode false positives (bug-116), preview truncation of ref-less injected rows made content unreachable (bug-117), no-persist skeleton shape drift for calibrate/set_recall_precision (bug-119), empty-content memories dropped by any exclude filter (bug-121).

Deliberate non-change: the datetime('now') schema defaults keep their naive (UTC) format — rewriting them offset-aware would mix formats inside one column and reintroduce the lexicographic-comparison hazard bug-116 fixes. The parse-side naive-as-UTC invariant covers old and new rows.

Verification before this cut: 276 passed / 5 skipped (20 new regression tests in test_audit_2500b2.py), ruff clean with DTZ enabled, verify-issues.sh 122 issues / 0 stale / 0 error.

Graduation line: 2.5.0b1 → 2.5.0b2 → (rc) → 2.5.0 final — the line then enters Current; Stable requires a later production-soak certification event (see SUPPORT.md).

🤖 Generated with Claude Code

v2.5.0b1 — beta: comprehensive audit fix bundle

Choose a tag to compare

@Cloto-dev Cloto-dev released this 16 Jul 03:42

Pre-release (2.5 Experimental line — the 2.4 series remains the Stable line, latest v2.4.40).

Beta graduation of the 2.5.0 internal-stabilization line. A comprehensive pre-release audit (hybrid: 10 Gemini finders + 3 Anthropic deep finders, every finding adversarially verified; then a differential a2-vs-b1 demonstration and a cross-provider regression hunt over the fix delta itself) landed 28 fixes (bug-086..113) — full details in qa/issue-registry.json.

Highlights

  • Read/write connection separation (bug-086/087): the read seam owns a dedicated connection, so WAL snapshot isolation actually applies — dirty reads on the shared connection could acknowledge a store as a duplicate of a row that was later rolled back (silent loss).
  • Atomic merge mode='move' (bug-088): the source wipe runs inside the merge transaction — a writer queued on the FIFO lock could previously lose an acknowledged row deterministically.
  • Durable export/import (bug-091/092/110): exports write via temp + rename (a mid-export fault no longer destroys the previous backup), imports validate the file body against its header across all three record types (truncated backups roll back), and recall_count/last_recalled_at/created_at survive round-trips.
  • Locked-row contract (bug-098): all fixers carry the locked = 0 guard — check_health(fix=true) can no longer rewrite a locked memory's fields.
  • γ-visible write dedup + scope-local temporal scoring (bug-106/107): write dedup matches read semantics; a scoped recall's confidence curve is no longer scaled by unrelated buckets.
  • Queue drain hardening (bug-089/090/093/109): single-transaction archive with a claim-token delete, failure dicts route into retry, agent wipes purge queue rows and cannot be resurrected.
  • Packaging/CI: skills/ ships in the wheel (cpersona/skills/), ruff joins CI as a gate, 30 new regression tests (test_audit_2500b1.py).

Verification before this cut: 335 passed / 5 skipped, ruff clean, verify-issues.sh 113 issues / 0 stale / 0 error; a differential harness reproduced 5 representative bugs live on v2.5.0a2 and confirmed all 5 resolved here.

Graduation line: 2.5.0a2 → 2.5.0b1 → (rc) → 2.5.0 final — the line then enters Current; Stable requires a later production-soak certification event (see SUPPORT.md).

🤖 Generated with Claude Code

v2.5.0a2

v2.5.0a2 Pre-release
Pre-release

Choose a tag to compare

@Cloto-dev Cloto-dev released this 15 Jul 09:37

Second pre-release on the 2.5.0 internal-stabilization line. Experimental — plain pip install cpersona still resolves to 2.4.40; opt in with pip install cpersona==2.5.0a2.

On top of 2.5.0a1 (behavioural harnesses + connection C-seam):

  • isolation_where() unification (#180) — the 3-axis read predicate (agent/project/channel) is built in one helper; the structural isolation gates now require helper derivation and accept no waivers.
  • Recall limit re-layering (#190) — the library clamps only to the vector scan window; the agent-facing 100 cap moved to the recall tools' JSON Schema (maximum: 100). Deep library callers (bench full-ranking) get real depth; the bench --unclamp_limit patch is retired.
  • Recall preview tier (#193) — recall responses carry a ref handle and return content as a 500-char pure-prefix preview (CPERSONA_RECALL_PREVIEW_CHARS, 0 disables) with content_truncated/content_len markers; full_content: true opts out, and the new get_contents tool (28 tools total) batch-expands refs to full rows. Design: docs/RECALL_PREVIEW_TIER_DESIGN.md. Consumers that inject recall content verbatim should pass full_content: true (safe against older servers).

No DB schema change (SCHEMA_VERSION 13). Verified: 305 passed / 5 skipped, ruff clean, verify-issues 85 issues 0 stale / 0 errors.

🤖 Generated with Claude Code