thth 3.3.1__tar.gz → 3.4.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 (525) hide show
  1. {thth-3.3.1 → thth-3.4.0}/PKG-INFO +2 -2
  2. {thth-3.3.1 → thth-3.4.0}/README.md +1 -1
  3. {thth-3.3.1 → thth-3.4.0}/mcp/server.py +95 -1
  4. {thth-3.3.1 → thth-3.4.0}/skills/thth/SKILL.md +4 -1
  5. {thth-3.3.1 → thth-3.4.0}/tests/test_admin_http_mcp.py +4 -1
  6. {thth-3.3.1 → thth-3.4.0}/tests/test_site.py +16 -1
  7. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_json_contract.py +5 -1
  8. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_approval_relay.py +4 -1
  9. thth-3.4.0/tests/test_v340_plaza_cli.py +136 -0
  10. thth-3.4.0/tests/test_v340_plaza_docs.py +69 -0
  11. thth-3.4.0/tests/test_v340_plaza_fields.py +109 -0
  12. thth-3.4.0/tests/test_v340_plaza_mcp.py +235 -0
  13. thth-3.4.0/tests/test_v340_plaza_morning.py +205 -0
  14. thth-3.4.0/tests/test_v340_plaza_observe.py +238 -0
  15. thth-3.4.0/tests/test_v340_plaza_open.py +338 -0
  16. thth-3.4.0/tests/test_v340_plaza_open_confirm.py +118 -0
  17. thth-3.4.0/tests/test_v340_plaza_reply.py +181 -0
  18. thth-3.4.0/tests/test_v340_plaza_store.py +205 -0
  19. thth-3.4.0/thth/VERSION +1 -0
  20. {thth-3.3.1 → thth-3.4.0}/thth/account_cli.py +4 -1
  21. {thth-3.3.1 → thth-3.4.0}/thth/admin_log.py +7 -2
  22. {thth-3.3.1 → thth-3.4.0}/thth/admin_report.py +3 -0
  23. {thth-3.3.1 → thth-3.4.0}/thth/cli.py +5 -0
  24. {thth-3.3.1 → thth-3.4.0}/thth/leave.py +13 -2
  25. {thth-3.3.1 → thth-3.4.0}/thth/morning.py +73 -4
  26. thth-3.4.0/thth/plaza.py +1279 -0
  27. thth-3.4.0/thth/plaza_cli.py +437 -0
  28. thth-3.4.0/thth/plaza_observe.py +195 -0
  29. thth-3.4.0/thth/plaza_redact.py +229 -0
  30. thth-3.4.0/thth/private_store.py +271 -0
  31. {thth-3.3.1 → thth-3.4.0}/thth/report_inbox.py +34 -168
  32. {thth-3.3.1 → thth-3.4.0}/thth/report_service.py +159 -0
  33. thth-3.3.1/thth/VERSION +0 -1
  34. {thth-3.3.1 → thth-3.4.0}/.gitignore +0 -0
  35. {thth-3.3.1 → thth-3.4.0}/LICENSE +0 -0
  36. {thth-3.3.1 → thth-3.4.0}/accounts.example/bluesky.json +0 -0
  37. {thth-3.3.1 → thth-3.4.0}/accounts.example/mastodon.json +0 -0
  38. {thth-3.3.1 → thth-3.4.0}/accounts.example/threads.json +0 -0
  39. {thth-3.3.1 → thth-3.4.0}/accounts.example/x.json +0 -0
  40. {thth-3.3.1 → thth-3.4.0}/bin/thth +0 -0
  41. {thth-3.3.1 → thth-3.4.0}/bin/thth-remote +0 -0
  42. {thth-3.3.1 → thth-3.4.0}/bin/thth-run +0 -0
  43. {thth-3.3.1 → thth-3.4.0}/pyproject.toml +0 -0
  44. {thth-3.3.1 → thth-3.4.0}/skills/thth-admin/SKILL.md +0 -0
  45. {thth-3.3.1 → thth-3.4.0}/systemd/thth@.service +0 -0
  46. {thth-3.3.1 → thth-3.4.0}/tests/__init__.py +0 -0
  47. {thth-3.3.1 → thth-3.4.0}/tests/conftest.py +0 -0
  48. {thth-3.3.1 → thth-3.4.0}/tests/coordination_snapshot.py +0 -0
  49. {thth-3.3.1 → thth-3.4.0}/tests/fixtures/GraphemeBreakTest-17.0.0.txt +0 -0
  50. {thth-3.3.1 → thth-3.4.0}/tests/fixtures/build_injection_eval.py +0 -0
  51. {thth-3.3.1 → thth-3.4.0}/tests/fixtures/callback/worker_responses.json +0 -0
  52. {thth-3.3.1 → thth-3.4.0}/tests/fixtures/injection_eval.json +0 -0
  53. {thth-3.3.1 → thth-3.4.0}/tests/fixtures/umami-bile.md +0 -0
  54. {thth-3.3.1 → thth-3.4.0}/tests/helpers/__init__.py +0 -0
  55. {thth-3.3.1 → thth-3.4.0}/tests/helpers/fake_oauth_server.py +0 -0
  56. {thth-3.3.1 → thth-3.4.0}/tests/helpers/fake_push_adapter.py +0 -0
  57. {thth-3.3.1 → thth-3.4.0}/tests/helpers/fake_redirect_server.py +0 -0
  58. {thth-3.3.1 → thth-3.4.0}/tests/helpers/fake_search_adapter.py +0 -0
  59. {thth-3.3.1 → thth-3.4.0}/tests/helpers/fake_threads_reader.py +0 -0
  60. {thth-3.3.1 → thth-3.4.0}/tests/helpers/fake_threads_status.py +0 -0
  61. {thth-3.3.1 → thth-3.4.0}/tests/helpers/fake_threads_writes.py +0 -0
  62. {thth-3.3.1 → thth-3.4.0}/tests/helpers/fake_x.py +0 -0
  63. {thth-3.3.1 → thth-3.4.0}/tests/helpers/hold_lock.py +0 -0
  64. {thth-3.3.1 → thth-3.4.0}/tests/helpers/hold_repo_lock.py +0 -0
  65. {thth-3.3.1 → thth-3.4.0}/tests/helpers/worker_probe.mjs +0 -0
  66. {thth-3.3.1 → thth-3.4.0}/tests/test_27_acceptance_fixes.py +0 -0
  67. {thth-3.3.1 → thth-3.4.0}/tests/test_account_name_check.py +0 -0
  68. {thth-3.3.1 → thth-3.4.0}/tests/test_account_report.py +0 -0
  69. {thth-3.3.1 → thth-3.4.0}/tests/test_accounts_dir.py +0 -0
  70. {thth-3.3.1 → thth-3.4.0}/tests/test_adapter_boundary.py +0 -0
  71. {thth-3.3.1 → thth-3.4.0}/tests/test_admin_log.py +0 -0
  72. {thth-3.3.1 → thth-3.4.0}/tests/test_admin_notifications.py +0 -0
  73. {thth-3.3.1 → thth-3.4.0}/tests/test_admin_repairs.py +0 -0
  74. {thth-3.3.1 → thth-3.4.0}/tests/test_admin_report.py +0 -0
  75. {thth-3.3.1 → thth-3.4.0}/tests/test_after_cli.py +0 -0
  76. {thth-3.3.1 → thth-3.4.0}/tests/test_analytics_comparison.py +0 -0
  77. {thth-3.3.1 → thth-3.4.0}/tests/test_analytics_report.py +0 -0
  78. {thth-3.3.1 → thth-3.4.0}/tests/test_api_diagnostic.py +0 -0
  79. {thth-3.3.1 → thth-3.4.0}/tests/test_api_error_body.py +0 -0
  80. {thth-3.3.1 → thth-3.4.0}/tests/test_app_env_cli.py +0 -0
  81. {thth-3.3.1 → thth-3.4.0}/tests/test_approval.py +0 -0
  82. {thth-3.3.1 → thth-3.4.0}/tests/test_approve_cmd.py +0 -0
  83. {thth-3.3.1 → thth-3.4.0}/tests/test_ask.py +0 -0
  84. {thth-3.3.1 → thth-3.4.0}/tests/test_atlas_fifth_review.py +0 -0
  85. {thth-3.3.1 → thth-3.4.0}/tests/test_atlas_fourth_review.py +0 -0
  86. {thth-3.3.1 → thth-3.4.0}/tests/test_atlas_review.py +0 -0
  87. {thth-3.3.1 → thth-3.4.0}/tests/test_atlas_second_review.py +0 -0
  88. {thth-3.3.1 → thth-3.4.0}/tests/test_atlas_third_review.py +0 -0
  89. {thth-3.3.1 → thth-3.4.0}/tests/test_audit1_fixes.py +0 -0
  90. {thth-3.3.1 → thth-3.4.0}/tests/test_audit2_fixes.py +0 -0
  91. {thth-3.3.1 → thth-3.4.0}/tests/test_audit_b_counterexamples.py +0 -0
  92. {thth-3.3.1 → thth-3.4.0}/tests/test_audit_b_m1_semantics.py +0 -0
  93. {thth-3.3.1 → thth-3.4.0}/tests/test_audit_b_n1_depth.py +0 -0
  94. {thth-3.3.1 → thth-3.4.0}/tests/test_audit_b_r1_closure.py +0 -0
  95. {thth-3.3.1 → thth-3.4.0}/tests/test_audit_v201.py +0 -0
  96. {thth-3.3.1 → thth-3.4.0}/tests/test_audit_v23_details.py +0 -0
  97. {thth-3.3.1 → thth-3.4.0}/tests/test_auth.py +0 -0
  98. {thth-3.3.1 → thth-3.4.0}/tests/test_auth_identity_required.py +0 -0
  99. {thth-3.3.1 → thth-3.4.0}/tests/test_behind_remote.py +0 -0
  100. {thth-3.3.1 → thth-3.4.0}/tests/test_bluesky_adapter.py +0 -0
  101. {thth-3.3.1 → thth-3.4.0}/tests/test_bluesky_secret_scrub.py +0 -0
  102. {thth-3.3.1 → thth-3.4.0}/tests/test_board_needs_review.py +0 -0
  103. {thth-3.3.1 → thth-3.4.0}/tests/test_board_running.py +0 -0
  104. {thth-3.3.1 → thth-3.4.0}/tests/test_bundle.py +0 -0
  105. {thth-3.3.1 → thth-3.4.0}/tests/test_charcount.py +0 -0
  106. {thth-3.3.1 → thth-3.4.0}/tests/test_cli_entrypoint.py +0 -0
  107. {thth-3.3.1 → thth-3.4.0}/tests/test_cli_help.py +0 -0
  108. {thth-3.3.1 → thth-3.4.0}/tests/test_cli_vm_path.py +0 -0
  109. {thth-3.3.1 → thth-3.4.0}/tests/test_collect.py +0 -0
  110. {thth-3.3.1 → thth-3.4.0}/tests/test_collect_engagements.py +0 -0
  111. {thth-3.3.1 → thth-3.4.0}/tests/test_collect_runs.py +0 -0
  112. {thth-3.3.1 → thth-3.4.0}/tests/test_collect_sent.py +0 -0
  113. {thth-3.3.1 → thth-3.4.0}/tests/test_comparability.py +0 -0
  114. {thth-3.3.1 → thth-3.4.0}/tests/test_control_chars_refused.py +0 -0
  115. {thth-3.3.1 → thth-3.4.0}/tests/test_conversation_paging.py +0 -0
  116. {thth-3.3.1 → thth-3.4.0}/tests/test_count_for.py +0 -0
  117. {thth-3.3.1 → thth-3.4.0}/tests/test_docs_links.py +0 -0
  118. {thth-3.3.1 → thth-3.4.0}/tests/test_doctor.py +0 -0
  119. {thth-3.3.1 → thth-3.4.0}/tests/test_doctor_all_permissions.py +0 -0
  120. {thth-3.3.1 → thth-3.4.0}/tests/test_doctor_next_step.py +0 -0
  121. {thth-3.3.1 → thth-3.4.0}/tests/test_duplicate_front_matter_keys.py +0 -0
  122. {thth-3.3.1 → thth-3.4.0}/tests/test_editorial_audit.py +0 -0
  123. {thth-3.3.1 → thth-3.4.0}/tests/test_editorial_n3_n4.py +0 -0
  124. {thth-3.3.1 → thth-3.4.0}/tests/test_engagements.py +0 -0
  125. {thth-3.3.1 → thth-3.4.0}/tests/test_external_acceptance.py +0 -0
  126. {thth-3.3.1 → thth-3.4.0}/tests/test_fake_api.py +0 -0
  127. {thth-3.3.1 → thth-3.4.0}/tests/test_form_spec.py +0 -0
  128. {thth-3.3.1 → thth-3.4.0}/tests/test_form_spec_cli.py +0 -0
  129. {thth-3.3.1 → thth-3.4.0}/tests/test_forms.py +0 -0
  130. {thth-3.3.1 → thth-3.4.0}/tests/test_fresh_install.py +0 -0
  131. {thth-3.3.1 → thth-3.4.0}/tests/test_front_matter_injection.py +0 -0
  132. {thth-3.3.1 → thth-3.4.0}/tests/test_healthcheck_notifications.py +0 -0
  133. {thth-3.3.1 → thth-3.4.0}/tests/test_hypotheses.py +0 -0
  134. {thth-3.3.1 → thth-3.4.0}/tests/test_hypothesis_boundaries.py +0 -0
  135. {thth-3.3.1 → thth-3.4.0}/tests/test_hypothesis_units.py +0 -0
  136. {thth-3.3.1 → thth-3.4.0}/tests/test_impact_cli.py +0 -0
  137. {thth-3.3.1 → thth-3.4.0}/tests/test_inbox_granted_scopes.py +0 -0
  138. {thth-3.3.1 → thth-3.4.0}/tests/test_incident_notifications.py +0 -0
  139. {thth-3.3.1 → thth-3.4.0}/tests/test_independent_thread_review.py +0 -0
  140. {thth-3.3.1 → thth-3.4.0}/tests/test_independent_topic_review.py +0 -0
  141. {thth-3.3.1 → thth-3.4.0}/tests/test_independent_topic_review_r2.py +0 -0
  142. {thth-3.3.1 → thth-3.4.0}/tests/test_inflight.py +0 -0
  143. {thth-3.3.1 → thth-3.4.0}/tests/test_inflight_tag_body_hash.py +0 -0
  144. {thth-3.3.1 → thth-3.4.0}/tests/test_jst.py +0 -0
  145. {thth-3.3.1 → thth-3.4.0}/tests/test_lint.py +0 -0
  146. {thth-3.3.1 → thth-3.4.0}/tests/test_list_projections.py +0 -0
  147. {thth-3.3.1 → thth-3.4.0}/tests/test_llm_trial.py +0 -0
  148. {thth-3.3.1 → thth-3.4.0}/tests/test_llm_trial_engage.py +0 -0
  149. {thth-3.3.1 → thth-3.4.0}/tests/test_lock.py +0 -0
  150. {thth-3.3.1 → thth-3.4.0}/tests/test_m2_independent.py +0 -0
  151. {thth-3.3.1 → thth-3.4.0}/tests/test_maintain.py +0 -0
  152. {thth-3.3.1 → thth-3.4.0}/tests/test_mastodon_adapter.py +0 -0
  153. {thth-3.3.1 → thth-3.4.0}/tests/test_mastodon_visibility.py +0 -0
  154. {thth-3.3.1 → thth-3.4.0}/tests/test_max_per_run.py +0 -0
  155. {thth-3.3.1 → thth-3.4.0}/tests/test_mcp.py +0 -0
  156. {thth-3.3.1 → thth-3.4.0}/tests/test_mcp_robustness.py +0 -0
  157. {thth-3.3.1 → thth-3.4.0}/tests/test_measured.py +0 -0
  158. {thth-3.3.1 → thth-3.4.0}/tests/test_media_accounts.py +0 -0
  159. {thth-3.3.1 → thth-3.4.0}/tests/test_media_next_steps.py +0 -0
  160. {thth-3.3.1 → thth-3.4.0}/tests/test_media_roundtrip.py +0 -0
  161. {thth-3.3.1 → thth-3.4.0}/tests/test_mismatch_diagnostics.py +0 -0
  162. {thth-3.3.1 → thth-3.4.0}/tests/test_n5_reply_null_element.py +0 -0
  163. {thth-3.3.1 → thth-3.4.0}/tests/test_n6_replies_incomplete_ledger.py +0 -0
  164. {thth-3.3.1 → thth-3.4.0}/tests/test_n7_doctor_error_body.py +0 -0
  165. {thth-3.3.1 → thth-3.4.0}/tests/test_no_limits.py +0 -0
  166. {thth-3.3.1 → thth-3.4.0}/tests/test_observation_provenance.py +0 -0
  167. {thth-3.3.1 → thth-3.4.0}/tests/test_operations_handoff.py +0 -0
  168. {thth-3.3.1 → thth-3.4.0}/tests/test_packaging.py +0 -0
  169. {thth-3.3.1 → thth-3.4.0}/tests/test_preview.py +0 -0
  170. {thth-3.3.1 → thth-3.4.0}/tests/test_production_mode.py +0 -0
  171. {thth-3.3.1 → thth-3.4.0}/tests/test_pull_cli.py +0 -0
  172. {thth-3.3.1 → thth-3.4.0}/tests/test_queue.py +0 -0
  173. {thth-3.3.1 → thth-3.4.0}/tests/test_quiet_hours.py +0 -0
  174. {thth-3.3.1 → thth-3.4.0}/tests/test_r2_r4_r5.py +0 -0
  175. {thth-3.3.1 → thth-3.4.0}/tests/test_read_cli_bare_runtimeerror.py +0 -0
  176. {thth-3.3.1 → thth-3.4.0}/tests/test_reason_adoption.py +0 -0
  177. {thth-3.3.1 → thth-3.4.0}/tests/test_redact.py +0 -0
  178. {thth-3.3.1 → thth-3.4.0}/tests/test_redirect_token_leak.py +0 -0
  179. {thth-3.3.1 → thth-3.4.0}/tests/test_refresh.py +0 -0
  180. {thth-3.3.1 → thth-3.4.0}/tests/test_refresh_destination.py +0 -0
  181. {thth-3.3.1 → thth-3.4.0}/tests/test_rejudge_new.py +0 -0
  182. {thth-3.3.1 → thth-3.4.0}/tests/test_rejudge_r2_r3.py +0 -0
  183. {thth-3.3.1 → thth-3.4.0}/tests/test_replies.py +0 -0
  184. {thth-3.3.1 → thth-3.4.0}/tests/test_replies_refresh.py +0 -0
  185. {thth-3.3.1 → thth-3.4.0}/tests/test_repo_lock.py +0 -0
  186. {thth-3.3.1 → thth-3.4.0}/tests/test_repo_sync.py +0 -0
  187. {thth-3.3.1 → thth-3.4.0}/tests/test_report_checkout_import.py +0 -0
  188. {thth-3.3.1 → thth-3.4.0}/tests/test_report_http.py +0 -0
  189. {thth-3.3.1 → thth-3.4.0}/tests/test_report_isolation.py +0 -0
  190. {thth-3.3.1 → thth-3.4.0}/tests/test_report_service.py +0 -0
  191. {thth-3.3.1 → thth-3.4.0}/tests/test_reverse_audit.py +0 -0
  192. {thth-3.3.1 → thth-3.4.0}/tests/test_review_case_id_cluster_m2.py +0 -0
  193. {thth-3.3.1 → thth-3.4.0}/tests/test_review_case_id_cluster_r2.py +0 -0
  194. {thth-3.3.1 → thth-3.4.0}/tests/test_review_cli.py +0 -0
  195. {thth-3.3.1 → thth-3.4.0}/tests/test_review_evidence_account.py +0 -0
  196. {thth-3.3.1 → thth-3.4.0}/tests/test_review_form_spec_and_case_id.py +0 -0
  197. {thth-3.3.1 → thth-3.4.0}/tests/test_review_provenance.py +0 -0
  198. {thth-3.3.1 → thth-3.4.0}/tests/test_review_records.py +0 -0
  199. {thth-3.3.1 → thth-3.4.0}/tests/test_revoke.py +0 -0
  200. {thth-3.3.1 → thth-3.4.0}/tests/test_roundtrip.py +0 -0
  201. {thth-3.3.1 → thth-3.4.0}/tests/test_run_missing_env.py +0 -0
  202. {thth-3.3.1 → thth-3.4.0}/tests/test_run_record_failclosed.py +0 -0
  203. {thth-3.3.1 → thth-3.4.0}/tests/test_runs_collected.py +0 -0
  204. {thth-3.3.1 → thth-3.4.0}/tests/test_runs_read_rows.py +0 -0
  205. {thth-3.3.1 → thth-3.4.0}/tests/test_schedule_multiple.py +0 -0
  206. {thth-3.3.1 → thth-3.4.0}/tests/test_schedule_view.py +0 -0
  207. {thth-3.3.1 → thth-3.4.0}/tests/test_scopes_source_requested.py +0 -0
  208. {thth-3.3.1 → thth-3.4.0}/tests/test_search_engage_targets.py +0 -0
  209. {thth-3.3.1 → thth-3.4.0}/tests/test_search_media_routes.py +0 -0
  210. {thth-3.3.1 → thth-3.4.0}/tests/test_secret_value_redact.py +0 -0
  211. {thth-3.3.1 → thth-3.4.0}/tests/test_secrets_grep.py +0 -0
  212. {thth-3.3.1 → thth-3.4.0}/tests/test_security_small_fixes.py +0 -0
  213. {thth-3.3.1 → thth-3.4.0}/tests/test_select.py +0 -0
  214. {thth-3.3.1 → thth-3.4.0}/tests/test_selfupdate.py +0 -0
  215. {thth-3.3.1 → thth-3.4.0}/tests/test_send.py +0 -0
  216. {thth-3.3.1 → thth-3.4.0}/tests/test_send_cli.py +0 -0
  217. {thth-3.3.1 → thth-3.4.0}/tests/test_send_control_chars.py +0 -0
  218. {thth-3.3.1 → thth-3.4.0}/tests/test_send_media.py +0 -0
  219. {thth-3.3.1 → thth-3.4.0}/tests/test_send_visibility.py +0 -0
  220. {thth-3.3.1 → thth-3.4.0}/tests/test_sent_integrity.py +0 -0
  221. {thth-3.3.1 → thth-3.4.0}/tests/test_server_json.py +0 -0
  222. {thth-3.3.1 → thth-3.4.0}/tests/test_skill_doc.py +0 -0
  223. {thth-3.3.1 → thth-3.4.0}/tests/test_study_report.py +0 -0
  224. {thth-3.3.1 → thth-3.4.0}/tests/test_systemd.py +0 -0
  225. {thth-3.3.1 → thth-3.4.0}/tests/test_thread_publish.py +0 -0
  226. {thth-3.3.1 → thth-3.4.0}/tests/test_thread_read.py +0 -0
  227. {thth-3.3.1 → thth-3.4.0}/tests/test_threadrun.py +0 -0
  228. {thth-3.3.1 → thth-3.4.0}/tests/test_threads_read_permissions.py +0 -0
  229. {thth-3.3.1 → thth-3.4.0}/tests/test_threadshape.py +0 -0
  230. {thth-3.3.1 → thth-3.4.0}/tests/test_thth_root.py +0 -0
  231. {thth-3.3.1 → thth-3.4.0}/tests/test_token_exists.py +0 -0
  232. {thth-3.3.1 → thth-3.4.0}/tests/test_token_scopes_record.py +0 -0
  233. {thth-3.3.1 → thth-3.4.0}/tests/test_token_set.py +0 -0
  234. {thth-3.3.1 → thth-3.4.0}/tests/test_topic.py +0 -0
  235. {thth-3.3.1 → thth-3.4.0}/tests/test_topic_advice.py +0 -0
  236. {thth-3.3.1 → thth-3.4.0}/tests/test_topic_cli.py +0 -0
  237. {thth-3.3.1 → thth-3.4.0}/tests/test_topic_models.py +0 -0
  238. {thth-3.3.1 → thth-3.4.0}/tests/test_topic_review_fixes.py +0 -0
  239. {thth-3.3.1 → thth-3.4.0}/tests/test_topic_shelf_observers.py +0 -0
  240. {thth-3.3.1 → thth-3.4.0}/tests/test_topic_store.py +0 -0
  241. {thth-3.3.1 → thth-3.4.0}/tests/test_topics_record_concurrent.py +0 -0
  242. {thth-3.3.1 → thth-3.4.0}/tests/test_topics_tool.py +0 -0
  243. {thth-3.3.1 → thth-3.4.0}/tests/test_trial_frictions.py +0 -0
  244. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_admin_transaction.py +0 -0
  245. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_approve_account.py +0 -0
  246. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_length.py +0 -0
  247. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_lock_wait.py +0 -0
  248. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_mastodon_scopes.py +0 -0
  249. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_mentions.py +0 -0
  250. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_post_urls.py +0 -0
  251. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_repo_paths.py +0 -0
  252. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_reposts.py +0 -0
  253. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_study_add.py +0 -0
  254. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_tool_version.py +0 -0
  255. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_unanswered.py +0 -0
  256. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_unanswered_scope.py +0 -0
  257. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_where_arguments.py +0 -0
  258. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_where_filters.py +0 -0
  259. {thth-3.3.1 → thth-3.4.0}/tests/test_v210_who_arguments.py +0 -0
  260. {thth-3.3.1 → thth-3.4.0}/tests/test_v211_auth_integration.py +0 -0
  261. {thth-3.3.1 → thth-3.4.0}/tests/test_v211_authflow.py +0 -0
  262. {thth-3.3.1 → thth-3.4.0}/tests/test_v211_bluesky_stdin.py +0 -0
  263. {thth-3.3.1 → thth-3.4.0}/tests/test_v211_doctor_binding.py +0 -0
  264. {thth-3.3.1 → thth-3.4.0}/tests/test_v211_mastodon_auth.py +0 -0
  265. {thth-3.3.1 → thth-3.4.0}/tests/test_v211_x_auth.py +0 -0
  266. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_ask_coordination.py +0 -0
  267. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_budget.py +0 -0
  268. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_budget_auth.py +0 -0
  269. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_budget_mcp.py +0 -0
  270. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_deletion.py +0 -0
  271. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_job_completion_fault.py +0 -0
  272. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_leave.py +0 -0
  273. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_leave_gate.py +0 -0
  274. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_leave_providers.py +0 -0
  275. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_leave_token_safety.py +0 -0
  276. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_mastodon_guide.py +0 -0
  277. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_mcp_compat.py +0 -0
  278. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_missing_token.py +0 -0
  279. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_read_coordination.py +0 -0
  280. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_server_writes.py +0 -0
  281. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_token_parent.py +0 -0
  282. {thth-3.3.1 → thth-3.4.0}/tests/test_v212_write_storage.py +0 -0
  283. {thth-3.3.1 → thth-3.4.0}/tests/test_v2131_send_dry_run.py +0 -0
  284. {thth-3.3.1 → thth-3.4.0}/tests/test_v2131_threads_publish_wait.py +0 -0
  285. {thth-3.3.1 → thth-3.4.0}/tests/test_v2132_send_inflight_hint.py +0 -0
  286. {thth-3.3.1 → thth-3.4.0}/tests/test_v2132_threads_create_timeout.py +0 -0
  287. {thth-3.3.1 → thth-3.4.0}/tests/test_v2132_threads_create_unknown.py +0 -0
  288. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_acceptance_fixes.py +0 -0
  289. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_adts_asf.py +0 -0
  290. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_approval_attachments.py +0 -0
  291. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_attachment_kinds.py +0 -0
  292. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bluesky_external.py +0 -0
  293. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bluesky_media.py +0 -0
  294. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bluesky_metadata.py +0 -0
  295. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bluesky_metadata_uri.py +0 -0
  296. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bluesky_metadata_video.py +0 -0
  297. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bluesky_record.py +0 -0
  298. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bluesky_record_video.py +0 -0
  299. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bluesky_video.py +0 -0
  300. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bluesky_webm_named.py +0 -0
  301. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_bmff_typed_metadata.py +0 -0
  302. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_budget_lock.py +0 -0
  303. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_capabilities.py +0 -0
  304. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_carryover_timeout.py +0 -0
  305. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_graphemes.py +0 -0
  306. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_auth_media.py +0 -0
  307. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_bmff_audio.py +0 -0
  308. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_flac.py +0 -0
  309. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_gif.py +0 -0
  310. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_link.py +0 -0
  311. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_media.py +0 -0
  312. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_mp3.py +0 -0
  313. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_ogg_opus.py +0 -0
  314. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_ogg_vorbis.py +0 -0
  315. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_poll.py +0 -0
  316. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_primary_size_boundary.py +0 -0
  317. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_quote.py +0 -0
  318. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_thumbnail.py +0 -0
  319. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_wav.py +0 -0
  320. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_webm_audio.py +0 -0
  321. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_mastodon_webm_video.py +0 -0
  322. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_media_cleanup.py +0 -0
  323. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_media_formats.py +0 -0
  324. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_media_foundation.py +0 -0
  325. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_media_prepared.py +0 -0
  326. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_media_relay.py +0 -0
  327. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_media_relay_refusals.py +0 -0
  328. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_media_upload.py +0 -0
  329. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_receipt_deadline.py +0 -0
  330. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_review_followups.py +0 -0
  331. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_stage810_audit.py +0 -0
  332. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_static_diagnostics.py +0 -0
  333. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_stop_observation.py +0 -0
  334. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_systemd_worker.py +0 -0
  335. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_ghost.py +0 -0
  336. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_gif.py +0 -0
  337. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_link.py +0 -0
  338. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_media.py +0 -0
  339. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_media_spoiler.py +0 -0
  340. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_poll.py +0 -0
  341. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_quote.py +0 -0
  342. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_quote_media.py +0 -0
  343. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_replies.py +0 -0
  344. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_text.py +0 -0
  345. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_text_spoiler.py +0 -0
  346. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_threads_video.py +0 -0
  347. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_transport_review.py +0 -0
  348. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_video_typed_metadata.py +0 -0
  349. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_vorbis_comment_boundary.py +0 -0
  350. {thth-3.3.1 → thth-3.4.0}/tests/test_v213_webm_schema_boundary.py +0 -0
  351. {thth-3.3.1 → thth-3.4.0}/tests/test_v2141_draft_lint_reason.py +0 -0
  352. {thth-3.3.1 → thth-3.4.0}/tests/test_v2141_managed_repo_umask.py +0 -0
  353. {thth-3.3.1 → thth-3.4.0}/tests/test_v214_release_signing.py +0 -0
  354. {thth-3.3.1 → thth-3.4.0}/tests/test_v214_x_adapter.py +0 -0
  355. {thth-3.3.1 → thth-3.4.0}/tests/test_v214_x_auth.py +0 -0
  356. {thth-3.3.1 → thth-3.4.0}/tests/test_v214_x_media.py +0 -0
  357. {thth-3.3.1 → thth-3.4.0}/tests/test_v214_x_posts_budget.py +0 -0
  358. {thth-3.3.1 → thth-3.4.0}/tests/test_v21b_approved_writes.py +0 -0
  359. {thth-3.3.1 → thth-3.4.0}/tests/test_v28_tags.py +0 -0
  360. {thth-3.3.1 → thth-3.4.0}/tests/test_v310_morning.py +0 -0
  361. {thth-3.3.1 → thth-3.4.0}/tests/test_v310_search_timeout.py +0 -0
  362. {thth-3.3.1 → thth-3.4.0}/tests/test_v310_watch_words.py +0 -0
  363. {thth-3.3.1 → thth-3.4.0}/tests/test_v311_leave_same_user.py +0 -0
  364. {thth-3.3.1 → thth-3.4.0}/tests/test_v311_mastodon_retract.py +0 -0
  365. {thth-3.3.1 → thth-3.4.0}/tests/test_v311_morning_overdue.py +0 -0
  366. {thth-3.3.1 → thth-3.4.0}/tests/test_v311_morning_own_exclusion.py +0 -0
  367. {thth-3.3.1 → thth-3.4.0}/tests/test_v311_morning_unanswered_window.py +0 -0
  368. {thth-3.3.1 → thth-3.4.0}/tests/test_v311_notifications_guidance.py +0 -0
  369. {thth-3.3.1 → thth-3.4.0}/tests/test_v311_replies_owned.py +0 -0
  370. {thth-3.3.1 → thth-3.4.0}/tests/test_v312_leave_shared_token.py +0 -0
  371. {thth-3.3.1 → thth-3.4.0}/tests/test_v312_own_handles_stopped.py +0 -0
  372. {thth-3.3.1 → thth-3.4.0}/tests/test_v312_report_admin.py +0 -0
  373. {thth-3.3.1 → thth-3.4.0}/tests/test_v312_report_channel.py +0 -0
  374. {thth-3.3.1 → thth-3.4.0}/tests/test_v312_report_inbox.py +0 -0
  375. {thth-3.3.1 → thth-3.4.0}/tests/test_v312_report_mcp_user.py +0 -0
  376. {thth-3.3.1 → thth-3.4.0}/tests/test_v31_analysis.py +0 -0
  377. {thth-3.3.1 → thth-3.4.0}/tests/test_v31_collection.py +0 -0
  378. {thth-3.3.1 → thth-3.4.0}/tests/test_v320_reply_to_file_display.py +0 -0
  379. {thth-3.3.1 → thth-3.4.0}/tests/test_v320_reply_to_file_lint.py +0 -0
  380. {thth-3.3.1 → thth-3.4.0}/tests/test_v320_reply_to_file_resolve.py +0 -0
  381. {thth-3.3.1 → thth-3.4.0}/tests/test_v320_report_fixes.py +0 -0
  382. {thth-3.3.1 → thth-3.4.0}/tests/test_v330_fingerprint_version.py +0 -0
  383. {thth-3.3.1 → thth-3.4.0}/tests/test_v330_held.py +0 -0
  384. {thth-3.3.1 → thth-3.4.0}/tests/test_v330_held_names.py +0 -0
  385. {thth-3.3.1 → thth-3.4.0}/tests/test_v330_last_refusals.py +0 -0
  386. {thth-3.3.1 → thth-3.4.0}/tests/test_v330_notification_route.py +0 -0
  387. {thth-3.3.1 → thth-3.4.0}/tests/test_v330_observe.py +0 -0
  388. {thth-3.3.1 → thth-3.4.0}/tests/test_v330_report_friction.py +0 -0
  389. {thth-3.3.1 → thth-3.4.0}/tests/test_v330_report_words.py +0 -0
  390. {thth-3.3.1 → thth-3.4.0}/tests/test_v330_reports_effect.py +0 -0
  391. {thth-3.3.1 → thth-3.4.0}/tests/test_v331_inflight_cli.py +0 -0
  392. {thth-3.3.1 → thth-3.4.0}/tests/test_v331_listing_resolve.py +0 -0
  393. {thth-3.3.1 → thth-3.4.0}/tests/test_v331_outbox_retention.py +0 -0
  394. {thth-3.3.1 → thth-3.4.0}/tests/test_v331_publish_status.py +0 -0
  395. {thth-3.3.1 → thth-3.4.0}/tests/test_v331_self_resolve.py +0 -0
  396. {thth-3.3.1 → thth-3.4.0}/tests/test_value_domains_shown.py +0 -0
  397. {thth-3.3.1 → thth-3.4.0}/tests/test_version.py +0 -0
  398. {thth-3.3.1 → thth-3.4.0}/tests/test_where_cli.py +0 -0
  399. {thth-3.3.1 → thth-3.4.0}/tests/test_who_cli.py +0 -0
  400. {thth-3.3.1 → thth-3.4.0}/tests/test_writeback_rebase.py +0 -0
  401. {thth-3.3.1 → thth-3.4.0}/tests/test_writeback_validate.py +0 -0
  402. {thth-3.3.1 → thth-3.4.0}/tests/test_x_metrics.py +0 -0
  403. {thth-3.3.1 → thth-3.4.0}/thth/__init__.py +0 -0
  404. {thth-3.3.1 → thth-3.4.0}/thth/__main__.py +0 -0
  405. {thth-3.3.1 → thth-3.4.0}/thth/_grapheme_data.py +0 -0
  406. {thth-3.3.1 → thth-3.4.0}/thth/account_report.py +0 -0
  407. {thth-3.3.1 → thth-3.4.0}/thth/accounts.py +0 -0
  408. {thth-3.3.1 → thth-3.4.0}/thth/adapters/__init__.py +0 -0
  409. {thth-3.3.1 → thth-3.4.0}/thth/adapters/auth_mastodon.py +0 -0
  410. {thth-3.3.1 → thth-3.4.0}/thth/adapters/auth_threads.py +0 -0
  411. {thth-3.3.1 → thth-3.4.0}/thth/adapters/auth_x.py +0 -0
  412. {thth-3.3.1 → thth-3.4.0}/thth/adapters/base.py +0 -0
  413. {thth-3.3.1 → thth-3.4.0}/thth/adapters/bluesky.py +0 -0
  414. {thth-3.3.1 → thth-3.4.0}/thth/adapters/bluesky_media.py +0 -0
  415. {thth-3.3.1 → thth-3.4.0}/thth/adapters/bluesky_video.py +0 -0
  416. {thth-3.3.1 → thth-3.4.0}/thth/adapters/mastodon.py +0 -0
  417. {thth-3.3.1 → thth-3.4.0}/thth/adapters/mastodon_media.py +0 -0
  418. {thth-3.3.1 → thth-3.4.0}/thth/adapters/threads.py +0 -0
  419. {thth-3.3.1 → thth-3.4.0}/thth/adapters/threads_media.py +0 -0
  420. {thth-3.3.1 → thth-3.4.0}/thth/adapters/x.py +0 -0
  421. {thth-3.3.1 → thth-3.4.0}/thth/adapters/x_media.py +0 -0
  422. {thth-3.3.1 → thth-3.4.0}/thth/admin_notifications.py +0 -0
  423. {thth-3.3.1 → thth-3.4.0}/thth/after_cli.py +0 -0
  424. {thth-3.3.1 → thth-3.4.0}/thth/analytics_comparison.py +0 -0
  425. {thth-3.3.1 → thth-3.4.0}/thth/analytics_report.py +0 -0
  426. {thth-3.3.1 → thth-3.4.0}/thth/analytics_shapes.py +0 -0
  427. {thth-3.3.1 → thth-3.4.0}/thth/analytics_threads.py +0 -0
  428. {thth-3.3.1 → thth-3.4.0}/thth/api_diagnostic.py +0 -0
  429. {thth-3.3.1 → thth-3.4.0}/thth/appconfig.py +0 -0
  430. {thth-3.3.1 → thth-3.4.0}/thth/appenv.py +0 -0
  431. {thth-3.3.1 → thth-3.4.0}/thth/approval.py +0 -0
  432. {thth-3.3.1 → thth-3.4.0}/thth/approval_jobs.py +0 -0
  433. {thth-3.3.1 → thth-3.4.0}/thth/approval_relay.py +0 -0
  434. {thth-3.3.1 → thth-3.4.0}/thth/ask.py +0 -0
  435. {thth-3.3.1 → thth-3.4.0}/thth/ask_cli.py +0 -0
  436. {thth-3.3.1 → thth-3.4.0}/thth/audioformats.py +0 -0
  437. {thth-3.3.1 → thth-3.4.0}/thth/authclients.py +0 -0
  438. {thth-3.3.1 → thth-3.4.0}/thth/authflow.py +0 -0
  439. {thth-3.3.1 → thth-3.4.0}/thth/authpassword.py +0 -0
  440. {thth-3.3.1 → thth-3.4.0}/thth/bluesky_metadata.py +0 -0
  441. {thth-3.3.1 → thth-3.4.0}/thth/budget_x.py +0 -0
  442. {thth-3.3.1 → thth-3.4.0}/thth/budget_x_posts.py +0 -0
  443. {thth-3.3.1 → thth-3.4.0}/thth/bundle.py +0 -0
  444. {thth-3.3.1 → thth-3.4.0}/thth/collect.py +0 -0
  445. {thth-3.3.1 → thth-3.4.0}/thth/collection_context.py +0 -0
  446. {thth-3.3.1 → thth-3.4.0}/thth/collection_status.py +0 -0
  447. {thth-3.3.1 → thth-3.4.0}/thth/core.py +0 -0
  448. {thth-3.3.1 → thth-3.4.0}/thth/deletion.py +0 -0
  449. {thth-3.3.1 → thth-3.4.0}/thth/doctor.py +0 -0
  450. {thth-3.3.1 → thth-3.4.0}/thth/ebmlaudio.py +0 -0
  451. {thth-3.3.1 → thth-3.4.0}/thth/ebmlvideo.py +0 -0
  452. {thth-3.3.1 → thth-3.4.0}/thth/engagements.py +0 -0
  453. {thth-3.3.1 → thth-3.4.0}/thth/fingerprint_golden.py +0 -0
  454. {thth-3.3.1 → thth-3.4.0}/thth/flacformats.py +0 -0
  455. {thth-3.3.1 → thth-3.4.0}/thth/forms.py +0 -0
  456. {thth-3.3.1 → thth-3.4.0}/thth/graphemes.py +0 -0
  457. {thth-3.3.1 → thth-3.4.0}/thth/handoff_cursor.py +0 -0
  458. {thth-3.3.1 → thth-3.4.0}/thth/healthcheck.py +0 -0
  459. {thth-3.3.1 → thth-3.4.0}/thth/httpsafe.py +0 -0
  460. {thth-3.3.1 → thth-3.4.0}/thth/incident.py +0 -0
  461. {thth-3.3.1 → thth-3.4.0}/thth/inflight.py +0 -0
  462. {thth-3.3.1 → thth-3.4.0}/thth/inflight_cli.py +0 -0
  463. {thth-3.3.1 → thth-3.4.0}/thth/inflight_resolve.py +0 -0
  464. {thth-3.3.1 → thth-3.4.0}/thth/jst.py +0 -0
  465. {thth-3.3.1 → thth-3.4.0}/thth/leave_gate.py +0 -0
  466. {thth-3.3.1 → thth-3.4.0}/thth/lint.py +0 -0
  467. {thth-3.3.1 → thth-3.4.0}/thth/lock.py +0 -0
  468. {thth-3.3.1 → thth-3.4.0}/thth/maintain.py +0 -0
  469. {thth-3.3.1 → thth-3.4.0}/thth/managed_repo.py +0 -0
  470. {thth-3.3.1 → thth-3.4.0}/thth/measured.py +0 -0
  471. {thth-3.3.1 → thth-3.4.0}/thth/media.py +0 -0
  472. {thth-3.3.1 → thth-3.4.0}/thth/media_capabilities.py +0 -0
  473. {thth-3.3.1 → thth-3.4.0}/thth/media_cleanup.py +0 -0
  474. {thth-3.3.1 → thth-3.4.0}/thth/media_delivery.py +0 -0
  475. {thth-3.3.1 → thth-3.4.0}/thth/media_relay.py +0 -0
  476. {thth-3.3.1 → thth-3.4.0}/thth/media_uploads.py +0 -0
  477. {thth-3.3.1 → thth-3.4.0}/thth/mediaformats.py +0 -0
  478. {thth-3.3.1 → thth-3.4.0}/thth/mpeg_audio.py +0 -0
  479. {thth-3.3.1 → thth-3.4.0}/thth/notification_route.py +0 -0
  480. {thth-3.3.1 → thth-3.4.0}/thth/oauth.py +0 -0
  481. {thth-3.3.1 → thth-3.4.0}/thth/oggformats.py +0 -0
  482. {thth-3.3.1 → thth-3.4.0}/thth/operations_handoff.py +0 -0
  483. {thth-3.3.1 → thth-3.4.0}/thth/postid.py +0 -0
  484. {thth-3.3.1 → thth-3.4.0}/thth/queuefile.py +0 -0
  485. {thth-3.3.1 → thth-3.4.0}/thth/read_coordination.py +0 -0
  486. {thth-3.3.1 → thth-3.4.0}/thth/read_window.py +0 -0
  487. {thth-3.3.1 → thth-3.4.0}/thth/redact.py +0 -0
  488. {thth-3.3.1 → thth-3.4.0}/thth/refusals.py +0 -0
  489. {thth-3.3.1 → thth-3.4.0}/thth/replies.py +0 -0
  490. {thth-3.3.1 → thth-3.4.0}/thth/report.py +0 -0
  491. {thth-3.3.1 → thth-3.4.0}/thth/report_details.py +0 -0
  492. {thth-3.3.1 → thth-3.4.0}/thth/report_http.py +0 -0
  493. {thth-3.3.1 → thth-3.4.0}/thth/report_isolation.py +0 -0
  494. {thth-3.3.1 → thth-3.4.0}/thth/retract_cli.py +0 -0
  495. {thth-3.3.1 → thth-3.4.0}/thth/runs.py +0 -0
  496. {thth-3.3.1 → thth-3.4.0}/thth/scopes.py +0 -0
  497. {thth-3.3.1 → thth-3.4.0}/thth/secrets_fs.py +0 -0
  498. {thth-3.3.1 → thth-3.4.0}/thth/select.py +0 -0
  499. {thth-3.3.1 → thth-3.4.0}/thth/selfupdate.py +0 -0
  500. {thth-3.3.1 → thth-3.4.0}/thth/sent.py +0 -0
  501. {thth-3.3.1 → thth-3.4.0}/thth/server_files.py +0 -0
  502. {thth-3.3.1 → thth-3.4.0}/thth/server_writes.py +0 -0
  503. {thth-3.3.1 → thth-3.4.0}/thth/stop_observation.py +0 -0
  504. {thth-3.3.1 → thth-3.4.0}/thth/study_cli.py +0 -0
  505. {thth-3.3.1 → thth-3.4.0}/thth/study_report.py +0 -0
  506. {thth-3.3.1 → thth-3.4.0}/thth/systemd_gen.py +0 -0
  507. {thth-3.3.1 → thth-3.4.0}/thth/tags.py +0 -0
  508. {thth-3.3.1 → thth-3.4.0}/thth/thread_read.py +0 -0
  509. {thth-3.3.1 → thth-3.4.0}/thth/threadrun.py +0 -0
  510. {thth-3.3.1 → thth-3.4.0}/thth/threads_read_cli.py +0 -0
  511. {thth-3.3.1 → thth-3.4.0}/thth/threadshape.py +0 -0
  512. {thth-3.3.1 → thth-3.4.0}/thth/threadthrow.py +0 -0
  513. {thth-3.3.1 → thth-3.4.0}/thth/tool_version.py +0 -0
  514. {thth-3.3.1 → thth-3.4.0}/thth/topic_advice.py +0 -0
  515. {thth-3.3.1 → thth-3.4.0}/thth/topic_cli.py +0 -0
  516. {thth-3.3.1 → thth-3.4.0}/thth/topic_models.py +0 -0
  517. {thth-3.3.1 → thth-3.4.0}/thth/topic_store.py +0 -0
  518. {thth-3.3.1 → thth-3.4.0}/thth/topics.py +0 -0
  519. {thth-3.3.1 → thth-3.4.0}/thth/unanswered.py +0 -0
  520. {thth-3.3.1 → thth-3.4.0}/thth/vorbisformats.py +0 -0
  521. {thth-3.3.1 → thth-3.4.0}/thth/watch_cli.py +0 -0
  522. {thth-3.3.1 → thth-3.4.0}/thth/where_cli.py +0 -0
  523. {thth-3.3.1 → thth-3.4.0}/thth/who_cli.py +0 -0
  524. {thth-3.3.1 → thth-3.4.0}/thth/writeback.py +0 -0
  525. {thth-3.3.1 → thth-3.4.0}/thth/x_metrics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: thth
