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.
Files changed (152) hide show
  1. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/PKG-INFO +1 -1
  2. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/_native.pyi +31 -27
  3. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/corrections.py +42 -21
  4. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/decisions.py +24 -17
  5. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/evidence.py +2 -2
  6. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/extract/correct.py +2 -2
  7. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/heartbeats.py +40 -20
  8. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/judge/similar.py +27 -26
  9. cc_transcript-14.5.0/cc_transcript/ledger.py +163 -0
  10. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/store.py +95 -74
  11. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/watch.py +23 -10
  12. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/pyproject.toml +1 -1
  13. cc_transcript-14.5.0/rust/crates/core/src/actor.rs +166 -0
  14. cc_transcript-14.5.0/rust/crates/core/src/codex/lower.rs +1342 -0
  15. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/codex/mod.rs +2 -0
  16. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/lib.rs +2 -0
  17. cc_transcript-14.5.0/rust/crates/py/src/actor_bridge.rs +135 -0
  18. cc_transcript-14.5.0/rust/crates/py/src/corrections.rs +260 -0
  19. cc_transcript-14.5.0/rust/crates/py/src/feedback.rs +405 -0
  20. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/lib.rs +1 -0
  21. cc_transcript-14.4.0/cc_transcript/ledger.py +0 -94
  22. cc_transcript-14.4.0/rust/crates/py/src/corrections.rs +0 -200
  23. cc_transcript-14.4.0/rust/crates/py/src/feedback.rs +0 -321
  24. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/Cargo.lock +0 -0
  25. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/Cargo.toml +0 -0
  26. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/LICENSE +0 -0
  27. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/README.md +0 -0
  28. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/__init__.py +0 -0
  29. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/__init__.pyi +0 -0
  30. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/activity.py +0 -0
  31. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/activity_probe.py +0 -0
  32. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/builders.py +0 -0
  33. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/command.py +0 -0
  34. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/context.py +0 -0
  35. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/cost.py +0 -0
  36. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/discovery.py +0 -0
  37. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/disktruth.py +0 -0
  38. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/extract/__init__.py +0 -0
  39. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/facts.py +0 -0
  40. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/filterspec.py +0 -0
  41. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/ids.py +0 -0
  42. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/judge/__init__.py +0 -0
  43. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/judge/llm.py +0 -0
  44. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/judge/verdicts.py +0 -0
  45. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/literals.py +0 -0
  46. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/__init__.py +0 -0
  47. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/candidates.py +0 -0
  48. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/confidence.py +0 -0
  49. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/engine.py +0 -0
  50. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/formats.py +0 -0
  51. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/sampling.py +0 -0
  52. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/signals.py +0 -0
  53. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/sourcekind.py +0 -0
  54. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/mining/spec.py +0 -0
  55. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/models.py +0 -0
  56. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/nlp.py +0 -0
  57. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/notifications.py +0 -0
  58. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/parser.py +0 -0
  59. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/py.typed +0 -0
  60. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/query.py +0 -0
  61. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/render.py +0 -0
  62. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/__init__.py +0 -0
  63. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/buckets.py +0 -0
  64. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
  65. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
  66. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
  67. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/engine.py +0 -0
  68. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/lexicon.py +0 -0
  69. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/sentiment/scorespec.py +0 -0
  70. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/synthetic.py +0 -0
  71. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/cc_transcript/tools.py +0 -0
  72. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/Cargo.toml +0 -0
  73. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/corrections.rs +0 -0
  74. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/digest.rs +0 -0
  75. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/facts.rs +0 -0
  76. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/grep.rs +0 -0
  77. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/list.rs +0 -0
  78. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/mod.rs +0 -0
  79. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
  80. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/show.rs +0 -0
  81. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/slice.rs +0 -0
  82. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/stats.rs +0 -0
  83. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/commands/watch.rs +0 -0
  84. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/lib.rs +0 -0
  85. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/main.rs +0 -0
  86. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/output.rs +0 -0
  87. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/cli/src/target.rs +0 -0
  88. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/Cargo.toml +0 -0
  89. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/benches/parse.rs +0 -0
  90. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/benches/render.rs +0 -0
  91. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/benches/toolcall.rs +0 -0
  92. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/activity.rs +0 -0
  93. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/buckets.rs +0 -0
  94. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/codex/parse.rs +0 -0
  95. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/codex/protocol.rs +0 -0
  96. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/codex/types.rs +0 -0
  97. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/command.rs +0 -0
  98. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/context.rs +0 -0
  99. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/corrections.rs +0 -0
  100. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/cost.rs +0 -0
  101. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/discovery.rs +0 -0
  102. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/facts.rs +0 -0
  103. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/feedback.rs +0 -0
  104. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/filter.rs +0 -0
  105. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/ids.rs +0 -0
  106. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/command.rs +0 -0
  107. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/corrections.rs +0 -0
  108. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/feedback.rs +0 -0
  109. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/mining.rs +0 -0
  110. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/mod.rs +0 -0
  111. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/literals/protocol.rs +0 -0
  112. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/notifications.rs +0 -0
  113. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/parse.rs +0 -0
  114. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/protocol.rs +0 -0
  115. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/pystr.rs +0 -0
  116. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/query.rs +0 -0
  117. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/render.rs +0 -0
  118. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/sqlite.rs +0 -0
  119. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/toolcall.rs +0 -0
  120. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/types.rs +0 -0
  121. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/value.rs +0 -0
  122. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/core/src/watch.rs +0 -0
  123. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/Cargo.toml +0 -0
  124. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/build.rs +0 -0
  125. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/data/command_prefix_pins.tsv +0 -0
  126. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/bin/stub_gen.rs +0 -0
  127. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/command.rs +0 -0
  128. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/context.rs +0 -0
  129. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/discovery.rs +0 -0
  130. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/lexicon.rs +0 -0
  131. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/mining.rs +0 -0
  132. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/nlp.rs +0 -0
  133. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/python.rs +0 -0
  134. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/render.rs +0 -0
  135. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/score.rs +0 -0
  136. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/sqlite.rs +0 -0
  137. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/toolcall.rs +0 -0
  138. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/attachment.rs +0 -0
  139. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/blocks.rs +0 -0
  140. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/command.rs +0 -0
  141. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/convert.rs +0 -0
  142. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/dunder.rs +0 -0
  143. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/events.rs +0 -0
  144. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/meta.rs +0 -0
  145. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/mod.rs +0 -0
  146. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/print.rs +0 -0
  147. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/store.rs +0 -0
  148. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/system.rs +0 -0
  149. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/toolcall.rs +0 -0
  150. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/toolresult.rs +0 -0
  151. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/views/transcript.rs +0 -0
  152. {cc_transcript-14.4.0 → cc_transcript-14.5.0}/rust/crates/py/src/watch.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-transcript
