thth 2.3.1__tar.gz → 2.3.2__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 (253) hide show
  1. {thth-2.3.1 → thth-2.3.2}/PKG-INFO +3 -3
  2. {thth-2.3.1 → thth-2.3.2}/README.md +2 -2
  3. thth-2.3.2/tests/test_behind_remote.py +137 -0
  4. thth-2.3.2/tests/test_cli_vm_path.py +80 -0
  5. thth-2.3.2/tests/test_pull_cli.py +96 -0
  6. thth-2.3.2/thth/VERSION +1 -0
  7. {thth-2.3.1 → thth-2.3.2}/thth/cli.py +269 -1
  8. {thth-2.3.1 → thth-2.3.2}/thth/topic_cli.py +24 -0
  9. {thth-2.3.1 → thth-2.3.2}/thth/writeback.py +66 -0
  10. thth-2.3.1/thth/VERSION +0 -1
  11. {thth-2.3.1 → thth-2.3.2}/.gitignore +0 -0
  12. {thth-2.3.1 → thth-2.3.2}/LICENSE +0 -0
  13. {thth-2.3.1 → thth-2.3.2}/accounts.example/bluesky.json +0 -0
  14. {thth-2.3.1 → thth-2.3.2}/accounts.example/mastodon.json +0 -0
  15. {thth-2.3.1 → thth-2.3.2}/accounts.example/threads.json +0 -0
  16. {thth-2.3.1 → thth-2.3.2}/bin/thth +0 -0
  17. {thth-2.3.1 → thth-2.3.2}/bin/thth-remote +0 -0
  18. {thth-2.3.1 → thth-2.3.2}/bin/thth-run +0 -0
  19. {thth-2.3.1 → thth-2.3.2}/mcp/server.py +0 -0
  20. {thth-2.3.1 → thth-2.3.2}/pyproject.toml +0 -0
  21. {thth-2.3.1 → thth-2.3.2}/skills/thth/SKILL.md +0 -0
  22. {thth-2.3.1 → thth-2.3.2}/systemd/thth@.service +0 -0
  23. {thth-2.3.1 → thth-2.3.2}/tests/__init__.py +0 -0
  24. {thth-2.3.1 → thth-2.3.2}/tests/conftest.py +0 -0
  25. {thth-2.3.1 → thth-2.3.2}/tests/fixtures/build_injection_eval.py +0 -0
  26. {thth-2.3.1 → thth-2.3.2}/tests/fixtures/callback/worker_responses.json +0 -0
  27. {thth-2.3.1 → thth-2.3.2}/tests/fixtures/injection_eval.json +0 -0
  28. {thth-2.3.1 → thth-2.3.2}/tests/fixtures/umami-bile.md +0 -0
  29. {thth-2.3.1 → thth-2.3.2}/tests/helpers/__init__.py +0 -0
  30. {thth-2.3.1 → thth-2.3.2}/tests/helpers/fake_oauth_server.py +0 -0
  31. {thth-2.3.1 → thth-2.3.2}/tests/helpers/fake_push_adapter.py +0 -0
  32. {thth-2.3.1 → thth-2.3.2}/tests/helpers/fake_redirect_server.py +0 -0
  33. {thth-2.3.1 → thth-2.3.2}/tests/helpers/fake_search_adapter.py +0 -0
  34. {thth-2.3.1 → thth-2.3.2}/tests/helpers/fake_threads_reader.py +0 -0
  35. {thth-2.3.1 → thth-2.3.2}/tests/helpers/fake_threads_writes.py +0 -0
  36. {thth-2.3.1 → thth-2.3.2}/tests/helpers/hold_lock.py +0 -0
  37. {thth-2.3.1 → thth-2.3.2}/tests/helpers/hold_repo_lock.py +0 -0
  38. {thth-2.3.1 → thth-2.3.2}/tests/helpers/worker_probe.mjs +0 -0
  39. {thth-2.3.1 → thth-2.3.2}/tests/test_account_name_check.py +0 -0
  40. {thth-2.3.1 → thth-2.3.2}/tests/test_account_report.py +0 -0
  41. {thth-2.3.1 → thth-2.3.2}/tests/test_accounts_dir.py +0 -0
  42. {thth-2.3.1 → thth-2.3.2}/tests/test_adapter_boundary.py +0 -0
  43. {thth-2.3.1 → thth-2.3.2}/tests/test_after_cli.py +0 -0
  44. {thth-2.3.1 → thth-2.3.2}/tests/test_api_error_body.py +0 -0
  45. {thth-2.3.1 → thth-2.3.2}/tests/test_app_env_cli.py +0 -0
  46. {thth-2.3.1 → thth-2.3.2}/tests/test_approval.py +0 -0
  47. {thth-2.3.1 → thth-2.3.2}/tests/test_approve_cmd.py +0 -0
  48. {thth-2.3.1 → thth-2.3.2}/tests/test_ask.py +0 -0
  49. {thth-2.3.1 → thth-2.3.2}/tests/test_atlas_fifth_review.py +0 -0
  50. {thth-2.3.1 → thth-2.3.2}/tests/test_atlas_fourth_review.py +0 -0
  51. {thth-2.3.1 → thth-2.3.2}/tests/test_atlas_review.py +0 -0
  52. {thth-2.3.1 → thth-2.3.2}/tests/test_atlas_second_review.py +0 -0
  53. {thth-2.3.1 → thth-2.3.2}/tests/test_atlas_third_review.py +0 -0
  54. {thth-2.3.1 → thth-2.3.2}/tests/test_audit1_fixes.py +0 -0
  55. {thth-2.3.1 → thth-2.3.2}/tests/test_audit2_fixes.py +0 -0
  56. {thth-2.3.1 → thth-2.3.2}/tests/test_audit_b_counterexamples.py +0 -0
  57. {thth-2.3.1 → thth-2.3.2}/tests/test_audit_b_m1_semantics.py +0 -0
  58. {thth-2.3.1 → thth-2.3.2}/tests/test_audit_b_n1_depth.py +0 -0
  59. {thth-2.3.1 → thth-2.3.2}/tests/test_audit_b_r1_closure.py +0 -0
  60. {thth-2.3.1 → thth-2.3.2}/tests/test_audit_v201.py +0 -0
  61. {thth-2.3.1 → thth-2.3.2}/tests/test_audit_v23_details.py +0 -0
  62. {thth-2.3.1 → thth-2.3.2}/tests/test_auth.py +0 -0
  63. {thth-2.3.1 → thth-2.3.2}/tests/test_auth_identity_required.py +0 -0
  64. {thth-2.3.1 → thth-2.3.2}/tests/test_bluesky_adapter.py +0 -0
  65. {thth-2.3.1 → thth-2.3.2}/tests/test_bluesky_secret_scrub.py +0 -0
  66. {thth-2.3.1 → thth-2.3.2}/tests/test_board_needs_review.py +0 -0
  67. {thth-2.3.1 → thth-2.3.2}/tests/test_board_running.py +0 -0
  68. {thth-2.3.1 → thth-2.3.2}/tests/test_bundle.py +0 -0
  69. {thth-2.3.1 → thth-2.3.2}/tests/test_charcount.py +0 -0
  70. {thth-2.3.1 → thth-2.3.2}/tests/test_cli_entrypoint.py +0 -0
  71. {thth-2.3.1 → thth-2.3.2}/tests/test_cli_help.py +0 -0
  72. {thth-2.3.1 → thth-2.3.2}/tests/test_collect.py +0 -0
  73. {thth-2.3.1 → thth-2.3.2}/tests/test_collect_runs.py +0 -0
  74. {thth-2.3.1 → thth-2.3.2}/tests/test_collect_sent.py +0 -0
  75. {thth-2.3.1 → thth-2.3.2}/tests/test_comparability.py +0 -0
  76. {thth-2.3.1 → thth-2.3.2}/tests/test_control_chars_refused.py +0 -0
  77. {thth-2.3.1 → thth-2.3.2}/tests/test_conversation_paging.py +0 -0
  78. {thth-2.3.1 → thth-2.3.2}/tests/test_count_for.py +0 -0
  79. {thth-2.3.1 → thth-2.3.2}/tests/test_docs_links.py +0 -0
  80. {thth-2.3.1 → thth-2.3.2}/tests/test_doctor.py +0 -0
  81. {thth-2.3.1 → thth-2.3.2}/tests/test_doctor_all_permissions.py +0 -0
  82. {thth-2.3.1 → thth-2.3.2}/tests/test_doctor_next_step.py +0 -0
  83. {thth-2.3.1 → thth-2.3.2}/tests/test_duplicate_front_matter_keys.py +0 -0
  84. {thth-2.3.1 → thth-2.3.2}/tests/test_editorial_audit.py +0 -0
  85. {thth-2.3.1 → thth-2.3.2}/tests/test_editorial_n3_n4.py +0 -0
  86. {thth-2.3.1 → thth-2.3.2}/tests/test_engagements.py +0 -0
  87. {thth-2.3.1 → thth-2.3.2}/tests/test_external_acceptance.py +0 -0
  88. {thth-2.3.1 → thth-2.3.2}/tests/test_fake_api.py +0 -0
  89. {thth-2.3.1 → thth-2.3.2}/tests/test_form_spec.py +0 -0
  90. {thth-2.3.1 → thth-2.3.2}/tests/test_form_spec_cli.py +0 -0
  91. {thth-2.3.1 → thth-2.3.2}/tests/test_forms.py +0 -0
  92. {thth-2.3.1 → thth-2.3.2}/tests/test_fresh_install.py +0 -0
  93. {thth-2.3.1 → thth-2.3.2}/tests/test_front_matter_injection.py +0 -0
  94. {thth-2.3.1 → thth-2.3.2}/tests/test_hypotheses.py +0 -0
  95. {thth-2.3.1 → thth-2.3.2}/tests/test_hypothesis_boundaries.py +0 -0
  96. {thth-2.3.1 → thth-2.3.2}/tests/test_hypothesis_units.py +0 -0
  97. {thth-2.3.1 → thth-2.3.2}/tests/test_impact_cli.py +0 -0
  98. {thth-2.3.1 → thth-2.3.2}/tests/test_inbox_granted_scopes.py +0 -0
  99. {thth-2.3.1 → thth-2.3.2}/tests/test_independent_thread_review.py +0 -0
  100. {thth-2.3.1 → thth-2.3.2}/tests/test_independent_topic_review.py +0 -0
  101. {thth-2.3.1 → thth-2.3.2}/tests/test_independent_topic_review_r2.py +0 -0
  102. {thth-2.3.1 → thth-2.3.2}/tests/test_inflight.py +0 -0
  103. {thth-2.3.1 → thth-2.3.2}/tests/test_jst.py +0 -0
  104. {thth-2.3.1 → thth-2.3.2}/tests/test_lint.py +0 -0
  105. {thth-2.3.1 → thth-2.3.2}/tests/test_list_projections.py +0 -0
  106. {thth-2.3.1 → thth-2.3.2}/tests/test_llm_trial.py +0 -0
  107. {thth-2.3.1 → thth-2.3.2}/tests/test_llm_trial_engage.py +0 -0
  108. {thth-2.3.1 → thth-2.3.2}/tests/test_lock.py +0 -0
  109. {thth-2.3.1 → thth-2.3.2}/tests/test_m2_independent.py +0 -0
  110. {thth-2.3.1 → thth-2.3.2}/tests/test_maintain.py +0 -0
  111. {thth-2.3.1 → thth-2.3.2}/tests/test_mastodon_adapter.py +0 -0
  112. {thth-2.3.1 → thth-2.3.2}/tests/test_mastodon_visibility.py +0 -0
  113. {thth-2.3.1 → thth-2.3.2}/tests/test_max_per_run.py +0 -0
  114. {thth-2.3.1 → thth-2.3.2}/tests/test_mcp.py +0 -0
  115. {thth-2.3.1 → thth-2.3.2}/tests/test_mcp_robustness.py +0 -0
  116. {thth-2.3.1 → thth-2.3.2}/tests/test_measured.py +0 -0
  117. {thth-2.3.1 → thth-2.3.2}/tests/test_media_accounts.py +0 -0
  118. {thth-2.3.1 → thth-2.3.2}/tests/test_media_next_steps.py +0 -0
  119. {thth-2.3.1 → thth-2.3.2}/tests/test_media_roundtrip.py +0 -0
  120. {thth-2.3.1 → thth-2.3.2}/tests/test_mismatch_diagnostics.py +0 -0
  121. {thth-2.3.1 → thth-2.3.2}/tests/test_n5_reply_null_element.py +0 -0
  122. {thth-2.3.1 → thth-2.3.2}/tests/test_n6_replies_incomplete_ledger.py +0 -0
  123. {thth-2.3.1 → thth-2.3.2}/tests/test_n7_doctor_error_body.py +0 -0
  124. {thth-2.3.1 → thth-2.3.2}/tests/test_no_limits.py +0 -0
  125. {thth-2.3.1 → thth-2.3.2}/tests/test_observation_provenance.py +0 -0
  126. {thth-2.3.1 → thth-2.3.2}/tests/test_packaging.py +0 -0
  127. {thth-2.3.1 → thth-2.3.2}/tests/test_preview.py +0 -0
  128. {thth-2.3.1 → thth-2.3.2}/tests/test_production_mode.py +0 -0
  129. {thth-2.3.1 → thth-2.3.2}/tests/test_queue.py +0 -0
  130. {thth-2.3.1 → thth-2.3.2}/tests/test_quiet_hours.py +0 -0
  131. {thth-2.3.1 → thth-2.3.2}/tests/test_r2_r4_r5.py +0 -0
  132. {thth-2.3.1 → thth-2.3.2}/tests/test_reason_adoption.py +0 -0
  133. {thth-2.3.1 → thth-2.3.2}/tests/test_redact.py +0 -0
  134. {thth-2.3.1 → thth-2.3.2}/tests/test_redirect_token_leak.py +0 -0
  135. {thth-2.3.1 → thth-2.3.2}/tests/test_refresh.py +0 -0
  136. {thth-2.3.1 → thth-2.3.2}/tests/test_refresh_destination.py +0 -0
  137. {thth-2.3.1 → thth-2.3.2}/tests/test_rejudge_new.py +0 -0
  138. {thth-2.3.1 → thth-2.3.2}/tests/test_rejudge_r2_r3.py +0 -0
  139. {thth-2.3.1 → thth-2.3.2}/tests/test_replies.py +0 -0
  140. {thth-2.3.1 → thth-2.3.2}/tests/test_replies_refresh.py +0 -0
  141. {thth-2.3.1 → thth-2.3.2}/tests/test_repo_lock.py +0 -0
  142. {thth-2.3.1 → thth-2.3.2}/tests/test_repo_sync.py +0 -0
  143. {thth-2.3.1 → thth-2.3.2}/tests/test_reverse_audit.py +0 -0
  144. {thth-2.3.1 → thth-2.3.2}/tests/test_review_case_id_cluster_m2.py +0 -0
  145. {thth-2.3.1 → thth-2.3.2}/tests/test_review_case_id_cluster_r2.py +0 -0
  146. {thth-2.3.1 → thth-2.3.2}/tests/test_review_cli.py +0 -0
  147. {thth-2.3.1 → thth-2.3.2}/tests/test_review_evidence_account.py +0 -0
  148. {thth-2.3.1 → thth-2.3.2}/tests/test_review_form_spec_and_case_id.py +0 -0
  149. {thth-2.3.1 → thth-2.3.2}/tests/test_review_provenance.py +0 -0
  150. {thth-2.3.1 → thth-2.3.2}/tests/test_review_records.py +0 -0
  151. {thth-2.3.1 → thth-2.3.2}/tests/test_revoke.py +0 -0
  152. {thth-2.3.1 → thth-2.3.2}/tests/test_roundtrip.py +0 -0
  153. {thth-2.3.1 → thth-2.3.2}/tests/test_run_missing_env.py +0 -0
  154. {thth-2.3.1 → thth-2.3.2}/tests/test_run_record_failclosed.py +0 -0
  155. {thth-2.3.1 → thth-2.3.2}/tests/test_runs_collected.py +0 -0
  156. {thth-2.3.1 → thth-2.3.2}/tests/test_runs_read_rows.py +0 -0
  157. {thth-2.3.1 → thth-2.3.2}/tests/test_schedule_multiple.py +0 -0
  158. {thth-2.3.1 → thth-2.3.2}/tests/test_schedule_view.py +0 -0
  159. {thth-2.3.1 → thth-2.3.2}/tests/test_scopes_source_requested.py +0 -0
  160. {thth-2.3.1 → thth-2.3.2}/tests/test_search_engage_targets.py +0 -0
  161. {thth-2.3.1 → thth-2.3.2}/tests/test_secret_value_redact.py +0 -0
  162. {thth-2.3.1 → thth-2.3.2}/tests/test_secrets_grep.py +0 -0
  163. {thth-2.3.1 → thth-2.3.2}/tests/test_security_small_fixes.py +0 -0
  164. {thth-2.3.1 → thth-2.3.2}/tests/test_select.py +0 -0
  165. {thth-2.3.1 → thth-2.3.2}/tests/test_selfupdate.py +0 -0
  166. {thth-2.3.1 → thth-2.3.2}/tests/test_send.py +0 -0
  167. {thth-2.3.1 → thth-2.3.2}/tests/test_send_cli.py +0 -0
  168. {thth-2.3.1 → thth-2.3.2}/tests/test_send_media.py +0 -0
  169. {thth-2.3.1 → thth-2.3.2}/tests/test_send_visibility.py +0 -0
  170. {thth-2.3.1 → thth-2.3.2}/tests/test_sent_integrity.py +0 -0
  171. {thth-2.3.1 → thth-2.3.2}/tests/test_server_json.py +0 -0
  172. {thth-2.3.1 → thth-2.3.2}/tests/test_site.py +0 -0
  173. {thth-2.3.1 → thth-2.3.2}/tests/test_skill_doc.py +0 -0
  174. {thth-2.3.1 → thth-2.3.2}/tests/test_systemd.py +0 -0
  175. {thth-2.3.1 → thth-2.3.2}/tests/test_thread_publish.py +0 -0
  176. {thth-2.3.1 → thth-2.3.2}/tests/test_thread_read.py +0 -0
  177. {thth-2.3.1 → thth-2.3.2}/tests/test_threadrun.py +0 -0
  178. {thth-2.3.1 → thth-2.3.2}/tests/test_threads_read_permissions.py +0 -0
  179. {thth-2.3.1 → thth-2.3.2}/tests/test_threadshape.py +0 -0
  180. {thth-2.3.1 → thth-2.3.2}/tests/test_thth_root.py +0 -0
  181. {thth-2.3.1 → thth-2.3.2}/tests/test_token_exists.py +0 -0
  182. {thth-2.3.1 → thth-2.3.2}/tests/test_token_scopes_record.py +0 -0
  183. {thth-2.3.1 → thth-2.3.2}/tests/test_token_set.py +0 -0
  184. {thth-2.3.1 → thth-2.3.2}/tests/test_topic.py +0 -0
  185. {thth-2.3.1 → thth-2.3.2}/tests/test_topic_advice.py +0 -0
  186. {thth-2.3.1 → thth-2.3.2}/tests/test_topic_cli.py +0 -0
  187. {thth-2.3.1 → thth-2.3.2}/tests/test_topic_models.py +0 -0
  188. {thth-2.3.1 → thth-2.3.2}/tests/test_topic_review_fixes.py +0 -0
  189. {thth-2.3.1 → thth-2.3.2}/tests/test_topic_shelf_observers.py +0 -0
  190. {thth-2.3.1 → thth-2.3.2}/tests/test_topic_store.py +0 -0
  191. {thth-2.3.1 → thth-2.3.2}/tests/test_topics_record_concurrent.py +0 -0
  192. {thth-2.3.1 → thth-2.3.2}/tests/test_topics_tool.py +0 -0
  193. {thth-2.3.1 → thth-2.3.2}/tests/test_trial_frictions.py +0 -0
  194. {thth-2.3.1 → thth-2.3.2}/tests/test_v21b_approved_writes.py +0 -0
  195. {thth-2.3.1 → thth-2.3.2}/tests/test_value_domains_shown.py +0 -0
  196. {thth-2.3.1 → thth-2.3.2}/tests/test_version.py +0 -0
  197. {thth-2.3.1 → thth-2.3.2}/tests/test_where_cli.py +0 -0
  198. {thth-2.3.1 → thth-2.3.2}/tests/test_who_cli.py +0 -0
  199. {thth-2.3.1 → thth-2.3.2}/tests/test_writeback_rebase.py +0 -0
  200. {thth-2.3.1 → thth-2.3.2}/tests/test_writeback_validate.py +0 -0
  201. {thth-2.3.1 → thth-2.3.2}/thth/__init__.py +0 -0
  202. {thth-2.3.1 → thth-2.3.2}/thth/__main__.py +0 -0
  203. {thth-2.3.1 → thth-2.3.2}/thth/account_cli.py +0 -0
  204. {thth-2.3.1 → thth-2.3.2}/thth/account_report.py +0 -0
  205. {thth-2.3.1 → thth-2.3.2}/thth/accounts.py +0 -0
  206. {thth-2.3.1 → thth-2.3.2}/thth/adapters/__init__.py +0 -0
  207. {thth-2.3.1 → thth-2.3.2}/thth/adapters/base.py +0 -0
  208. {thth-2.3.1 → thth-2.3.2}/thth/adapters/bluesky.py +0 -0
  209. {thth-2.3.1 → thth-2.3.2}/thth/adapters/mastodon.py +0 -0
  210. {thth-2.3.1 → thth-2.3.2}/thth/adapters/threads.py +0 -0
  211. {thth-2.3.1 → thth-2.3.2}/thth/after_cli.py +0 -0
  212. {thth-2.3.1 → thth-2.3.2}/thth/appenv.py +0 -0
  213. {thth-2.3.1 → thth-2.3.2}/thth/approval.py +0 -0
  214. {thth-2.3.1 → thth-2.3.2}/thth/ask.py +0 -0
  215. {thth-2.3.1 → thth-2.3.2}/thth/ask_cli.py +0 -0
  216. {thth-2.3.1 → thth-2.3.2}/thth/bundle.py +0 -0
  217. {thth-2.3.1 → thth-2.3.2}/thth/collect.py +0 -0
  218. {thth-2.3.1 → thth-2.3.2}/thth/core.py +0 -0
  219. {thth-2.3.1 → thth-2.3.2}/thth/doctor.py +0 -0
  220. {thth-2.3.1 → thth-2.3.2}/thth/engagements.py +0 -0
  221. {thth-2.3.1 → thth-2.3.2}/thth/forms.py +0 -0
  222. {thth-2.3.1 → thth-2.3.2}/thth/httpsafe.py +0 -0
  223. {thth-2.3.1 → thth-2.3.2}/thth/inflight.py +0 -0
  224. {thth-2.3.1 → thth-2.3.2}/thth/jst.py +0 -0
  225. {thth-2.3.1 → thth-2.3.2}/thth/lint.py +0 -0
  226. {thth-2.3.1 → thth-2.3.2}/thth/lock.py +0 -0
  227. {thth-2.3.1 → thth-2.3.2}/thth/maintain.py +0 -0
  228. {thth-2.3.1 → thth-2.3.2}/thth/measured.py +0 -0
  229. {thth-2.3.1 → thth-2.3.2}/thth/oauth.py +0 -0
  230. {thth-2.3.1 → thth-2.3.2}/thth/postid.py +0 -0
  231. {thth-2.3.1 → thth-2.3.2}/thth/queuefile.py +0 -0
  232. {thth-2.3.1 → thth-2.3.2}/thth/redact.py +0 -0
  233. {thth-2.3.1 → thth-2.3.2}/thth/replies.py +0 -0
  234. {thth-2.3.1 → thth-2.3.2}/thth/report.py +0 -0
  235. {thth-2.3.1 → thth-2.3.2}/thth/retract_cli.py +0 -0
  236. {thth-2.3.1 → thth-2.3.2}/thth/runs.py +0 -0
  237. {thth-2.3.1 → thth-2.3.2}/thth/scopes.py +0 -0
  238. {thth-2.3.1 → thth-2.3.2}/thth/secrets_fs.py +0 -0
  239. {thth-2.3.1 → thth-2.3.2}/thth/select.py +0 -0
  240. {thth-2.3.1 → thth-2.3.2}/thth/selfupdate.py +0 -0
  241. {thth-2.3.1 → thth-2.3.2}/thth/sent.py +0 -0
  242. {thth-2.3.1 → thth-2.3.2}/thth/systemd_gen.py +0 -0
  243. {thth-2.3.1 → thth-2.3.2}/thth/thread_read.py +0 -0
  244. {thth-2.3.1 → thth-2.3.2}/thth/threadrun.py +0 -0
  245. {thth-2.3.1 → thth-2.3.2}/thth/threads_read_cli.py +0 -0
  246. {thth-2.3.1 → thth-2.3.2}/thth/threadshape.py +0 -0
  247. {thth-2.3.1 → thth-2.3.2}/thth/threadthrow.py +0 -0
  248. {thth-2.3.1 → thth-2.3.2}/thth/topic_advice.py +0 -0
  249. {thth-2.3.1 → thth-2.3.2}/thth/topic_models.py +0 -0
  250. {thth-2.3.1 → thth-2.3.2}/thth/topic_store.py +0 -0
  251. {thth-2.3.1 → thth-2.3.2}/thth/topics.py +0 -0
  252. {thth-2.3.1 → thth-2.3.2}/thth/where_cli.py +0 -0
  253. {thth-2.3.1 → thth-2.3.2}/thth/who_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: thth