3
- Version: 3.3.1
3
+ Version: 3.4.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`・`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`・`inflight`・`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`・`inflight`・`location`・`notifications`・`report`・`plaza`・`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`・`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`・`inflight`・`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`・`inflight`・`location`・`notifications`・`report`・`plaza`・`admin`。
105
105
  - **最初の本番投稿の記録**: 2026-09-09、@aoking に疎通確認を 1 本(`17916074118445631`)。
106
106
  - **未着手**: X・Facebook ページ・Instagram の各アダプタ。トピック検索の権限(tester には降りない)。泉のサーバ(v2-5)。
107
107
  - **権限の制約**: tester に降りる scope は 5 つ。削除はできない。
@@ -568,6 +568,25 @@ ADMIN_TOOLS += [
568
568
  'required':['report_id','reason','version','by'],'additionalProperties':False}},
569
569
  ]
570
570
 
571
+ # 施策の広場の管理者側(設計 3.4.0 §4)。参加・退出・非表示は `by` 必須・変更ログに
572
+ # presence-only で残る。
573
+ ADMIN_TOOLS += [
574
+ {'name':'thth_admin_plaza_list','description':'List every plaza post (measures, findings, questions) including hidden ones, and the joined projects; administrator only; read-only',
575
+ 'inputSchema':{'type':'object','properties':{},'required':[],'additionalProperties':False}},
576
+ {'name':'thth_admin_plaza_show','description':'Read one plaza post as stored (original text, observations, replies); administrator only; read-only',
577
+ 'inputSchema':{'type':'object','properties':{'plaza_id':{'type':'string'}},
578
+ 'required':['plaza_id'],'additionalProperties':False}},
579
+ {'name':'thth_admin_plaza_join','description':'Let one project (owner) read and post open plaza entries; administrator only, by required; default is not joined',
580
+ 'inputSchema':{'type':'object','properties':{'project':{'type':'string'},'by':{'type':'string'}},
581
+ 'required':['project','by'],'additionalProperties':False}},
582
+ {'name':'thth_admin_plaza_leave','description':'Remove one project (owner) from the open plaza; its open entries stop being visible to others; administrator only, by required',
583
+ 'inputSchema':{'type':'object','properties':{'project':{'type':'string'},'by':{'type':'string'}},
584
+ 'required':['project','by'],'additionalProperties':False}},
585
+ {'name':'thth_admin_plaza_hide','description':'Hide one plaza post with a reason (the owner still sees it with the reason); administrator only, by required',
586
+ 'inputSchema':{'type':'object','properties':{'plaza_id':{'type':'string'},'reason':{'type':'string'},'by':{'type':'string'}},
587
+ 'required':['plaza_id','reason','by'],'additionalProperties':False}},
588
+ ]
589
+
571
590
  SERVER_TOOLS = [
572
591
  {"name":"thth_"+name,"description":"Scoped server "+name,
573
592
  "inputSchema":{"type":"object","properties":{key:{"type":"string"} for key in ("account",*keys)},
@@ -644,6 +663,68 @@ REPORT_TOOLS = [
644
663
  ]
645
664
 
646
665
 
666
+ # 施策の広場の利用者側(設計 3.4.0 §4)。**サーバ型だけに出す**(報告の口と同じ——
667
+ # 置き場は VM の私有)。誰が書いたかは credential の `actor`。読めるのは credential が
668
+ # 許した account の持ち主(project)の書き込みと、参加していれば open の写しだけ。
669
+ PLAZA_WELCOME = ("施策を試したら広場に置き、次を決める前に他の媒体の施策を読む——"
670
+ "どちらも利用者の作業の一部です")
671
+ PLAZA_TOOLS = [
672
+ {"name": "thth_plaza_post",
673
+ "description": PLAZA_WELCOME + "。施策(measure: 観測は道具が study-report と同じ計算で付ける・"
674
+ "how〔数字を出し直せる thth の命令〕が必須)・気づき(finding)・問い(question)を"
675
+ "置く。scope(媒体・企画の範囲)は必須。置いたものは同じ持ち主の全 account だけに"
676
+ "見える(他の持ち主に見せる open は人が CLI の二段確認で行う・この口には無い)。"
677
+ "evidence_level の observed は道具だけが付ける",
678
+ "inputSchema": {"type": "object", "properties": {
679
+ "account": {"type": "string"},
680
+ "kind": {"type": "string", "enum": ["measure", "finding", "question"]},
681
+ "title": {"type": "string", "description": "1 行・120 字まで"},
682
+ "body": {"type": "string", "description": "8,000 字まで"},
683
+ "scope": {"type": "string", "description": "媒体・企画の範囲(1 行・120 字・必須)"},
684
+ "kind_detail": {"type": "string", "enum": ["pattern", "rule", "pitfall", "tool_tip"],
685
+ "description": "finding の細目(任意)"},
686
+ "how": {"type": "string", "description": "数字を出し直せる thth の命令 1 行(measure は必須・実行しない)"},
687
+ "evidence_level": {"type": "string", "enum": ["stated", "hypothesis"]},
688
+ "declarations": {"type": "array", "description": "measure の宣言(study-report の形・媒体ごとに 1 つ)"},
689
+ "hypothesis": {"type": "string"}, "change": {"type": "string"},
690
+ "until": {"type": "string", "description": "期間の終わり(timezone 付きの時刻)"},
691
+ "min_n": {"type": "integer"}},
692
+ "required": ["account", "kind", "title", "body", "scope"], "additionalProperties": False}},
693
+ {"name": "thth_plaza_list",
694
+ "description": "広場の一覧(既定は自分の持ち主の書き込み・open: true は open の広場)。次を決める前に"
695
+ "他の媒体の施策を読む(読むだけ)",
696
+ "inputSchema": {"type": "object", "properties": {
697
+ "project": {"type": "string"}, "open": {"type": "boolean"}},
698
+ "additionalProperties": False}},
699
+ {"name": "thth_plaza_show",
700
+ "description": "広場の 1 件(本文・道具が付けた観測・媒体をまたぐ比較の表・追試の数・返信)を読む(読むだけ)",
701
+ "inputSchema": {"type": "object", "properties": {"plaza_id": {"type": "string"}},
702
+ "required": ["plaza_id"], "additionalProperties": False}},
703
+ {"name": "thth_plaza_reply",
704
+ "description": "返信を 1 つ足す(comment・agree・disagree〔理由必須〕・tried〔自分の measure の id 必須〕・"
705
+ "trial〔追試: result に reproduced・not_reproduced・not_tried。再現しなかった報告も"
706
+ "同じ重さで数える〕)",
707
+ "inputSchema": {"type": "object", "properties": {
708
+ "plaza_id": {"type": "string"}, "account": {"type": "string"},
709
+ "kind": {"type": "string", "enum": ["comment", "tried", "agree", "disagree", "trial"]},
710
+ "text": {"type": "string", "description": "4,000 字まで"},
711
+ "measure_id": {"type": "string"},
712
+ "result": {"type": "string", "enum": ["reproduced", "not_reproduced", "not_tried"]}},
713
+ "required": ["plaza_id", "account", "kind"], "additionalProperties": False}},
714
+ {"name": "thth_plaza_update",
715
+ "description": "自分の持ち主の施策の結果を取り直す(refresh)・判定(verdict: adopted・dropped・"
716
+ "inconclusive と reason)・project の範囲に戻す(visibility: project。open への切り替えは"
717
+ "人の CLI の二段確認だけ)",
718
+ "inputSchema": {"type": "object", "properties": {
719
+ "plaza_id": {"type": "string"}, "account": {"type": "string"},
720
+ "refresh": {"type": "boolean"},
721
+ "verdict": {"type": "string", "enum": ["adopted", "dropped", "inconclusive"]},
722
+ "reason": {"type": "string"},
723
+ "visibility": {"type": "string", "enum": ["project"]}},
724
+ "required": ["plaza_id", "account"], "additionalProperties": False}},
725
+ ]
726
+
727
+
647
728
  # **うまくいかなければ報告の口へ**(設計 3.1.2 §3.5)。利用者の道具の説明文の
648
729
  # 末尾に 1 句。定数(TOOLS 等)は書き換えず、並べるときに足す——「売り文句」の
649
730
  # 正本(設計 v2 §1・「自分の泉」§2)はそのまま残る。
@@ -681,7 +762,8 @@ def server_tools(context):
681
762
  reports = reports + [tool for tool in TOOLS if tool['name'] in ('thth_observe', 'thth_morning')]
682
763
  from thth.server_writes import WRITE_OPERATIONS
683
764
  return _with_report_hint(reports + [tool for tool in SERVER_TOOLS
684
- if context.writes or tool['name'][5:] not in WRITE_OPERATIONS]) + REPORT_TOOLS
765
+ if context.writes or tool['name'][5:] not in WRITE_OPERATIONS]
766
+ + PLAZA_TOOLS) + REPORT_TOOLS
685
767
 
686
768
 
687
769
  def server_call(name, arguments):
@@ -721,6 +803,12 @@ def server_call(name, arguments):
721
803
  elif operation.startswith('admin_reports_'):
722
804
  from thth.report_service import execute_admin_reports
723
805
  result=execute_admin_reports(context,request)
806
+ elif operation.startswith('admin_plaza_'):
807
+ from thth.report_service import execute_admin_plaza
808
+ result=execute_admin_plaza(context,request)
809
+ elif operation.startswith('plaza_'):
810
+ from thth.report_service import execute_user_plaza
811
+ result=execute_user_plaza(context,request)
724
812
  elif operation in ('report_file','report_list','report_show','report_add'):
725
813
  from thth.report_service import execute_user_reports
726
814
  result=execute_user_reports(context,request)
@@ -742,6 +830,12 @@ def server_call(name, arguments):
742
830
  if str(exc)=='duplicate_report' and getattr(exc,'report_id',None):
743
831
  # 既存の id は同じ account の報告だけ(重複の検査がそう絞っている)。
744
832
  return failure('duplicate_report: '+exc.report_id)
833
+ from thth.plaza import REASONS as PLAZA_REASONS
834
+ if str(exc)=='duplicate_post' and getattr(exc,'plaza_id',None):
835
+ # 既存の id は同じ account の書き込みだけ(重複の検査がそう絞っている)。
836
+ return failure('duplicate_post: '+exc.plaza_id)
837
+ if str(exc) in PLAZA_REASONS:
838
+ return failure(str(exc))
745
839
  return failure(str(exc) if str(exc) in SAFE_ERRORS or str(exc) in REPORT_REASONS or str(exc) in ('budget_change_durability_unconfirmed','budget_change_partially_recorded','budget_change_refused','watch_change_refused') else 'request_unavailable')
746
840
  except Exception:
747
841
  return failure('request_unavailable')
@@ -1,12 +1,14 @@
1
1
  ---
2
2
  name: thth
3
- description: 投稿する前・返信を書く前・出したあと・絡みに行く先を選ぶときに呼ぶ道具。Threads・Bluesky・Mastodon への下書きを人の承認を通してから出し、枝を読み、相手を知り、伸びを測る。
3
+ description: 投稿する前・返信を書く前・出したあと・絡みに行く先を選ぶときに呼ぶ道具。Threads・Bluesky・Mastodon への下書きを人の承認を通してから出し、枝を読み、相手を知り、伸びを測る。施策を試したら広場に置き、他の媒体の施策を読んでから次を決める。
4
4
  ---
5
5
 
6
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
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
+ **施策を試したら広場に置き、次を決める前に他の媒体の施策を読む**(3.4.0・施策の広場)。どちらも利用者の作業の一部です。小さな気づきも。`thth_plaza_post`(CLI は `thth plaza post <account> --kind measure|finding|question --title … --body-file … --scope "<媒体・企画の範囲>" --by <名前>`)。置いたものは**同じ持ち主の全 account(媒体をまたぐ)**だけに見える。施策(`measure`)は `--declaration <study-report の宣言>` を媒体ごとに並べると**観測は道具が付ける**(study-report と同じ計算・両群の分母・欠測は null と理由)。`--how` に数字を出し直せる thth の命令を 1 行(measure は必須・道具は実行しない)。本文に書いた数字は「本文」として分けて表示され、観測とは呼ばれない。`evidence_level` の `observed` は道具だけが付ける(名乗ると断られる)。読むのは `thth_plaza_list`・`thth_plaza_show`(CLI は `thth plaza list <project>`・`thth plaza show <id> --as <account>`)——媒体をまたぐ比較の表と追試の数(再現した・しなかった・試していないを同じ重さで)が出る。他の媒体の施策を自分の媒体で試したら `thth_plaza_reply` の `trial`(結果は reproduced・not_reproduced・not_tried)で返す。判定は `thth_plaza_update`(adopted・dropped・inconclusive・理由必須)。新着と「まだ試していない媒体」は `thth_observe` の 0 段と次の一手に出る。
11
+
10
12
  段だけを読み直したいときは `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
