thth 3.1.2__tar.gz → 3.2.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 (490) hide show
  1. {thth-3.1.2 → thth-3.2.0}/PKG-INFO +1 -1
  2. {thth-3.1.2 → thth-3.2.0}/mcp/server.py +9 -1
  3. {thth-3.1.2 → thth-3.2.0}/skills/thth/SKILL.md +7 -0
  4. {thth-3.1.2 → thth-3.2.0}/tests/conftest.py +4 -2
  5. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_json_contract.py +1 -1
  6. thth-3.2.0/tests/test_v320_reply_to_file_display.py +146 -0
  7. thth-3.2.0/tests/test_v320_reply_to_file_lint.py +122 -0
  8. thth-3.2.0/tests/test_v320_reply_to_file_resolve.py +318 -0
  9. thth-3.2.0/tests/test_v320_report_fixes.py +174 -0
  10. thth-3.2.0/thth/VERSION +1 -0
  11. {thth-3.1.2 → thth-3.2.0}/thth/admin_log.py +4 -2
  12. {thth-3.1.2 → thth-3.2.0}/thth/approval.py +23 -0
  13. {thth-3.1.2 → thth-3.2.0}/thth/cli.py +34 -5
  14. {thth-3.1.2 → thth-3.2.0}/thth/core.py +82 -14
  15. {thth-3.1.2 → thth-3.2.0}/thth/lint.py +72 -1
  16. {thth-3.1.2 → thth-3.2.0}/thth/morning.py +42 -2
  17. {thth-3.1.2 → thth-3.2.0}/thth/operations_handoff.py +14 -6
  18. {thth-3.1.2 → thth-3.2.0}/thth/queuefile.py +118 -0
  19. {thth-3.1.2 → thth-3.2.0}/thth/report.py +29 -0
  20. {thth-3.1.2 → thth-3.2.0}/thth/report_inbox.py +113 -6
  21. {thth-3.1.2 → thth-3.2.0}/thth/report_service.py +12 -1
  22. {thth-3.1.2 → thth-3.2.0}/thth/runs.py +3 -1
  23. {thth-3.1.2 → thth-3.2.0}/thth/select.py +146 -13
  24. {thth-3.1.2 → thth-3.2.0}/thth/sent.py +9 -1
  25. thth-3.1.2/thth/VERSION +0 -1
  26. {thth-3.1.2 → thth-3.2.0}/.gitignore +0 -0
  27. {thth-3.1.2 → thth-3.2.0}/LICENSE +0 -0
  28. {thth-3.1.2 → thth-3.2.0}/README.md +0 -0
  29. {thth-3.1.2 → thth-3.2.0}/accounts.example/bluesky.json +0 -0
  30. {thth-3.1.2 → thth-3.2.0}/accounts.example/mastodon.json +0 -0
  31. {thth-3.1.2 → thth-3.2.0}/accounts.example/threads.json +0 -0
  32. {thth-3.1.2 → thth-3.2.0}/accounts.example/x.json +0 -0
  33. {thth-3.1.2 → thth-3.2.0}/bin/thth +0 -0
  34. {thth-3.1.2 → thth-3.2.0}/bin/thth-remote +0 -0
  35. {thth-3.1.2 → thth-3.2.0}/bin/thth-run +0 -0
  36. {thth-3.1.2 → thth-3.2.0}/pyproject.toml +0 -0
  37. {thth-3.1.2 → thth-3.2.0}/skills/thth-admin/SKILL.md +0 -0
  38. {thth-3.1.2 → thth-3.2.0}/systemd/thth@.service +0 -0
  39. {thth-3.1.2 → thth-3.2.0}/tests/__init__.py +0 -0
  40. {thth-3.1.2 → thth-3.2.0}/tests/coordination_snapshot.py +0 -0
  41. {thth-3.1.2 → thth-3.2.0}/tests/fixtures/GraphemeBreakTest-17.0.0.txt +0 -0
  42. {thth-3.1.2 → thth-3.2.0}/tests/fixtures/build_injection_eval.py +0 -0
  43. {thth-3.1.2 → thth-3.2.0}/tests/fixtures/callback/worker_responses.json +0 -0
  44. {thth-3.1.2 → thth-3.2.0}/tests/fixtures/injection_eval.json +0 -0
  45. {thth-3.1.2 → thth-3.2.0}/tests/fixtures/umami-bile.md +0 -0
  46. {thth-3.1.2 → thth-3.2.0}/tests/helpers/__init__.py +0 -0
  47. {thth-3.1.2 → thth-3.2.0}/tests/helpers/fake_oauth_server.py +0 -0
  48. {thth-3.1.2 → thth-3.2.0}/tests/helpers/fake_push_adapter.py +0 -0
  49. {thth-3.1.2 → thth-3.2.0}/tests/helpers/fake_redirect_server.py +0 -0
  50. {thth-3.1.2 → thth-3.2.0}/tests/helpers/fake_search_adapter.py +0 -0
  51. {thth-3.1.2 → thth-3.2.0}/tests/helpers/fake_threads_reader.py +0 -0
  52. {thth-3.1.2 → thth-3.2.0}/tests/helpers/fake_threads_writes.py +0 -0
  53. {thth-3.1.2 → thth-3.2.0}/tests/helpers/fake_x.py +0 -0
  54. {thth-3.1.2 → thth-3.2.0}/tests/helpers/hold_lock.py +0 -0
  55. {thth-3.1.2 → thth-3.2.0}/tests/helpers/hold_repo_lock.py +0 -0
  56. {thth-3.1.2 → thth-3.2.0}/tests/helpers/worker_probe.mjs +0 -0
  57. {thth-3.1.2 → thth-3.2.0}/tests/test_27_acceptance_fixes.py +0 -0
  58. {thth-3.1.2 → thth-3.2.0}/tests/test_account_name_check.py +0 -0
  59. {thth-3.1.2 → thth-3.2.0}/tests/test_account_report.py +0 -0
  60. {thth-3.1.2 → thth-3.2.0}/tests/test_accounts_dir.py +0 -0
  61. {thth-3.1.2 → thth-3.2.0}/tests/test_adapter_boundary.py +0 -0
  62. {thth-3.1.2 → thth-3.2.0}/tests/test_admin_http_mcp.py +0 -0
  63. {thth-3.1.2 → thth-3.2.0}/tests/test_admin_log.py +0 -0
  64. {thth-3.1.2 → thth-3.2.0}/tests/test_admin_notifications.py +0 -0
  65. {thth-3.1.2 → thth-3.2.0}/tests/test_admin_repairs.py +0 -0
  66. {thth-3.1.2 → thth-3.2.0}/tests/test_admin_report.py +0 -0
  67. {thth-3.1.2 → thth-3.2.0}/tests/test_after_cli.py +0 -0
  68. {thth-3.1.2 → thth-3.2.0}/tests/test_analytics_comparison.py +0 -0
  69. {thth-3.1.2 → thth-3.2.0}/tests/test_analytics_report.py +0 -0
  70. {thth-3.1.2 → thth-3.2.0}/tests/test_api_diagnostic.py +0 -0
  71. {thth-3.1.2 → thth-3.2.0}/tests/test_api_error_body.py +0 -0
  72. {thth-3.1.2 → thth-3.2.0}/tests/test_app_env_cli.py +0 -0
  73. {thth-3.1.2 → thth-3.2.0}/tests/test_approval.py +0 -0
  74. {thth-3.1.2 → thth-3.2.0}/tests/test_approve_cmd.py +0 -0
  75. {thth-3.1.2 → thth-3.2.0}/tests/test_ask.py +0 -0
  76. {thth-3.1.2 → thth-3.2.0}/tests/test_atlas_fifth_review.py +0 -0
  77. {thth-3.1.2 → thth-3.2.0}/tests/test_atlas_fourth_review.py +0 -0
  78. {thth-3.1.2 → thth-3.2.0}/tests/test_atlas_review.py +0 -0
  79. {thth-3.1.2 → thth-3.2.0}/tests/test_atlas_second_review.py +0 -0
  80. {thth-3.1.2 → thth-3.2.0}/tests/test_atlas_third_review.py +0 -0
  81. {thth-3.1.2 → thth-3.2.0}/tests/test_audit1_fixes.py +0 -0
  82. {thth-3.1.2 → thth-3.2.0}/tests/test_audit2_fixes.py +0 -0
  83. {thth-3.1.2 → thth-3.2.0}/tests/test_audit_b_counterexamples.py +0 -0
  84. {thth-3.1.2 → thth-3.2.0}/tests/test_audit_b_m1_semantics.py +0 -0
  85. {thth-3.1.2 → thth-3.2.0}/tests/test_audit_b_n1_depth.py +0 -0
  86. {thth-3.1.2 → thth-3.2.0}/tests/test_audit_b_r1_closure.py +0 -0
  87. {thth-3.1.2 → thth-3.2.0}/tests/test_audit_v201.py +0 -0
  88. {thth-3.1.2 → thth-3.2.0}/tests/test_audit_v23_details.py +0 -0
  89. {thth-3.1.2 → thth-3.2.0}/tests/test_auth.py +0 -0
  90. {thth-3.1.2 → thth-3.2.0}/tests/test_auth_identity_required.py +0 -0
  91. {thth-3.1.2 → thth-3.2.0}/tests/test_behind_remote.py +0 -0
  92. {thth-3.1.2 → thth-3.2.0}/tests/test_bluesky_adapter.py +0 -0
  93. {thth-3.1.2 → thth-3.2.0}/tests/test_bluesky_secret_scrub.py +0 -0
  94. {thth-3.1.2 → thth-3.2.0}/tests/test_board_needs_review.py +0 -0
  95. {thth-3.1.2 → thth-3.2.0}/tests/test_board_running.py +0 -0
  96. {thth-3.1.2 → thth-3.2.0}/tests/test_bundle.py +0 -0
  97. {thth-3.1.2 → thth-3.2.0}/tests/test_charcount.py +0 -0
  98. {thth-3.1.2 → thth-3.2.0}/tests/test_cli_entrypoint.py +0 -0
  99. {thth-3.1.2 → thth-3.2.0}/tests/test_cli_help.py +0 -0
  100. {thth-3.1.2 → thth-3.2.0}/tests/test_cli_vm_path.py +0 -0
  101. {thth-3.1.2 → thth-3.2.0}/tests/test_collect.py +0 -0
  102. {thth-3.1.2 → thth-3.2.0}/tests/test_collect_engagements.py +0 -0
  103. {thth-3.1.2 → thth-3.2.0}/tests/test_collect_runs.py +0 -0
  104. {thth-3.1.2 → thth-3.2.0}/tests/test_collect_sent.py +0 -0
  105. {thth-3.1.2 → thth-3.2.0}/tests/test_comparability.py +0 -0
  106. {thth-3.1.2 → thth-3.2.0}/tests/test_control_chars_refused.py +0 -0
  107. {thth-3.1.2 → thth-3.2.0}/tests/test_conversation_paging.py +0 -0
  108. {thth-3.1.2 → thth-3.2.0}/tests/test_count_for.py +0 -0
  109. {thth-3.1.2 → thth-3.2.0}/tests/test_docs_links.py +0 -0
  110. {thth-3.1.2 → thth-3.2.0}/tests/test_doctor.py +0 -0
  111. {thth-3.1.2 → thth-3.2.0}/tests/test_doctor_all_permissions.py +0 -0
  112. {thth-3.1.2 → thth-3.2.0}/tests/test_doctor_next_step.py +0 -0
  113. {thth-3.1.2 → thth-3.2.0}/tests/test_duplicate_front_matter_keys.py +0 -0
  114. {thth-3.1.2 → thth-3.2.0}/tests/test_editorial_audit.py +0 -0
  115. {thth-3.1.2 → thth-3.2.0}/tests/test_editorial_n3_n4.py +0 -0
  116. {thth-3.1.2 → thth-3.2.0}/tests/test_engagements.py +0 -0
  117. {thth-3.1.2 → thth-3.2.0}/tests/test_external_acceptance.py +0 -0
  118. {thth-3.1.2 → thth-3.2.0}/tests/test_fake_api.py +0 -0
  119. {thth-3.1.2 → thth-3.2.0}/tests/test_form_spec.py +0 -0
  120. {thth-3.1.2 → thth-3.2.0}/tests/test_form_spec_cli.py +0 -0
  121. {thth-3.1.2 → thth-3.2.0}/tests/test_forms.py +0 -0
  122. {thth-3.1.2 → thth-3.2.0}/tests/test_fresh_install.py +0 -0
  123. {thth-3.1.2 → thth-3.2.0}/tests/test_front_matter_injection.py +0 -0
  124. {thth-3.1.2 → thth-3.2.0}/tests/test_healthcheck_notifications.py +0 -0
  125. {thth-3.1.2 → thth-3.2.0}/tests/test_hypotheses.py +0 -0
  126. {thth-3.1.2 → thth-3.2.0}/tests/test_hypothesis_boundaries.py +0 -0
  127. {thth-3.1.2 → thth-3.2.0}/tests/test_hypothesis_units.py +0 -0
  128. {thth-3.1.2 → thth-3.2.0}/tests/test_impact_cli.py +0 -0
  129. {thth-3.1.2 → thth-3.2.0}/tests/test_inbox_granted_scopes.py +0 -0
  130. {thth-3.1.2 → thth-3.2.0}/tests/test_incident_notifications.py +0 -0
  131. {thth-3.1.2 → thth-3.2.0}/tests/test_independent_thread_review.py +0 -0
  132. {thth-3.1.2 → thth-3.2.0}/tests/test_independent_topic_review.py +0 -0
  133. {thth-3.1.2 → thth-3.2.0}/tests/test_independent_topic_review_r2.py +0 -0
  134. {thth-3.1.2 → thth-3.2.0}/tests/test_inflight.py +0 -0
  135. {thth-3.1.2 → thth-3.2.0}/tests/test_inflight_tag_body_hash.py +0 -0
  136. {thth-3.1.2 → thth-3.2.0}/tests/test_jst.py +0 -0
  137. {thth-3.1.2 → thth-3.2.0}/tests/test_lint.py +0 -0
  138. {thth-3.1.2 → thth-3.2.0}/tests/test_list_projections.py +0 -0
  139. {thth-3.1.2 → thth-3.2.0}/tests/test_llm_trial.py +0 -0
  140. {thth-3.1.2 → thth-3.2.0}/tests/test_llm_trial_engage.py +0 -0
  141. {thth-3.1.2 → thth-3.2.0}/tests/test_lock.py +0 -0
  142. {thth-3.1.2 → thth-3.2.0}/tests/test_m2_independent.py +0 -0
  143. {thth-3.1.2 → thth-3.2.0}/tests/test_maintain.py +0 -0
  144. {thth-3.1.2 → thth-3.2.0}/tests/test_mastodon_adapter.py +0 -0
  145. {thth-3.1.2 → thth-3.2.0}/tests/test_mastodon_visibility.py +0 -0
  146. {thth-3.1.2 → thth-3.2.0}/tests/test_max_per_run.py +0 -0
  147. {thth-3.1.2 → thth-3.2.0}/tests/test_mcp.py +0 -0
  148. {thth-3.1.2 → thth-3.2.0}/tests/test_mcp_robustness.py +0 -0
  149. {thth-3.1.2 → thth-3.2.0}/tests/test_measured.py +0 -0
  150. {thth-3.1.2 → thth-3.2.0}/tests/test_media_accounts.py +0 -0
  151. {thth-3.1.2 → thth-3.2.0}/tests/test_media_next_steps.py +0 -0
  152. {thth-3.1.2 → thth-3.2.0}/tests/test_media_roundtrip.py +0 -0
  153. {thth-3.1.2 → thth-3.2.0}/tests/test_mismatch_diagnostics.py +0 -0
  154. {thth-3.1.2 → thth-3.2.0}/tests/test_n5_reply_null_element.py +0 -0
  155. {thth-3.1.2 → thth-3.2.0}/tests/test_n6_replies_incomplete_ledger.py +0 -0
  156. {thth-3.1.2 → thth-3.2.0}/tests/test_n7_doctor_error_body.py +0 -0
  157. {thth-3.1.2 → thth-3.2.0}/tests/test_no_limits.py +0 -0
  158. {thth-3.1.2 → thth-3.2.0}/tests/test_observation_provenance.py +0 -0
  159. {thth-3.1.2 → thth-3.2.0}/tests/test_operations_handoff.py +0 -0
  160. {thth-3.1.2 → thth-3.2.0}/tests/test_packaging.py +0 -0
  161. {thth-3.1.2 → thth-3.2.0}/tests/test_preview.py +0 -0
  162. {thth-3.1.2 → thth-3.2.0}/tests/test_production_mode.py +0 -0
  163. {thth-3.1.2 → thth-3.2.0}/tests/test_pull_cli.py +0 -0
  164. {thth-3.1.2 → thth-3.2.0}/tests/test_queue.py +0 -0
  165. {thth-3.1.2 → thth-3.2.0}/tests/test_quiet_hours.py +0 -0
  166. {thth-3.1.2 → thth-3.2.0}/tests/test_r2_r4_r5.py +0 -0
  167. {thth-3.1.2 → thth-3.2.0}/tests/test_read_cli_bare_runtimeerror.py +0 -0
  168. {thth-3.1.2 → thth-3.2.0}/tests/test_reason_adoption.py +0 -0
  169. {thth-3.1.2 → thth-3.2.0}/tests/test_redact.py +0 -0
  170. {thth-3.1.2 → thth-3.2.0}/tests/test_redirect_token_leak.py +0 -0
  171. {thth-3.1.2 → thth-3.2.0}/tests/test_refresh.py +0 -0
  172. {thth-3.1.2 → thth-3.2.0}/tests/test_refresh_destination.py +0 -0
  173. {thth-3.1.2 → thth-3.2.0}/tests/test_rejudge_new.py +0 -0
  174. {thth-3.1.2 → thth-3.2.0}/tests/test_rejudge_r2_r3.py +0 -0
  175. {thth-3.1.2 → thth-3.2.0}/tests/test_replies.py +0 -0
  176. {thth-3.1.2 → thth-3.2.0}/tests/test_replies_refresh.py +0 -0
  177. {thth-3.1.2 → thth-3.2.0}/tests/test_repo_lock.py +0 -0
  178. {thth-3.1.2 → thth-3.2.0}/tests/test_repo_sync.py +0 -0
  179. {thth-3.1.2 → thth-3.2.0}/tests/test_report_checkout_import.py +0 -0
  180. {thth-3.1.2 → thth-3.2.0}/tests/test_report_http.py +0 -0
  181. {thth-3.1.2 → thth-3.2.0}/tests/test_report_isolation.py +0 -0
  182. {thth-3.1.2 → thth-3.2.0}/tests/test_report_service.py +0 -0
  183. {thth-3.1.2 → thth-3.2.0}/tests/test_reverse_audit.py +0 -0
  184. {thth-3.1.2 → thth-3.2.0}/tests/test_review_case_id_cluster_m2.py +0 -0
  185. {thth-3.1.2 → thth-3.2.0}/tests/test_review_case_id_cluster_r2.py +0 -0
  186. {thth-3.1.2 → thth-3.2.0}/tests/test_review_cli.py +0 -0
  187. {thth-3.1.2 → thth-3.2.0}/tests/test_review_evidence_account.py +0 -0
  188. {thth-3.1.2 → thth-3.2.0}/tests/test_review_form_spec_and_case_id.py +0 -0
  189. {thth-3.1.2 → thth-3.2.0}/tests/test_review_provenance.py +0 -0
  190. {thth-3.1.2 → thth-3.2.0}/tests/test_review_records.py +0 -0
  191. {thth-3.1.2 → thth-3.2.0}/tests/test_revoke.py +0 -0
  192. {thth-3.1.2 → thth-3.2.0}/tests/test_roundtrip.py +0 -0
  193. {thth-3.1.2 → thth-3.2.0}/tests/test_run_missing_env.py +0 -0
  194. {thth-3.1.2 → thth-3.2.0}/tests/test_run_record_failclosed.py +0 -0
  195. {thth-3.1.2 → thth-3.2.0}/tests/test_runs_collected.py +0 -0
  196. {thth-3.1.2 → thth-3.2.0}/tests/test_runs_read_rows.py +0 -0
  197. {thth-3.1.2 → thth-3.2.0}/tests/test_schedule_multiple.py +0 -0
  198. {thth-3.1.2 → thth-3.2.0}/tests/test_schedule_view.py +0 -0
  199. {thth-3.1.2 → thth-3.2.0}/tests/test_scopes_source_requested.py +0 -0
  200. {thth-3.1.2 → thth-3.2.0}/tests/test_search_engage_targets.py +0 -0
  201. {thth-3.1.2 → thth-3.2.0}/tests/test_search_media_routes.py +0 -0
  202. {thth-3.1.2 → thth-3.2.0}/tests/test_secret_value_redact.py +0 -0
  203. {thth-3.1.2 → thth-3.2.0}/tests/test_secrets_grep.py +0 -0
  204. {thth-3.1.2 → thth-3.2.0}/tests/test_security_small_fixes.py +0 -0
  205. {thth-3.1.2 → thth-3.2.0}/tests/test_select.py +0 -0
  206. {thth-3.1.2 → thth-3.2.0}/tests/test_selfupdate.py +0 -0
  207. {thth-3.1.2 → thth-3.2.0}/tests/test_send.py +0 -0
  208. {thth-3.1.2 → thth-3.2.0}/tests/test_send_cli.py +0 -0
  209. {thth-3.1.2 → thth-3.2.0}/tests/test_send_control_chars.py +0 -0
  210. {thth-3.1.2 → thth-3.2.0}/tests/test_send_media.py +0 -0
  211. {thth-3.1.2 → thth-3.2.0}/tests/test_send_visibility.py +0 -0
  212. {thth-3.1.2 → thth-3.2.0}/tests/test_sent_integrity.py +0 -0
  213. {thth-3.1.2 → thth-3.2.0}/tests/test_server_json.py +0 -0
  214. {thth-3.1.2 → thth-3.2.0}/tests/test_site.py +0 -0
  215. {thth-3.1.2 → thth-3.2.0}/tests/test_skill_doc.py +0 -0
  216. {thth-3.1.2 → thth-3.2.0}/tests/test_study_report.py +0 -0
  217. {thth-3.1.2 → thth-3.2.0}/tests/test_systemd.py +0 -0
  218. {thth-3.1.2 → thth-3.2.0}/tests/test_thread_publish.py +0 -0
  219. {thth-3.1.2 → thth-3.2.0}/tests/test_thread_read.py +0 -0
  220. {thth-3.1.2 → thth-3.2.0}/tests/test_threadrun.py +0 -0
  221. {thth-3.1.2 → thth-3.2.0}/tests/test_threads_read_permissions.py +0 -0
  222. {thth-3.1.2 → thth-3.2.0}/tests/test_threadshape.py +0 -0
  223. {thth-3.1.2 → thth-3.2.0}/tests/test_thth_root.py +0 -0
  224. {thth-3.1.2 → thth-3.2.0}/tests/test_token_exists.py +0 -0
  225. {thth-3.1.2 → thth-3.2.0}/tests/test_token_scopes_record.py +0 -0
  226. {thth-3.1.2 → thth-3.2.0}/tests/test_token_set.py +0 -0
  227. {thth-3.1.2 → thth-3.2.0}/tests/test_topic.py +0 -0
  228. {thth-3.1.2 → thth-3.2.0}/tests/test_topic_advice.py +0 -0
  229. {thth-3.1.2 → thth-3.2.0}/tests/test_topic_cli.py +0 -0
  230. {thth-3.1.2 → thth-3.2.0}/tests/test_topic_models.py +0 -0
  231. {thth-3.1.2 → thth-3.2.0}/tests/test_topic_review_fixes.py +0 -0
  232. {thth-3.1.2 → thth-3.2.0}/tests/test_topic_shelf_observers.py +0 -0
  233. {thth-3.1.2 → thth-3.2.0}/tests/test_topic_store.py +0 -0
  234. {thth-3.1.2 → thth-3.2.0}/tests/test_topics_record_concurrent.py +0 -0
  235. {thth-3.1.2 → thth-3.2.0}/tests/test_topics_tool.py +0 -0
  236. {thth-3.1.2 → thth-3.2.0}/tests/test_trial_frictions.py +0 -0
  237. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_admin_transaction.py +0 -0
  238. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_approve_account.py +0 -0
  239. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_length.py +0 -0
  240. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_lock_wait.py +0 -0
  241. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_mastodon_scopes.py +0 -0
  242. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_mentions.py +0 -0
  243. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_post_urls.py +0 -0
  244. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_repo_paths.py +0 -0
  245. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_reposts.py +0 -0
  246. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_study_add.py +0 -0
  247. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_tool_version.py +0 -0
  248. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_unanswered.py +0 -0
  249. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_unanswered_scope.py +0 -0
  250. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_where_arguments.py +0 -0
  251. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_where_filters.py +0 -0
  252. {thth-3.1.2 → thth-3.2.0}/tests/test_v210_who_arguments.py +0 -0
  253. {thth-3.1.2 → thth-3.2.0}/tests/test_v211_auth_integration.py +0 -0
  254. {thth-3.1.2 → thth-3.2.0}/tests/test_v211_authflow.py +0 -0
  255. {thth-3.1.2 → thth-3.2.0}/tests/test_v211_bluesky_stdin.py +0 -0
  256. {thth-3.1.2 → thth-3.2.0}/tests/test_v211_doctor_binding.py +0 -0
  257. {thth-3.1.2 → thth-3.2.0}/tests/test_v211_mastodon_auth.py +0 -0
  258. {thth-3.1.2 → thth-3.2.0}/tests/test_v211_x_auth.py +0 -0
  259. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_approval_relay.py +0 -0
  260. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_ask_coordination.py +0 -0
  261. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_budget.py +0 -0
  262. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_budget_auth.py +0 -0
  263. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_budget_mcp.py +0 -0
  264. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_deletion.py +0 -0
  265. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_job_completion_fault.py +0 -0
  266. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_leave.py +0 -0
  267. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_leave_gate.py +0 -0
  268. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_leave_providers.py +0 -0
  269. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_leave_token_safety.py +0 -0
  270. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_mastodon_guide.py +0 -0
  271. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_mcp_compat.py +0 -0
  272. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_missing_token.py +0 -0
  273. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_read_coordination.py +0 -0
  274. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_server_writes.py +0 -0
  275. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_token_parent.py +0 -0
  276. {thth-3.1.2 → thth-3.2.0}/tests/test_v212_write_storage.py +0 -0
  277. {thth-3.1.2 → thth-3.2.0}/tests/test_v2131_send_dry_run.py +0 -0
  278. {thth-3.1.2 → thth-3.2.0}/tests/test_v2131_threads_publish_wait.py +0 -0
  279. {thth-3.1.2 → thth-3.2.0}/tests/test_v2132_send_inflight_hint.py +0 -0
  280. {thth-3.1.2 → thth-3.2.0}/tests/test_v2132_threads_create_timeout.py +0 -0
  281. {thth-3.1.2 → thth-3.2.0}/tests/test_v2132_threads_create_unknown.py +0 -0
  282. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_acceptance_fixes.py +0 -0
  283. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_adts_asf.py +0 -0
  284. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_approval_attachments.py +0 -0
  285. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_attachment_kinds.py +0 -0
  286. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bluesky_external.py +0 -0
  287. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bluesky_media.py +0 -0
  288. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bluesky_metadata.py +0 -0
  289. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bluesky_metadata_uri.py +0 -0
  290. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bluesky_metadata_video.py +0 -0
  291. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bluesky_record.py +0 -0
  292. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bluesky_record_video.py +0 -0
  293. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bluesky_video.py +0 -0
  294. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bluesky_webm_named.py +0 -0
  295. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_bmff_typed_metadata.py +0 -0
  296. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_budget_lock.py +0 -0
  297. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_capabilities.py +0 -0
  298. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_carryover_timeout.py +0 -0
  299. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_graphemes.py +0 -0
  300. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_auth_media.py +0 -0
  301. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_bmff_audio.py +0 -0
  302. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_flac.py +0 -0
  303. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_gif.py +0 -0
  304. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_link.py +0 -0
  305. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_media.py +0 -0
  306. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_mp3.py +0 -0
  307. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_ogg_opus.py +0 -0
  308. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_ogg_vorbis.py +0 -0
  309. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_poll.py +0 -0
  310. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_primary_size_boundary.py +0 -0
  311. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_quote.py +0 -0
  312. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_thumbnail.py +0 -0
  313. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_wav.py +0 -0
  314. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_webm_audio.py +0 -0
  315. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_mastodon_webm_video.py +0 -0
  316. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_media_cleanup.py +0 -0
  317. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_media_formats.py +0 -0
  318. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_media_foundation.py +0 -0
  319. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_media_prepared.py +0 -0
  320. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_media_relay.py +0 -0
  321. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_media_relay_refusals.py +0 -0
  322. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_media_upload.py +0 -0
  323. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_receipt_deadline.py +0 -0
  324. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_review_followups.py +0 -0
  325. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_stage810_audit.py +0 -0
  326. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_static_diagnostics.py +0 -0
  327. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_stop_observation.py +0 -0
  328. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_systemd_worker.py +0 -0
  329. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_ghost.py +0 -0
  330. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_gif.py +0 -0
  331. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_link.py +0 -0
  332. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_media.py +0 -0
  333. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_media_spoiler.py +0 -0
  334. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_poll.py +0 -0
  335. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_quote.py +0 -0
  336. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_quote_media.py +0 -0
  337. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_replies.py +0 -0
  338. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_text.py +0 -0
  339. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_text_spoiler.py +0 -0
  340. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_threads_video.py +0 -0
  341. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_transport_review.py +0 -0
  342. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_video_typed_metadata.py +0 -0
  343. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_vorbis_comment_boundary.py +0 -0
  344. {thth-3.1.2 → thth-3.2.0}/tests/test_v213_webm_schema_boundary.py +0 -0
  345. {thth-3.1.2 → thth-3.2.0}/tests/test_v2141_draft_lint_reason.py +0 -0
  346. {thth-3.1.2 → thth-3.2.0}/tests/test_v2141_managed_repo_umask.py +0 -0
  347. {thth-3.1.2 → thth-3.2.0}/tests/test_v214_release_signing.py +0 -0
  348. {thth-3.1.2 → thth-3.2.0}/tests/test_v214_x_adapter.py +0 -0
  349. {thth-3.1.2 → thth-3.2.0}/tests/test_v214_x_auth.py +0 -0
  350. {thth-3.1.2 → thth-3.2.0}/tests/test_v214_x_media.py +0 -0
  351. {thth-3.1.2 → thth-3.2.0}/tests/test_v214_x_posts_budget.py +0 -0
  352. {thth-3.1.2 → thth-3.2.0}/tests/test_v21b_approved_writes.py +0 -0
  353. {thth-3.1.2 → thth-3.2.0}/tests/test_v28_tags.py +0 -0
  354. {thth-3.1.2 → thth-3.2.0}/tests/test_v310_morning.py +0 -0
  355. {thth-3.1.2 → thth-3.2.0}/tests/test_v310_search_timeout.py +0 -0
  356. {thth-3.1.2 → thth-3.2.0}/tests/test_v310_watch_words.py +0 -0
  357. {thth-3.1.2 → thth-3.2.0}/tests/test_v311_leave_same_user.py +0 -0
  358. {thth-3.1.2 → thth-3.2.0}/tests/test_v311_mastodon_retract.py +0 -0
  359. {thth-3.1.2 → thth-3.2.0}/tests/test_v311_morning_overdue.py +0 -0
  360. {thth-3.1.2 → thth-3.2.0}/tests/test_v311_morning_own_exclusion.py +0 -0
  361. {thth-3.1.2 → thth-3.2.0}/tests/test_v311_morning_unanswered_window.py +0 -0
  362. {thth-3.1.2 → thth-3.2.0}/tests/test_v311_notifications_guidance.py +0 -0
  363. {thth-3.1.2 → thth-3.2.0}/tests/test_v311_replies_owned.py +0 -0
  364. {thth-3.1.2 → thth-3.2.0}/tests/test_v312_leave_shared_token.py +0 -0
  365. {thth-3.1.2 → thth-3.2.0}/tests/test_v312_own_handles_stopped.py +0 -0
  366. {thth-3.1.2 → thth-3.2.0}/tests/test_v312_report_admin.py +0 -0
  367. {thth-3.1.2 → thth-3.2.0}/tests/test_v312_report_channel.py +0 -0
  368. {thth-3.1.2 → thth-3.2.0}/tests/test_v312_report_inbox.py +0 -0
  369. {thth-3.1.2 → thth-3.2.0}/tests/test_v312_report_mcp_user.py +0 -0
  370. {thth-3.1.2 → thth-3.2.0}/tests/test_v31_analysis.py +0 -0
  371. {thth-3.1.2 → thth-3.2.0}/tests/test_v31_collection.py +0 -0
  372. {thth-3.1.2 → thth-3.2.0}/tests/test_value_domains_shown.py +0 -0
  373. {thth-3.1.2 → thth-3.2.0}/tests/test_version.py +0 -0
  374. {thth-3.1.2 → thth-3.2.0}/tests/test_where_cli.py +0 -0
  375. {thth-3.1.2 → thth-3.2.0}/tests/test_who_cli.py +0 -0
  376. {thth-3.1.2 → thth-3.2.0}/tests/test_writeback_rebase.py +0 -0
  377. {thth-3.1.2 → thth-3.2.0}/tests/test_writeback_validate.py +0 -0
  378. {thth-3.1.2 → thth-3.2.0}/tests/test_x_metrics.py +0 -0
  379. {thth-3.1.2 → thth-3.2.0}/thth/__init__.py +0 -0
  380. {thth-3.1.2 → thth-3.2.0}/thth/__main__.py +0 -0
  381. {thth-3.1.2 → thth-3.2.0}/thth/_grapheme_data.py +0 -0
  382. {thth-3.1.2 → thth-3.2.0}/thth/account_cli.py +0 -0
  383. {thth-3.1.2 → thth-3.2.0}/thth/account_report.py +0 -0
  384. {thth-3.1.2 → thth-3.2.0}/thth/accounts.py +0 -0
  385. {thth-3.1.2 → thth-3.2.0}/thth/adapters/__init__.py +0 -0
  386. {thth-3.1.2 → thth-3.2.0}/thth/adapters/auth_mastodon.py +0 -0
  387. {thth-3.1.2 → thth-3.2.0}/thth/adapters/auth_threads.py +0 -0
  388. {thth-3.1.2 → thth-3.2.0}/thth/adapters/auth_x.py +0 -0
  389. {thth-3.1.2 → thth-3.2.0}/thth/adapters/base.py +0 -0
  390. {thth-3.1.2 → thth-3.2.0}/thth/adapters/bluesky.py +0 -0
  391. {thth-3.1.2 → thth-3.2.0}/thth/adapters/bluesky_media.py +0 -0
  392. {thth-3.1.2 → thth-3.2.0}/thth/adapters/bluesky_video.py +0 -0
  393. {thth-3.1.2 → thth-3.2.0}/thth/adapters/mastodon.py +0 -0
  394. {thth-3.1.2 → thth-3.2.0}/thth/adapters/mastodon_media.py +0 -0
  395. {thth-3.1.2 → thth-3.2.0}/thth/adapters/threads.py +0 -0
  396. {thth-3.1.2 → thth-3.2.0}/thth/adapters/threads_media.py +0 -0
  397. {thth-3.1.2 → thth-3.2.0}/thth/adapters/x.py +0 -0
  398. {thth-3.1.2 → thth-3.2.0}/thth/adapters/x_media.py +0 -0
  399. {thth-3.1.2 → thth-3.2.0}/thth/admin_notifications.py +0 -0
  400. {thth-3.1.2 → thth-3.2.0}/thth/admin_report.py +0 -0
  401. {thth-3.1.2 → thth-3.2.0}/thth/after_cli.py +0 -0
  402. {thth-3.1.2 → thth-3.2.0}/thth/analytics_comparison.py +0 -0
  403. {thth-3.1.2 → thth-3.2.0}/thth/analytics_report.py +0 -0
  404. {thth-3.1.2 → thth-3.2.0}/thth/analytics_shapes.py +0 -0
  405. {thth-3.1.2 → thth-3.2.0}/thth/analytics_threads.py +0 -0
  406. {thth-3.1.2 → thth-3.2.0}/thth/api_diagnostic.py +0 -0
  407. {thth-3.1.2 → thth-3.2.0}/thth/appconfig.py +0 -0
  408. {thth-3.1.2 → thth-3.2.0}/thth/appenv.py +0 -0
  409. {thth-3.1.2 → thth-3.2.0}/thth/approval_jobs.py +0 -0
  410. {thth-3.1.2 → thth-3.2.0}/thth/approval_relay.py +0 -0
  411. {thth-3.1.2 → thth-3.2.0}/thth/ask.py +0 -0
  412. {thth-3.1.2 → thth-3.2.0}/thth/ask_cli.py +0 -0
  413. {thth-3.1.2 → thth-3.2.0}/thth/audioformats.py +0 -0
  414. {thth-3.1.2 → thth-3.2.0}/thth/authclients.py +0 -0
  415. {thth-3.1.2 → thth-3.2.0}/thth/authflow.py +0 -0
  416. {thth-3.1.2 → thth-3.2.0}/thth/authpassword.py +0 -0
  417. {thth-3.1.2 → thth-3.2.0}/thth/bluesky_metadata.py +0 -0
  418. {thth-3.1.2 → thth-3.2.0}/thth/budget_x.py +0 -0
  419. {thth-3.1.2 → thth-3.2.0}/thth/budget_x_posts.py +0 -0
  420. {thth-3.1.2 → thth-3.2.0}/thth/bundle.py +0 -0
  421. {thth-3.1.2 → thth-3.2.0}/thth/collect.py +0 -0
  422. {thth-3.1.2 → thth-3.2.0}/thth/collection_context.py +0 -0
  423. {thth-3.1.2 → thth-3.2.0}/thth/collection_status.py +0 -0
  424. {thth-3.1.2 → thth-3.2.0}/thth/deletion.py +0 -0
  425. {thth-3.1.2 → thth-3.2.0}/thth/doctor.py +0 -0
  426. {thth-3.1.2 → thth-3.2.0}/thth/ebmlaudio.py +0 -0
  427. {thth-3.1.2 → thth-3.2.0}/thth/ebmlvideo.py +0 -0
  428. {thth-3.1.2 → thth-3.2.0}/thth/engagements.py +0 -0
  429. {thth-3.1.2 → thth-3.2.0}/thth/flacformats.py +0 -0
  430. {thth-3.1.2 → thth-3.2.0}/thth/forms.py +0 -0
  431. {thth-3.1.2 → thth-3.2.0}/thth/graphemes.py +0 -0
  432. {thth-3.1.2 → thth-3.2.0}/thth/handoff_cursor.py +0 -0
  433. {thth-3.1.2 → thth-3.2.0}/thth/healthcheck.py +0 -0
  434. {thth-3.1.2 → thth-3.2.0}/thth/httpsafe.py +0 -0
  435. {thth-3.1.2 → thth-3.2.0}/thth/incident.py +0 -0
  436. {thth-3.1.2 → thth-3.2.0}/thth/inflight.py +0 -0
  437. {thth-3.1.2 → thth-3.2.0}/thth/jst.py +0 -0
  438. {thth-3.1.2 → thth-3.2.0}/thth/leave.py +0 -0
  439. {thth-3.1.2 → thth-3.2.0}/thth/leave_gate.py +0 -0
  440. {thth-3.1.2 → thth-3.2.0}/thth/lock.py +0 -0
  441. {thth-3.1.2 → thth-3.2.0}/thth/maintain.py +0 -0
  442. {thth-3.1.2 → thth-3.2.0}/thth/managed_repo.py +0 -0
  443. {thth-3.1.2 → thth-3.2.0}/thth/measured.py +0 -0
  444. {thth-3.1.2 → thth-3.2.0}/thth/media.py +0 -0
  445. {thth-3.1.2 → thth-3.2.0}/thth/media_capabilities.py +0 -0
  446. {thth-3.1.2 → thth-3.2.0}/thth/media_cleanup.py +0 -0
  447. {thth-3.1.2 → thth-3.2.0}/thth/media_delivery.py +0 -0
  448. {thth-3.1.2 → thth-3.2.0}/thth/media_relay.py +0 -0
  449. {thth-3.1.2 → thth-3.2.0}/thth/media_uploads.py +0 -0
  450. {thth-3.1.2 → thth-3.2.0}/thth/mediaformats.py +0 -0
  451. {thth-3.1.2 → thth-3.2.0}/thth/mpeg_audio.py +0 -0
  452. {thth-3.1.2 → thth-3.2.0}/thth/oauth.py +0 -0
  453. {thth-3.1.2 → thth-3.2.0}/thth/oggformats.py +0 -0
  454. {thth-3.1.2 → thth-3.2.0}/thth/postid.py +0 -0
  455. {thth-3.1.2 → thth-3.2.0}/thth/read_coordination.py +0 -0
  456. {thth-3.1.2 → thth-3.2.0}/thth/read_window.py +0 -0
  457. {thth-3.1.2 → thth-3.2.0}/thth/redact.py +0 -0
  458. {thth-3.1.2 → thth-3.2.0}/thth/replies.py +0 -0
  459. {thth-3.1.2 → thth-3.2.0}/thth/report_details.py +0 -0
  460. {thth-3.1.2 → thth-3.2.0}/thth/report_http.py +0 -0
  461. {thth-3.1.2 → thth-3.2.0}/thth/report_isolation.py +0 -0
  462. {thth-3.1.2 → thth-3.2.0}/thth/retract_cli.py +0 -0
  463. {thth-3.1.2 → thth-3.2.0}/thth/scopes.py +0 -0
  464. {thth-3.1.2 → thth-3.2.0}/thth/secrets_fs.py +0 -0
  465. {thth-3.1.2 → thth-3.2.0}/thth/selfupdate.py +0 -0
  466. {thth-3.1.2 → thth-3.2.0}/thth/server_files.py +0 -0
  467. {thth-3.1.2 → thth-3.2.0}/thth/server_writes.py +0 -0
  468. {thth-3.1.2 → thth-3.2.0}/thth/stop_observation.py +0 -0
  469. {thth-3.1.2 → thth-3.2.0}/thth/study_cli.py +0 -0
  470. {thth-3.1.2 → thth-3.2.0}/thth/study_report.py +0 -0
  471. {thth-3.1.2 → thth-3.2.0}/thth/systemd_gen.py +0 -0
  472. {thth-3.1.2 → thth-3.2.0}/thth/tags.py +0 -0
  473. {thth-3.1.2 → thth-3.2.0}/thth/thread_read.py +0 -0
  474. {thth-3.1.2 → thth-3.2.0}/thth/threadrun.py +0 -0
  475. {thth-3.1.2 → thth-3.2.0}/thth/threads_read_cli.py +0 -0
  476. {thth-3.1.2 → thth-3.2.0}/thth/threadshape.py +0 -0
  477. {thth-3.1.2 → thth-3.2.0}/thth/threadthrow.py +0 -0
  478. {thth-3.1.2 → thth-3.2.0}/thth/tool_version.py +0 -0
  479. {thth-3.1.2 → thth-3.2.0}/thth/topic_advice.py +0 -0
  480. {thth-3.1.2 → thth-3.2.0}/thth/topic_cli.py +0 -0
  481. {thth-3.1.2 → thth-3.2.0}/thth/topic_models.py +0 -0
  482. {thth-3.1.2 → thth-3.2.0}/thth/topic_store.py +0 -0
  483. {thth-3.1.2 → thth-3.2.0}/thth/topics.py +0 -0
  484. {thth-3.1.2 → thth-3.2.0}/thth/unanswered.py +0 -0
  485. {thth-3.1.2 → thth-3.2.0}/thth/vorbisformats.py +0 -0
  486. {thth-3.1.2 → thth-3.2.0}/thth/watch_cli.py +0 -0
  487. {thth-3.1.2 → thth-3.2.0}/thth/where_cli.py +0 -0
  488. {thth-3.1.2 → thth-3.2.0}/thth/who_cli.py +0 -0
  489. {thth-3.1.2 → thth-3.2.0}/thth/writeback.py +0 -0
  490. {thth-3.1.2 → thth-3.2.0}/thth/x_metrics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: thth