3
- Version: 2.3.1
3
+ Version: 2.3.2
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
@@ -45,7 +45,7 @@ watchtower(`~/Developer/watchtower`)の隣に同じ流儀で並べる。watc
45
45
 
46
46
  ## いまの状態(2026-09-13)
47
47
 
48
- 版 **2.3.1**。全件テスト **2287 件**(`python -m pytest tests/ -q -n auto -p no:cacheprovider`・1 skip・rc=0)。
48
+ 版 **2.3.2**。全件テスト **2310 件**(`python -m pytest tests/ -q -n auto -p no:cacheprovider`・1 skip・rc=0)。
49
49
 
50
50
  **媒体**: Threads(稼働)・Bluesky・Mastodon(同席用の台帳あり・未稼働)。
51
51
 
@@ -79,7 +79,7 @@ timer(systemd・`thth systemd` で生成)で毎時投稿、返信の採集
79
79
  - **英語の文書**: [README.en.md](README.en.md)・[docs/usage.en.md](docs/usage.en.md)・[llms.txt](llms.txt)。
80
80
  - **skill**: `skills/thth/SKILL.md`(wheel にも入る)。
81
81
 
82
- - **動くもの**(`thth --help` の全 34 サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`。
82
+ - **動くもの**(`thth --help` の全 35 サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`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`。
83
83
  - **最初の本番投稿の記録**: 2026-09-09、@aoking に疎通確認を 1 本(`17916074118445631`)。
84
84
  - **未着手**: X・Facebook ページ・Instagram の各アダプタ。トピック検索の権限(tester には降りない)。泉のサーバ(v2-5)。
85
85
  - **権限の制約**: tester に降りる scope は 5 つ。削除はできない。
@@ -21,7 +21,7 @@ watchtower(`~/Developer/watchtower`)の隣に同じ流儀で並べる。watc
21
21
 
22
22
  ## いまの状態(2026-09-13)
23
23
 
24
- 版 **2.3.1**。全件テスト **2287 件**(`python -m pytest tests/ -q -n auto -p no:cacheprovider`・1 skip・rc=0)。
24
+ 版 **2.3.2**。全件テスト **2310 件**(`python -m pytest tests/ -q -n auto -p no:cacheprovider`・1 skip・rc=0)。
25
25
 
26
26
  **媒体**: Threads(稼働)・Bluesky・Mastodon(同席用の台帳あり・未稼働)。
27
27
 
@@ -55,7 +55,7 @@ timer(systemd・`thth systemd` で生成)で毎時投稿、返信の採集
55
55
  - **英語の文書**: [README.en.md](README.en.md)・[docs/usage.en.md](docs/usage.en.md)・[llms.txt](llms.txt)。
56
56
  - **skill**: `skills/thth/SKILL.md`(wheel にも入る)。
57
57
 
58
- - **動くもの**(`thth --help` の全 34 サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`。
58
+ - **動くもの**(`thth --help` の全 35 サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`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`。
59
59
  - **最初の本番投稿の記録**: 2026-09-09、@aoking に疎通確認を 1 本(`17916074118445631`)。
60
60
  - **未着手**: X・Facebook ページ・Instagram の各アダプタ。トピック検索の権限(tester には降りない)。泉のサーバ(v2-5)。
61
61
  - **権限の制約**: tester に降りる scope は 5 つ。削除はできない。
@@ -0,0 +1,137 @@
1
+ """`writeback.behind_remote()` と `thth queue`/`schedule`/`board` の遅れ表示
2
+ (発注 T8-2・kopicha 続報)。
3
+
4
+ `thth approve`(と `revoke`・`throw`・`collect`・`retract`)は既に
5
+ `writeback.sync_repo()` を通しているが、**読むだけの口**(`queue`・
6
+ `schedule`・`board`)は通していない。push した直後に読むだけの口を見ると、
7
+ timer の `collect` が pull するまでの間、まだ取り込み前の一覧が出る
8
+ ——**読むだけの口が勝手に pull するのは避ける**のが設計の芯なので、
9
+ ここでは「遅れていることを言う」だけを確かめる(取り込みは T8-3 の
10
+ `thth pull` か `thth approve` が行う)。
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from tests.conftest import init_git_pair, run_git, run_thth, write_queue_file
15
+ from thth import writeback
16
+
17
+
18
+ def test_behind_remoteは追いついていれば0(tmp_path):
19
+ pair = init_git_pair(tmp_path / "a", seed_content="", seed_name=".gitkeep")
20
+ info = writeback.behind_remote(pair["work"])
21
+ assert info == {"behind": 0, "ahead": 0, "head": info["head"],
22
+ "fetched_at": info["fetched_at"], "reason": None}
23
+ assert info["head"]
24
+ assert info["reason"] is None
25
+
26
+
27
+ def test_behind_remoteはfetchだけでpullしない(tmp_path):
28
+ """**pull はしない**——fetch のあと、work の作業ツリー(HEAD)は動かない。"""
29
+ pair = init_git_pair(tmp_path / "a", seed_content="旧", seed_name="a.md")
30
+ head_before = run_git(pair["work"], ["rev-parse", "HEAD"]).stdout.strip()
31
+
32
+ run_git(pair["seed"], ["commit", "--allow-empty", "-m", "second"])
33
+ run_git(pair["seed"], ["push"])
34
+
35
+ info = writeback.behind_remote(pair["work"])
36
+ assert info["behind"] == 1
37
+ assert info["ahead"] == 0
38
+
39
+ head_after = run_git(pair["work"], ["rev-parse", "HEAD"]).stdout.strip()
40
+ assert head_after == head_before, "fetch だけのはずが HEAD が動いた(pull してしまった)"
41
+
42
+
43
+ def test_behind_remoteはfetchできないとNoneとreason(tmp_path):
44
+ broken = tmp_path / "broken"
45
+ run_git_init = __import__("subprocess").run(
46
+ ["git", "init", "-b", "main", str(broken)], capture_output=True, text=True)
47
+ assert run_git_init.returncode == 0
48
+ run_git(str(broken), ["remote", "add", "origin", "/does/not/exist/repo.git"])
49
+ run_git(str(broken), ["commit", "--allow-empty", "-m", "x"])
50
+
51
+ info = writeback.behind_remote(str(broken))
52
+ assert info["behind"] is None
53
+ assert info["ahead"] is None
54
+ assert info["reason"]
55
+ assert "0" != info["reason"] # **0 と混ぜない**: reason があるのに behind: 0 ではない
56
+
57
+
58
+ def test_queueは遅れているときに先頭に1行(isolated_account_factory, tmp_path):
59
+ pair = init_git_pair(tmp_path / "acct", seed_content="", seed_name=".gitkeep")
60
+ account = isolated_account_factory(repo_dir=pair["work"])
61
+ write_queue_file(account["queue_dir"], "a.md",
62
+ fm_overrides={"status": "draft", "account": account["name"]})
63
+
64
+ # **`seed` を origin の最新に合わせてから**新しい commit を積む(`work` の
65
+ # `write_queue_file()` push で origin が進んでいるため、合わせないと
66
+ # `seed` の push 自体が non-fast-forward で失敗する)。
67
+ run_git(pair["seed"], ["pull"])
68
+ run_git(pair["seed"], ["commit", "--allow-empty", "-m", "second"])
69
+ run_git(pair["seed"], ["push"])
70
+
71
+ result = run_thth(["queue", account["name"]])
72
+ assert result.returncode == 0, result.stderr
73
+ assert "remote に 1 commit 分の新しいものがあります" in result.stdout
74
+ assert f"thth pull {account['name']}" in result.stdout
75
+
76
+
77
+ def test_queueは遅れていなければ何も出さない(isolated_account_factory, tmp_path):
78
+ pair = init_git_pair(tmp_path / "acct", seed_content="", seed_name=".gitkeep")
79
+ account = isolated_account_factory(repo_dir=pair["work"])
80
+ write_queue_file(account["queue_dir"], "a.md",
81
+ fm_overrides={"status": "draft", "account": account["name"]})
82
+
83
+ result = run_thth(["queue", account["name"]])
84
+ assert result.returncode == 0, result.stderr
85
+ assert "remote に" not in result.stdout
86
+ assert "thth pull" not in result.stdout
87
+
88
+
89
+ def test_queueのjsonにrepoが乗る(isolated_account_factory, tmp_path):
90
+ pair = init_git_pair(tmp_path / "acct", seed_content="", seed_name=".gitkeep")
91
+ account = isolated_account_factory(repo_dir=pair["work"])
92
+ write_queue_file(account["queue_dir"], "a.md",
93
+ fm_overrides={"status": "draft", "account": account["name"]})
94
+
95
+ result = run_thth(["queue", account["name"], "--json"])
96
+ assert result.returncode == 0, result.stderr
97
+ import json
98
+ out = json.loads(result.stdout)
99
+ assert out[account["name"]]["repo"]["behind"] == 0
100
+
101
+
102
+ def test_scheduleは遅れているときに先頭に1行(isolated_account_factory, tmp_path):
103
+ pair = init_git_pair(tmp_path / "acct", seed_content="", seed_name=".gitkeep")
104
+ account = isolated_account_factory(repo_dir=pair["work"])
105
+ write_queue_file(account["queue_dir"], "a.md",
106
+ fm_overrides={"status": "draft", "account": account["name"],
107
+ "publish_at": "2026-09-20T08:00:00+09:00"})
108
+
109
+ run_git(pair["seed"], ["pull"])
110
+ run_git(pair["seed"], ["commit", "--allow-empty", "-m", "second"])
111
+ run_git(pair["seed"], ["push"])
112
+
113
+ result = run_thth(["schedule", account["name"]])
114
+ assert result.returncode == 0, result.stderr
115
+ assert "remote に 1 commit 分の新しいものがあります" in result.stdout
116
+
117
+
118
+ def test_boardは遅れているaccountの行の下に1行(isolated_account_factory, tmp_path):
119
+ pair = init_git_pair(tmp_path / "acct", seed_content="", seed_name=".gitkeep")
120
+ account = isolated_account_factory(repo_dir=pair["work"])
121
+
122
+ run_git(pair["seed"], ["commit", "--allow-empty", "-m", "second"])
123
+ run_git(pair["seed"], ["push"])
124
+
125
+ result = run_thth(["board"])
126
+ assert result.returncode == 0, result.stderr
127
+ assert "repo が 1 commit 遅れています" in result.stdout
128
+ assert f"thth pull {account['name']}" in result.stdout
129
+
130
+
131
+ def test_boardは遅れていなければ何も出さない(isolated_account_factory, tmp_path):
132
+ pair = init_git_pair(tmp_path / "acct", seed_content="", seed_name=".gitkeep")
133
+ isolated_account_factory(repo_dir=pair["work"])
134
+
135
+ result = run_thth(["board"])
136
+ assert result.returncode == 0, result.stderr
137
+ assert "repo が" not in result.stdout
@@ -0,0 +1,80 @@
1
+ """VM に無いパスを渡したときの断り方(発注 T8-1・kopicha 続報)。
2
+
3
+ `~/.local/bin/thth` は Mac から VM へ ssh する薄いラッパで、引数はそのまま
4
+ VM 側の `thth` に渡る。**Mac 側の相対パスも絶対パスも VM には無い**ので、
5
+ いままでは `FileNotFoundError` の素の traceback が出ていた(`--article
6
+ /tmp/x.json` で踏んだのと同じ根)。
7
+
8
+ ファイルを受け取る全ての引数(`lint`・`approve`・`revoke`・`preview`・
9
+ `send --text-file`・`topics suggest` の queue ファイル・`--article`・
10
+ `--proposal`・`--input`・`--draft`・`--revised-draft`)が、共通の
11
+ `cli._require_vm_path()` を通って rc=2・案内つきで断ることを確かめる。
12
+ 存在するパスは従来どおり動くことも確かめる(回帰)。
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import os
17
+
18
+ from tests.conftest import run_thth, write_queue_file
19
+
20
+ MISSING = "/tmp/thth-t8-does-not-exist-まさかここには無い.md"
21
+ MISSING_JSON = "/tmp/thth-t8-does-not-exist-まさかここには無い.json"
22
+
23
+
24
+ def _assert_vm_refusal(result, *, rc=2):
25
+ combined = result.stdout + result.stderr
26
+ assert result.returncode == rc, f"rc={result.returncode}: {combined}"
27
+ assert "VM" in combined, combined
28
+ assert "thth queue" in combined, combined
29
+ assert "Traceback" not in result.stderr, result.stderr
30
+
31
+
32
+ def test_lintにVMに無いパスを渡すとrc2で案内する():
33
+ result = run_thth(["lint", MISSING])
34
+ _assert_vm_refusal(result)
35
+
36
+
37
+ def test_approveにVMに無いパスを渡すとrc2で案内する():
38
+ result = run_thth(["approve", MISSING])
39
+ _assert_vm_refusal(result)
40
+
41
+
42
+ def test_sendのtext_fileにVMに無いパスを渡すとrc2で案内する(isolated_account):
43
+ result = run_thth(["send", isolated_account["name"], "--text-file", MISSING])
44
+ _assert_vm_refusal(result)
45
+
46
+
47
+ def test_topics_suggestのarticleにVMに無いパスを渡すとrc2で案内する(isolated_account):
48
+ queue_path = write_queue_file(
49
+ isolated_account["queue_dir"], "q.md",
50
+ body="## threads\n\nhello https://example.test/x\n",
51
+ fm_overrides={"status": "draft", "account": isolated_account["name"]})
52
+ result = run_thth(["topics", "suggest", queue_path, "--article", MISSING_JSON])
53
+ _assert_vm_refusal(result)
54
+
55
+
56
+ def test_revokeにVMに無いパスを渡すとrc2で案内する():
57
+ result = run_thth(["revoke", MISSING, "--by", "test"])
58
+ _assert_vm_refusal(result)
59
+
60
+
61
+ def test_previewにVMに無いパスを渡すとrc2で案内する():
62
+ result = run_thth(["preview", MISSING])
63
+ _assert_vm_refusal(result)
64
+
65
+
66
+ def test_存在するパスは従来どおり動く(isolated_account):
67
+ """回帰: 存在するパスには案内を出さない(発注書「存在するパスは従来どおり」)。"""
68
+ queue_path = write_queue_file(
69
+ isolated_account["queue_dir"], "ok.md",
70
+ body="## threads\n\nこんにちは https://example.test/x\n",
71
+ fm_overrides={"status": "draft", "account": isolated_account["name"]})
72
+ assert os.path.exists(queue_path)
73
+
74
+ result = run_thth(["lint", queue_path])
75
+ assert "VM" not in (result.stdout + result.stderr)
76
+ assert "thth queue" not in (result.stdout + result.stderr)
77
+
78
+ result = run_thth(["preview", queue_path])
79
+ assert result.returncode == 0, result.stderr
80
+ assert "こんにちは" in result.stdout
@@ -0,0 +1,96 @@
1
+ """`thth pull <account|--project P>`(発注 T8-3・kopicha 続報)。
2
+
3
+ 読むだけの口(`queue`・`schedule`・`board`。T8-2)は遅れを**言うだけ**で
4
+ pull しない。この道具は、その案内から誘導される**明示の**取り込み口——
5
+ `writeback.sync_repo()` を呼ぶだけで、`thth approve` が既に通しているのと
6
+ 同じ関数・同じ repo ロックを使う。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+
12
+ from tests.conftest import init_git_pair, run_git, run_thth
13
+
14
+
15
+ def test_pullはffして取り込む(isolated_account_factory, tmp_path):
16
+ pair = init_git_pair(tmp_path / "acct", seed_content="旧", seed_name="a.md")
17
+ account = isolated_account_factory(repo_dir=pair["work"])
18
+ head_before = run_git(pair["work"], ["rev-parse", "--short", "HEAD"]).stdout.strip()
19
+
20
+ run_git(pair["seed"], ["commit", "--allow-empty", "-m", "second"])
21
+ run_git(pair["seed"], ["push"])
22
+ head_after_remote = run_git(pair["seed"], ["rev-parse", "--short", "HEAD"]).stdout.strip()
23
+
24
+ result = run_thth(["pull", account["name"]])
25
+ assert result.returncode == 0, result.stderr
26
+ assert f"取り込みました: {head_before} → {head_after_remote}(1 commit)" in result.stdout
27
+
28
+ # 本当に取り込まれている(work の HEAD が動いた)。
29
+ head_now = run_git(pair["work"], ["rev-parse", "--short", "HEAD"]).stdout.strip()
30
+ assert head_now == head_after_remote
31
+
32
+
33
+ def test_pullはすでに最新なら何も変えない(isolated_account_factory, tmp_path):
34
+ pair = init_git_pair(tmp_path / "acct", seed_content="", seed_name=".gitkeep")
35
+ account = isolated_account_factory(repo_dir=pair["work"])
36
+ head_before = run_git(pair["work"], ["rev-parse", "--short", "HEAD"]).stdout.strip()
37
+
38
+ result = run_thth(["pull", account["name"]])
39
+ assert result.returncode == 0, result.stderr
40
+ assert "すでに最新です" in result.stdout
41
+
42
+ head_after = run_git(pair["work"], ["rev-parse", "--short", "HEAD"]).stdout.strip()
43
+ assert head_after == head_before
44
+
45
+
46
+ def test_pullのjsonはbehind_remoteの形(isolated_account_factory, tmp_path):
47
+ pair = init_git_pair(tmp_path / "acct", seed_content="", seed_name=".gitkeep")
48
+ account = isolated_account_factory(repo_dir=pair["work"])
49
+
50
+ result = run_thth(["pull", account["name"], "--json"])
51
+ assert result.returncode == 0, result.stderr
52
+ out = json.loads(result.stdout)
53
+ assert len(out) == 1
54
+ row = out[0]
55
+ assert row["account"] == account["name"]
56
+ assert row["ok"] is True
57
+ assert set(row["repo"].keys()) == {"behind", "ahead", "head", "fetched_at", "reason"}
58
+ assert row["repo"]["behind"] == 0
59
+
60
+
61
+ def test_pullのprojectは同じrepoを共有する2accountで1回だけ引く(
62
+ isolated_account_factory, tmp_path):
63
+ pair = init_git_pair(tmp_path / "acct", seed_content="旧", seed_name="a.md")
64
+ account_a = isolated_account_factory("a-threads", repo_dir=pair["work"], project="shared")
65
+ account_b = isolated_account_factory("b-threads", repo_dir=pair["work"], project="shared")
66
+ assert account_a["repo_dir"] == account_b["repo_dir"]
67
+
68
+ run_git(pair["seed"], ["commit", "--allow-empty", "-m", "second"])
69
+ run_git(pair["seed"], ["push"])
70
+
71
+ result = run_thth(["pull", "--project", "shared", "--json"])
72
+ assert result.returncode == 0, result.stderr
73
+ out = json.loads(result.stdout)
74
+ # **重複なく 1 回だけ**——2 account だが repo は 1 つ。
75
+ assert len(out) == 1
76
+ assert out[0]["repo"]["behind"] == 0
77
+
78
+
79
+ def test_pullは失敗をsync_repoの理由のままrc1(isolated_account_factory, tmp_path):
80
+ account = isolated_account_factory(repo_dir=str(tmp_path / "no-such-repo-dir"))
81
+
82
+ result = run_thth(["pull", account["name"]])
83
+ assert result.returncode == 1, result.stdout
84
+ assert "取り込めませんでした" in result.stderr or "見当たりません" in result.stderr
85
+
86
+
87
+ def test_pullはaccountもprojectも無ければ断る(isolated_account_factory):
88
+ result = run_thth(["pull"])
89
+ assert result.returncode == 2
90
+ assert "account" in result.stderr
91
+
92
+
93
+ def test_pullは未知のprojectを断る(isolated_account_factory):
94
+ result = run_thth(["pull", "--project", "no-such-project"])
95
+ assert result.returncode == 2
96
+ assert "no-such-project" in result.stderr
@@ -0,0 +1 @@
1
+ 2.3.2