cc-transcript 14.4.0__tar.gz → 14.5.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.
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/PKG-INFO +1 -1
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/_native.pyi +31 -27
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/corrections.py +42 -21
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/decisions.py +24 -17
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/evidence.py +2 -2
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/extract/correct.py +2 -2
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/heartbeats.py +40 -20
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/judge/similar.py +27 -26
- cc_transcript-14.5.0/cc_transcript/ledger.py +163 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/store.py +95 -74
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/watch.py +23 -10
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/pyproject.toml +1 -1
- cc_transcript-14.5.0/rust/crates/core/src/actor.rs +166 -0
- cc_transcript-14.5.0/rust/crates/core/src/codex/lower.rs +1342 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/codex/mod.rs +2 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/lib.rs +2 -0
- cc_transcript-14.5.0/rust/crates/py/src/actor_bridge.rs +135 -0
- cc_transcript-14.5.0/rust/crates/py/src/corrections.rs +260 -0
- cc_transcript-14.5.0/rust/crates/py/src/feedback.rs +405 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/lib.rs +1 -0
- cc_transcript-14.4.0/cc_transcript/ledger.py +0 -94
- cc_transcript-14.4.0/rust/crates/py/src/corrections.rs +0 -200
- cc_transcript-14.4.0/rust/crates/py/src/feedback.rs +0 -321
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/Cargo.lock +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/Cargo.toml +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/LICENSE +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/README.md +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/__init__.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/__init__.pyi +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/activity.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/activity_probe.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/builders.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/command.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/context.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/cost.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/discovery.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/disktruth.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/extract/__init__.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/facts.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/filterspec.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/ids.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/judge/__init__.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/judge/llm.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/judge/verdicts.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/literals.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/__init__.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/candidates.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/confidence.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/engine.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/formats.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/sampling.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/signals.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/sourcekind.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/spec.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/models.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/nlp.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/notifications.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/parser.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/py.typed +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/query.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/render.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/__init__.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/buckets.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/engine.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/lexicon.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/scorespec.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/synthetic.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/tools.py +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/Cargo.toml +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/corrections.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/digest.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/facts.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/grep.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/list.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/mod.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/show.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/slice.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/stats.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/watch.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/lib.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/main.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/output.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/target.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/Cargo.toml +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/benches/parse.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/benches/render.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/benches/toolcall.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/activity.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/buckets.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/codex/parse.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/codex/protocol.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/codex/types.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/command.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/context.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/corrections.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/cost.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/discovery.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/facts.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/feedback.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/filter.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/ids.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/command.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/corrections.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/feedback.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/mining.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/mod.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/protocol.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/notifications.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/parse.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/protocol.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/pystr.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/query.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/render.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/sqlite.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/toolcall.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/types.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/value.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/watch.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/Cargo.toml +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/build.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/data/command_prefix_pins.tsv +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/bin/stub_gen.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/command.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/context.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/discovery.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/lexicon.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/mining.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/nlp.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/python.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/render.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/score.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/sqlite.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/toolcall.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/attachment.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/blocks.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/command.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/convert.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/dunder.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/events.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/meta.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/mod.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/print.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/store.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/system.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/toolcall.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/toolresult.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/transcript.rs +0 -0
- {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/watch.rs +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# This file is automatically generated by pyo3_stub_gen
|
|
2
2
|
# ruff: noqa: E501, F401, F403, F405
|
|
3
3
|
|
|
4
|
+
import asyncio
|
|
4
5
|
import builtins
|
|
5
6
|
from cc_transcript import command
|
|
6
7
|
from cc_transcript import ids
|
|
@@ -1447,37 +1448,40 @@ class Redirect:
|
|
|
1447
1448
|
@typing.final
|
|
1448
1449
|
class RustCorrectionLog:
|
|
1449
1450
|
def __new__(cls, path: builtins.str) -> RustCorrectionLog: ...
|
|
1450
|
-
def
|
|
1451
|
-
def
|
|
1452
|
-
def
|
|
1453
|
-
def
|
|
1454
|
-
def
|
|
1455
|
-
def
|
|
1456
|
-
def
|
|
1451
|
+
def open(self) -> asyncio.Future[None]: ...
|
|
1452
|
+
def close(self) -> asyncio.Future[None]: ...
|
|
1453
|
+
def append(self, ts_ms: builtins.int, session_id: builtins.str, source: builtins.str, anchor_uuid: builtins.str, incorrect_digest: typing.Optional[builtins.str], incorrect_file: builtins.str, incorrect_old: builtins.str, incorrect_new: builtins.str, correction_origin: typing.Optional[builtins.str], correction_file: typing.Optional[builtins.str], correction_old: typing.Optional[builtins.str], correction_new: typing.Optional[builtins.str], correction_commit: typing.Optional[builtins.str], correction_text: typing.Optional[builtins.str], overlap: builtins.float, detail: typing.Any) -> asyncio.Future[None]: ...
|
|
1454
|
+
def for_session(self, session_id: builtins.str) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1455
|
+
def for_repo(self, repo: builtins.str) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1456
|
+
def since(self, ts_ms: builtins.int, source: typing.Optional[builtins.str] = None) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1457
|
+
def for_anchor(self, session_id: builtins.str, anchor_uuid: builtins.str) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1458
|
+
def by_digest(self, session_id: builtins.str, incorrect_digest: builtins.str) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1459
|
+
def sql(self, statement: builtins.str) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1457
1460
|
|
|
1458
1461
|
@typing.final
|
|
1459
1462
|
class RustFeedbackStore:
|
|
1460
1463
|
def __new__(cls, path: builtins.str, extra_ddl: typing.Sequence[builtins.str], event_columns: typing.Sequence[builtins.str], migrations: typing.Sequence[tuple[builtins.str, builtins.str, builtins.str, typing.Optional[builtins.str]]], verdict_table: builtins.str, accepted_column: builtins.str, summary_column: builtins.str, event_filter: typing.Optional[builtins.str], readonly: builtins.bool = False, busy_timeout_ms: typing.Optional[builtins.int] = None) -> RustFeedbackStore: ...
|
|
1461
|
-
def
|
|
1462
|
-
def
|
|
1463
|
-
def
|
|
1464
|
-
def
|
|
1465
|
-
def
|
|
1466
|
-
def
|
|
1467
|
-
def
|
|
1468
|
-
def
|
|
1469
|
-
def
|
|
1470
|
-
def
|
|
1471
|
-
def
|
|
1472
|
-
def
|
|
1473
|
-
def
|
|
1474
|
-
def
|
|
1475
|
-
def
|
|
1476
|
-
def
|
|
1477
|
-
def
|
|
1478
|
-
def
|
|
1479
|
-
def
|
|
1480
|
-
def
|
|
1464
|
+
def open(self) -> asyncio.Future[None]: ...
|
|
1465
|
+
def close(self) -> asyncio.Future[None]: ...
|
|
1466
|
+
def sql(self, statement: builtins.str, params: typing.Optional[typing.Sequence[typing.Any]] = None) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1467
|
+
def execute(self, statement: builtins.str, params: typing.Optional[typing.Sequence[typing.Any]] = None) -> asyncio.Future[int]: ...
|
|
1468
|
+
def executemany(self, statement: builtins.str, seq: typing.Sequence[typing.Sequence[typing.Any]]) -> asyncio.Future[int]: ...
|
|
1469
|
+
def executescript(self, script: builtins.str) -> asyncio.Future[None]: ...
|
|
1470
|
+
def last_insert_rowid(self) -> asyncio.Future[int]: ...
|
|
1471
|
+
def load_extension(self, path: builtins.str) -> asyncio.Future[None]: ...
|
|
1472
|
+
def begin_immediate(self) -> asyncio.Future[None]: ...
|
|
1473
|
+
def commit(self) -> asyncio.Future[None]: ...
|
|
1474
|
+
def rollback(self) -> asyncio.Future[None]: ...
|
|
1475
|
+
def insert_candidates(self, rows: typing.Sequence[typing.Sequence[typing.Any]], extras: typing.Optional[typing.Sequence[typing.Sequence[typing.Any]]] = None) -> asyncio.Future[list[str]]: ...
|
|
1476
|
+
def record_file(self, path: builtins.str, mtime: builtins.float) -> asyncio.Future[None]: ...
|
|
1477
|
+
def file_mtimes(self) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1478
|
+
def stats(self) -> asyncio.Future[tuple[int, int, list[dict[str, typing.Any]]]]: ...
|
|
1479
|
+
def recent(self, source_kind: typing.Optional[builtins.str] = None, limit: builtins.int = 20) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1480
|
+
def events(self, source_kind: typing.Optional[builtins.str] = None) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1481
|
+
def dedup_keys(self) -> asyncio.Future[list[str]]: ...
|
|
1482
|
+
def record_verdict(self, dedup_key: builtins.str, role: builtins.str, prompt_version: builtins.int, model: builtins.str, category: builtins.str, accepted: builtins.bool, summary: builtins.str, confidence: builtins.float, rationale: builtins.str, canonical_key: typing.Optional[builtins.str], fidelity: builtins.str, judged_at: builtins.str) -> asyncio.Future[bool]: ...
|
|
1483
|
+
def unjudged(self, role: builtins.str, prompt_version: builtins.int, refresh_summary: builtins.bool = False, limit: typing.Optional[builtins.int] = None, offset: typing.Optional[builtins.int] = None) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1484
|
+
def judged(self, role: builtins.str, prompt_version: builtins.int) -> asyncio.Future[list[dict[str, typing.Any]]]: ...
|
|
1481
1485
|
|
|
1482
1486
|
@typing.final
|
|
1483
1487
|
class ServerToolUse:
|
|
@@ -26,6 +26,7 @@ from cc_transcript.literals import literal_str
|
|
|
26
26
|
|
|
27
27
|
if TYPE_CHECKING:
|
|
28
28
|
from collections.abc import Mapping
|
|
29
|
+
from types import TracebackType
|
|
29
30
|
from typing import Any
|
|
30
31
|
|
|
31
32
|
from cc_transcript.ids import EventUuid, SessionId, ToolDigest
|
|
@@ -93,16 +94,16 @@ class CorrectionLog:
|
|
|
93
94
|
the module docstring).
|
|
94
95
|
|
|
95
96
|
Example:
|
|
96
|
-
>>>
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
>>> async with await CorrectionLog.open() as log:
|
|
98
|
+
... await log.append(correction)
|
|
99
|
+
... await log.by_digest(session_id, incorrect_digest=digest)
|
|
99
100
|
"""
|
|
100
101
|
|
|
101
102
|
def __init__(self, engine: _native.RustCorrectionLog) -> None:
|
|
102
103
|
self._engine = engine
|
|
103
104
|
|
|
104
105
|
@classmethod
|
|
105
|
-
def open(cls, path: Path | None = None) -> Self:
|
|
106
|
+
async def open(cls, path: Path | None = None) -> Self:
|
|
106
107
|
"""Opens (creating if needed) the ledger at ``path``.
|
|
107
108
|
|
|
108
109
|
Args:
|
|
@@ -113,17 +114,37 @@ class CorrectionLog:
|
|
|
113
114
|
The opened log.
|
|
114
115
|
"""
|
|
115
116
|
path = path or Path.home() / ".cc-transcript" / "corrections.db"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
engine = _native.RustCorrectionLog(str(path))
|
|
118
|
+
try:
|
|
119
|
+
await engine.open()
|
|
120
|
+
except BaseException:
|
|
121
|
+
await engine.close()
|
|
122
|
+
raise
|
|
123
|
+
return cls(engine)
|
|
124
|
+
|
|
125
|
+
async def close(self) -> None:
|
|
126
|
+
"""Closes the underlying connection; a second close is a no-op."""
|
|
127
|
+
await self._engine.close()
|
|
128
|
+
|
|
129
|
+
async def __aenter__(self) -> Self:
|
|
130
|
+
return self
|
|
131
|
+
|
|
132
|
+
async def __aexit__(
|
|
133
|
+
self,
|
|
134
|
+
exc_type: type[BaseException] | None,
|
|
135
|
+
exc: BaseException | None,
|
|
136
|
+
tb: TracebackType | None,
|
|
137
|
+
) -> None:
|
|
138
|
+
await self.close()
|
|
139
|
+
|
|
140
|
+
async def append(self, record: Correction) -> None:
|
|
120
141
|
"""Appends ``record`` as a single ``INSERT OR IGNORE``.
|
|
121
142
|
|
|
122
143
|
Idempotent on the table's UNIQUE key, so re-running a writer writes one
|
|
123
144
|
row; SQLite treats NULL key columns as distinct, so rows whose key
|
|
124
145
|
carries a NULL rely on the writer not repeating the same values.
|
|
125
146
|
"""
|
|
126
|
-
self._engine.append(
|
|
147
|
+
await self._engine.append(
|
|
127
148
|
record.ts_ms,
|
|
128
149
|
record.session_id,
|
|
129
150
|
record.source,
|
|
@@ -142,39 +163,39 @@ class CorrectionLog:
|
|
|
142
163
|
record.detail,
|
|
143
164
|
)
|
|
144
165
|
|
|
145
|
-
def for_session(self, session_id: SessionId) -> tuple[Correction, ...]:
|
|
166
|
+
async def for_session(self, session_id: SessionId) -> tuple[Correction, ...]:
|
|
146
167
|
"""All records for ``session_id``, ordered by timestamp."""
|
|
147
|
-
return tuple(Correction(**row) for row in self._engine.for_session(session_id))
|
|
168
|
+
return tuple(Correction(**row) for row in await self._engine.for_session(session_id))
|
|
148
169
|
|
|
149
|
-
def for_repo(self, repo: str) -> tuple[Correction, ...]:
|
|
170
|
+
async def for_repo(self, repo: str) -> tuple[Correction, ...]:
|
|
150
171
|
"""All corrections whose ``detail.repo`` is ``repo``, ordered by timestamp.
|
|
151
172
|
|
|
152
173
|
The repo key producers stamp into ``detail`` so a per-repo consumer (the
|
|
153
174
|
captain-hook reviewer) can pull every correction for its repo at once.
|
|
154
175
|
"""
|
|
155
|
-
return tuple(Correction(**row) for row in self._engine.for_repo(repo))
|
|
176
|
+
return tuple(Correction(**row) for row in await self._engine.for_repo(repo))
|
|
156
177
|
|
|
157
|
-
def since(self, ts_ms: int, *, source: str | None = None) -> tuple[Correction, ...]:
|
|
178
|
+
async def since(self, ts_ms: int, *, source: str | None = None) -> tuple[Correction, ...]:
|
|
158
179
|
"""Corrections with ``ts_ms`` strictly greater than ``ts_ms``, oldest first.
|
|
159
180
|
|
|
160
181
|
A cursor read for incremental consumers; pass ``source`` to scope to one
|
|
161
182
|
producer.
|
|
162
183
|
"""
|
|
163
|
-
return tuple(Correction(**row) for row in self._engine.since(ts_ms, source))
|
|
184
|
+
return tuple(Correction(**row) for row in await self._engine.since(ts_ms, source))
|
|
164
185
|
|
|
165
|
-
def for_anchor(self, session_id: SessionId, anchor_uuid: EventUuid) -> tuple[Correction, ...]:
|
|
186
|
+
async def for_anchor(self, session_id: SessionId, anchor_uuid: EventUuid) -> tuple[Correction, ...]:
|
|
166
187
|
"""The corrections harvested around one feedback ``anchor_uuid``."""
|
|
167
|
-
return tuple(Correction(**row) for row in self._engine.for_anchor(session_id, anchor_uuid))
|
|
188
|
+
return tuple(Correction(**row) for row in await self._engine.for_anchor(session_id, anchor_uuid))
|
|
168
189
|
|
|
169
|
-
def by_digest(self, session_id: SessionId, *, incorrect_digest: ToolDigest) -> tuple[Correction, ...]:
|
|
190
|
+
async def by_digest(self, session_id: SessionId, *, incorrect_digest: ToolDigest) -> tuple[Correction, ...]:
|
|
170
191
|
"""Corrections of the tool call with ``incorrect_digest`` in ``session_id``.
|
|
171
192
|
|
|
172
193
|
The cross-consumer join: pass the ``tool_digest`` a hook recorded in
|
|
173
194
|
the ``decisions`` ledger to learn whether that exact edit was later
|
|
174
195
|
corrected.
|
|
175
196
|
"""
|
|
176
|
-
return tuple(Correction(**row) for row in self._engine.by_digest(session_id, incorrect_digest))
|
|
197
|
+
return tuple(Correction(**row) for row in await self._engine.by_digest(session_id, incorrect_digest))
|
|
177
198
|
|
|
178
|
-
def sql(self, statement: str) -> list[dict[str, Any]]:
|
|
199
|
+
async def sql(self, statement: str) -> list[dict[str, Any]]:
|
|
179
200
|
"""Runs one raw SQL ``statement`` — the escape hatch behind ``corrections sql``."""
|
|
180
|
-
return self._engine.sql(statement)
|
|
201
|
+
return await self._engine.sql(statement)
|
|
@@ -14,7 +14,7 @@ import sqlite3
|
|
|
14
14
|
from dataclasses import dataclass, field
|
|
15
15
|
from typing import TYPE_CHECKING, Literal
|
|
16
16
|
|
|
17
|
-
from cc_transcript.ledger import
|
|
17
|
+
from cc_transcript.ledger import AsyncLedger
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
20
|
from collections.abc import Mapping
|
|
@@ -87,7 +87,7 @@ class Decision:
|
|
|
87
87
|
detail: Mapping[str, Any] = field(default_factory=dict)
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
class DecisionLog(
|
|
90
|
+
class DecisionLog(AsyncLedger[Decision]):
|
|
91
91
|
"""The ``decisions`` ledger at ``~/.cc-transcript/decisions.db``.
|
|
92
92
|
|
|
93
93
|
Opened in WAL mode with a busy timeout because cc-review's Go daemon
|
|
@@ -95,9 +95,10 @@ class DecisionLog(SyncLedger[Decision]):
|
|
|
95
95
|
auto-dropped. Requires a local disk — WAL does not work over NFS.
|
|
96
96
|
|
|
97
97
|
Example:
|
|
98
|
-
>>> log = DecisionLog.open()
|
|
99
|
-
>>> log
|
|
100
|
-
|
|
98
|
+
>>> log = await DecisionLog.open()
|
|
99
|
+
>>> async with log:
|
|
100
|
+
... await log.append(decision)
|
|
101
|
+
... await log.attribute_tool(session_id, tool_digest=digest, near_ts_ms=ts)
|
|
101
102
|
"""
|
|
102
103
|
|
|
103
104
|
DDL = DECISIONS_DDL
|
|
@@ -134,7 +135,7 @@ class DecisionLog(SyncLedger[Decision]):
|
|
|
134
135
|
detail=json.loads(row["detail_json"]),
|
|
135
136
|
)
|
|
136
137
|
|
|
137
|
-
def attribute_tool(
|
|
138
|
+
async def attribute_tool(
|
|
138
139
|
self, session_id: SessionId, *, tool_digest: ToolDigest, near_ts_ms: int, window_ms: int = 300_000
|
|
139
140
|
) -> Decision | None:
|
|
140
141
|
"""The nearest decision preceding ``near_ts_ms`` with this digest.
|
|
@@ -148,14 +149,17 @@ class DecisionLog(SyncLedger[Decision]):
|
|
|
148
149
|
The matching decision, or None when no digest-equal row lies in
|
|
149
150
|
``[near_ts_ms - window_ms, near_ts_ms]``.
|
|
150
151
|
"""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
conn = self._actor.conn
|
|
153
|
+
row = await self._actor.run(
|
|
154
|
+
lambda: conn.execute(
|
|
155
|
+
"SELECT * FROM decisions WHERE session_id = ? AND tool_digest = ? AND ts_ms BETWEEN ? AND ?"
|
|
156
|
+
" ORDER BY ts_ms DESC, id DESC LIMIT 1",
|
|
157
|
+
(session_id, tool_digest, near_ts_ms - window_ms, near_ts_ms),
|
|
158
|
+
).fetchone()
|
|
159
|
+
)
|
|
156
160
|
return self.row_to_record(row) if row else None
|
|
157
161
|
|
|
158
|
-
def attribute_nearest(
|
|
162
|
+
async def attribute_nearest(
|
|
159
163
|
self,
|
|
160
164
|
session_id: SessionId,
|
|
161
165
|
*,
|
|
@@ -175,10 +179,13 @@ class DecisionLog(SyncLedger[Decision]):
|
|
|
175
179
|
The nearest matching decision, or None when none lies in
|
|
176
180
|
``[near_ts_ms - window_ms, near_ts_ms + window_ms]``.
|
|
177
181
|
"""
|
|
182
|
+
conn = self._actor.conn
|
|
178
183
|
kind_clause, kind_params = ("AND kind = ? ", (kind,)) if kind is not None else ("", ())
|
|
179
|
-
row = self.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
row = await self._actor.run(
|
|
185
|
+
lambda: conn.execute(
|
|
186
|
+
f"SELECT * FROM decisions WHERE session_id = ? AND event = ? {kind_clause}AND ts_ms BETWEEN ? AND ?"
|
|
187
|
+
" ORDER BY ABS(ts_ms - ?), id DESC LIMIT 1",
|
|
188
|
+
(session_id, event, *kind_params, near_ts_ms - window_ms, near_ts_ms + window_ms, near_ts_ms),
|
|
189
|
+
).fetchone()
|
|
190
|
+
)
|
|
184
191
|
return self.row_to_record(row) if row else None
|
|
@@ -199,7 +199,7 @@ def run_git(repo: Path, *args: str) -> str | None:
|
|
|
199
199
|
return proc.stdout if proc.returncode == 0 else None
|
|
200
200
|
|
|
201
201
|
|
|
202
|
-
def record_harvest(
|
|
202
|
+
async def record_harvest(
|
|
203
203
|
log: CorrectionLog,
|
|
204
204
|
activity: SessionActivity,
|
|
205
205
|
anchor: EventRef,
|
|
@@ -219,7 +219,7 @@ def record_harvest(
|
|
|
219
219
|
"""
|
|
220
220
|
rows = [correction for pair in pairs if (correction := lower_pair(activity, anchor, pair, source=source))]
|
|
221
221
|
for row in rows:
|
|
222
|
-
log.append(row)
|
|
222
|
+
await log.append(row)
|
|
223
223
|
return len(rows)
|
|
224
224
|
|
|
225
225
|
|
|
@@ -163,7 +163,7 @@ async def extract_correction(
|
|
|
163
163
|
already has a row, so re-runs and overlapping producers never duplicate.
|
|
164
164
|
Returns the appended correction, or None when nothing is harvested or picked.
|
|
165
165
|
"""
|
|
166
|
-
if log.for_anchor(anchor.session_id, anchor.event_uuid):
|
|
166
|
+
if await log.for_anchor(anchor.session_id, anchor.event_uuid):
|
|
167
167
|
return None
|
|
168
168
|
pairs = await asyncio.to_thread(harvest_pairs, activity, anchor, repo=repo)
|
|
169
169
|
if not pairs or (turn := activity.turn_of(anchor)) is None:
|
|
@@ -173,5 +173,5 @@ async def extract_correction(
|
|
|
173
173
|
return None
|
|
174
174
|
if repo is not None:
|
|
175
175
|
row = replace(row, detail={"repo": str(repo)})
|
|
176
|
-
log.append(row)
|
|
176
|
+
await log.append(row)
|
|
177
177
|
return row
|
|
@@ -12,15 +12,15 @@ most one row per ``(session, event)``, so the table stays small however hot the
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
-
import sqlite3
|
|
16
15
|
from dataclasses import dataclass
|
|
17
|
-
from pathlib import Path
|
|
18
16
|
from typing import TYPE_CHECKING, Self
|
|
19
17
|
|
|
20
18
|
from cc_transcript.decisions import DecisionLog
|
|
21
|
-
from cc_transcript.ledger import open_sqlite
|
|
19
|
+
from cc_transcript.ledger import ConnectionActor, open_sqlite
|
|
22
20
|
|
|
23
21
|
if TYPE_CHECKING:
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
24
|
from cc_transcript.ids import SessionId
|
|
25
25
|
|
|
26
26
|
HEARTBEATS_DDL = """\
|
|
@@ -63,30 +63,42 @@ class HeartbeatLog:
|
|
|
63
63
|
most one row per event. Requires a local disk — WAL does not work over NFS.
|
|
64
64
|
|
|
65
65
|
Example:
|
|
66
|
-
>>> log = HeartbeatLog.open()
|
|
67
|
-
>>>
|
|
68
|
-
|
|
66
|
+
>>> log = await HeartbeatLog.open()
|
|
67
|
+
>>> async with log:
|
|
68
|
+
... await log.beat(session_id, "PreToolUse", ts_ms)
|
|
69
|
+
... await log.for_session(session_id)
|
|
69
70
|
"""
|
|
70
71
|
|
|
71
|
-
def __init__(self,
|
|
72
|
-
self.
|
|
72
|
+
def __init__(self, actor: ConnectionActor) -> None:
|
|
73
|
+
self._actor = actor
|
|
73
74
|
|
|
74
75
|
@classmethod
|
|
75
|
-
def open(cls, path: Path | None = None) -> Self:
|
|
76
|
+
async def open(cls, path: Path | None = None) -> Self:
|
|
76
77
|
"""Opens (creating if needed) the heartbeat table at ``path`` (defaults to ``decisions.db``)."""
|
|
77
|
-
|
|
78
|
+
actor = ConnectionActor()
|
|
79
|
+
await actor.start(lambda: open_sqlite(path, filename=DecisionLog.FILENAME, ddl=HEARTBEATS_DDL))
|
|
80
|
+
return cls(actor)
|
|
78
81
|
|
|
79
|
-
def beat(self, session_id: SessionId, event: str, ts_ms: int) -> None:
|
|
82
|
+
async def beat(self, session_id: SessionId, event: str, ts_ms: int) -> None:
|
|
80
83
|
"""Records one dispatch of ``event`` in ``session_id`` at ``ts_ms`` (upsert; ``count += 1``)."""
|
|
81
|
-
self.conn
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
conn = self._actor.conn
|
|
85
|
+
await self._actor.run(
|
|
86
|
+
lambda: conn.execute(
|
|
87
|
+
"INSERT INTO dispatch_heartbeats (session_id, event, first_ts_ms, last_ts_ms, count) "
|
|
88
|
+
"VALUES (?, ?, ?, ?, 1) "
|
|
89
|
+
"ON CONFLICT (session_id, event) DO UPDATE SET last_ts_ms = excluded.last_ts_ms, count = count + 1",
|
|
90
|
+
(session_id, event, ts_ms, ts_ms),
|
|
91
|
+
)
|
|
86
92
|
)
|
|
87
93
|
|
|
88
|
-
def for_session(self, session_id: SessionId) -> tuple[Heartbeat, ...]:
|
|
94
|
+
async def for_session(self, session_id: SessionId) -> tuple[Heartbeat, ...]:
|
|
89
95
|
"""Every ``(session, event)`` beat for ``session_id``, ordered by first dispatch."""
|
|
96
|
+
conn = self._actor.conn
|
|
97
|
+
rows = await self._actor.run(
|
|
98
|
+
lambda: conn.execute(
|
|
99
|
+
"SELECT * FROM dispatch_heartbeats WHERE session_id = ? ORDER BY first_ts_ms, event", (session_id,)
|
|
100
|
+
).fetchall()
|
|
101
|
+
)
|
|
90
102
|
return tuple(
|
|
91
103
|
Heartbeat(
|
|
92
104
|
session_id=row["session_id"],
|
|
@@ -95,7 +107,15 @@ class HeartbeatLog:
|
|
|
95
107
|
last_ts_ms=row["last_ts_ms"],
|
|
96
108
|
count=row["count"],
|
|
97
109
|
)
|
|
98
|
-
for row in
|
|
99
|
-
"SELECT * FROM dispatch_heartbeats WHERE session_id = ? ORDER BY first_ts_ms, event", (session_id,)
|
|
100
|
-
)
|
|
110
|
+
for row in rows
|
|
101
111
|
)
|
|
112
|
+
|
|
113
|
+
async def close(self) -> None:
|
|
114
|
+
"""Closes the underlying connection; a second call is a no-op."""
|
|
115
|
+
await self._actor.close()
|
|
116
|
+
|
|
117
|
+
async def __aenter__(self) -> Self:
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
async def __aexit__(self, *exc: object) -> None:
|
|
121
|
+
await self.close()
|
|
@@ -16,6 +16,7 @@ fails loud with an :class:`ImportError` naming it.
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
+
import asyncio
|
|
19
20
|
from functools import cache
|
|
20
21
|
from importlib.util import find_spec
|
|
21
22
|
from typing import TYPE_CHECKING, NamedTuple
|
|
@@ -125,7 +126,7 @@ def default_embedder() -> Embedder:
|
|
|
125
126
|
return embed
|
|
126
127
|
|
|
127
128
|
|
|
128
|
-
def prepare_connection(store: FeedbackStore) -> None:
|
|
129
|
+
async def prepare_connection(store: FeedbackStore) -> None:
|
|
129
130
|
"""Loads the sqlite-vec extension and creates the companion tables, once per store.
|
|
130
131
|
|
|
131
132
|
The schema-creating ``executescript`` implicitly commits the connection, so
|
|
@@ -137,8 +138,8 @@ def prepare_connection(store: FeedbackStore) -> None:
|
|
|
137
138
|
|
|
138
139
|
if store._vec_prepared:
|
|
139
140
|
return
|
|
140
|
-
store.load_extension(sqlite_vec.loadable_path())
|
|
141
|
-
store.executescript(VECTOR_SCHEMA)
|
|
141
|
+
await store.load_extension(sqlite_vec.loadable_path())
|
|
142
|
+
await store.executescript(VECTOR_SCHEMA)
|
|
142
143
|
store._vec_prepared = True
|
|
143
144
|
|
|
144
145
|
|
|
@@ -148,7 +149,7 @@ def serialize_vector(vector: np.ndarray) -> bytes:
|
|
|
148
149
|
return sqlite_vec.serialize_float32(vector.tolist())
|
|
149
150
|
|
|
150
151
|
|
|
151
|
-
def embed_evidence(store: FeedbackStore, *, dedup_key: DedupKey, canonical_key: str, summary: str) -> Evidence:
|
|
152
|
+
async def embed_evidence(store: FeedbackStore, *, dedup_key: DedupKey, canonical_key: str, summary: str) -> Evidence:
|
|
152
153
|
"""Prepares the vector store and embeds a judged event's feedback for upsert.
|
|
153
154
|
|
|
154
155
|
Fetches the event's feedback text from ``feedback_events`` and embeds it
|
|
@@ -174,12 +175,12 @@ def embed_evidence(store: FeedbackStore, *, dedup_key: DedupKey, canonical_key:
|
|
|
174
175
|
ImportError: When the ``cc-transcript[judge]`` extra is not installed.
|
|
175
176
|
"""
|
|
176
177
|
require_judge_extra()
|
|
177
|
-
prepare_connection(store)
|
|
178
|
-
rows = store.sql("SELECT text FROM feedback_events WHERE dedup_key = ?", [dedup_key])
|
|
178
|
+
await prepare_connection(store)
|
|
179
|
+
rows = await store.sql("SELECT text FROM feedback_events WHERE dedup_key = ?", [dedup_key])
|
|
179
180
|
assert rows, "verdict dedup keys always resolve to a stored feedback event"
|
|
180
181
|
text = str(rows[0]["text"])
|
|
181
|
-
embedder = default_embedder
|
|
182
|
-
vector = embedder
|
|
182
|
+
embedder = await asyncio.to_thread(default_embedder)
|
|
183
|
+
vector = await asyncio.to_thread(embedder, f"{text}\n{summary}")
|
|
183
184
|
return Evidence(serialize_vector(vector), text, canonical_key)
|
|
184
185
|
|
|
185
186
|
|
|
@@ -187,7 +188,7 @@ def evidence_vector_id(dedup_key: DedupKey, role: str, prompt_version: int) -> s
|
|
|
187
188
|
return f"{dedup_key}::{role}::{prompt_version}"
|
|
188
189
|
|
|
189
190
|
|
|
190
|
-
def clear_evidence(store: FeedbackStore, *, dedup_key: DedupKey, role: str, prompt_version: int) -> None:
|
|
191
|
+
async def clear_evidence(store: FeedbackStore, *, dedup_key: DedupKey, role: str, prompt_version: int) -> None:
|
|
191
192
|
"""Deletes any evidence vector for one verdict identity, on the caller's transaction.
|
|
192
193
|
|
|
193
194
|
Both the delete pair :func:`record_evidence` runs before it re-inserts, and
|
|
@@ -203,11 +204,11 @@ def clear_evidence(store: FeedbackStore, *, dedup_key: DedupKey, role: str, prom
|
|
|
203
204
|
prompt_version: The prompt version that produced the verdict.
|
|
204
205
|
"""
|
|
205
206
|
vector_id = evidence_vector_id(dedup_key, role, prompt_version)
|
|
206
|
-
store.execute("DELETE FROM verdict_vectors WHERE vector_id = ?", [vector_id])
|
|
207
|
-
store.execute("DELETE FROM verdict_evidence WHERE vector_id = ?", [vector_id])
|
|
207
|
+
await store.execute("DELETE FROM verdict_vectors WHERE vector_id = ?", [vector_id])
|
|
208
|
+
await store.execute("DELETE FROM verdict_evidence WHERE vector_id = ?", [vector_id])
|
|
208
209
|
|
|
209
210
|
|
|
210
|
-
def record_evidence(
|
|
211
|
+
async def record_evidence(
|
|
211
212
|
store: FeedbackStore, *, dedup_key: DedupKey, role: str, prompt_version: int, evidence: Evidence
|
|
212
213
|
) -> None:
|
|
213
214
|
"""Upserts a judged event's evidence vector inside the caller's open transaction.
|
|
@@ -226,17 +227,17 @@ def record_evidence(
|
|
|
226
227
|
prompt_version: The prompt version that produced the verdict.
|
|
227
228
|
evidence: The embedded feedback from :func:`embed_evidence`.
|
|
228
229
|
"""
|
|
229
|
-
clear_evidence(store, dedup_key=dedup_key, role=role, prompt_version=prompt_version)
|
|
230
|
+
await clear_evidence(store, dedup_key=dedup_key, role=role, prompt_version=prompt_version)
|
|
230
231
|
vector_id = evidence_vector_id(dedup_key, role, prompt_version)
|
|
231
|
-
store.execute("INSERT INTO verdict_vectors(vector_id, embedding) VALUES (?, ?)", [vector_id, evidence.vector])
|
|
232
|
-
store.execute(
|
|
232
|
+
await store.execute("INSERT INTO verdict_vectors(vector_id, embedding) VALUES (?, ?)", [vector_id, evidence.vector])
|
|
233
|
+
await store.execute(
|
|
233
234
|
"INSERT INTO verdict_evidence(vector_id, dedup_key, role, prompt_version, canonical_key, evidence_text) "
|
|
234
235
|
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
235
236
|
[vector_id, dedup_key, role, prompt_version, evidence.canonical_key, evidence.text],
|
|
236
237
|
)
|
|
237
238
|
|
|
238
239
|
|
|
239
|
-
def prepare_evidence_removal(store: FeedbackStore) -> bool:
|
|
240
|
+
async def prepare_evidence_removal(store: FeedbackStore) -> bool:
|
|
240
241
|
"""Readies the vec companion so an in-transaction :func:`clear_evidence` can reach it.
|
|
241
242
|
|
|
242
243
|
Returns False — skip the removal — whenever no evidence could exist: ``sqlite_vec``
|
|
@@ -257,13 +258,13 @@ def prepare_evidence_removal(store: FeedbackStore) -> bool:
|
|
|
257
258
|
return True
|
|
258
259
|
if find_spec("sqlite_vec") is None:
|
|
259
260
|
return False
|
|
260
|
-
if not store.sql("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'verdict_evidence'"):
|
|
261
|
+
if not await store.sql("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'verdict_evidence'"):
|
|
261
262
|
return False
|
|
262
|
-
prepare_connection(store)
|
|
263
|
+
await prepare_connection(store)
|
|
263
264
|
return True
|
|
264
265
|
|
|
265
266
|
|
|
266
|
-
def suggest_canonical_keys(store: FeedbackStore, text: str, *, prompt_version: int, k: int = 5) -> list[Suggestion]:
|
|
267
|
+
async def suggest_canonical_keys(store: FeedbackStore, text: str, *, prompt_version: int, k: int = 5) -> list[Suggestion]:
|
|
267
268
|
"""Ranks stored canonical keys by evidence similarity to ``text``.
|
|
268
269
|
|
|
269
270
|
Embeds ``text`` and scans every evidence vector recorded at ``prompt_version``,
|
|
@@ -284,11 +285,11 @@ def suggest_canonical_keys(store: FeedbackStore, text: str, *, prompt_version: i
|
|
|
284
285
|
ImportError: When the ``cc-transcript[judge]`` extra is not installed.
|
|
285
286
|
"""
|
|
286
287
|
require_judge_extra()
|
|
287
|
-
prepare_connection(store)
|
|
288
|
-
embedder = default_embedder
|
|
289
|
-
query = serialize_vector(embedder
|
|
288
|
+
await prepare_connection(store)
|
|
289
|
+
embedder = await asyncio.to_thread(default_embedder)
|
|
290
|
+
query = serialize_vector(await asyncio.to_thread(embedder, text))
|
|
290
291
|
ranked: dict[str, list[tuple[float, str]]] = {}
|
|
291
|
-
for row in store.sql(
|
|
292
|
+
for row in await store.sql(
|
|
292
293
|
"SELECT e.canonical_key AS ck, e.evidence_text AS ev, vec_distance_cosine(v.embedding, ?) AS dist "
|
|
293
294
|
"FROM verdict_vectors v JOIN verdict_evidence e ON e.vector_id = v.vector_id "
|
|
294
295
|
"WHERE e.prompt_version = ? ORDER BY dist",
|
|
@@ -302,7 +303,7 @@ def suggest_canonical_keys(store: FeedbackStore, text: str, *, prompt_version: i
|
|
|
302
303
|
)[:k]
|
|
303
304
|
|
|
304
305
|
|
|
305
|
-
def near_duplicate_keys(store: FeedbackStore, *, prompt_version: int, threshold: float) -> list[KeyOverlap]:
|
|
306
|
+
async def near_duplicate_keys(store: FeedbackStore, *, prompt_version: int, threshold: float) -> list[KeyOverlap]:
|
|
306
307
|
"""Finds distinct canonical keys whose evidence centroids nearly coincide.
|
|
307
308
|
|
|
308
309
|
Groups the evidence vectors recorded at ``prompt_version`` by canonical key,
|
|
@@ -326,9 +327,9 @@ def near_duplicate_keys(store: FeedbackStore, *, prompt_version: int, threshold:
|
|
|
326
327
|
require_judge_extra()
|
|
327
328
|
import numpy as np
|
|
328
329
|
|
|
329
|
-
prepare_connection(store)
|
|
330
|
+
await prepare_connection(store)
|
|
330
331
|
groups: dict[str, list[np.ndarray]] = {}
|
|
331
|
-
for row in store.sql(
|
|
332
|
+
for row in await store.sql(
|
|
332
333
|
"SELECT e.canonical_key AS ck, v.embedding AS emb "
|
|
333
334
|
"FROM verdict_vectors v JOIN verdict_evidence e ON e.vector_id = v.vector_id "
|
|
334
335
|
"WHERE e.prompt_version = ?",
|