3
- Version: 3.1.2
3
+ Version: 3.2.0
4
4
  Summary: 承認済みの投稿だけを投げる門(Threads・Bluesky・Mastodon)。人が承認し、記録は利用者の git に残る。
5
5
  Project-URL: Homepage, https://thth.me
6
6
  Project-URL: Source, https://github.com/aokings/thth
@@ -608,6 +608,14 @@ REPORT_TOOLS = [
608
608
  "description": "報告 1 件の本文と実装側の返事を読む(読むだけ)",
609
609
  "inputSchema": {"type": "object", "properties": {"report_id": {"type": "string"}},
610
610
  "required": ["report_id"], "additionalProperties": False}},
611
+ # 報告した側の追記(設計 3.2.0 §4.5-1)。
612
+ {"name": "thth_report_add",
613
+ "description": "自分の project の開いている報告に書き足す(返事と同じ列に並ぶ・4,000 字まで・"
614
+ "閉じた報告には足せない・秘密らしき値が含まれていたら足さない)",
615
+ "inputSchema": {"type": "object", "properties": {
616
+ "report_id": {"type": "string"},
617
+ "text": {"type": "string", "description": "4,000 字まで"}},
618
+ "required": ["report_id", "text"], "additionalProperties": False}},
611
619
  ]
