thth 3.1.2__tar.gz → 3.3.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 (502) hide show
  1. {thth-3.1.2 → thth-3.3.0}/PKG-INFO +2 -2
  2. {thth-3.1.2 → thth-3.3.0}/README.md +1 -1
  3. {thth-3.1.2 → thth-3.3.0}/mcp/server.py +92 -23
  4. {thth-3.1.2 → thth-3.3.0}/skills/thth/SKILL.md +11 -3
  5. {thth-3.1.2 → thth-3.3.0}/tests/conftest.py +16 -2
  6. {thth-3.1.2 → thth-3.3.0}/tests/test_mcp.py +2 -1
  7. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_json_contract.py +3 -1
  8. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_read_coordination.py +3 -3
  9. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_carryover_timeout.py +1 -1
  10. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_review_followups.py +1 -1
  11. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_stop_observation.py +1 -1
  12. {thth-3.1.2 → thth-3.3.0}/tests/test_v310_morning.py +6 -4
  13. {thth-3.1.2 → thth-3.3.0}/tests/test_v312_report_channel.py +21 -9
  14. thth-3.3.0/tests/test_v320_reply_to_file_display.py +146 -0
  15. thth-3.3.0/tests/test_v320_reply_to_file_lint.py +122 -0
  16. thth-3.3.0/tests/test_v320_reply_to_file_resolve.py +318 -0
  17. thth-3.3.0/tests/test_v320_report_fixes.py +174 -0
  18. thth-3.3.0/tests/test_v330_fingerprint_version.py +127 -0
  19. thth-3.3.0/tests/test_v330_held.py +270 -0
  20. thth-3.3.0/tests/test_v330_held_names.py +92 -0
  21. thth-3.3.0/tests/test_v330_last_refusals.py +156 -0
  22. thth-3.3.0/tests/test_v330_notification_route.py +97 -0
  23. thth-3.3.0/tests/test_v330_observe.py +137 -0
  24. thth-3.3.0/tests/test_v330_report_friction.py +50 -0
  25. thth-3.3.0/tests/test_v330_report_words.py +103 -0
  26. thth-3.3.0/tests/test_v330_reports_effect.py +66 -0
  27. thth-3.3.0/thth/VERSION +1 -0
  28. {thth-3.1.2 → thth-3.3.0}/thth/admin_log.py +4 -2
  29. {thth-3.1.2 → thth-3.3.0}/thth/approval.py +58 -0
  30. {thth-3.1.2 → thth-3.3.0}/thth/cli.py +140 -14
  31. {thth-3.1.2 → thth-3.3.0}/thth/core.py +112 -14
  32. {thth-3.1.2 → thth-3.3.0}/thth/doctor.py +11 -0
  33. thth-3.3.0/thth/fingerprint_golden.py +101 -0
  34. {thth-3.1.2 → thth-3.3.0}/thth/healthcheck.py +46 -2
  35. {thth-3.1.2 → thth-3.3.0}/thth/incident.py +108 -16
  36. {thth-3.1.2 → thth-3.3.0}/thth/lint.py +72 -1
  37. {thth-3.1.2 → thth-3.3.0}/thth/morning.py +217 -33
  38. thth-3.3.0/thth/notification_route.py +70 -0
  39. {thth-3.1.2 → thth-3.3.0}/thth/operations_handoff.py +56 -7
  40. {thth-3.1.2 → thth-3.3.0}/thth/queuefile.py +118 -0
  41. {thth-3.1.2 → thth-3.3.0}/thth/read_coordination.py +2 -2
  42. thth-3.3.0/thth/refusals.py +162 -0
  43. {thth-3.1.2 → thth-3.3.0}/thth/report.py +43 -2
  44. {thth-3.1.2 → thth-3.3.0}/thth/report_inbox.py +242 -23
  45. {thth-3.1.2 → thth-3.3.0}/thth/report_service.py +33 -9
  46. {thth-3.1.2 → thth-3.3.0}/thth/runs.py +3 -1
  47. {thth-3.1.2 → thth-3.3.0}/thth/select.py +239 -13
  48. {thth-3.1.2 → thth-3.3.0}/thth/sent.py +9 -1
  49. {thth-3.1.2 → thth-3.3.0}/thth/tool_version.py +8 -0
  50. thth-3.1.2/thth/VERSION +0 -1
  51. {thth-3.1.2 → thth-3.3.0}/.gitignore +0 -0
  52. {thth-3.1.2 → thth-3.3.0}/LICENSE +0 -0
  53. {thth-3.1.2 → thth-3.3.0}/accounts.example/bluesky.json +0 -0
  54. {thth-3.1.2 → thth-3.3.0}/accounts.example/mastodon.json +0 -0
  55. {thth-3.1.2 → thth-3.3.0}/accounts.example/threads.json +0 -0
  56. {thth-3.1.2 → thth-3.3.0}/accounts.example/x.json +0 -0
  57. {thth-3.1.2 → thth-3.3.0}/bin/thth +0 -0
  58. {thth-3.1.2 → thth-3.3.0}/bin/thth-remote +0 -0
  59. {thth-3.1.2 → thth-3.3.0}/bin/thth-run +0 -0
  60. {thth-3.1.2 → thth-3.3.0}/pyproject.toml +0 -0
  61. {thth-3.1.2 → thth-3.3.0}/skills/thth-admin/SKILL.md +0 -0
  62. {thth-3.1.2 → thth-3.3.0}/systemd/thth@.service +0 -0
  63. {thth-3.1.2 → thth-3.3.0}/tests/__init__.py +0 -0
  64. {thth-3.1.2 → thth-3.3.0}/tests/coordination_snapshot.py +0 -0
  65. {thth-3.1.2 → thth-3.3.0}/tests/fixtures/GraphemeBreakTest-17.0.0.txt +0 -0
  66. {thth-3.1.2 → thth-3.3.0}/tests/fixtures/build_injection_eval.py +0 -0
  67. {thth-3.1.2 → thth-3.3.0}/tests/fixtures/callback/worker_responses.json +0 -0
  68. {thth-3.1.2 → thth-3.3.0}/tests/fixtures/injection_eval.json +0 -0
  69. {thth-3.1.2 → thth-3.3.0}/tests/fixtures/umami-bile.md +0 -0
  70. {thth-3.1.2 → thth-3.3.0}/tests/helpers/__init__.py +0 -0
  71. {thth-3.1.2 → thth-3.3.0}/tests/helpers/fake_oauth_server.py +0 -0
  72. {thth-3.1.2 → thth-3.3.0}/tests/helpers/fake_push_adapter.py +0 -0
  73. {thth-3.1.2 → thth-3.3.0}/tests/helpers/fake_redirect_server.py +0 -0
  74. {thth-3.1.2 → thth-3.3.0}/tests/helpers/fake_search_adapter.py +0 -0
  75. {thth-3.1.2 → thth-3.3.0}/tests/helpers/fake_threads_reader.py +0 -0
  76. {thth-3.1.2 → thth-3.3.0}/tests/helpers/fake_threads_writes.py +0 -0
  77. {thth-3.1.2 → thth-3.3.0}/tests/helpers/fake_x.py +0 -0
  78. {thth-3.1.2 → thth-3.3.0}/tests/helpers/hold_lock.py +0 -0
  79. {thth-3.1.2 → thth-3.3.0}/tests/helpers/hold_repo_lock.py +0 -0
  80. {thth-3.1.2 → thth-3.3.0}/tests/helpers/worker_probe.mjs +0 -0
  81. {thth-3.1.2 → thth-3.3.0}/tests/test_27_acceptance_fixes.py +0 -0
  82. {thth-3.1.2 → thth-3.3.0}/tests/test_account_name_check.py +0 -0
  83. {thth-3.1.2 → thth-3.3.0}/tests/test_account_report.py +0 -0
  84. {thth-3.1.2 → thth-3.3.0}/tests/test_accounts_dir.py +0 -0
  85. {thth-3.1.2 → thth-3.3.0}/tests/test_adapter_boundary.py +0 -0
  86. {thth-3.1.2 → thth-3.3.0}/tests/test_admin_http_mcp.py +0 -0
  87. {thth-3.1.2 → thth-3.3.0}/tests/test_admin_log.py +0 -0
  88. {thth-3.1.2 → thth-3.3.0}/tests/test_admin_notifications.py +0 -0
  89. {thth-3.1.2 → thth-3.3.0}/tests/test_admin_repairs.py +0 -0
  90. {thth-3.1.2 → thth-3.3.0}/tests/test_admin_report.py +0 -0
  91. {thth-3.1.2 → thth-3.3.0}/tests/test_after_cli.py +0 -0
  92. {thth-3.1.2 → thth-3.3.0}/tests/test_analytics_comparison.py +0 -0
  93. {thth-3.1.2 → thth-3.3.0}/tests/test_analytics_report.py +0 -0
  94. {thth-3.1.2 → thth-3.3.0}/tests/test_api_diagnostic.py +0 -0
  95. {thth-3.1.2 → thth-3.3.0}/tests/test_api_error_body.py +0 -0
  96. {thth-3.1.2 → thth-3.3.0}/tests/test_app_env_cli.py +0 -0
  97. {thth-3.1.2 → thth-3.3.0}/tests/test_approval.py +0 -0
  98. {thth-3.1.2 → thth-3.3.0}/tests/test_approve_cmd.py +0 -0
  99. {thth-3.1.2 → thth-3.3.0}/tests/test_ask.py +0 -0
  100. {thth-3.1.2 → thth-3.3.0}/tests/test_atlas_fifth_review.py +0 -0
  101. {thth-3.1.2 → thth-3.3.0}/tests/test_atlas_fourth_review.py +0 -0
  102. {thth-3.1.2 → thth-3.3.0}/tests/test_atlas_review.py +0 -0
  103. {thth-3.1.2 → thth-3.3.0}/tests/test_atlas_second_review.py +0 -0
  104. {thth-3.1.2 → thth-3.3.0}/tests/test_atlas_third_review.py +0 -0
  105. {thth-3.1.2 → thth-3.3.0}/tests/test_audit1_fixes.py +0 -0
  106. {thth-3.1.2 → thth-3.3.0}/tests/test_audit2_fixes.py +0 -0
  107. {thth-3.1.2 → thth-3.3.0}/tests/test_audit_b_counterexamples.py +0 -0
  108. {thth-3.1.2 → thth-3.3.0}/tests/test_audit_b_m1_semantics.py +0 -0
  109. {thth-3.1.2 → thth-3.3.0}/tests/test_audit_b_n1_depth.py +0 -0
  110. {thth-3.1.2 → thth-3.3.0}/tests/test_audit_b_r1_closure.py +0 -0
  111. {thth-3.1.2 → thth-3.3.0}/tests/test_audit_v201.py +0 -0
  112. {thth-3.1.2 → thth-3.3.0}/tests/test_audit_v23_details.py +0 -0
  113. {thth-3.1.2 → thth-3.3.0}/tests/test_auth.py +0 -0
  114. {thth-3.1.2 → thth-3.3.0}/tests/test_auth_identity_required.py +0 -0
  115. {thth-3.1.2 → thth-3.3.0}/tests/test_behind_remote.py +0 -0
  116. {thth-3.1.2 → thth-3.3.0}/tests/test_bluesky_adapter.py +0 -0
  117. {thth-3.1.2 → thth-3.3.0}/tests/test_bluesky_secret_scrub.py +0 -0
  118. {thth-3.1.2 → thth-3.3.0}/tests/test_board_needs_review.py +0 -0
  119. {thth-3.1.2 → thth-3.3.0}/tests/test_board_running.py +0 -0
  120. {thth-3.1.2 → thth-3.3.0}/tests/test_bundle.py +0 -0
  121. {thth-3.1.2 → thth-3.3.0}/tests/test_charcount.py +0 -0
  122. {thth-3.1.2 → thth-3.3.0}/tests/test_cli_entrypoint.py +0 -0
  123. {thth-3.1.2 → thth-3.3.0}/tests/test_cli_help.py +0 -0
  124. {thth-3.1.2 → thth-3.3.0}/tests/test_cli_vm_path.py +0 -0
  125. {thth-3.1.2 → thth-3.3.0}/tests/test_collect.py +0 -0
  126. {thth-3.1.2 → thth-3.3.0}/tests/test_collect_engagements.py +0 -0
  127. {thth-3.1.2 → thth-3.3.0}/tests/test_collect_runs.py +0 -0
  128. {thth-3.1.2 → thth-3.3.0}/tests/test_collect_sent.py +0 -0
  129. {thth-3.1.2 → thth-3.3.0}/tests/test_comparability.py +0 -0
  130. {thth-3.1.2 → thth-3.3.0}/tests/test_control_chars_refused.py +0 -0
  131. {thth-3.1.2 → thth-3.3.0}/tests/test_conversation_paging.py +0 -0
  132. {thth-3.1.2 → thth-3.3.0}/tests/test_count_for.py +0 -0
  133. {thth-3.1.2 → thth-3.3.0}/tests/test_docs_links.py +0 -0
  134. {thth-3.1.2 → thth-3.3.0}/tests/test_doctor.py +0 -0
  135. {thth-3.1.2 → thth-3.3.0}/tests/test_doctor_all_permissions.py +0 -0
  136. {thth-3.1.2 → thth-3.3.0}/tests/test_doctor_next_step.py +0 -0
  137. {thth-3.1.2 → thth-3.3.0}/tests/test_duplicate_front_matter_keys.py +0 -0
  138. {thth-3.1.2 → thth-3.3.0}/tests/test_editorial_audit.py +0 -0
  139. {thth-3.1.2 → thth-3.3.0}/tests/test_editorial_n3_n4.py +0 -0
  140. {thth-3.1.2 → thth-3.3.0}/tests/test_engagements.py +0 -0
  141. {thth-3.1.2 → thth-3.3.0}/tests/test_external_acceptance.py +0 -0
  142. {thth-3.1.2 → thth-3.3.0}/tests/test_fake_api.py +0 -0
  143. {thth-3.1.2 → thth-3.3.0}/tests/test_form_spec.py +0 -0
  144. {thth-3.1.2 → thth-3.3.0}/tests/test_form_spec_cli.py +0 -0
  145. {thth-3.1.2 → thth-3.3.0}/tests/test_forms.py +0 -0
  146. {thth-3.1.2 → thth-3.3.0}/tests/test_fresh_install.py +0 -0
  147. {thth-3.1.2 → thth-3.3.0}/tests/test_front_matter_injection.py +0 -0
  148. {thth-3.1.2 → thth-3.3.0}/tests/test_healthcheck_notifications.py +0 -0
  149. {thth-3.1.2 → thth-3.3.0}/tests/test_hypotheses.py +0 -0
  150. {thth-3.1.2 → thth-3.3.0}/tests/test_hypothesis_boundaries.py +0 -0
  151. {thth-3.1.2 → thth-3.3.0}/tests/test_hypothesis_units.py +0 -0
  152. {thth-3.1.2 → thth-3.3.0}/tests/test_impact_cli.py +0 -0
  153. {thth-3.1.2 → thth-3.3.0}/tests/test_inbox_granted_scopes.py +0 -0
  154. {thth-3.1.2 → thth-3.3.0}/tests/test_incident_notifications.py +0 -0
  155. {thth-3.1.2 → thth-3.3.0}/tests/test_independent_thread_review.py +0 -0
  156. {thth-3.1.2 → thth-3.3.0}/tests/test_independent_topic_review.py +0 -0
  157. {thth-3.1.2 → thth-3.3.0}/tests/test_independent_topic_review_r2.py +0 -0
  158. {thth-3.1.2 → thth-3.3.0}/tests/test_inflight.py +0 -0
  159. {thth-3.1.2 → thth-3.3.0}/tests/test_inflight_tag_body_hash.py +0 -0
  160. {thth-3.1.2 → thth-3.3.0}/tests/test_jst.py +0 -0
  161. {thth-3.1.2 → thth-3.3.0}/tests/test_lint.py +0 -0
  162. {thth-3.1.2 → thth-3.3.0}/tests/test_list_projections.py +0 -0
  163. {thth-3.1.2 → thth-3.3.0}/tests/test_llm_trial.py +0 -0
  164. {thth-3.1.2 → thth-3.3.0}/tests/test_llm_trial_engage.py +0 -0
  165. {thth-3.1.2 → thth-3.3.0}/tests/test_lock.py +0 -0
  166. {thth-3.1.2 → thth-3.3.0}/tests/test_m2_independent.py +0 -0
  167. {thth-3.1.2 → thth-3.3.0}/tests/test_maintain.py +0 -0
  168. {thth-3.1.2 → thth-3.3.0}/tests/test_mastodon_adapter.py +0 -0
  169. {thth-3.1.2 → thth-3.3.0}/tests/test_mastodon_visibility.py +0 -0
  170. {thth-3.1.2 → thth-3.3.0}/tests/test_max_per_run.py +0 -0
  171. {thth-3.1.2 → thth-3.3.0}/tests/test_mcp_robustness.py +0 -0
  172. {thth-3.1.2 → thth-3.3.0}/tests/test_measured.py +0 -0
  173. {thth-3.1.2 → thth-3.3.0}/tests/test_media_accounts.py +0 -0
  174. {thth-3.1.2 → thth-3.3.0}/tests/test_media_next_steps.py +0 -0
  175. {thth-3.1.2 → thth-3.3.0}/tests/test_media_roundtrip.py +0 -0
  176. {thth-3.1.2 → thth-3.3.0}/tests/test_mismatch_diagnostics.py +0 -0
  177. {thth-3.1.2 → thth-3.3.0}/tests/test_n5_reply_null_element.py +0 -0
  178. {thth-3.1.2 → thth-3.3.0}/tests/test_n6_replies_incomplete_ledger.py +0 -0
  179. {thth-3.1.2 → thth-3.3.0}/tests/test_n7_doctor_error_body.py +0 -0
  180. {thth-3.1.2 → thth-3.3.0}/tests/test_no_limits.py +0 -0
  181. {thth-3.1.2 → thth-3.3.0}/tests/test_observation_provenance.py +0 -0
  182. {thth-3.1.2 → thth-3.3.0}/tests/test_operations_handoff.py +0 -0
  183. {thth-3.1.2 → thth-3.3.0}/tests/test_packaging.py +0 -0
  184. {thth-3.1.2 → thth-3.3.0}/tests/test_preview.py +0 -0
  185. {thth-3.1.2 → thth-3.3.0}/tests/test_production_mode.py +0 -0
  186. {thth-3.1.2 → thth-3.3.0}/tests/test_pull_cli.py +0 -0
  187. {thth-3.1.2 → thth-3.3.0}/tests/test_queue.py +0 -0
  188. {thth-3.1.2 → thth-3.3.0}/tests/test_quiet_hours.py +0 -0
  189. {thth-3.1.2 → thth-3.3.0}/tests/test_r2_r4_r5.py +0 -0
  190. {thth-3.1.2 → thth-3.3.0}/tests/test_read_cli_bare_runtimeerror.py +0 -0
  191. {thth-3.1.2 → thth-3.3.0}/tests/test_reason_adoption.py +0 -0
  192. {thth-3.1.2 → thth-3.3.0}/tests/test_redact.py +0 -0
  193. {thth-3.1.2 → thth-3.3.0}/tests/test_redirect_token_leak.py +0 -0
  194. {thth-3.1.2 → thth-3.3.0}/tests/test_refresh.py +0 -0
  195. {thth-3.1.2 → thth-3.3.0}/tests/test_refresh_destination.py +0 -0
  196. {thth-3.1.2 → thth-3.3.0}/tests/test_rejudge_new.py +0 -0
  197. {thth-3.1.2 → thth-3.3.0}/tests/test_rejudge_r2_r3.py +0 -0
  198. {thth-3.1.2 → thth-3.3.0}/tests/test_replies.py +0 -0
  199. {thth-3.1.2 → thth-3.3.0}/tests/test_replies_refresh.py +0 -0
  200. {thth-3.1.2 → thth-3.3.0}/tests/test_repo_lock.py +0 -0
  201. {thth-3.1.2 → thth-3.3.0}/tests/test_repo_sync.py +0 -0
  202. {thth-3.1.2 → thth-3.3.0}/tests/test_report_checkout_import.py +0 -0
  203. {thth-3.1.2 → thth-3.3.0}/tests/test_report_http.py +0 -0
  204. {thth-3.1.2 → thth-3.3.0}/tests/test_report_isolation.py +0 -0
  205. {thth-3.1.2 → thth-3.3.0}/tests/test_report_service.py +0 -0
  206. {thth-3.1.2 → thth-3.3.0}/tests/test_reverse_audit.py +0 -0
  207. {thth-3.1.2 → thth-3.3.0}/tests/test_review_case_id_cluster_m2.py +0 -0
  208. {thth-3.1.2 → thth-3.3.0}/tests/test_review_case_id_cluster_r2.py +0 -0
  209. {thth-3.1.2 → thth-3.3.0}/tests/test_review_cli.py +0 -0
  210. {thth-3.1.2 → thth-3.3.0}/tests/test_review_evidence_account.py +0 -0
  211. {thth-3.1.2 → thth-3.3.0}/tests/test_review_form_spec_and_case_id.py +0 -0
  212. {thth-3.1.2 → thth-3.3.0}/tests/test_review_provenance.py +0 -0
  213. {thth-3.1.2 → thth-3.3.0}/tests/test_review_records.py +0 -0
  214. {thth-3.1.2 → thth-3.3.0}/tests/test_revoke.py +0 -0
  215. {thth-3.1.2 → thth-3.3.0}/tests/test_roundtrip.py +0 -0
  216. {thth-3.1.2 → thth-3.3.0}/tests/test_run_missing_env.py +0 -0
  217. {thth-3.1.2 → thth-3.3.0}/tests/test_run_record_failclosed.py +0 -0
  218. {thth-3.1.2 → thth-3.3.0}/tests/test_runs_collected.py +0 -0
  219. {thth-3.1.2 → thth-3.3.0}/tests/test_runs_read_rows.py +0 -0
  220. {thth-3.1.2 → thth-3.3.0}/tests/test_schedule_multiple.py +0 -0
  221. {thth-3.1.2 → thth-3.3.0}/tests/test_schedule_view.py +0 -0
  222. {thth-3.1.2 → thth-3.3.0}/tests/test_scopes_source_requested.py +0 -0
  223. {thth-3.1.2 → thth-3.3.0}/tests/test_search_engage_targets.py +0 -0
  224. {thth-3.1.2 → thth-3.3.0}/tests/test_search_media_routes.py +0 -0
  225. {thth-3.1.2 → thth-3.3.0}/tests/test_secret_value_redact.py +0 -0
  226. {thth-3.1.2 → thth-3.3.0}/tests/test_secrets_grep.py +0 -0
  227. {thth-3.1.2 → thth-3.3.0}/tests/test_security_small_fixes.py +0 -0
  228. {thth-3.1.2 → thth-3.3.0}/tests/test_select.py +0 -0
  229. {thth-3.1.2 → thth-3.3.0}/tests/test_selfupdate.py +0 -0
  230. {thth-3.1.2 → thth-3.3.0}/tests/test_send.py +0 -0
  231. {thth-3.1.2 → thth-3.3.0}/tests/test_send_cli.py +0 -0
  232. {thth-3.1.2 → thth-3.3.0}/tests/test_send_control_chars.py +0 -0
  233. {thth-3.1.2 → thth-3.3.0}/tests/test_send_media.py +0 -0
  234. {thth-3.1.2 → thth-3.3.0}/tests/test_send_visibility.py +0 -0
  235. {thth-3.1.2 → thth-3.3.0}/tests/test_sent_integrity.py +0 -0
  236. {thth-3.1.2 → thth-3.3.0}/tests/test_server_json.py +0 -0
  237. {thth-3.1.2 → thth-3.3.0}/tests/test_site.py +0 -0
  238. {thth-3.1.2 → thth-3.3.0}/tests/test_skill_doc.py +0 -0
  239. {thth-3.1.2 → thth-3.3.0}/tests/test_study_report.py +0 -0
  240. {thth-3.1.2 → thth-3.3.0}/tests/test_systemd.py +0 -0
  241. {thth-3.1.2 → thth-3.3.0}/tests/test_thread_publish.py +0 -0
  242. {thth-3.1.2 → thth-3.3.0}/tests/test_thread_read.py +0 -0
  243. {thth-3.1.2 → thth-3.3.0}/tests/test_threadrun.py +0 -0
  244. {thth-3.1.2 → thth-3.3.0}/tests/test_threads_read_permissions.py +0 -0
  245. {thth-3.1.2 → thth-3.3.0}/tests/test_threadshape.py +0 -0
  246. {thth-3.1.2 → thth-3.3.0}/tests/test_thth_root.py +0 -0
  247. {thth-3.1.2 → thth-3.3.0}/tests/test_token_exists.py +0 -0
  248. {thth-3.1.2 → thth-3.3.0}/tests/test_token_scopes_record.py +0 -0
  249. {thth-3.1.2 → thth-3.3.0}/tests/test_token_set.py +0 -0
  250. {thth-3.1.2 → thth-3.3.0}/tests/test_topic.py +0 -0
  251. {thth-3.1.2 → thth-3.3.0}/tests/test_topic_advice.py +0 -0
  252. {thth-3.1.2 → thth-3.3.0}/tests/test_topic_cli.py +0 -0
  253. {thth-3.1.2 → thth-3.3.0}/tests/test_topic_models.py +0 -0
  254. {thth-3.1.2 → thth-3.3.0}/tests/test_topic_review_fixes.py +0 -0
  255. {thth-3.1.2 → thth-3.3.0}/tests/test_topic_shelf_observers.py +0 -0
  256. {thth-3.1.2 → thth-3.3.0}/tests/test_topic_store.py +0 -0
  257. {thth-3.1.2 → thth-3.3.0}/tests/test_topics_record_concurrent.py +0 -0
  258. {thth-3.1.2 → thth-3.3.0}/tests/test_topics_tool.py +0 -0
  259. {thth-3.1.2 → thth-3.3.0}/tests/test_trial_frictions.py +0 -0
  260. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_admin_transaction.py +0 -0
  261. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_approve_account.py +0 -0
  262. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_length.py +0 -0
  263. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_lock_wait.py +0 -0
  264. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_mastodon_scopes.py +0 -0
  265. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_mentions.py +0 -0
  266. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_post_urls.py +0 -0
  267. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_repo_paths.py +0 -0
  268. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_reposts.py +0 -0
  269. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_study_add.py +0 -0
  270. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_tool_version.py +0 -0
  271. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_unanswered.py +0 -0
  272. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_unanswered_scope.py +0 -0
  273. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_where_arguments.py +0 -0
  274. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_where_filters.py +0 -0
  275. {thth-3.1.2 → thth-3.3.0}/tests/test_v210_who_arguments.py +0 -0
  276. {thth-3.1.2 → thth-3.3.0}/tests/test_v211_auth_integration.py +0 -0
  277. {thth-3.1.2 → thth-3.3.0}/tests/test_v211_authflow.py +0 -0
  278. {thth-3.1.2 → thth-3.3.0}/tests/test_v211_bluesky_stdin.py +0 -0
  279. {thth-3.1.2 → thth-3.3.0}/tests/test_v211_doctor_binding.py +0 -0
  280. {thth-3.1.2 → thth-3.3.0}/tests/test_v211_mastodon_auth.py +0 -0
  281. {thth-3.1.2 → thth-3.3.0}/tests/test_v211_x_auth.py +0 -0
  282. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_approval_relay.py +0 -0
  283. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_ask_coordination.py +0 -0
  284. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_budget.py +0 -0
  285. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_budget_auth.py +0 -0
  286. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_budget_mcp.py +0 -0
  287. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_deletion.py +0 -0
  288. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_job_completion_fault.py +0 -0
  289. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_leave.py +0 -0
  290. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_leave_gate.py +0 -0
  291. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_leave_providers.py +0 -0
  292. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_leave_token_safety.py +0 -0
  293. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_mastodon_guide.py +0 -0
  294. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_mcp_compat.py +0 -0
  295. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_missing_token.py +0 -0
  296. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_server_writes.py +0 -0
  297. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_token_parent.py +0 -0
  298. {thth-3.1.2 → thth-3.3.0}/tests/test_v212_write_storage.py +0 -0
  299. {thth-3.1.2 → thth-3.3.0}/tests/test_v2131_send_dry_run.py +0 -0
  300. {thth-3.1.2 → thth-3.3.0}/tests/test_v2131_threads_publish_wait.py +0 -0
  301. {thth-3.1.2 → thth-3.3.0}/tests/test_v2132_send_inflight_hint.py +0 -0
  302. {thth-3.1.2 → thth-3.3.0}/tests/test_v2132_threads_create_timeout.py +0 -0
  303. {thth-3.1.2 → thth-3.3.0}/tests/test_v2132_threads_create_unknown.py +0 -0
  304. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_acceptance_fixes.py +0 -0
  305. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_adts_asf.py +0 -0
  306. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_approval_attachments.py +0 -0
  307. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_attachment_kinds.py +0 -0
  308. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bluesky_external.py +0 -0
  309. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bluesky_media.py +0 -0
  310. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bluesky_metadata.py +0 -0
  311. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bluesky_metadata_uri.py +0 -0
  312. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bluesky_metadata_video.py +0 -0
  313. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bluesky_record.py +0 -0
  314. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bluesky_record_video.py +0 -0
  315. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bluesky_video.py +0 -0
  316. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bluesky_webm_named.py +0 -0
  317. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_bmff_typed_metadata.py +0 -0
  318. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_budget_lock.py +0 -0
  319. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_capabilities.py +0 -0
  320. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_graphemes.py +0 -0
  321. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_auth_media.py +0 -0
  322. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_bmff_audio.py +0 -0
  323. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_flac.py +0 -0
  324. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_gif.py +0 -0
  325. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_link.py +0 -0
  326. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_media.py +0 -0
  327. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_mp3.py +0 -0
  328. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_ogg_opus.py +0 -0
  329. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_ogg_vorbis.py +0 -0
  330. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_poll.py +0 -0
  331. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_primary_size_boundary.py +0 -0
  332. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_quote.py +0 -0
  333. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_thumbnail.py +0 -0
  334. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_wav.py +0 -0
  335. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_webm_audio.py +0 -0
  336. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_mastodon_webm_video.py +0 -0
  337. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_media_cleanup.py +0 -0
  338. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_media_formats.py +0 -0
  339. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_media_foundation.py +0 -0
  340. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_media_prepared.py +0 -0
  341. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_media_relay.py +0 -0
  342. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_media_relay_refusals.py +0 -0
  343. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_media_upload.py +0 -0
  344. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_receipt_deadline.py +0 -0
  345. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_stage810_audit.py +0 -0
  346. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_static_diagnostics.py +0 -0
  347. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_systemd_worker.py +0 -0
  348. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_ghost.py +0 -0
  349. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_gif.py +0 -0
  350. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_link.py +0 -0
  351. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_media.py +0 -0
  352. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_media_spoiler.py +0 -0
  353. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_poll.py +0 -0
  354. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_quote.py +0 -0
  355. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_quote_media.py +0 -0
  356. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_replies.py +0 -0
  357. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_text.py +0 -0
  358. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_text_spoiler.py +0 -0
  359. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_threads_video.py +0 -0
  360. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_transport_review.py +0 -0
  361. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_video_typed_metadata.py +0 -0
  362. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_vorbis_comment_boundary.py +0 -0
  363. {thth-3.1.2 → thth-3.3.0}/tests/test_v213_webm_schema_boundary.py +0 -0
  364. {thth-3.1.2 → thth-3.3.0}/tests/test_v2141_draft_lint_reason.py +0 -0
  365. {thth-3.1.2 → thth-3.3.0}/tests/test_v2141_managed_repo_umask.py +0 -0
  366. {thth-3.1.2 → thth-3.3.0}/tests/test_v214_release_signing.py +0 -0
  367. {thth-3.1.2 → thth-3.3.0}/tests/test_v214_x_adapter.py +0 -0
  368. {thth-3.1.2 → thth-3.3.0}/tests/test_v214_x_auth.py +0 -0
  369. {thth-3.1.2 → thth-3.3.0}/tests/test_v214_x_media.py +0 -0
  370. {thth-3.1.2 → thth-3.3.0}/tests/test_v214_x_posts_budget.py +0 -0
  371. {thth-3.1.2 → thth-3.3.0}/tests/test_v21b_approved_writes.py +0 -0
  372. {thth-3.1.2 → thth-3.3.0}/tests/test_v28_tags.py +0 -0
  373. {thth-3.1.2 → thth-3.3.0}/tests/test_v310_search_timeout.py +0 -0
  374. {thth-3.1.2 → thth-3.3.0}/tests/test_v310_watch_words.py +0 -0
  375. {thth-3.1.2 → thth-3.3.0}/tests/test_v311_leave_same_user.py +0 -0
  376. {thth-3.1.2 → thth-3.3.0}/tests/test_v311_mastodon_retract.py +0 -0
  377. {thth-3.1.2 → thth-3.3.0}/tests/test_v311_morning_overdue.py +0 -0
  378. {thth-3.1.2 → thth-3.3.0}/tests/test_v311_morning_own_exclusion.py +0 -0
  379. {thth-3.1.2 → thth-3.3.0}/tests/test_v311_morning_unanswered_window.py +0 -0
  380. {thth-3.1.2 → thth-3.3.0}/tests/test_v311_notifications_guidance.py +0 -0
  381. {thth-3.1.2 → thth-3.3.0}/tests/test_v311_replies_owned.py +0 -0
  382. {thth-3.1.2 → thth-3.3.0}/tests/test_v312_leave_shared_token.py +0 -0
  383. {thth-3.1.2 → thth-3.3.0}/tests/test_v312_own_handles_stopped.py +0 -0
  384. {thth-3.1.2 → thth-3.3.0}/tests/test_v312_report_admin.py +0 -0
  385. {thth-3.1.2 → thth-3.3.0}/tests/test_v312_report_inbox.py +0 -0
  386. {thth-3.1.2 → thth-3.3.0}/tests/test_v312_report_mcp_user.py +0 -0
  387. {thth-3.1.2 → thth-3.3.0}/tests/test_v31_analysis.py +0 -0
  388. {thth-3.1.2 → thth-3.3.0}/tests/test_v31_collection.py +0 -0
  389. {thth-3.1.2 → thth-3.3.0}/tests/test_value_domains_shown.py +0 -0
  390. {thth-3.1.2 → thth-3.3.0}/tests/test_version.py +0 -0
  391. {thth-3.1.2 → thth-3.3.0}/tests/test_where_cli.py +0 -0
  392. {thth-3.1.2 → thth-3.3.0}/tests/test_who_cli.py +0 -0
  393. {thth-3.1.2 → thth-3.3.0}/tests/test_writeback_rebase.py +0 -0
  394. {thth-3.1.2 → thth-3.3.0}/tests/test_writeback_validate.py +0 -0
  395. {thth-3.1.2 → thth-3.3.0}/tests/test_x_metrics.py +0 -0
  396. {thth-3.1.2 → thth-3.3.0}/thth/__init__.py +0 -0
  397. {thth-3.1.2 → thth-3.3.0}/thth/__main__.py +0 -0
  398. {thth-3.1.2 → thth-3.3.0}/thth/_grapheme_data.py +0 -0
  399. {thth-3.1.2 → thth-3.3.0}/thth/account_cli.py +0 -0
  400. {thth-3.1.2 → thth-3.3.0}/thth/account_report.py +0 -0
  401. {thth-3.1.2 → thth-3.3.0}/thth/accounts.py +0 -0
  402. {thth-3.1.2 → thth-3.3.0}/thth/adapters/__init__.py +0 -0
  403. {thth-3.1.2 → thth-3.3.0}/thth/adapters/auth_mastodon.py +0 -0
  404. {thth-3.1.2 → thth-3.3.0}/thth/adapters/auth_threads.py +0 -0
  405. {thth-3.1.2 → thth-3.3.0}/thth/adapters/auth_x.py +0 -0
  406. {thth-3.1.2 → thth-3.3.0}/thth/adapters/base.py +0 -0
  407. {thth-3.1.2 → thth-3.3.0}/thth/adapters/bluesky.py +0 -0
  408. {thth-3.1.2 → thth-3.3.0}/thth/adapters/bluesky_media.py +0 -0
  409. {thth-3.1.2 → thth-3.3.0}/thth/adapters/bluesky_video.py +0 -0
  410. {thth-3.1.2 → thth-3.3.0}/thth/adapters/mastodon.py +0 -0
  411. {thth-3.1.2 → thth-3.3.0}/thth/adapters/mastodon_media.py +0 -0
  412. {thth-3.1.2 → thth-3.3.0}/thth/adapters/threads.py +0 -0
  413. {thth-3.1.2 → thth-3.3.0}/thth/adapters/threads_media.py +0 -0
  414. {thth-3.1.2 → thth-3.3.0}/thth/adapters/x.py +0 -0
  415. {thth-3.1.2 → thth-3.3.0}/thth/adapters/x_media.py +0 -0
  416. {thth-3.1.2 → thth-3.3.0}/thth/admin_notifications.py +0 -0
  417. {thth-3.1.2 → thth-3.3.0}/thth/admin_report.py +0 -0
  418. {thth-3.1.2 → thth-3.3.0}/thth/after_cli.py +0 -0
  419. {thth-3.1.2 → thth-3.3.0}/thth/analytics_comparison.py +0 -0
  420. {thth-3.1.2 → thth-3.3.0}/thth/analytics_report.py +0 -0
  421. {thth-3.1.2 → thth-3.3.0}/thth/analytics_shapes.py +0 -0
  422. {thth-3.1.2 → thth-3.3.0}/thth/analytics_threads.py +0 -0
  423. {thth-3.1.2 → thth-3.3.0}/thth/api_diagnostic.py +0 -0
  424. {thth-3.1.2 → thth-3.3.0}/thth/appconfig.py +0 -0
  425. {thth-3.1.2 → thth-3.3.0}/thth/appenv.py +0 -0
  426. {thth-3.1.2 → thth-3.3.0}/thth/approval_jobs.py +0 -0
  427. {thth-3.1.2 → thth-3.3.0}/thth/approval_relay.py +0 -0
  428. {thth-3.1.2 → thth-3.3.0}/thth/ask.py +0 -0
  429. {thth-3.1.2 → thth-3.3.0}/thth/ask_cli.py +0 -0
  430. {thth-3.1.2 → thth-3.3.0}/thth/audioformats.py +0 -0
  431. {thth-3.1.2 → thth-3.3.0}/thth/authclients.py +0 -0
  432. {thth-3.1.2 → thth-3.3.0}/thth/authflow.py +0 -0
  433. {thth-3.1.2 → thth-3.3.0}/thth/authpassword.py +0 -0
  434. {thth-3.1.2 → thth-3.3.0}/thth/bluesky_metadata.py +0 -0
  435. {thth-3.1.2 → thth-3.3.0}/thth/budget_x.py +0 -0
  436. {thth-3.1.2 → thth-3.3.0}/thth/budget_x_posts.py +0 -0
  437. {thth-3.1.2 → thth-3.3.0}/thth/bundle.py +0 -0
  438. {thth-3.1.2 → thth-3.3.0}/thth/collect.py +0 -0
  439. {thth-3.1.2 → thth-3.3.0}/thth/collection_context.py +0 -0
  440. {thth-3.1.2 → thth-3.3.0}/thth/collection_status.py +0 -0
  441. {thth-3.1.2 → thth-3.3.0}/thth/deletion.py +0 -0
  442. {thth-3.1.2 → thth-3.3.0}/thth/ebmlaudio.py +0 -0
  443. {thth-3.1.2 → thth-3.3.0}/thth/ebmlvideo.py +0 -0
  444. {thth-3.1.2 → thth-3.3.0}/thth/engagements.py +0 -0
  445. {thth-3.1.2 → thth-3.3.0}/thth/flacformats.py +0 -0
  446. {thth-3.1.2 → thth-3.3.0}/thth/forms.py +0 -0
  447. {thth-3.1.2 → thth-3.3.0}/thth/graphemes.py +0 -0
  448. {thth-3.1.2 → thth-3.3.0}/thth/handoff_cursor.py +0 -0
  449. {thth-3.1.2 → thth-3.3.0}/thth/httpsafe.py +0 -0
  450. {thth-3.1.2 → thth-3.3.0}/thth/inflight.py +0 -0
  451. {thth-3.1.2 → thth-3.3.0}/thth/jst.py +0 -0
  452. {thth-3.1.2 → thth-3.3.0}/thth/leave.py +0 -0
  453. {thth-3.1.2 → thth-3.3.0}/thth/leave_gate.py +0 -0
  454. {thth-3.1.2 → thth-3.3.0}/thth/lock.py +0 -0
  455. {thth-3.1.2 → thth-3.3.0}/thth/maintain.py +0 -0
  456. {thth-3.1.2 → thth-3.3.0}/thth/managed_repo.py +0 -0
  457. {thth-3.1.2 → thth-3.3.0}/thth/measured.py +0 -0
  458. {thth-3.1.2 → thth-3.3.0}/thth/media.py +0 -0
  459. {thth-3.1.2 → thth-3.3.0}/thth/media_capabilities.py +0 -0
  460. {thth-3.1.2 → thth-3.3.0}/thth/media_cleanup.py +0 -0
  461. {thth-3.1.2 → thth-3.3.0}/thth/media_delivery.py +0 -0
  462. {thth-3.1.2 → thth-3.3.0}/thth/media_relay.py +0 -0
  463. {thth-3.1.2 → thth-3.3.0}/thth/media_uploads.py +0 -0
  464. {thth-3.1.2 → thth-3.3.0}/thth/mediaformats.py +0 -0
  465. {thth-3.1.2 → thth-3.3.0}/thth/mpeg_audio.py +0 -0
  466. {thth-3.1.2 → thth-3.3.0}/thth/oauth.py +0 -0
  467. {thth-3.1.2 → thth-3.3.0}/thth/oggformats.py +0 -0
  468. {thth-3.1.2 → thth-3.3.0}/thth/postid.py +0 -0
  469. {thth-3.1.2 → thth-3.3.0}/thth/read_window.py +0 -0
  470. {thth-3.1.2 → thth-3.3.0}/thth/redact.py +0 -0
  471. {thth-3.1.2 → thth-3.3.0}/thth/replies.py +0 -0
  472. {thth-3.1.2 → thth-3.3.0}/thth/report_details.py +0 -0
  473. {thth-3.1.2 → thth-3.3.0}/thth/report_http.py +0 -0
  474. {thth-3.1.2 → thth-3.3.0}/thth/report_isolation.py +0 -0
  475. {thth-3.1.2 → thth-3.3.0}/thth/retract_cli.py +0 -0
  476. {thth-3.1.2 → thth-3.3.0}/thth/scopes.py +0 -0
  477. {thth-3.1.2 → thth-3.3.0}/thth/secrets_fs.py +0 -0
  478. {thth-3.1.2 → thth-3.3.0}/thth/selfupdate.py +0 -0
  479. {thth-3.1.2 → thth-3.3.0}/thth/server_files.py +0 -0
  480. {thth-3.1.2 → thth-3.3.0}/thth/server_writes.py +0 -0
  481. {thth-3.1.2 → thth-3.3.0}/thth/stop_observation.py +0 -0
  482. {thth-3.1.2 → thth-3.3.0}/thth/study_cli.py +0 -0
  483. {thth-3.1.2 → thth-3.3.0}/thth/study_report.py +0 -0
  484. {thth-3.1.2 → thth-3.3.0}/thth/systemd_gen.py +0 -0
  485. {thth-3.1.2 → thth-3.3.0}/thth/tags.py +0 -0
  486. {thth-3.1.2 → thth-3.3.0}/thth/thread_read.py +0 -0
  487. {thth-3.1.2 → thth-3.3.0}/thth/threadrun.py +0 -0
  488. {thth-3.1.2 → thth-3.3.0}/thth/threads_read_cli.py +0 -0
  489. {thth-3.1.2 → thth-3.3.0}/thth/threadshape.py +0 -0
  490. {thth-3.1.2 → thth-3.3.0}/thth/threadthrow.py +0 -0
  491. {thth-3.1.2 → thth-3.3.0}/thth/topic_advice.py +0 -0
  492. {thth-3.1.2 → thth-3.3.0}/thth/topic_cli.py +0 -0
  493. {thth-3.1.2 → thth-3.3.0}/thth/topic_models.py +0 -0
  494. {thth-3.1.2 → thth-3.3.0}/thth/topic_store.py +0 -0
  495. {thth-3.1.2 → thth-3.3.0}/thth/topics.py +0 -0
  496. {thth-3.1.2 → thth-3.3.0}/thth/unanswered.py +0 -0
  497. {thth-3.1.2 → thth-3.3.0}/thth/vorbisformats.py +0 -0
  498. {thth-3.1.2 → thth-3.3.0}/thth/watch_cli.py +0 -0
  499. {thth-3.1.2 → thth-3.3.0}/thth/where_cli.py +0 -0
  500. {thth-3.1.2 → thth-3.3.0}/thth/who_cli.py +0 -0
  501. {thth-3.1.2 → thth-3.3.0}/thth/writeback.py +0 -0
  502. {thth-3.1.2 → thth-3.3.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.3.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
@@ -125,7 +125,7 @@ timer(systemd・`thth systemd` で生成)は10分ごとに実行し、投稿
125
125
  - **英語の文書**: [README.en.md](README.en.md)・[docs/usage.en.md](docs/usage.en.md)・[llms.txt](llms.txt)。
126
126
  - **skill**: `skills/thth/SKILL.md`(wheel にも入る)。
127
127
 
128
- - **動くもの**(`thth --help` の全サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`analytics-report`・`study-report`・`study`・`unanswered`・`handoff-report`・`morning`・`serve-reports`・`approval-worker`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`pull`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`・`notifications`・`report`・`admin`。
128
+ - **動くもの**(`thth --help` の全サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`analytics-report`・`study-report`・`study`・`unanswered`・`handoff-report`・`observe`・`morning`・`serve-reports`・`approval-worker`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`pull`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`・`notifications`・`report`・`admin`。
129
129
  - **最初の本番投稿の記録**: 2026-09-09、@aoking に疎通確認を 1 本(`17916074118445631`)。
130
130
  - **未着手**: X・Facebook ページ・Instagram の各アダプタ。トピック検索の権限(tester には降りない)。泉のサーバ(v2-5)。
131
131
  - **権限の制約**: tester に降りる scope は 5 つ。削除はできない。
@@ -101,7 +101,7 @@ timer(systemd・`thth systemd` で生成)は10分ごとに実行し、投稿
101
101
  - **英語の文書**: [README.en.md](README.en.md)・[docs/usage.en.md](docs/usage.en.md)・[llms.txt](llms.txt)。
102
102
  - **skill**: `skills/thth/SKILL.md`(wheel にも入る)。
103
103
 
104
- - **動くもの**(`thth --help` の全サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`analytics-report`・`study-report`・`study`・`unanswered`・`handoff-report`・`morning`・`serve-reports`・`approval-worker`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`pull`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`・`notifications`・`report`・`admin`。
104
+ - **動くもの**(`thth --help` の全サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`analytics-report`・`study-report`・`study`・`unanswered`・`handoff-report`・`observe`・`morning`・`serve-reports`・`approval-worker`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`pull`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`・`notifications`・`report`・`admin`。
105
105
  - **最初の本番投稿の記録**: 2026-09-09、@aoking に疎通確認を 1 本(`17916074118445631`)。
106
106
  - **未着手**: X・Facebook ページ・Instagram の各アダプタ。トピック検索の権限(tester には降りない)。泉のサーバ(v2-5)。
107
107
  - **権限の制約**: tester に降りる scope は 5 つ。削除はできない。
@@ -295,12 +295,28 @@ TOOLS = [
295
295
  },
296
296
  },
297
297
  {
298
- # **名前と説明文がそのまま売り文句**(設計 3.1.0 §1)。
298
+ # **名前と説明文がそのまま売り文句**(設計 3.1.0 §1・3.3.0 §F で観測に)。
299
+ "name": "thth_observe",
300
+ "description": (
301
+ "セッションの始めと区切りごとに呼ぶ。前回の観測から何があって、いまなにを"
302
+ "すればよいかを、道具が事実だけで 1 枚にする。本文は作らない"
303
+ ),
304
+ "inputSchema": {
305
+ "type": "object",
306
+ "properties": {
307
+ "target": {"type": "string", "description": "project 名か account 名"},
308
+ "mark": {"type": "boolean",
309
+ "description": "栞(前回読んだ時点)を進める(既定 true)"},
310
+ },
311
+ "required": ["target"],
312
+ },
313
+ },
314
+ {
315
+ # 別名(3.3.0 §F)。既存の skill・セッションを壊さないために残す。
299
316
  "name": "thth_morning",
300
- # **設計 3.1.0 §0 の約束そのまま。**
301
317
  "description": (
302
- "朝いちばんに呼ぶ。昨日から何があって、今日なにをすればよいかを、"
303
- "道具が事実だけで 1 枚にする。本文は作らない"
318
+ "thth_observe の別名(同じ 1 枚を返す)。セッションの始めと区切りごとに呼ぶ。"
319
+ "前回の観測から何があって、いまなにをすればよいかを、道具が事実だけで 1 枚にする"
304
320
  ),
305
321
  "inputSchema": {
306
322
  "type": "object",
@@ -589,16 +605,25 @@ del _tool
589
605
  # credential の `actor` が決める(要求の欄からは作らない)。
590
606
  REPORT_TOOLS = [
591
607
  {"name": "thth_report_file",
592
- "description": "道具が断った・結果が期待と違った・欲しい形がある、のどれかならこれで置く"
593
- "(kind は bug か request。実装側が読み、返事は thth_report_show と "
608
+ "description": "道具が断った・結果が期待と違った・欲しい形がある・迷った、のどれかならこれで置く。"
609
+ "つまずき・迷い・期待との違いの報告は、利用者の作業の一部として歓迎します。"
610
+ "小さいものも。重複は道具が束ねます。"
611
+ "例: 「止まったのに気づかなかった」「断られた理由が分からなかった」"
612
+ "「同じ操作を 3 回繰り返した」。断られた直後なら from_last_refusal: true で"
613
+ "道具が控えた断りを再現手順に添えられる"
614
+ "(kind は bug・request・friction〔迷った・分かりにくかった・同じ操作を繰り返した〕。"
615
+ "実装側が読み、返事は thth_report_show と "
594
616
  "operations_handoff の tool.reports に出る。秘密らしき値が含まれていたら置かない)",
595
617
  "inputSchema": {"type": "object", "properties": {
596
618
  "account": {"type": "string"},
597
- "kind": {"type": "string", "enum": ["bug", "request"]},
619
+ "kind": {"type": "string", "enum": ["bug", "request", "friction"]},
598
620
  "title": {"type": "string", "description": "1 行・120 字まで"},
599
- "body": {"type": "string", "description": "8,000 字まで"},
600
- "repro": {"type": "string", "description": "再現手順(任意・4,000 字まで)"}},
601
- "required": ["account", "kind", "title", "body"], "additionalProperties": False}},
621
+ "body": {"type": "string", "description": "8,000 字まで(from_last_refusal のときは省略可)"},
622
+ "repro": {"type": "string", "description": "再現手順(任意・4,000 字まで)"},
623
+ "from_last_refusal": {"type": "boolean",
624
+ "description": "この account で直前に道具が断った記録(時刻・版・命令・"
625
+ "理由の符丁)を再現手順として添える。kind の既定は friction"}},
626
+ "required": ["account", "title"], "additionalProperties": False}},
602
627
  {"name": "thth_report_list",
603
628
  "description": "自分の project の報告と、実装側の返事の数を読む(読むだけ)",
604
629
  "inputSchema": {"type": "object", "properties": {
@@ -608,6 +633,14 @@ REPORT_TOOLS = [
608
633
  "description": "報告 1 件の本文と実装側の返事を読む(読むだけ)",
609
634
  "inputSchema": {"type": "object", "properties": {"report_id": {"type": "string"}},
610
635
  "required": ["report_id"], "additionalProperties": False}},
636
+ # 報告した側の追記(設計 3.2.0 §4.5-1)。
637
+ {"name": "thth_report_add",
638
+ "description": "自分の project の開いている報告に書き足す(返事と同じ列に並ぶ・4,000 字まで・"
639
+ "閉じた報告には足せない・秘密らしき値が含まれていたら足さない)",
640
+ "inputSchema": {"type": "object", "properties": {
641
+ "report_id": {"type": "string"},
642
+ "text": {"type": "string", "description": "4,000 字まで"}},
643
+ "required": ["report_id", "text"], "additionalProperties": False}},
611
644
  ]
612
645
 
613
646
 
@@ -622,12 +655,16 @@ def _with_report_hint(tools):
622
655
  else dict(tool, description=tool["description"] + REPORT_HINT) for tool in tools]
623
656
 
624
657
 
625
- def _channel_note():
626
- """断りの応答に添える 2 つ目の text(静的な 1 行・本文も account 名も入れない)。"""
658
+ def _channel_note(account=None, reason=None):
659
+ """断りの応答に添える 2 つ目の text。本文は入れない。
660
+
661
+ account(credential が許した・台帳にある名前)と理由の符丁が分かれば、そのまま
662
+ 呼べる `thth_report_file` の 1 行(設計 3.3.0 B3)。分からなければ静的な 1 行。
663
+ """
627
664
  if APP_DIR not in sys.path:
628
665
  sys.path.insert(0, APP_DIR)
629
- from thth.report_inbox import CHANNEL_LINE
630
- return {"type": "text", "text": CHANNEL_LINE}
666
+ from thth.report_inbox import mcp_refusal_line
667
+ return {"type": "text", "text": mcp_refusal_line(account, reason)}
631
668
 
632
669
 
633
670
  def server_mode():
@@ -641,7 +678,7 @@ def server_tools(context):
641
678
  if context.scope=='admin': return reports + ADMIN_TOOLS
642
679
  # 毎朝の一枚は**利用者の scope だけ**(設計 3.1.0 §1)。credential が許した
643
680
  # account/project しか対象にできない(`report_service.execute_morning()`)。
644
- reports = reports + [tool for tool in TOOLS if tool['name']=='thth_morning']
681
+ reports = reports + [tool for tool in TOOLS if tool['name'] in ('thth_observe', 'thth_morning')]
645
682
  from thth.server_writes import WRITE_OPERATIONS
646
683
  return _with_report_hint(reports + [tool for tool in SERVER_TOOLS
647
684
  if context.writes or tool['name'][5:] not in WRITE_OPERATIONS]) + REPORT_TOOLS
@@ -651,8 +688,11 @@ def server_call(name, arguments):
651
688
  from thth.report_service import execute_report, execute_mcp_report, ReportServiceError
652
689
  from thth.server_writes import execute, WRITE_OPERATIONS, SAFE_ERRORS, DRAFT_REASONS
653
690
  context=authenticated_context()
654
- # 断りは 1 つ目の text が静的な理由、2 つ目が受け口の案内(設計 3.1.2 §3.5)。
655
- failure=lambda value:{'content':[{'type':'text','text':value},_channel_note()],'isError':True}
691
+
692
+ def failure(value):
693
+ # 断りは 1 つ目の text が静的な理由、2 つ目が受け口の案内(設計 3.1.2 §3.5)。
694
+ account, code = _remember_refusal(context, name, arguments, value)
695
+ return {'content':[{'type':'text','text':value},_channel_note(account, code)],'isError':True}
656
696
  if context is None: return failure('unauthorized')
657
697
  tool=next((tool for tool in server_tools(context) if tool['name']==name),None)
658
698
  if tool is None: return failure('unsupported_operation')
@@ -681,12 +721,13 @@ def server_call(name, arguments):
681
721
  elif operation.startswith('admin_reports_'):
682
722
  from thth.report_service import execute_admin_reports
683
723
  result=execute_admin_reports(context,request)
684
- elif operation in ('report_file','report_list','report_show'):
724
+ elif operation in ('report_file','report_list','report_show','report_add'):
685
725
  from thth.report_service import execute_user_reports
686
726
  result=execute_user_reports(context,request)
687
- elif operation=='morning':
727
+ elif operation in ('morning', 'observe'):
688
728
  from thth.report_service import execute_morning
689
- result=execute_morning(context,request)
729
+ request={**request,'operation':'morning'}
730
+ result=execute_morning(context,request,invoked_as=operation)
690
731
  elif operation in ('analytics_report', 'operations_handoff', 'study_report'):
691
732
  result=execute_mcp_report(context,request)
692
733
  else:
@@ -706,6 +747,34 @@ def server_call(name, arguments):
706
747
  return failure('request_unavailable')
707
748
 
708
749
 
750
+ def _remember_refusal(context, name, arguments, value):
751
+ """サーバ型の断りを account ごとに控える(設計 3.3.0 B2)。控えの失敗で断りを変えない。
752
+
753
+ 戻り値は断りの 2 つ目の text に埋める `(account, 符丁)`(B3・分からなければ None)。
754
+
755
+ account は credential が許した名前のときだけ。残すのは道具の名前と、断りの
756
+ 先頭の静的な符丁だけ(`thth/refusals.py`・引数の値は残さない)。報告の口の
757
+ 断りは控えない(報告しようとしていた元の断りを押し出さないため)。
758
+ """
759
+ try:
760
+ if context is None or not isinstance(arguments, dict) or not isinstance(name, str):
761
+ return None, None
762
+ account = arguments.get('account')
763
+ if not isinstance(account, str) or account not in (context.allowed_accounts or {}):
764
+ return None, None
765
+ if not name.replace('_', '').isalnum():
766
+ return None, None
767
+ from thth import refusals
768
+ command = 'mcp ' + name
769
+ values = [v for v in arguments.values() if isinstance(v, str)]
770
+ code = refusals.reason_code(value, argv=values, command=command, rc=2)
771
+ if not name.startswith('thth_report_'):
772
+ refusals.record(account, command=command, reason_code=code)
773
+ return account, code
774
+ except Exception:
775
+ return None, None
776
+
777
+
709
778
  def admin_context():
710
779
  context=authenticated_context()
711
780
  return context if context is not None and context.scope=='admin' else None
@@ -884,10 +953,10 @@ def call_tool(name: str, arguments: dict | None) -> dict:
884
953
  args.append("--json")
885
954
  proc = run_cli(args)
886
955
  text = proc.stdout
887
- elif name == "thth_morning":
956
+ elif name in ("thth_morning", "thth_observe"):
888
957
  # **`where_to_appear` と同じ型**: CLI(`thth morning`)を `--json` で
889
958
  # 呼ぶだけ(設計 3.1.0 §1・§5)。栞を進めないときだけ旗を足す。
890
- args = ["morning", arguments["target"]]
959
+ args = [name[len("thth_"):], arguments["target"]]
891
960
  if arguments.get("mark") is False:
892
961
  args.append("--no-mark")
893
962
  args.append("--json")
@@ -917,7 +986,7 @@ def call_tool(name: str, arguments: dict | None) -> dict:
917
986
 
918
987
  if name.startswith("thth_topic_") or name in (
919
988
  "before_you_post", "after_you_posted", "analytics_report", "operations_handoff", "study_report", "thread_read", "where_to_appear",
920
- "who_is_this", "thth_morning"):
989
+ "who_is_this", "thth_morning", "thth_observe"):
921
990
  # **新しい道具は exit 1 も isError**(設計 §7)。lint の exit 1(検査結果)
922
991
  # とは意味が違う——こちらは stale_context・不正な候補比較で、
923
992
  # **そのまま使ってはいけない**応答。既存の扱いは変えない。
@@ -3,9 +3,9 @@ name: thth
3
3
  description: 投稿する前・返信を書く前・出したあと・絡みに行く先を選ぶときに呼ぶ道具。Threads・Bluesky・Mastodon への下書きを人の承認を通してから出し、枝を読み、相手を知り、伸びを測る。
4
4
  ---
5
5
 
6
- **開始時は、まず `thth_morning`(CLI は `thth morning <project|account> --json`)を 1 回。** 昨日から何があって今日なにをすればよいかが 6 段(道具・返していないもの・昨日の自分・世間・予定・次の一手)で返る。`handoff-report --since-last-read` を包含するので、版の確認もこれ 1 回で済む。呼ぶと栞(前回読んだ時点)が進む——進めたくないときだけ `mark: false`(CLI は `--no-mark`)。取れなかった段は `{"value": null, "cannot_say": "<理由>"}`(`provider_timeout`・`budget_exhausted`・`scope_missing`・`no_watch_words`・`not_supported`・`unavailable`)で、**0 件とは別**。第 3 段の監視語は**管理者が入れる**(`thth admin watch set <account> 語 … --by <名前>`)——語が無い account は `no_watch_words` で飛ぶので、**代わりに語を選ばない**。第 5 段は候補の列挙だけで、**本文は返らない**(書くのはあなたと人の会話の側)。詳しくは `docs/使い方_毎朝の一枚_3.1.md`。
6
+ **セッションの始めと区切りごとに `thth_observe`(CLI は `thth observe <project|account> --json`)。** 前回の観測から何があって、いまなにをすればよいかが 6 段(道具・返していないもの・前回の観測から・世間・予定・次の一手)で返る(3.3.0。`thth_morning`/`thth morning` は同じものの別名で、JSON の `invoked_as` だけが違う。第 2 段は前回の栞の時刻から今まで・栞が無い/7 日より古いときは前日 JST で、`window.basis` が `since_last_observe` か `yesterday_jst`)。`handoff-report --since-last-read` を包含するので、版の確認もこれ 1 回で済む。呼ぶと栞(前回読んだ時点)が進む——進めたくないときだけ `mark: false`(CLI は `--no-mark`)。取れなかった段は `{"value": null, "cannot_say": "<理由>"}`(`provider_timeout`・`budget_exhausted`・`scope_missing`・`no_watch_words`・`not_supported`・`unavailable`)で、**0 件とは別**。第 3 段の監視語は**管理者が入れる**(`thth admin watch set <account> 語 … --by <名前>`)——語が無い account は `no_watch_words` で飛ぶので、**代わりに語を選ばない**。第 5 段は候補の列挙だけで、**本文は返らない**(書くのはあなたと人の会話の側)。詳しくは `docs/使い方_毎朝の一枚_3.1.md`。
7
7
 
8
- **断られたら・違和感があれば報告の口へ**: `thth_report_file`(CLI は `thth report file <account> --kind bug|request --title … --body-file … --by <名前>`)。道具が断った・結果が期待と違った・欲しい形がある、のどれかなら 1 回で置ける。返事は `thth_report_show` と `handoff-report --since-last-read` の `tool.reports` に出る。秘密らしき値が含まれていたら置かれない(伏せてから置き直す)。
8
+ **断られたら・違和感があれば報告の口へ**: `thth_report_file`(CLI は `thth report file <account> --kind bug|request|friction --title … --body-file … --by <名前>`)。つまずき・迷い・期待との違いの報告は、利用者の作業の一部として歓迎します。小さいものも。重複は道具が束ねます。例: 「止まったのに気づかなかった」「断られた理由が分からなかった」「同じ操作を 3 回繰り返した」。道具が断った・結果が期待と違った・欲しい形がある・迷った(`friction`)、のどれかなら 1 回で置ける。**断られた直後なら、断りの最後の `report_channel:` の行をそのまま打てばよい**(`--from-last-refusal` が、道具が控えた直前の断り——時刻・版・命令・理由の符丁だけで、本文と引数の値は控えていない——を再現手順に添える。MCP は `thth_report_file {"account": …, "title": …, "from_last_refusal": true}`)。置いた報告がこの版で閉じたかは `thth_observe` の 0 段 `project_reports` に出る。返事は `thth_report_show` と `handoff-report --since-last-read` の `tool.reports` に出る。秘密らしき値が含まれていたら置かれない(伏せてから置き直す)。
9
9
 
10
10
  段だけを読み直したいときは `thth handoff-report <account> --since-last-read --json` を読む。`tool.changed_since_last_read` が真なら、手元の `tool.notes_root_local_hint` を手掛かりに `tool.release_notes` の相対名を読んでから作業する。既読の記録は `--mark-read --by <名前>` の明示時だけ。`tool.notes_reason` が `notes_directory_unavailable` なら、このインストールには読める docs がない。空の `release_notes` を「変更なし」と解釈しない。
11
11
 
@@ -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
@@ -143,7 +149,9 @@ MCP `after_you_posted`。**24h の刻みが無ければ `null`**(0 と混ぜ
143
149
 
144
150
  - `thth --help` / `thth <subcommand> --help` が正本。
145
151
  - `thth doctor <account>` が「いま投稿できる状態か」を最初から最後まで言う。
146
- - 道具の不具合・欲しい形は `thth report file`(MCP `thth_report_file`)で実装側に届ける。
152
+ - 道具の不具合・欲しい形・迷ったことは `thth report file`(MCP `thth_report_file`)で実装側に届ける(小さいものも歓迎・重複は道具が束ねる)。
153
+ - 承認済みなのに出ない原稿は `thth_observe` の予定の段 `held_items`(file・理由・予定・経過)と `thth board` の要確認(先頭 5 本)に名前が出る。`approval_stale` は再承認(`thth approve` の二段)で直る。時刻を過ぎて出られないものがあると `thth run` は `held` として死活通知と運用通知に知らせる。
154
+ - 自分の開いている報告に書き足すなら `thth report add <report_id> --body-file <path|-> --by <名前>`(MCP `thth_report_add`)。
147
155
  - 使い方の全文: `docs/使い方_プロジェクトのセッション向け_2026-09-09.md`。
148
156
 
149
157
  ## 運用と分析のレポート(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 が壊れている型外テスト等: 計算できないので触らない
@@ -427,3 +429,15 @@ def init_git_pair(tmp_path, *, seed_content: str, seed_name: str = "a.md"):
427
429
 
428
430
  return {"bare": bare, "seed": seed, "work": work,
429
431
  "queue_dir": os.path.join(work, "docs", "sns", "queue")}
432
+
433
+
434
+ def is_refusal(err: str, code: str) -> bool:
435
+ """断りの stderr が「理由の符丁 1 行+打てる報告の 1 行」か(3.1.2 §3.5・3.3.0 B3)。
436
+
437
+ 3.3.0 から 2 行目は account と理由の符丁を埋めた `report_inbox.refusal_line()`。
438
+ account は命令と台帳しだいで `<account>` のままになるので、ここでは形と符丁だけ見る。
439
+ """
440
+ lines = err.strip().splitlines()
441
+ return (len(lines) == 2 and lines[0] == code
442
+ and lines[1].startswith("report_channel: thth report file ")
443
+ and f'--title "{code} で断られた"' in lines[1])
@@ -102,11 +102,12 @@ def test_mcp_stdioでtools_listとtools_callが通る(isolated_account):
102
102
  # 2026-09-16 に `where_to_appear` を足した(設計「自分の泉」§2.3・T2-3・読むだけ)。
103
103
  # 2026-09-16 に `who_is_this` を足した(設計「自分の泉」§2.4・T3-3・読むだけ)。
104
104
  # 2026-09-23 に `thth_morning` を足した(設計 3.1.0 §1・読むだけ・栞だけ進む)。
105
+ # 2026-09-23 に `thth_observe` を足した(設計 3.3.0 §F・thth_morning は別名)。
105
106
  assert tool_names == {"thth_lint", "thth_queue", "thth_preview", "thth_board",
106
107
  "thth_topic_context", "thth_topic_evaluate",
107
108
  "thth_topic_decision", "before_you_post", "after_you_posted",
108
109
  "thread_read", "where_to_appear", "who_is_this", "analytics_report", "operations_handoff", "study_report",
109
- "thth_morning"}
110
+ "thth_morning", "thth_observe"}
110
111
  # 副作用のあるものは 1 つも出ていない。
111
112
  assert not (tool_names & {"thth_approve", "thth_throw", "thth_token",
112
113
  "thth_auth", "thth_refresh", "thth_revoke",
@@ -26,8 +26,10 @@ def json_parsers():
26
26
  COMMANDS = '''lint|preview|approve|account|revoke|posts|replies|measured|threads|study-report|analytics-report|after|topics|forms|queue|schedule|throw|handoff-report|board|pull|maintain|doctor|app show|admin inventory|admin account|admin log|admin tokens|admin timers|admin release|admin diff|ask before-you-post|mentions|profile|thread|where|who|retract|location|topics suggest|topics observe|topics record-decision|topics decision|topics observation|topics retract|topics unretract|topics profile|topics adopt-reason|topics adoptions|topics record-vocabulary|topics vocabulary|topics record-review|topics review|topics record-form-spec|topics form-spec|topics form-check|topics improvements|topics impact|topics record-hypothesis|topics hypotheses'''.split('|') + ['account add', 'account migrate', 'unanswered', 'study add', 'admin deletion sync', 'admin budget', 'admin media cleanup-retry', 'admin media gc',
27
27
  # 3.1.0: 毎朝の一枚と監視語(設計 3.1.0 §1・§3)。
28
28
  'morning', 'admin watch set', 'admin watch show',
29
+ # 3.3.0 §F: 観測(observe)。morning は別名として残る。
30
+ 'observe',
29
31
  # 3.1.2: 報告の口(設計 3.1.2 §1)。
30
- 'report file', 'report list', 'report show', 'admin reports list', 'admin reports show',
32
+ 'report file', 'report list', 'report show', 'report add', 'admin reports list', 'admin reports show',
31
33
  'admin reports reply', 'admin reports close', 'admin reports export']
32
34
 
33
35
 
@@ -61,7 +61,7 @@ def test_cli_reads_do_not_wait_or_call_core_when_leave_holds_exclusive(env,conte
61
61
  start=time.monotonic();rc=cli.main(command);elapsed=time.monotonic()-start
62
62
  captured=capsys.readouterr();assert rc==2 and elapsed<1
63
63
  assert json.loads(captured.out)=={'error':'account_leaving','cannot_say':['account_leaving']}
64
- assert captured.err.strip()=='account_leaving\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
64
+ assert __import__('tests.conftest').conftest.is_refusal(captured.err, 'account_leaving') # 3.1.2 §3.5・3.3.0 B3: 断りの理由行の後ろに打てる報告の 1 行
65
65
 
66
66
 
67
67
  def test_dry_send_input_then_nonwaiting_lease(env,tmp_path,capsys):
@@ -69,7 +69,7 @@ def test_dry_send_input_then_nonwaiting_lease(env,tmp_path,capsys):
69
69
  with exclusive():
70
70
  start=time.monotonic();rc=cli.main(['send','alpha','--text-file',str(text)])
71
71
  assert time.monotonic()-start<1 and rc==2
72
- assert capsys.readouterr().err.strip()=='account_leaving\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
72
+ assert __import__('tests.conftest').conftest.is_refusal(capsys.readouterr().err, 'account_leaving') # 3.1.2 §3.5・3.3.0 B3: 断りの理由行の後ろに打てる報告の 1 行
73
73
  assert not (env['root']/'state/alpha/sent').exists()
74
74
 
75
75
 
@@ -145,7 +145,7 @@ def test_real_cli_child_rejects_ex_under_deadline(env,content):
145
145
  result=subprocess.run([sys.executable,'-c',"from thth.cli import main;raise SystemExit(main(['queue','alpha','--json']))"],capture_output=True,text=True,timeout=2)
146
146
  except subprocess.TimeoutExpired:pytest.fail('reader waited for the exit drain')
147
147
  assert result.returncode==2 and json.loads(result.stdout)['cannot_say']==['account_leaving']
148
- assert result.stderr.strip()=='account_leaving\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
148
+ assert __import__('tests.conftest').conftest.is_refusal(result.stderr, 'account_leaving') # 3.1.2 §3.5・3.3.0 B3: 断りの理由行の後ろに打てる報告の 1 行
149
149
 
150
150
 
151
151
  def test_report_holds_lease_through_calculation_and_release(env,monkeypatch):
@@ -51,7 +51,7 @@ raise SystemExit(cli.main(json.loads(sys.argv[1])))
51
51
  # cannot trap this pytest process before an elapsed-time assertion.
52
52
  result = subprocess.run([sys.executable, '-c', code, json.dumps(command), str(marker)],
53
53
  capture_output=True, text=True, timeout=3)
54
- assert result.returncode == 2 and result.stderr.strip() == 'account_leaving\n' + __import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
54
+ assert result.returncode == 2 and __import__('tests.conftest').conftest.is_refusal(result.stderr, 'account_leaving') # 3.1.2 §3.5・3.3.0 B3: 断りの理由行の後ろに打てる報告の 1 行
55
55
  if operation == 'replies':
56
56
  assert json.loads(result.stdout) == {'error': 'account_leaving', 'cannot_say': ['account_leaving']}
57
57
  assert not marker.exists()
@@ -14,7 +14,7 @@ def test_run_invalid_name_before_any_storage_or_effect(monkeypatch,capsys,name):
14
14
  monkeypatch.setattr('thth.incident.notify',forbidden)
15
15
  assert cli.main(['run',name])==2
16
16
  captured=capsys.readouterr();value=json.loads(captured.out)
17
- assert captured.err.strip()=='invalid_account_name\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
17
+ assert __import__('tests.conftest').conftest.is_refusal(captured.err, 'invalid_account_name') # 3.1.2 §3.5・3.3.0 B3: 断りの理由行の後ろに打てる報告の 1 行
18
18
  assert value['error']==value['record_unavailable']=='invalid_account_name'
19
19
  assert value['account'] is None and value['runs_recorded'] is False
20
20
  assert '使えない字' in value['message'] and 'account_stopped' not in captured.out
@@ -30,7 +30,7 @@ def test_run_reason_and_no_side_effect(env,monkeypatch,capsys,state,expected):
30
30
  monkeypatch.setattr('thth.healthcheck.notify',lambda *a,**kw:pytest.fail('healthcheck notification'))
31
31
  monkeypatch.setattr('thth.incident.notify',lambda *a,**kw:pytest.fail('incident notification'))
32
32
  assert cli.main(['run','alpha'])==2
33
- shown=capsys.readouterr();assert shown.err.strip()==expected+'\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
33
+ shown=capsys.readouterr();assert __import__('tests.conftest').conftest.is_refusal(shown.err, expected) # 3.1.2 §3.5・3.3.0 B3: 断りの理由行の後ろに打てる報告の 1 行
34
34
  value=json.loads(shown.out)
35
35
  assert value=={'account':'alpha','mode':'production','action':'skip','status':'error','error':expected,
36
36
  'runs_recorded':False,'record_unavailable':expected}
@@ -285,9 +285,10 @@ def test_既定で栞が進み_no_markでは進まない(one, thth_root):
285
285
  assert morning.build(PROJECT, now=NOW, mark=False)["marked"] == []
286
286
  assert not path.exists(), "--no-mark なのに栞が進んだ"
287
287
  payload = morning.build(PROJECT, now=NOW)
288
- assert payload["marked"] == [ACCOUNT] and payload["marked_by"] == "morning"
288
+ # 3.3.0 §F: 栞の名乗りは呼んだ名前(build の既定は observe)。
289
+ assert payload["marked"] == [ACCOUNT] and payload["marked_by"] == "observe"
289
290
  saved = json.loads(path.read_text(encoding="utf-8"))
290
- assert saved["by"] == "morning" and saved["snapshot"]["tool_version"]
291
+ assert saved["by"] == "observe" and saved["snapshot"]["tool_version"]
291
292
 
292
293
 
293
294
  def test_栞を進めたあとは版の変化が読める(one, thth_root):
@@ -349,9 +350,10 @@ def test_MCPのthth_morningは設計の文言でCLIを呼ぶだけ(one, monkeypa
349
350
  monkeypatch.delenv(key, raising=False)
350
351
  server = _load_server_module()
351
352
  tool = next(item for item in server.TOOLS if item["name"] == "thth_morning")
353
+ # 3.3.0 §F: thth_morning は thth_observe の別名になった(説明文も観測の文言)。
352
354
  assert tool["description"] == (
353
- "朝いちばんに呼ぶ。昨日から何があって、今日なにをすればよいかを、"
354
- "道具が事実だけで 1 枚にする。本文は作らない")
355
+ "thth_observe の別名(同じ 1 枚を返す)。セッションの始めと区切りごとに呼ぶ。"
356
+ "前回の観測から何があって、いまなにをすればよいかを、道具が事実だけで 1 枚にする")
355
357
  seen = []
356
358
 
357
359
  class Done:
@@ -21,17 +21,26 @@ import pytest
21
21
 
22
22
  from thth import accounts, cli, jst, morning, operations_handoff, report_inbox
23
23
 
24
- # **文面は 1 種類・静的**(設計 §3.5)。ここが正本の写し——変えるなら設計と一緒に。
25
- CHANNEL_LINE = "report_channel: thth report file <account> --kind bug|request(MCP: thth_report_file)"
26
- LIMITATION = "不具合と要望は report の口へ(thth_report_file)"
24
+ # **文面は静的**(設計 §3.5)。ここが正本の写し——変えるなら設計と一緒に。
25
+ # 3.3.0 B1・B4: friction と「報告は依頼の範囲内」の 1 文を足した。断りの行は
26
+ # account と理由の符丁を埋めた「打てる 1 行」になった(B3・`refusal_line()`)。
27
+ WELCOME = ("つまずき・迷い・期待との違いの報告は、利用者の作業の一部として歓迎します。"
28
+ "小さいものも。重複は道具が束ねます")
29
+ CHANNEL_LINE = ("report_channel: thth report file <account> --kind bug|request|friction"
30
+ "(MCP: thth_report_file)——" + WELCOME)
31
+ LIMITATION = "不具合・要望・つまずきは report の口へ(thth_report_file)"
27
32
  HINT = "(うまくいかなければ thth_report_file)"
28
33
 
29
34
 
30
35
  def test_受け口の文面は1種類で設計のまま():
31
36
  assert report_inbox.CHANNEL_LINE == CHANNEL_LINE
32
37
  assert report_inbox.CHANNEL == {
33
- "cli": "thth report file <account> --kind bug|request --title … --body-file … --by <名前>",
34
- "mcp": "thth_report_file", "when": "unexpected_or_unsupported"}
38
+ "cli": "thth report file <account> --kind bug|request|friction --title … --body-file … --by <名前>",
39
+ "from_last_refusal": "thth report file <account> --from-last-refusal --title … --by <名前>",
40
+ "mcp": "thth_report_file", "when": "unexpected_or_unsupported",
41
+ "welcome": WELCOME,
42
+ "examples": ["止まったのに気づかなかった", "断られた理由が分からなかった",
43
+ "同じ操作を 3 回繰り返した"]}
35
44
 
36
45
 
37
46
  @pytest.fixture
@@ -169,7 +178,10 @@ def test_サーバ型の利用者の一枚には件数を出さない(projects,
169
178
  def test_CLIの断りの後ろに受け口の1行_成功には載せない(projects, tmp_path, capsys):
170
179
  assert cli.main(["morning", "nobody", "--json"]) == 2
171
180
  err = capsys.readouterr().err.strip().splitlines()
172
- assert err == ["target_unknown", CHANNEL_LINE]
181
+ # 3.3.0 B3: 理由の符丁を埋めた打てる 1 行(account を持たない命令は <account> のまま)。
182
+ assert err == ["target_unknown",
183
+ 'report_channel: thth report file <account> --kind friction --from-last-refusal'
184
+ ' --title "target_unknown で断られた" --by <名前>(' + WELCOME + ')']
173
185
  assert cli.main(["report", "list", "kopicha", "--json"]) == 0
174
186
  assert CHANNEL_LINE not in capsys.readouterr().err
175
187
 
@@ -179,14 +191,14 @@ def test_lintの検査結果の1は断りではない(projects, tmp_path, capsys
179
191
  bad.write_text("---\nthth: 1\n---\n本文\n", encoding="utf-8")
180
192
  rc = cli.main(["lint", str(bad), "--json"])
181
193
  assert rc == 1
182
- assert CHANNEL_LINE not in capsys.readouterr().err
194
+ assert "report_channel" not in capsys.readouterr().err
183
195
 
184
196
 
185
197
  def test_helpの末尾に1行(capsys):
186
198
  with pytest.raises(SystemExit):
187
199
  cli.main(["--help"])
188
200
  out = capsys.readouterr().out.rstrip().splitlines()
189
- assert out[-1] == "不具合と要望は report の口へ: thth report file <account> --kind bug|request(MCP: thth_report_file)"
201
+ assert out[-1] == "不具合・要望・つまずきは report の口へ: thth report file <account> --kind bug|request|friction(MCP: thth_report_file)"
190
202
  with pytest.raises(SystemExit):
191
203
  cli.main(["report", "--help"])
192
204
  assert "道具が断った・結果が期待と違った・欲しい形がある" in capsys.readouterr().out
@@ -255,7 +267,7 @@ def test_利用者のtools_listの説明文の末尾に1句(user_server, monkeyp
255
267
  else:
256
268
  assert tool["description"].endswith(HINT), tool["name"]
257
269
  file_tool = next(tool for tool in listed if tool["name"] == "thth_report_file")
258
- assert file_tool["description"].startswith("道具が断った・結果が期待と違った・欲しい形がある、のどれかならこれで置く")
270
+ assert file_tool["description"].startswith("道具が断った・結果が期待と違った・欲しい形がある・迷った、のどれかならこれで置く")
259
271
  # 定数(売り文句の正本)は書き換えない。
260
272
  assert not any(tool["description"].endswith(HINT) for tool in server.TOOLS)
261
273
  # 管理者の一覧には足さない(管理者は報告を置く側ではない)。