3
- Version: 14.4.0
3
+ Version: 14.5.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -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 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) -> None: ...
1451
- def for_session(self, session_id: builtins.str) -> list[dict[str, typing.Any]]: ...
1452
- def for_repo(self, repo: builtins.str) -> list[dict[str, typing.Any]]: ...
1453
- def since(self, ts_ms: builtins.int, source: typing.Optional[builtins.str] = None) -> list[dict[str, typing.Any]]: ...
1454
- def for_anchor(self, session_id: builtins.str, anchor_uuid: builtins.str) -> list[dict[str, typing.Any]]: ...
1455
- def by_digest(self, session_id: builtins.str, incorrect_digest: builtins.str) -> list[dict[str, typing.Any]]: ...
1456
- def sql(self, statement: builtins.str) -> list[dict[str, typing.Any]]: ...
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 close(self) -> None: ...
1462
- def sql(self, statement: builtins.str, params: typing.Optional[typing.Sequence[typing.Any]] = None) -> list[dict[str, typing.Any]]: ...
1463
- def execute(self, statement: builtins.str, params: typing.Optional[typing.Sequence[typing.Any]] = None) -> builtins.int: ...
1464
- def executemany(self, statement: builtins.str, seq: typing.Sequence[typing.Sequence[typing.Any]]) -> builtins.int: ...
1465
- def executescript(self, script: builtins.str) -> None: ...
1466
- def last_insert_rowid(self) -> builtins.int: ...
1467
- def load_extension(self, path: builtins.str) -> None: ...
1468
- def begin_immediate(self) -> None: ...
1469
- def commit(self) -> None: ...
1470
- def rollback(self) -> None: ...
1471
- def insert_candidates(self, rows: typing.Sequence[typing.Sequence[typing.Any]], extras: typing.Optional[typing.Sequence[typing.Sequence[typing.Any]]] = None) -> builtins.list[builtins.str]: ...
1472
- def record_file(self, path: builtins.str, mtime: builtins.float) -> None: ...
1473
- def file_mtimes(self) -> list[dict[str, typing.Any]]: ...
1474
- def stats(self) -> tuple[int, int, list[dict[str, typing.Any]]]: ...
1475
- def recent(self, source_kind: typing.Optional[builtins.str] = None, limit: builtins.int = 20) -> list[dict[str, typing.Any]]: ...
1476
- def events(self, source_kind: typing.Optional[builtins.str] = None) -> list[dict[str, typing.Any]]: ...
1477
- def dedup_keys(self) -> builtins.list[builtins.str]: ...
1478
- 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) -> builtins.bool: ...
1479
- 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) -> list[dict[str, typing.Any]]: ...
1480
- def judged(self, role: builtins.str, prompt_version: builtins.int) -> list[dict[str, typing.Any]]: ...
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
- >>> log = CorrectionLog.open()
97
- >>> log.append(correction)
98
- >>> log.by_digest(session_id, incorrect_digest=digest)
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
- path.parent.mkdir(parents=True, exist_ok=True)
117
- return cls(_native.RustCorrectionLog(str(path)))
118
-
119
- def append(self, record: Correction) -> None:
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 SyncLedger
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(SyncLedger[Decision]):
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.append(decision)
100
- >>> log.attribute_tool(session_id, tool_digest=digest, near_ts_ms=ts)
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
- row = self.conn.execute(
152
- "SELECT * FROM decisions WHERE session_id = ? AND tool_digest = ? AND ts_ms BETWEEN ? AND ?"
153
- " ORDER BY ts_ms DESC, id DESC LIMIT 1",
154
- (session_id, tool_digest, near_ts_ms - window_ms, near_ts_ms),
155
- ).fetchone()
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.conn.execute(
180
- f"SELECT * FROM decisions WHERE session_id = ? AND event = ? {kind_clause}AND ts_ms BETWEEN ? AND ?"
181
- " ORDER BY ABS(ts_ms - ?), id DESC LIMIT 1",
182
- (session_id, event, *kind_params, near_ts_ms - window_ms, near_ts_ms + window_ms, near_ts_ms),
183
- ).fetchone()
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
- >>> log.beat(session_id, "PreToolUse", ts_ms)
68
- >>> log.for_session(session_id)
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, conn: sqlite3.Connection) -> None:
72
- self.conn = conn
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
- return cls(open_sqlite(path, filename=DecisionLog.FILENAME, ddl=HEARTBEATS_DDL))
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.execute(
82
- "INSERT INTO dispatch_heartbeats (session_id, event, first_ts_ms, last_ts_ms, count) "
83
- "VALUES (?, ?, ?, ?, 1) "
84
- "ON CONFLICT (session_id, event) DO UPDATE SET last_ts_ms = excluded.last_ts_ms, count = count + 1",
85
- (session_id, event, ts_ms, ts_ms),
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 self.conn.execute(
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(f"{text}\n{summary}")
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(text))
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 = ?",