612
620
 
613
621
 
@@ -681,7 +689,7 @@ def server_call(name, arguments):
681
689
  elif operation.startswith('admin_reports_'):
682
690
  from thth.report_service import execute_admin_reports
683
691
  result=execute_admin_reports(context,request)
684
- elif operation in ('report_file','report_list','report_show'):
692
+ elif operation in ('report_file','report_list','report_show','report_add'):
685
693
  from thth.report_service import execute_user_reports
686
694
  result=execute_user_reports(context,request)
687
695
  elif operation=='morning':
@@ -108,6 +108,12 @@ found_by: where_to_appear|manual|mention
108
108
  untracked/コミット前のファイル)。語を選ぶときだけ `before_you_post`
109
109
  (原稿本文は渡さない・件数と期間つきで返す)。
110
110
 
111
+ **まだ出ていない自分の原稿へ返すとき(3.2.0)**: `reply_to` の代わりに
112
+ `reply_to_file: <同じ queue・同じ account の原稿のファイル名>` と書く(パスは書けない・
113
+ `reply_to` との併用不可)。指した原稿が出るまで待ち、出たらその post_id に返す——
114
+ **root には落とさない**。待っている間は `thth board`・`thth morning` に「返信待ち」として
115
+ 名前と待ち先が出る(時刻超過には数えない)。承認の指紋は `file:<名前>`。
116
+
111
117
  ### 5. 承認——二段(そのまま)