13
 
12
14
  添付を付ける前に `tool.capabilities` を読む(媒体ごとの「出せるもの」表。`thth doctor` にも同じ表が出る。`unverified` は「非対応」ではない)。
@@ -152,6 +154,7 @@ MCP `after_you_posted`。**24h の刻みが無ければ `null`**(0 と混ぜ
152
154
  - 道具の不具合・欲しい形・迷ったことは `thth report file`(MCP `thth_report_file`)で実装側に届ける(小さいものも歓迎・重複は道具が束ねる)。
153
155
  - 承認済みなのに出ない原稿は `thth_observe` の予定の段 `held_items`(file・理由・予定・経過)と `thth board` の要確認(先頭 5 本)に名前が出る。`approval_stale` は再承認(`thth approve` の二段)で直る。時刻を過ぎて出られないものがあると `thth run` は `held` として死活通知と運用通知に知らせる。
154
156
  - 自分の開いている報告に書き足すなら `thth report add <report_id> --body-file <path|-> --by <名前>`(MCP `thth_report_add`)。
157
+ - 他の媒体で試した施策と結果は `thth plaza list <project>`(MCP `thth_plaza_list`)で読める。試したら `thth plaza post`(MCP `thth_plaza_post`)で置く。
155
158
  - 使い方の全文: `docs/使い方_プロジェクトのセッション向け_2026-09-09.md`。
156
159
 
157
160
  ## 運用と分析のレポート(v3系・2.5.0 から)
@@ -41,7 +41,10 @@ def test_admin_all_registry_and_mcp_auth_rechecked(credentials,monkeypatch):
41
41
  'thth_admin_inventory','thth_admin_account','thth_admin_diff','thth_admin_log',
42
42
  'thth_admin_tokens','thth_admin_release','thth_admin_budget_set','thth_admin_watch_set',
43
43
  # 報告の口の実装側(設計 3.1.2 §1)。
44
- 'thth_admin_reports_list','thth_admin_reports_show','thth_admin_reports_reply','thth_admin_reports_close'}
44
+ 'thth_admin_reports_list','thth_admin_reports_show','thth_admin_reports_reply','thth_admin_reports_close',
45
+ # 施策の広場の管理者側(設計 3.4.0 §4)。
46
+ 'thth_admin_plaza_list','thth_admin_plaza_show','thth_admin_plaza_join','thth_admin_plaza_leave',
47
+ 'thth_admin_plaza_hide'}
45
48
  result=server.call_tool('thth_admin_inventory',{})
46
49
  assert set(json.loads(result['content'][0]['text'])['by_account'])=={'first','second'}
47
50
  assert server.call_tool('thth_admin_diff',{'mark_read':True})['isError']
@@ -251,8 +251,23 @@ def test_プライバシーポリシーは正本から生成され_外へ出る
251
251
  "https://github.com/aokings/thth/issues"):
252
252
  assert must in page, must
253
253
  for obsolete in ("does not store, log, or transmit the code", "your own computer or server",
254
- "どこにも保存・記録・送信せず", "2026-09-15 施行"):
254
+ "どこにも保存・記録・送信せず", "2026-09-15 施行",
255
+ # 2026-09-23: 退出(thth account leave)と削除依頼の照合は実装済み。
256
+ # 「後の版で実装」の文は実態と合わない(Platform Terms §7(a))。
257
+ "belong to a later implementation", "別の版で実装・検証する予定",
258
+ "600,000"):
255
259
  assert obsolete not in page
260
+ # 2026-09-23 の書き直し(照合 論点 D)で書いた事実。どれもコードで裏を取った:
261
+ # 返信・言及は相手の username と本文ごと保存(collect.py)・検索結果は保存しない
262
+ # (where_cli.py・threads_read_cli.py)・退出(leave.py)・削除依頼(deletion.py)・
263
+ # 添付(media-object.js)・LLM への受け渡し(mcp/server.py)。英語と日本語の両方。
264
+ for must in ("the other person's username, the text", "相手のユーザー名・本文",
265
+ "The text and authors of search results are not saved", "検索結果の本文と投稿者は保存しません",
266
+ "thth account leave", "30 days", "24 hours after it was created",
267
+ "THTH itself does not send data to an AI model provider",
268
+ "THTH 自身は AI モデルの提供者へデータを送りません",
269
+ "unverified, verified or completed", "未照合・照合済み・完了"):
270
+ assert must in page, must
256
271
  # 紹介ページから辿れる・robots は開いている