112
118
 
113
119
  ```bash
@@ -144,6 +150,7 @@ MCP `after_you_posted`。**24h の刻みが無ければ `null`**(0 と混ぜ
144
150
  - `thth --help` / `thth <subcommand> --help` が正本。
145
151
  - `thth doctor <account>` が「いま投稿できる状態か」を最初から最後まで言う。
146
152
  - 道具の不具合・欲しい形は `thth report file`(MCP `thth_report_file`)で実装側に届ける。
153
+ - 自分の開いている報告に書き足すなら `thth report add <report_id> --body-file <path|-> --by <名前>`(MCP `thth_report_add`)。
147
154
  - 使い方の全文: `docs/使い方_プロジェクトのセッション向け_2026-09-09.md`。
148
155
 
149
156
  ## 運用と分析のレポート(v3系・2.5.0 から)
@@ -35,8 +35,10 @@ DEFAULT_FM = {
35
35
  "posted_at": None,
36
36
  }
37
37
 
38
+ # `reply_to_file`(設計 3.2.0)・`retracted_at` は既定の front-matter に入れない
39
+ # (`fm_overrides` で渡したときだけ書く)——既存の試験の原稿を 1 行も変えないため。
38
40
  FM_ORDER = ["thth", "account", "publish_at", "status", "approved_sha", "approved_at",
39
- "topic", "reply_to", "post_id", "posted_at"]
41
+ "topic", "reply_to", "reply_to_file", "post_id", "posted_at", "retracted_at"]
40
42
 
41
43
  # quiet_hours の既定(22:00〜07:00)の外にある値。frozen_now_jst() の既定値に使う。
42
44
  FIXED_NOW_JST = datetime.datetime(2026, 9, 9, 10, 0, 0, tzinfo=jst.JST)
@@ -143,7 +145,7 @@ def make_queue_text(fm_overrides=None, body="## threads\n\n本文です。\n",
143
145
  try:
144
146
  fm["approved_sha"] = approval_mod.compute_approved_sha(
145
147
  section=section, account=fm.get("account"),
146
- reply_to=fm.get("reply_to"), topic=fm.get("topic"),
148
+ reply_to=approval_mod.reply_to_for_fingerprint(fm), topic=fm.get("topic"),
147
149
  publish_at=publish_at)
148
150
  except ValueError:
149
151
  pass # publish_at が壊れている型外テスト等: 計算できないので触らない
@@ -27,7 +27,7 @@ COMMANDS = '''lint|preview|approve|account|revoke|posts|replies|measured|threads
27
27
  # 3.1.0: 毎朝の一枚と監視語(設計 3.1.0 §1・§3)。
28
28
  'morning', 'admin watch set', 'admin watch show',
29
29
  # 3.1.2: 報告の口(設計 3.1.2 §1)。
30
- 'report file', 'report list', 'report show', 'admin reports list', 'admin reports show',
30
+ 'report file', 'report list', 'report show', 'report add', 'admin reports list', 'admin reports show',
31
31
  'admin reports reply', 'admin reports close', 'admin reports export']
32
32
 
33
33
 
@@ -0,0 +1,146 @@
1
+ """3.2.0 `reply_to_file` の見せ方(設計 3.2.0 §2・§4・§6)。
2
+
3
+ 待ちは**時刻超過に数えない**。代わりに「返信待ち」として数え、board の要確認と
4
+ morning の予定の段に**待っている原稿の名前と待ち先**を出す。handoff の queue に
5
+ `waiting_reply` の数。`thth preview` は「返信先: <file>(未解決・待ち)」または
6
+ 「返信先: <file> → <post_id>」。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import datetime
11
+ import json
12
+
13
+ from tests.conftest import run_thth, write_queue_file
14
+ from tests.test_v310_morning import ACCOUNT, NOW, PROJECT, one, sections # noqa: F401
15
+ from thth import lint as lint_mod
16
+ from thth import morning
17
+ from thth import operations_handoff as handoff
18
+ from thth import report as report_mod
19
+
20
+ Q = "q.md"
21
+ A = "a.md"
22
+ Q_POST_ID = "17900000000000001"
23
+ BOARD_NOW = datetime.datetime(2026, 9, 9, 10, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(hours=9)))
24
+
25
+
26
+ def _question(queue_dir, *, posted, account=None, publish_at="2026-09-09T05:00:00+09:00"):
27
+ fm = {"publish_at": publish_at}
28
+ if account:
29
+ fm["account"] = account
30
+ if posted:
31
+ fm.update(status="posted", post_id=Q_POST_ID, posted_at=publish_at)
32
+ return write_queue_file(queue_dir, Q, fm_overrides=fm, body="## threads\n\n問い\n")
33
+
34
+
35
+ def _answer(queue_dir, *, account=None, publish_at="2026-09-09T07:00:00+09:00"):
36
+ fm = {"reply_to_file": Q, "publish_at": publish_at}
37
+ if account:
38
+ fm["account"] = account
39
+ return write_queue_file(queue_dir, A, fm_overrides=fm, body="## threads\n\n答え\n")
40
+
41
+
42
+ # ---------------------------------------------------------------- board
43
+
44
+ def test_boardは待ちを時刻超過に数えず名前と待ち先を出す(isolated_account):
45
+ qdir = isolated_account["queue_dir"]
46
+ # 問いは承認済みだが未来(まだ出ない)・答えは 3 時間前の予定(時刻は過ぎている)。
47
+ _question(qdir, posted=False, publish_at="2026-09-09T23:00:00+09:00")
48
+ _answer(qdir)
49
+ summary = report_mod.board_summary(now=BOARD_NOW)
50
+ row = next(r for r in summary["accounts"] if r["account"] == isolated_account["name"])
51
+ assert row["waiting_items"] == [{"file": A, "waiting_for": Q}]
52
+ assert row["waiting_reply_count"] == 1
53
+ reasons = {item["file"]: item["reason"] for item in row["needs_review"]}
54
+ assert reasons[A] == "reply_to_unresolved: waiting_for q.md"
55
+ assert not any(item["reason"] in ("overdue", "rehearsal") for item in row["needs_review"])
56
+
57
+
58
+ def test_boardの文面に返信待ちが出る(isolated_account):
59
+ qdir = isolated_account["queue_dir"]
60
+ _question(qdir, posted=False, publish_at="2030-01-01T23:00:00+09:00")
61
+ _answer(qdir, publish_at="2020-01-01T08:00:00+09:00")
62
+ result = run_thth(["board"])
63
+ assert result.returncode == 0, result.stderr
64
+ assert "返信待ち 1 件" in result.stdout
65
+ assert f"{A} — 返信待ち: {Q} が出たら返信します" in result.stdout
66
+
67
+
68
+ def test_queueとscheduleにも待ちが出る(isolated_account):
69
+ qdir = isolated_account["queue_dir"]
70
+ _question(qdir, posted=False, publish_at="2030-01-01T23:00:00+09:00")
71
+ _answer(qdir, publish_at="2020-01-01T08:00:00+09:00")
72
+ info = report_mod.queue_summary(isolated_account["name"], now=BOARD_NOW)[isolated_account["name"]]
73
+ assert info["waiting_reply"] == 1
74
+ rows = {row["file"]: row for row in report_mod.schedule(isolated_account["name"], now=BOARD_NOW)}
75
+ assert rows[A]["reply_to_file"] == Q and rows[A]["waiting_for"] == Q
76
+ shown = run_thth(["schedule", isolated_account["name"]])
77
+ assert f"← 返信待ち: {Q}" in shown.stdout
78
+
79
+
80
+ # ---------------------------------------------------------------- handoff
81
+
82
+ def test_handoffは待ちをwaiting_replyに数えoverdueに数えない(isolated_account):
83
+ qdir = isolated_account["queue_dir"]
84
+ _question(qdir, posted=False, publish_at="2026-09-09T23:00:00+09:00")
85
+ _answer(qdir, publish_at="2026-09-09T05:00:00+09:00") # 5 時間超過
86
+ name = isolated_account["name"]
87
+ counts = handoff.answer(name, now=BOARD_NOW)["by_account"][name]["queue"]["counts"]
88
+ assert counts["waiting_reply"] == 1
89
+ assert counts["overdue"] == 0
90
+ assert counts["approved_waiting"] == 2
91
+
92
+
93
+ def test_handoffは解決した答えが遅れていれば時刻超過に数える(isolated_account):
94
+ qdir = isolated_account["queue_dir"]
95
+ _question(qdir, posted=True)
96
+ _answer(qdir, publish_at="2026-09-09T05:00:00+09:00")
97
+ name = isolated_account["name"]
98
+ counts = handoff.answer(name, now=BOARD_NOW)["by_account"][name]["queue"]["counts"]
99
+ assert counts["waiting_reply"] == 0
100
+ assert counts["overdue"] == 1
101
+
102
+
103
+ # ---------------------------------------------------------------- morning
104
+
105
+ def test_morningは返信待ちを予定の段に名前と待ち先で出す(one):
106
+ qdir = one["queue_dir"]
107
+ _question(qdir, posted=False, account=ACCOUNT, publish_at="2026-09-23T23:00:00+09:00")
108
+ _answer(qdir, account=ACCOUNT, publish_at="2026-09-23T05:00:00+09:00") # 2h 超過
109
+ payload = morning.build(PROJECT, now=NOW, mark=False)
110
+ today = sections(payload)["today"]["value"]["by_account"][ACCOUNT]["value"]
111
+ assert today["overdue_items"]["value"]["n"] == 0, "待ちは時刻超過に数えない"
112
+ waiting = today["waiting_items"]["value"]
113
+ assert waiting["n"] == 1
114
+ [row] = waiting["items"]
115
+ assert row["file"] == A and row["waiting_for"] == Q
116
+ assert row["reason"] == "reply_to_unresolved: waiting_for q.md"
117
+ assert today["queue"]["value"]["waiting_reply"] == 1
118
+ assert today["queue"]["value"]["overdue"] == 0
119
+ assert not [s for s in sections(payload)["next_steps"]["value"]["steps"]
120
+ if s["kind"] == "overdue" and s["file"] == A]
121
+ lines = []
122
+ morning.render(payload, out=lines.append)
123
+ assert any(f"返信待ち: {A} → {Q} が出たら返信します" in line for line in lines)
124
+ assert any("返信待ち 1" in line for line in lines)
125
+
126
+
127
+ # ---------------------------------------------------------------- preview
128
+
129
+ def test_previewは返信先の原稿と解決の見込みを出す(isolated_account):
130
+ qdir = isolated_account["queue_dir"]
131
+ _question(qdir, posted=False)
132
+ path = _answer(qdir)
133
+ assert lint_mod.preview_file(path).endswith("\n返信先: q.md(未解決・待ち)")
134
+ _question(qdir, posted=True)
135
+ assert lint_mod.preview_file(path) == f"答え\n返信先: q.md → {Q_POST_ID}"
136
+ shown = run_thth(["preview", path, "--json"])
137
+ payload = json.loads(shown.stdout)
138
+ assert payload["reply_to_file"] == Q and payload["reply_to"] == Q_POST_ID
139
+ assert payload["unresolved"] is None
140
+
141
+
142
+ def test_reply_to_fileの無い原稿のpreviewは変わらない(isolated_account):
143
+ path = write_queue_file(isolated_account["queue_dir"], "plain.md")
144
+ assert lint_mod.preview_file(path) == "本文です。"
145
+ payload = json.loads(run_thth(["preview", path, "--json"]).stdout)
146
+ assert set(payload) == {"file", "text", "topic"}
@@ -0,0 +1,122 @@
1
+ """3.2.0 `reply_to_file` の lint(設計 3.2.0 §1・§6「lint 6 種」)。
2
+
3
+ 返信先を post_id ではなく**同じ queue の原稿の名前**で書く。書き方が誤っていれば
4
+ loud に断る(併用・自己参照・不在・account 違い・名前の形・束)。**指した原稿が
5
+ まだ出ていないこと自体は断らない**——それは待つのであって誤りではない(§2)。
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+
11
+ import pytest
12
+
13
+ from tests.conftest import write_queue_file
14
+ from thth import lint as lint_mod
15
+ from thth import queuefile
16
+
17
+ Q = "2026-09-23-q-decaf.md"
18
+ A = "2026-09-24-a-decaf.md"
19
+
20
+
21
+ def _errors(path):
22
+ return [e for e in lint_mod.lint_file(path) if not lint_mod.is_warning(e)]
23
+
24
+
25
+ def _pair(tmp_path, *, answer_fm=None, question_fm=None, question_text=None):
26
+ queue = str(tmp_path / "queue")
27
+ if question_text is not None:
28
+ os.makedirs(queue, exist_ok=True)
29
+ with open(os.path.join(queue, Q), "w", encoding="utf-8") as f:
30
+ f.write(question_text)
31
+ else:
32
+ write_queue_file(queue, Q, commit=False,
33
+ fm_overrides={"status": "draft", **(question_fm or {})},
34
+ body="## threads\n\n今日の問い。\n")
35
+ return write_queue_file(queue, A, commit=False,
36
+ fm_overrides={"status": "draft", "reply_to_file": Q,
37
+ **(answer_fm or {})},
38
+ body="## threads\n\n明日の答え。\n")
39
+
40
+
41
+ def test_まだ出ていない同じaccountの原稿を指すのは通る(isolated_account, tmp_path):
42
+ """指した原稿が draft(post_id 無し)でも lint は通す——待つのは誤りではない。"""
43
+ assert _errors(_pair(tmp_path)) == []
44
+
45
+
46
+ def test_reply_toとの併用は断る(isolated_account, tmp_path):
47
+ path = _pair(tmp_path, answer_fm={"reply_to": "18000000000000001"})
48
+ errors = _errors(path)
49
+ assert any(e.startswith("reply_to_conflict: ") for e in errors), errors
50
+ assert lint_mod.reason_code(errors) == "reply_to_conflict"
51
+
52
+
53
+ def test_自分自身を指すのは断る(isolated_account, tmp_path):
54
+ path = _pair(tmp_path, answer_fm={"reply_to_file": A})
55
+ errors = _errors(path)
56
+ assert any(e.startswith("reply_to_file_self: ") for e in errors), errors
57
+ assert lint_mod.reason_code(errors) == "reply_to_file_self"
58
+
59
+
60
+ def test_指した原稿が無いのは断る(isolated_account, tmp_path):
61
+ path = _pair(tmp_path, answer_fm={"reply_to_file": "2026-09-22-nothing.md"})
62
+ errors = _errors(path)
63
+ assert any(e.startswith("reply_to_file_missing: ") for e in errors), errors
64
+ assert lint_mod.reason_code(errors) == "reply_to_file_missing"
65
+
66
+
67
+ def test_指した原稿のaccountが違うのは断る(isolated_account, tmp_path):
68
+ path = _pair(tmp_path, question_fm={"account": "kopicha-threads"})
69
+ errors = _errors(path)
70
+ assert any(e.startswith("reply_to_file_account_mismatch: ") for e in errors), errors
71
+ assert lint_mod.reason_code(errors) == "reply_to_file_account_mismatch"
72
+
73
+
74
+ @pytest.mark.parametrize("name", [
75
+ "../" + Q, "sub/" + Q, "sub\\" + Q, "2026-09-23-q-decaf.txt", ".md",
76
+ ".hidden.md", "a..b.md",
77
+ ])
78
+ def test_名前の形が違うのは断る(isolated_account, tmp_path, name):
79
+ path = _pair(tmp_path, answer_fm={"reply_to_file": name})
80
+ errors = _errors(path)
81
+ assert any(e.startswith("reply_to_file_invalid: ") for e in errors), (name, errors)
82
+ assert lint_mod.reason_code(errors) == "reply_to_file_invalid"
83
+
84
+
85
+ def test_reply_toにfile印を書くのは断る(isolated_account, tmp_path):
86
+ """`reply_to: file:<名前>` は reply_to_file と同じ指紋になるので書かせない(§3)。"""
87
+ queue = str(tmp_path / "queue")
88
+ path = write_queue_file(queue, A, commit=False,
89
+ fm_overrides={"status": "draft", "reply_to": "file:" + Q})
90
+ errors = _errors(path)
91
+ assert lint_mod.reason_code(errors) == "reply_to_file_invalid", errors
92
+
93
+
94
+ def test_束の原稿は指せない(isolated_account, tmp_path):
95
+ bundle_text = ("---\nthth: 2\naccount: nigamilab-threads\n"
96
+ "publish_at: 2026-09-23T23:00:00+09:00\nstatus: draft\n---\n"
97
+ "## threads\n\n一段目\n")
98
+ path = _pair(tmp_path, question_text=bundle_text)
99
+ errors = _errors(path)
100
+ assert any(e.startswith("reply_to_file_bundle_unsupported: ") for e in errors), errors
101
+ assert lint_mod.reason_code(errors) == "reply_to_file_bundle_unsupported"
102
+
103
+
104
+ def test_指した原稿が型外なら読めないと言う(isolated_account, tmp_path):
105
+ path = _pair(tmp_path, question_text="front-matter の無い素の原稿\n")
106
+ errors = _errors(path)
107
+ assert any(e.startswith("reply_to_file_unreadable: ") for e in errors), errors
108
+
109
+
110
+ def test_指した原稿がさらにreply_to_fileを持っていてもよい(isolated_account, tmp_path):
111
+ """答えへの補足(指した原稿がさらに reply_to_file を持つ)は通る(§1)。"""
112
+ queue = str(tmp_path / "queue")
113
+ _pair(tmp_path)
114
+ extra = write_queue_file(queue, "2026-09-25-extra.md", commit=False,
115
+ fm_overrides={"status": "draft", "reply_to_file": A},
116
+ body="## threads\n\n補足。\n")
117
+ assert _errors(extra) == []
118
+
119
+
120
+ def test_理由の符丁はREASONSに閉じている():
121
+ for code in queuefile.REPLY_TO_FILE_MESSAGES:
122
+ assert code in lint_mod.REASONS