257
272
  index = (PUBLIC / "index.html").read_text(encoding="utf-8")
258
273
  assert 'href="/privacy/"' in index
@@ -32,7 +32,11 @@ COMMANDS = '''lint|preview|approve|account|revoke|posts|replies|measured|threads
32
32
  'report file', 'report list', 'report show', 'report add', 'admin reports list', 'admin reports show',
33
33
  'admin reports reply', 'admin reports close', 'admin reports export',
34
34
  # 3.3.1 §4: 人の口 thth inflight(show|resolve・--json)。
35
- 'inflight']
35
+ 'inflight',
36
+ # 3.4.0 §4: 施策の広場(利用者と管理者の口・--json)。
37
+ 'plaza post', 'plaza list', 'plaza show', 'plaza reply', 'plaza update',
38
+ 'admin plaza join', 'admin plaza leave', 'admin plaza hide', 'admin plaza list',
39
+ 'admin plaza show']
36
40
 
37
41
 
38
42
  def test_json_table_covers_every_parser():
@@ -185,8 +185,11 @@ def test_provision_network_does_not_hold_global_admin_flock(isolated,monkeypatch
185
185
  def test_privacy_logical_ttl_and_no_physical_erase_claim():
186
186
  from tests.test_site import build_site
187
187
  page=build_site.build_privacy()
188
- for phrase in ('600 seconds','600秒','600,000','PITR','30 days','30日','not a promise of immediate physical erasure','即時物理消去とは約束しません'):
188
+ # 100,000: the Worker's WebCrypto caps PBKDF2 at 100,000 (callback/src/approval.js ITERATIONS,
189
+ # thth/approval_relay.py ITERATIONS). The page said 600,000 until the 2026-09-23 rewrite.
190
+ for phrase in ('600 seconds','600秒','100,000','PITR','30 days','30日','not a promise of immediate physical erasure','即時物理消去とは約束しません'):
189
191
  assert phrase in page
192
+ assert '600,000' not in page
190
193
 
191
194
 
192
195
  def test_project_key_store_refused_without_traceback(isolated,monkeypatch,capsys):
@@ -0,0 +1,136 @@
1
+ """施策の広場 第 2 段——利用者の CLI(設計 3.4.0 §4)。
2
+
3
+ 見るのは:
4
+ - `thth plaza post → list → show --as` の往復(人向けと --json)。
5
+ - **project 範囲の書き込みは他の project から読めない**(list・show の両方・leak probe)。
6
+ 無い id と読めない id は同じ `plaza_not_found`。
7
+ - `show`・`reply`・`update` は `--as`(誰として読むか)が必須。
8
+ - 断りは理由と次の一手(stderr)・--json は `cannot_say`。
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import json
13
+
14
+ from thth import cli, plaza
15
+ from tests.test_v340_plaza_store import owners, BODY # noqa: F401 (fixture)
16
+
17
+ SECRET_TITLE = "KOPICHA-ONLY-TITLE"
18
+ SECRET_BODY = "KOPICHA-ONLY-BODY 朝の問いかけ"
19
+
20
+
21
+ def _body(tmp_path, text=BODY, name="body.txt"):
22
+ path = tmp_path / name
23
+ path.write_text(text, encoding="utf-8")
24
+ return str(path)
25
+
26
+
27
+ def _post(tmp_path, capsys, account="kopicha-threads", *, title=SECRET_TITLE, body=SECRET_BODY,
28
+ kind="finding", extra=()):
29
+ rc = cli.main(["plaza", "post", account, "--kind", kind, "--title", title,
30
+ "--body-file", _body(tmp_path, body), "--scope", "Threads の朝の投稿",
31
+ "--by", "kopicha-session", *extra,
32
+ "--json"])
33
+ captured = capsys.readouterr()
34
+ return rc, json.loads(captured.out) if captured.out.strip() else None, captured.err
35
+
36
+
37
+ def test_post_list_show_の往復(owners, tmp_path, capsys):
38
+ rc, posted, _ = _post(tmp_path, capsys)
39
+ assert rc == 0 and posted["scope"] == "project" and posted["kind"] == "finding"
40
+ plaza_id = posted["plaza_id"]
41
+
42
+ assert cli.main(["plaza", "list", "kopicha", "--json"]) == 0
43
+ listed = json.loads(capsys.readouterr().out)
44
+ assert [row["plaza_id"] for row in listed["posts"]] == [plaza_id]
45
+ # 一覧に本文は出さない(show で読む)。
46
+ assert SECRET_BODY not in json.dumps(listed, ensure_ascii=False)
47
+
48
+ # 同じ持ち主の別の媒体(account)として読める。
49
+ assert cli.main(["plaza", "show", plaza_id, "--as", "kopicha-bsky", "--json"]) == 0
50
+ shown = json.loads(capsys.readouterr().out)
51
+ assert shown["body"] == SECRET_BODY and shown["view"] == "own"
52
+ assert shown["observation"] is None and shown["observation_reason"] == "not_a_measure"
53
+
54
+
55
+ def test_人向けの出力(owners, tmp_path, capsys):
56
+ assert cli.main(["plaza", "post", "kopicha-threads", "--kind", "question", "--title",
57
+ "Bluesky でタグは効いていますか", "--body-file", _body(tmp_path),
58
+ "--scope", "Bluesky の全投稿", "--by", "s"]) == 0
59
+ out = capsys.readouterr().out
60
+ assert "広場に置きました: p20260909-" in out and "問い" in out
61
+ plaza_id = out.split("広場に置きました: ")[1].split("(")[0]
62
+ assert cli.main(["plaza", "list", "kopicha-mstdn"]) == 0
63
+ assert plaza_id in capsys.readouterr().out
64
+ assert cli.main(["plaza", "show", plaza_id, "--as", "kopicha"]) == 0
65
+ shown = capsys.readouterr().out
66
+ assert "[本文(人や LLM が書いた。数字は道具が確かめていない)]" in shown
67
+ assert "[観測(道具が付けた数字)] なし(施策ではないので道具は数字を付けていません)" in shown
68
+
69
+
70
+ def test_他のprojectからは一覧にも1件にも出ない(owners, tmp_path, capsys):
71
+ rc, posted, _ = _post(tmp_path, capsys)
72
+ assert rc == 0
73
+ plaza_id = posted["plaza_id"]
74
+ for target in ("other", "other-threads"):
75
+ assert cli.main(["plaza", "list", target, "--json"]) == 0
76
+ out = capsys.readouterr().out
77
+ listed = json.loads(out)
78
+ assert listed["n"] == 0 and listed["posts"] == []
79
+ assert SECRET_TITLE not in out and SECRET_BODY not in out and plaza_id not in out
80
+ # 読めない id と無い id は同じ断り(在ることを漏らさない)。
81
+ assert cli.main(["plaza", "show", plaza_id, "--as", "other-threads", "--json"]) == 2
82
+ captured = capsys.readouterr()
83
+ denied = json.loads(captured.out)
84
+ assert denied == {"cannot_say": ["plaza_not_found"]}
85
+ assert SECRET_TITLE not in captured.err + captured.out
86
+ assert cli.main(["plaza", "show", "p20260909-00000000", "--as", "other-threads", "--json"]) == 2
87
+ assert json.loads(capsys.readouterr().out) == denied
88
+
89
+
90
+ def test_show_reply_update_は読む側の名指しが必須(owners, tmp_path, capsys):
91
+ rc, posted, _ = _post(tmp_path, capsys)
92
+ for argv in (["plaza", "show", posted["plaza_id"]],
93
+ ["plaza", "reply", posted["plaza_id"], "--kind", "agree", "--by", "s"],
94
+ ["plaza", "update", posted["plaza_id"], "--refresh", "--by", "s"]):
95
+ try:
96
+ cli.main(argv)
97
+ except SystemExit as exc:
98
+ assert exc.code == 2
99
+ else:
100
+ raise AssertionError("--as が無いのに通った")
101
+ assert "--as" in capsys.readouterr().err
102
+
103
+
104
+ def test_断りは理由と次の一手(owners, tmp_path, capsys):
105
+ rc = cli.main(["plaza", "post", "kopicha-threads", "--kind", "finding", "--title", "t",
106
+ "--body-file", _body(tmp_path), "--scope", "朝", "--json"])
107
+ captured = capsys.readouterr()
108
+ assert rc == 2 and json.loads(captured.out)["cannot_say"] == ["by_required"]
109
+ assert captured.err.startswith("by_required: --by")
110
+ rc, payload, err = _post(tmp_path, capsys, body="ghp_" + "b" * 36)
111
+ assert rc == 2 and payload["cannot_say"] == ["secret_detected"]
112
+ assert "ghp_" not in err
113
+
114
+
115
+ def test_重複の断りは既存のidを添える(owners, tmp_path, capsys):
116
+ rc, first, _ = _post(tmp_path, capsys)
117
+ rc, again, err = _post(tmp_path, capsys)
118
+ assert rc == 2 and again == {"cannot_say": ["duplicate_post"], "plaza_id": first["plaza_id"]}
119
+
120
+
121
+ def test_知らないaccountは断る(owners, tmp_path, capsys):
122
+ rc, payload, _ = _post(tmp_path, capsys, account="nobody-threads")
123
+ assert rc == 2 and payload["cannot_say"] == ["account_unavailable"]
124
+ assert cli.main(["plaza", "list", "nobody", "--json"]) == 2
125
+ assert json.loads(capsys.readouterr().out)["cannot_say"] == ["invalid_account"]
126
+
127
+
128
+ def test_project範囲の一覧はprojectとaccountのどちらでも同じ(owners, tmp_path, capsys):
129
+ _post(tmp_path, capsys)
130
+ _post(tmp_path, capsys, account="kopicha-bsky", title="Bluesky の気づき")
131
+ views = []
132
+ for target in ("kopicha", "kopicha-threads", "kopicha-mstdn"):
133
+ assert cli.main(["plaza", "list", target, "--json"]) == 0
134
+ views.append([row["plaza_id"] for row in json.loads(capsys.readouterr().out)["posts"]])
135
+ assert views[0] == views[1] == views[2] and len(views[0]) == 2
136
+ assert plaza.list_posts(plaza.viewer_for_target("other"))["n"] == 0
@@ -0,0 +1,69 @@
1
+ """施策の広場 第 8 段——文書と skill と board(設計 3.4.0 §5・§7)。
2
+
3
+ 見るのは:
4
+ - skill の description と本文に「施策を試したら広場に置く・他の媒体の施策を読んでから
5
+ 次を決める」がある(置くのは作業の一部と明言)。
6
+ - 案内は project 範囲から(skill は open の使い方を案内しない・設計 §7)。
7
+ - observe の limitations と MCP の説明に同じ 1 句。
8
+ - `thth board` は広場を読まない(他の持ち主の書き込みの題が出ない・leak probe)。
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from pathlib import Path
14
+
15
+ from thth import cli, plaza
16
+ from tests.test_v340_plaza_store import owners, post # noqa: F401 (fixture)
17
+
18
+ ROOT = Path(__file__).resolve().parent.parent
19
+
20
+
21
+ def test_skillに広場の1段落():
22
+ skill = (ROOT / "skills" / "thth" / "SKILL.md").read_text(encoding="utf-8")
23
+ head = skill.split("---", 2)[1]
24
+ assert "施策を試したら広場に置き、他の媒体の施策を読んでから次を決める" in head
25
+ assert "**施策を試したら広場に置き、次を決める前に他の媒体の施策を読む**" in skill
26
+ assert "どちらも利用者の作業の一部です" in skill
27
+ for name in ("thth_plaza_post", "thth_plaza_list", "thth_plaza_show", "thth_plaza_reply",
28
+ "thth_plaza_update", "--scope", "--how", "observed"):
29
+ assert name in skill, name
30
+ # 案内は project 範囲から(open は招待者が 2 組以上になってから)。
31
+ assert "--open" not in skill and "admin plaza join" not in skill
32
+
33
+
34
+ def test_使い方の文書に広場の段():
35
+ doc = (ROOT / "docs" / "使い方_プロジェクトのセッション向け_2026-09-09.md").read_text(encoding="utf-8")
36
+ assert "## 3.4.0: 施策の広場(試したら置く・次を決める前に読む)" in doc
37
+ section = doc.split("## 3.4.0: 施策の広場")[1].split("\n## ")[0]
38
+ assert "--open" not in section
39
+
40
+
41
+ def test_リリースノートの見出しとセッション向けの5行():
42
+ # 版を上げる commit で見出しの「— 下書き」と「版は据え置き」は外れる(配布の手順)。
43
+ # 見るのは題と、各 project のセッション向けの 5 行があること。
44
+ note = (ROOT / "docs" / "リリースノート_3.4.0_2026-09-23.md").read_text(encoding="utf-8")
45
+ assert note.startswith("# リリースノート 3.4.0(施策の広場)")
46
+ section = note.split("## 各 project のセッション向け: いつ何を置くか")[1].split("\n## ")[0]
47
+ assert len([line for line in section.splitlines() if line[:2] in ("1.", "2.", "3.", "4.", "5.")]) == 5
48
+
49
+
50
+ def test_observeとMCPに同じ1句():
51
+ from thth import morning
52
+ from tests.test_mcp import _load_server_module
53
+ assert plaza.WELCOME.startswith("施策を試したら広場に置き、次を決める前に他の媒体の施策を読む")
54
+ server = _load_server_module()
55
+ assert server.PLAZA_WELCOME in plaza.WELCOME
56
+ post_tool = next(tool for tool in server.PLAZA_TOOLS if tool["name"] == "thth_plaza_post")
57
+ assert post_tool["description"].startswith(server.PLAZA_WELCOME)
58
+ source = (ROOT / "thth" / "morning.py").read_text(encoding="utf-8")
59
+ assert "施策を試したら広場へ(thth plaza post・thth_plaza_post)" in source
60
+ assert morning # import できること
61
+
62
+
63
+ def test_boardは広場を読まない(owners, capsys):
64
+ post(title="BOARD-LEAK-TITLE", account="other-threads", by="o")
65
+ rc = cli.main(["board", "--json"])
66
+ out = capsys.readouterr().out
67
+ assert rc in (0, 2)
68
+ assert "BOARD-LEAK-TITLE" not in out and "_plaza" not in out
69
+ json.loads(out)
@@ -0,0 +1,109 @@
1
+ """施策の広場 段 2 補——利用者側の設計材料(設計 §9・報告 r20260923-7c297144)。
2
+
3
+ 見るのは:
4
+ - finding の細目 `kind_detail`(pattern・rule・pitfall・tool_tip)。finding 以外には付けない。
5
+ - 媒体・企画の範囲(`--scope`・自由文 120 字)は**全部の書き込みで必須**。
6
+ - `how`(数字を出し直せる thth の命令)は measure で必須。形(`thth ` で始まる 1 行)と
7
+ 秘密だけを検査し、**実行はしない**。
8
+ - 見立てと事実の印 `evidence_level`: 人や LLM が名乗れるのは stated と hypothesis だけ。
9
+ **observed を名乗ると断る**(observed は道具が付けた観測があるときだけ)。
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import subprocess
15
+
16
+ import pytest
17
+
18
+ from thth import cli, plaza
19
+ from tests.test_v340_plaza_store import owners, post, viewer # noqa: F401 (fixture)
20
+
21
+
22
+ def test_findingの細目(owners):
23
+ result = post(kind_detail="pitfall")
24
+ shown = plaza.show(result["plaza_id"], viewer("kopicha-bsky"))
25
+ assert shown["kind_detail"] == "pitfall"
26
+ listed = plaza.list_posts(viewer("kopicha-threads"))["posts"][0]
27
+ assert listed["kind_detail"] == "pitfall"
28
+ for kind, detail in (("finding", "anecdote"), ("question", "rule"), ("measure", "pattern")):
29
+ with pytest.raises(plaza.PlazaError, match="^invalid_kind_detail$"):
30
+ post(kind=kind, kind_detail=detail, title=f"{kind} {detail}")
31
+ # 細目は任意(無くても finding は置ける)。
32
+ assert plaza.show(post(title="細目なし")["plaza_id"], viewer("kopicha-threads"))["kind_detail"] is None
33
+
34
+
35
+ def test_範囲は全部の書き込みで必須(owners):
36
+ for kind in ("finding", "question", "measure"):
37
+ for value in (None, "", " "):
38
+ with pytest.raises(plaza.PlazaError, match="^scope_required$"):
39
+ post(kind=kind, scope_note=value)
40
+ with pytest.raises(plaza.PlazaError, match="^post_too_long$"):
41
+ post(scope_note="範" * (plaza.SCOPE_NOTE_MAX + 1))
42
+ with pytest.raises(plaza.PlazaError, match="^invalid_post$"):
43
+ post(scope_note="一行目\n二行目")
44
+ shown = plaza.show(post(scope_note="Mastodon の夜の投稿")["plaza_id"], viewer("kopicha-threads"))
45
+ assert shown["scope_note"] == "Mastodon の夜の投稿"
46
+
47
+
48
+ def test_howはmeasureで必須_形だけ見て実行しない(owners, monkeypatch):
49
+ with pytest.raises(plaza.PlazaError, match="^how_required$"):
50
+ post(kind="measure", how=None)
51
+ for bad in ("ls -la", "thth", "rm -rf / thth measured x", "thth measured a\nthth run b",
52
+ "thth " + "x" * plaza.HOW_MAX):
53
+ with pytest.raises(plaza.PlazaError, match="^invalid_how$"):
54
+ post(kind="measure", how=bad, title=f"how {len(bad)}")
55
+ with pytest.raises(plaza.PlazaError, match="^secret_detected$"):
56
+ post(kind="measure", how="thth measured kopicha-threads --token ghp_" + "c" * 36)
57
+ # 実行しない(subprocess を呼ばない)。
58
+ monkeypatch.setattr(subprocess, "run", lambda *a, **k: pytest.fail("how を実行した"))
59
+ monkeypatch.setattr(subprocess, "Popen", lambda *a, **k: pytest.fail("how を実行した"))
60
+ result = post(kind="measure", how="thth measured kopicha-threads --since 2026-09-20")
61
+ shown = plaza.show(result["plaza_id"], viewer("kopicha-threads"))
62
+ assert shown["how"] == "thth measured kopicha-threads --since 2026-09-20"
63
+ # finding の how は任意。
64
+ assert post(title="how 付きの気づき", how="thth posts kopicha-threads")["plaza_id"]
65
+
66
+
67
+ def test_observedは名乗れない(owners):
68
+ with pytest.raises(plaza.PlazaError, match="^observed_is_tool_only$"):
69
+ post(evidence_level="observed")
70
+ with pytest.raises(plaza.PlazaError, match="^observed_is_tool_only$"):
71
+ post(kind="measure", evidence_level="observed", title="名乗る施策")
72
+ with pytest.raises(plaza.PlazaError, match="^invalid_evidence_level$"):
73
+ post(evidence_level="certain")
74
+
75
+
76
+ def test_名乗った印と道具が付ける印(owners):
77
+ stated = post(title="本文だけ")
78
+ hypothesis = post(title="見立て", evidence_level="hypothesis")
79
+ assert stated["evidence_level"] == "stated" and hypothesis["evidence_level"] == "hypothesis"
80
+ # 宣言の無い measure は観測が無いので observed にならない(名乗った印のまま)。
81
+ measure = post(kind="measure", title="宣言なしの施策", evidence_level="hypothesis")
82
+ assert measure["evidence_level"] == "hypothesis"
83
+ rows = {row["title"]: row["evidence_level"]
84
+ for row in plaza.list_posts(viewer("kopicha-threads"))["posts"]}
85
+ assert rows == {"本文だけ": "stated", "見立て": "hypothesis", "宣言なしの施策": "hypothesis"}
86
+
87
+
88
+ def test_CLIの旗(owners, tmp_path, capsys):
89
+ body = tmp_path / "b.txt"
90
+ body.write_text("朝は伸びる気がする", encoding="utf-8")
91
+ base = ["plaza", "post", "kopicha-mstdn", "--kind", "finding", "--title", "朝",
92
+ "--body-file", str(body), "--by", "s", "--json"]
93
+ assert cli.main(base) == 2
94
+ assert json.loads(capsys.readouterr().out)["cannot_say"] == ["scope_required"]
95
+ assert cli.main(base + ["--scope", "Mastodon の朝", "--evidence-level", "observed"]) == 2
96
+ captured = capsys.readouterr()
97
+ assert json.loads(captured.out)["cannot_say"] == ["observed_is_tool_only"]
98
+ assert captured.err.startswith("observed_is_tool_only: observed(観測あり)は道具だけ")
99
+ assert cli.main(base + ["--scope", "Mastodon の朝", "--kind-detail", "tool_tip",
100
+ "--evidence-level", "hypothesis", "--how", "thth posts kopicha-mstdn"]) == 0
101
+ plaza_id = json.loads(capsys.readouterr().out)["plaza_id"]
102
+ assert cli.main(["plaza", "show", plaza_id, "--as", "kopicha"]) == 0
103
+ out = capsys.readouterr().out
104
+ assert "印: 見立て・道具のコツ 範囲: Mastodon の朝" in out
105
+ assert "出し直し: thth posts kopicha-mstdn(道具は実行していません)" in out
106
+ measure = ["plaza", "post", "kopicha-mstdn", "--kind", "measure", "--title", "施策",
107
+ "--body-file", str(body), "--scope", "Mastodon の朝", "--by", "s", "--json"]
108
+ assert cli.main(measure) == 2
109
+ assert json.loads(capsys.readouterr().out)["cannot_say"] == ["how_required"]