thth 3.8.0__tar.gz → 3.8.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.
- {thth-3.8.0 → thth-3.8.2}/PKG-INFO +1 -1
- {thth-3.8.0 → thth-3.8.2}/mcp/server.py +5 -2
- {thth-3.8.0 → thth-3.8.2}/tests/test_v380_plaza_moments.py +6 -2
- {thth-3.8.0 → thth-3.8.2}/tests/test_v380_plaza_owner.py +5 -3
- thth-3.8.2/tests/test_v381_plaza_visibility_default.py +100 -0
- thth-3.8.2/tests/test_v382_confirm_file.py +218 -0
- thth-3.8.2/tests/test_v382_first_stage_not_refusal.py +112 -0
- thth-3.8.2/tests/test_v382_reader_lock_and_push_pending.py +105 -0
- thth-3.8.2/tests/test_v382_sync_without_fetch_head.py +191 -0
- thth-3.8.2/thth/VERSION +1 -0
- {thth-3.8.0 → thth-3.8.2}/thth/cli.py +331 -7
- {thth-3.8.0 → thth-3.8.2}/thth/plaza.py +29 -9
- {thth-3.8.0 → thth-3.8.2}/thth/plaza_cli.py +12 -3
- {thth-3.8.0 → thth-3.8.2}/thth/refusals.py +23 -0
- {thth-3.8.0 → thth-3.8.2}/thth/report_service.py +3 -1
- {thth-3.8.0 → thth-3.8.2}/thth/writeback.py +140 -17
- thth-3.8.0/thth/VERSION +0 -1
- {thth-3.8.0 → thth-3.8.2}/.gitignore +0 -0
- {thth-3.8.0 → thth-3.8.2}/LICENSE +0 -0
- {thth-3.8.0 → thth-3.8.2}/README.md +0 -0
- {thth-3.8.0 → thth-3.8.2}/accounts.example/bluesky.json +0 -0
- {thth-3.8.0 → thth-3.8.2}/accounts.example/mastodon.json +0 -0
- {thth-3.8.0 → thth-3.8.2}/accounts.example/threads.json +0 -0
- {thth-3.8.0 → thth-3.8.2}/accounts.example/x.json +0 -0
- {thth-3.8.0 → thth-3.8.2}/bin/thth +0 -0
- {thth-3.8.0 → thth-3.8.2}/bin/thth-remote +0 -0
- {thth-3.8.0 → thth-3.8.2}/bin/thth-run +0 -0
- {thth-3.8.0 → thth-3.8.2}/pyproject.toml +0 -0
- {thth-3.8.0 → thth-3.8.2}/skills/thth/SKILL.md +0 -0
- {thth-3.8.0 → thth-3.8.2}/skills/thth-admin/SKILL.md +0 -0
- {thth-3.8.0 → thth-3.8.2}/systemd/thth@.service +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/__init__.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/conftest.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/coordination_snapshot.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/fixtures/GraphemeBreakTest-17.0.0.txt +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/fixtures/build_injection_eval.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/fixtures/callback/worker_responses.json +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/fixtures/injection_eval.json +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/fixtures/umami-bile.md +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/__init__.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/fake_oauth_server.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/fake_push_adapter.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/fake_redirect_server.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/fake_search_adapter.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/fake_threads_reader.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/fake_threads_status.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/fake_threads_writes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/fake_x.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/hold_lock.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/hold_repo_lock.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/helpers/worker_probe.mjs +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_27_acceptance_fixes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_account_name_check.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_account_report.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_accounts_dir.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_adapter_boundary.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_admin_http_mcp.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_admin_log.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_admin_notifications.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_admin_repairs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_admin_report.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_after_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_analytics_comparison.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_analytics_report.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_api_diagnostic.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_api_error_body.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_app_env_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_approval.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_approve_cmd.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_ask.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_atlas_fifth_review.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_atlas_fourth_review.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_atlas_review.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_atlas_second_review.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_atlas_third_review.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_audit1_fixes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_audit2_fixes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_audit_b_counterexamples.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_audit_b_m1_semantics.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_audit_b_n1_depth.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_audit_b_r1_closure.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_audit_v201.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_audit_v23_details.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_auth.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_auth_identity_required.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_behind_remote.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_bluesky_adapter.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_bluesky_secret_scrub.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_board_needs_review.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_board_running.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_bundle.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_charcount.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_cli_entrypoint.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_cli_help.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_cli_vm_path.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_collect.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_collect_engagements.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_collect_runs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_collect_sent.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_comparability.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_control_chars_refused.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_conversation_paging.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_count_for.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_docs_links.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_doctor.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_doctor_all_permissions.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_doctor_next_step.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_duplicate_front_matter_keys.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_editorial_audit.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_editorial_n3_n4.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_engagements.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_external_acceptance.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_fake_api.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_form_spec.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_form_spec_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_forms.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_fresh_install.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_front_matter_injection.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_healthcheck_notifications.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_hypotheses.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_hypothesis_boundaries.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_hypothesis_units.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_impact_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_inbox_granted_scopes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_incident_notifications.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_independent_thread_review.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_independent_topic_review.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_independent_topic_review_r2.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_inflight.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_inflight_tag_body_hash.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_jst.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_lint.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_list_projections.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_llm_trial.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_llm_trial_engage.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_lock.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_m2_independent.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_maintain.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_mastodon_adapter.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_mastodon_visibility.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_max_per_run.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_mcp.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_mcp_robustness.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_measured.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_media_accounts.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_media_next_steps.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_media_roundtrip.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_mismatch_diagnostics.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_n5_reply_null_element.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_n6_replies_incomplete_ledger.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_n7_doctor_error_body.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_no_limits.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_observation_provenance.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_operations_handoff.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_packaging.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_preview.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_production_mode.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_pull_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_queue.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_quiet_hours.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_r2_r4_r5.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_read_cli_bare_runtimeerror.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_reason_adoption.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_redact.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_redirect_token_leak.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_refresh.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_refresh_destination.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_rejudge_new.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_rejudge_r2_r3.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_replies.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_replies_refresh.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_repo_lock.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_repo_sync.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_report_checkout_import.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_report_http.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_report_isolation.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_report_service.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_reverse_audit.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_review_case_id_cluster_m2.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_review_case_id_cluster_r2.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_review_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_review_evidence_account.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_review_form_spec_and_case_id.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_review_provenance.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_review_records.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_revoke.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_roundtrip.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_run_missing_env.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_run_record_failclosed.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_runs_collected.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_runs_read_rows.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_schedule_multiple.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_schedule_view.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_scopes_source_requested.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_search_engage_targets.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_search_media_routes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_secret_value_redact.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_secrets_grep.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_security_small_fixes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_select.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_selfupdate.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_send.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_send_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_send_control_chars.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_send_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_send_visibility.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_sent_integrity.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_server_json.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_site.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_skill_doc.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_study_report.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_systemd.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_thread_publish.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_thread_read.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_threadrun.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_threads_read_permissions.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_threadshape.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_thth_root.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_token_exists.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_token_scopes_record.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_token_set.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_topic.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_topic_advice.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_topic_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_topic_models.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_topic_review_fixes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_topic_shelf_observers.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_topic_store.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_topics_record_concurrent.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_topics_tool.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_trial_frictions.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_admin_transaction.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_approve_account.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_json_contract.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_length.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_lock_wait.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_mastodon_scopes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_mentions.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_post_urls.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_repo_paths.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_reposts.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_study_add.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_tool_version.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_unanswered.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_unanswered_scope.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_where_arguments.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_where_filters.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v210_who_arguments.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v211_auth_integration.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v211_authflow.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v211_bluesky_stdin.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v211_doctor_binding.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v211_mastodon_auth.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v211_x_auth.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_approval_relay.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_ask_coordination.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_budget.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_budget_auth.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_budget_mcp.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_deletion.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_job_completion_fault.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_leave.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_leave_gate.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_leave_providers.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_leave_token_safety.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_mastodon_guide.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_mcp_compat.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_missing_token.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_read_coordination.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_server_writes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_token_parent.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v212_write_storage.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v2131_send_dry_run.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v2131_threads_publish_wait.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v2132_send_inflight_hint.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v2132_threads_create_timeout.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v2132_threads_create_unknown.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_acceptance_fixes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_adts_asf.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_approval_attachments.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_attachment_kinds.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bluesky_external.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bluesky_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bluesky_metadata.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bluesky_metadata_uri.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bluesky_metadata_video.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bluesky_record.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bluesky_record_video.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bluesky_video.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bluesky_webm_named.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_bmff_typed_metadata.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_budget_lock.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_capabilities.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_carryover_timeout.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_graphemes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_auth_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_bmff_audio.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_flac.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_gif.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_link.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_mp3.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_ogg_opus.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_ogg_vorbis.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_poll.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_primary_size_boundary.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_quote.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_thumbnail.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_wav.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_webm_audio.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_mastodon_webm_video.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_media_cleanup.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_media_formats.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_media_foundation.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_media_prepared.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_media_relay.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_media_relay_refusals.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_media_upload.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_receipt_deadline.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_review_followups.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_stage810_audit.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_static_diagnostics.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_stop_observation.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_systemd_worker.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_ghost.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_gif.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_link.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_media_spoiler.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_poll.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_quote.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_quote_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_replies.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_text.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_text_spoiler.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_threads_video.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_transport_review.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_video_typed_metadata.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_vorbis_comment_boundary.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v213_webm_schema_boundary.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v2141_draft_lint_reason.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v2141_managed_repo_umask.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v214_release_signing.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v214_x_adapter.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v214_x_auth.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v214_x_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v214_x_posts_budget.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v21b_approved_writes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v28_tags.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v310_morning.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v310_search_timeout.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v310_watch_words.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v311_leave_same_user.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v311_mastodon_retract.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v311_morning_overdue.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v311_morning_own_exclusion.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v311_morning_unanswered_window.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v311_notifications_guidance.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v311_replies_owned.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v312_leave_shared_token.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v312_own_handles_stopped.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v312_report_admin.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v312_report_channel.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v312_report_inbox.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v312_report_mcp_user.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v31_analysis.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v31_collection.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v320_reply_to_file_display.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v320_reply_to_file_lint.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v320_reply_to_file_resolve.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v320_report_fixes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v330_fingerprint_version.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v330_held.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v330_held_names.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v330_last_refusals.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v330_notification_route.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v330_observe.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v330_report_friction.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v330_report_words.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v330_reports_effect.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v331_inflight_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v331_listing_resolve.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v331_outbox_retention.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v331_publish_status.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v331_self_resolve.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_docs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_fields.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_mcp.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_morning.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_observe.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_open.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_open_confirm.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_reply.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v340_plaza_store.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v350_map_docs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v350_map_nodes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v350_map_observe.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v350_map_plaza.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v350_map_retention.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v350_map_self.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v350_map_show.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v350_map_world.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v351_leave_timers.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v351_plaza_open_holes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v351_who_owned.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v360_goal_analytics.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v360_goal_docs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v360_goal_observe_map.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v360_goal_plaza.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v360_goal_record.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v360_goal_send.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v360_report_timeline.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_account_self_healing.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_approve_pending.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_click_attribution.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_collection_stale_basis.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_docs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_follow_comparison.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_local_path.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_weekly_goals.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_where_aggregate.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_where_also.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v370_where_zero.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v380_docs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v380_plaza_digest.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v380_plaza_from.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v380_plaza_mcp.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v380_plaza_received.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_v380_plaza_triggers.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_value_domains_shown.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_version.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_where_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_who_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_writeback_rebase.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_writeback_validate.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/tests/test_x_metrics.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/__init__.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/__main__.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/_grapheme_data.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/account_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/account_report.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/accounts.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/__init__.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/auth_mastodon.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/auth_threads.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/auth_x.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/base.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/bluesky.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/bluesky_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/bluesky_video.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/mastodon.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/mastodon_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/threads.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/threads_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/x.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/adapters/x_media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/admin_log.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/admin_notifications.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/admin_report.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/after_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/analytics_comparison.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/analytics_goals.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/analytics_report.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/analytics_shapes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/analytics_threads.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/analytics_weekly.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/api_diagnostic.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/appconfig.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/appenv.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/approval.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/approval_jobs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/approval_relay.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/approve_pending.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/ask.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/ask_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/audioformats.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/authclients.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/authflow.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/authpassword.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/bluesky_metadata.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/budget_x.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/budget_x_posts.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/bundle.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/click_attribution.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/collect.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/collection_context.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/collection_status.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/core.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/deletion.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/doctor.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/ebmlaudio.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/ebmlvideo.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/engagements.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/fingerprint_golden.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/flacformats.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/forms.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/goals.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/graphemes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/handoff_cursor.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/healthcheck.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/httpsafe.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/incident.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/inflight.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/inflight_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/inflight_resolve.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/jst.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/leave.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/leave_gate.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/lint.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/lock.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/maintain.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/managed_repo.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/map_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/map_store.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/map_view.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/map_world.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/measured.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/media.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/media_capabilities.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/media_cleanup.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/media_delivery.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/media_relay.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/media_uploads.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/mediaformats.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/morning.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/mpeg_audio.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/notification_route.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/oauth.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/oggformats.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/operations_handoff.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/plaza_from.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/plaza_moments.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/plaza_observe.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/plaza_reads.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/plaza_redact.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/postid.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/private_store.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/queuefile.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/read_coordination.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/read_window.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/redact.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/replies.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/report.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/report_details.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/report_http.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/report_inbox.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/report_isolation.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/report_timeline.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/retract_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/runs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/scopes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/secrets_fs.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/select.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/selfupdate.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/sent.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/server_files.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/server_writes.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/stop_observation.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/study_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/study_report.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/systemd_gen.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/tags.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/thread_read.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/threadrun.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/threads_read_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/threadshape.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/threadthrow.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/timer_cleanup.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/tool_version.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/topic_advice.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/topic_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/topic_models.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/topic_store.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/topics.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/unanswered.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/vorbisformats.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/watch_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/where_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/who_cli.py +0 -0
- {thth-3.8.0 → thth-3.8.2}/thth/x_metrics.py +0 -0
|
@@ -717,7 +717,9 @@ PLAZA_TOOLS = [
|
|
|
717
717
|
"how〔数字を出し直せる thth の命令〕が必須)・気づき(finding)・問い(question)を"
|
|
718
718
|
"置く。scope(媒体・企画の範囲)は必須。置いたものは同じ持ち主の全 account だけに"
|
|
719
719
|
"見える(visibility: owner は管理者が登録した持ち主の組の全 project に一段で見せる。"
|
|
720
|
-
"
|
|
720
|
+
"project が組に入っていれば visibility 省略の既定は owner・入っていなければ"
|
|
721
|
+
"従前どおり project(設計 3.8.1)。他の持ち主に見せる open は人が CLI の二段確認"
|
|
722
|
+
"で行う・この口には無い)。"
|
|
721
723
|
"evidence_level の observed は道具だけが付ける。from_tool(analytics-report・after・"
|
|
722
724
|
"study-report)・from_doc・from_report で、道具がその出力を置く時点で作り直して"
|
|
723
725
|
"数字を観測の欄と本文の下書きに入れる(body は任意の解釈)",
|
|
@@ -738,7 +740,8 @@ PLAZA_TOOLS = [
|
|
|
738
740
|
"goal": {"type": "string", "enum": ["reach", "click", "follow", "reply"],
|
|
739
741
|
"description": "施策の目的(任意)。媒体をまたいで同じ目的の施策を比べる札"},
|
|
740
742
|
"visibility": {"type": "string", "enum": ["project", "owner"],
|
|
741
|
-
"description": "project
|
|
743
|
+
"description": "project か owner(持ち主の組・管理者が組を登録したときだけ)。"
|
|
744
|
+
"省略時は project が組に入っていれば owner・入っていなければ project"},
|
|
742
745
|
"trial_due": {"type": "string", "description": "finding の追試の予定日(2026-10-08 か timezone 付きの時刻)"},
|
|
743
746
|
"from_tool": {"type": "string", "enum": ["analytics-report", "after", "study-report"],
|
|
744
747
|
"description": "道具の出力から置く(analytics-report・after は from_account・"
|
|
@@ -211,10 +211,14 @@ def test_listが自分の書き込みだけなら管理者に頼む命令の形
|
|
|
211
211
|
post(account="kopicha-bsky", title="自分の持ち主の")
|
|
212
212
|
payload = plaza.list_posts(viewer("kopicha-threads"))
|
|
213
213
|
assert payload["hint"]["reason"] == "only_own_posts"
|
|
214
|
-
#
|
|
214
|
+
# 組と参加がそろっても、組の他の project がまだ owner 範囲に何も置いていなければ、
|
|
215
|
+
# 頼むことは無いのでその旨だけ言う(「組の登録を頼んで」ではない・設計 3.8.1)。
|
|
215
216
|
plaza.set_owner("masaru", ["kopicha", "other"], by="operator")
|
|
216
217
|
plaza.set_membership("kopicha", joined=True, by="operator")
|
|
217
|
-
|
|
218
|
+
hint = plaza.list_posts(viewer("kopicha-threads"))["hint"]
|
|
219
|
+
assert hint == {"reason": "only_own_posts", "commands": [],
|
|
220
|
+
"line": "広場: 組(masaru)の他の project の書き込みは owner 範囲にした"
|
|
221
|
+
"ものだけ見えます。まだ 0 件です"}
|
|
218
222
|
# 組の相手の書き込みが読めるなら案内は出さない。
|
|
219
223
|
plaza.set_membership("kopicha", joined=False, by="operator")
|
|
220
224
|
post(account="other-threads", title="組", by="o", visibility="owner")
|
|
@@ -63,8 +63,9 @@ def test_組に登録したprojectだけにownerの書き込みが見える(grou
|
|
|
63
63
|
assert plaza.list_posts(viewer("third-threads"))["n"] == 0
|
|
64
64
|
third_text = json.dumps(plaza.list_posts(viewer("third-threads")), ensure_ascii=False)
|
|
65
65
|
assert "OWNER-TITLE" not in third_text
|
|
66
|
-
# project
|
|
67
|
-
|
|
66
|
+
# project の範囲の書き込みは組の相手にも見せない(従前どおり)。組があるので既定は
|
|
67
|
+
# owner に変わった(設計 3.8.1)ため、ここは project を明示して確かめる。
|
|
68
|
+
inside = post(title="PROJECT-ONLY-2", visibility="project")
|
|
68
69
|
with pytest.raises(plaza.PlazaError, match="^plaza_not_found$"):
|
|
69
70
|
plaza.show(inside["plaza_id"], viewer("other-threads"))
|
|
70
71
|
|
|
@@ -181,7 +182,8 @@ def test_observeの0段に組の新着(grouped, monkeypatch):
|
|
|
181
182
|
now = jst.now_jst()
|
|
182
183
|
plaza.set_owner("masaru", ["kopicha", "other"], by="operator")
|
|
183
184
|
post(title="組の気づき", visibility="owner", now=now - datetime.timedelta(hours=1))
|
|
184
|
-
|
|
185
|
+
# 組があるので既定は owner に変わった(設計 3.8.1)ため、project 側の 1 件は明示する。
|
|
186
|
+
post(title="PROJECT-INSIDE", visibility="project", now=now - datetime.timedelta(hours=1))
|
|
185
187
|
cell = next(s for s in morning.build("other-threads", now=now, mark=False)["sections"]
|
|
186
188
|
if s["section"] == "tool")["value"]["plaza"]
|
|
187
189
|
assert cell["owner_new"] == 1 and cell["owner_denominator"] == 1
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""3.8.1 visibility の既定は project の持ち主の組次第(設計 3.8.1)。
|
|
2
|
+
|
|
3
|
+
見るのは:
|
|
4
|
+
- project が持ち主の組に入っていれば、CLI の `plaza post`(--open・--owner 無し)と
|
|
5
|
+
MCP の `thth_plaza_post`(visibility 省略)の既定は owner に変わる。
|
|
6
|
+
- 組に入っていない project は従前どおり既定が project(回帰固定)。
|
|
7
|
+
- open は明示(`--open`/二段確認)が無ければ決して既定にならない(回帰固定・
|
|
8
|
+
組があっても・open に参加していても)。
|
|
9
|
+
- 明示の `--owner`/MCP の `visibility` があれば、組があってもそれに従う。
|
|
10
|
+
- 既定で owner にしたときだけ応答に「範囲: owner(組 <名前> の既定)」と `visibility_default`。
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
from thth import cli, plaza
|
|
17
|
+
from tests.test_v340_plaza_store import owners # noqa: F401 (fixture)
|
|
18
|
+
from tests.test_v340_plaza_mcp import _post, _tenant, _text
|
|
19
|
+
from tests.test_v380_plaza_owner import grouped # noqa: F401 (fixture)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _body(tmp_path, text="本文です。朝の投稿で試した", name="body.txt"):
|
|
23
|
+
path = tmp_path / name
|
|
24
|
+
path.write_text(text, encoding="utf-8")
|
|
25
|
+
return str(path)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _cli_post(tmp_path, capsys, account, title, *, extra=()):
|
|
29
|
+
rc = cli.main(["plaza", "post", account, "--kind", "finding", "--title", title,
|
|
30
|
+
"--body-file", _body(tmp_path, name=title + ".txt"),
|
|
31
|
+
"--scope", "Threads の朝の投稿", "--by", "k", *extra, "--json"])
|
|
32
|
+
out = capsys.readouterr().out
|
|
33
|
+
return rc, (json.loads(out) if out.strip() else None)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_CLI_組が無いうちは既定がproject_組に入れば既定がowner(grouped, capsys, tmp_path):
|
|
37
|
+
# 組が無い間は従前どおり既定が project(回帰固定)。
|
|
38
|
+
rc, posted = _cli_post(tmp_path, capsys, "kopicha-threads", "組なし")
|
|
39
|
+
assert rc == 0 and posted["scope"] == "project" and posted["visibility_default"] is None
|
|
40
|
+
|
|
41
|
+
plaza.set_owner("masaru", ["kopicha", "other"], by="operator")
|
|
42
|
+
rc, posted = _cli_post(tmp_path, capsys, "kopicha-threads", "組あり")
|
|
43
|
+
assert rc == 0 and posted["scope"] == "owner" and posted["visibility_default"] == "masaru"
|
|
44
|
+
|
|
45
|
+
# 組に入っていない project(third)は既定が project のまま(回帰固定)。
|
|
46
|
+
rc, posted = _cli_post(tmp_path, capsys, "third-threads", "組の外")
|
|
47
|
+
assert rc == 0 and posted["scope"] == "project" and posted["visibility_default"] is None
|
|
48
|
+
|
|
49
|
+
# 明示の --owner があれば、組が無くても(既に組があっても)それに従う。
|
|
50
|
+
rc, posted = _cli_post(tmp_path, capsys, "kopicha-threads", "明示のowner",
|
|
51
|
+
extra=("--owner",))
|
|
52
|
+
assert rc == 0 and posted["scope"] == "owner" and posted["visibility_default"] is None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_CLIの人向け応答に範囲の既定を明記する(grouped, capsys, tmp_path):
|
|
56
|
+
plaza.set_owner("masaru", ["kopicha", "other"], by="operator")
|
|
57
|
+
rc = cli.main(["plaza", "post", "kopicha-threads", "--kind", "finding", "--title", "人向け",
|
|
58
|
+
"--body-file", _body(tmp_path), "--scope", "Threads の朝の投稿", "--by", "k"])
|
|
59
|
+
out = capsys.readouterr().out
|
|
60
|
+
assert rc == 0 and "範囲: owner(組 masaru の既定)" in out
|
|
61
|
+
# 明示の --owner のときは既定の案内を出さない(すでに自分で選んでいる)。
|
|
62
|
+
rc = cli.main(["plaza", "post", "kopicha-threads", "--kind", "finding", "--title", "明示",
|
|
63
|
+
"--body-file", _body(tmp_path, name="明示.txt"), "--scope", "Threads の朝の投稿",
|
|
64
|
+
"--by", "k", "--owner"])
|
|
65
|
+
out = capsys.readouterr().out
|
|
66
|
+
assert rc == 0 and "範囲: owner(組" not in out
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_openは組があっても参加していても既定にならない(grouped, capsys, tmp_path):
|
|
70
|
+
plaza.set_owner("masaru", ["kopicha", "other"], by="operator")
|
|
71
|
+
plaza.set_membership("kopicha", joined=True, by="operator")
|
|
72
|
+
rc, posted = _cli_post(tmp_path, capsys, "kopicha-threads", "参加中でも既定はowner")
|
|
73
|
+
assert rc == 0 and posted["scope"] == "owner"
|
|
74
|
+
# --open は今までどおり明示 + 二段確認が要る(既定にはならない)。
|
|
75
|
+
rc = cli.main(["plaza", "post", "kopicha-threads", "--kind", "finding", "--title", "openは明示",
|
|
76
|
+
"--body-file", _body(tmp_path, name="open.txt"), "--scope", "Threads の朝の投稿",
|
|
77
|
+
"--by", "k", "--open", "--json"])
|
|
78
|
+
out = json.loads(capsys.readouterr().out)
|
|
79
|
+
# 一段目は何も書かずに終わる(approve の一段目と同じ rc 1・`_emit_preview`)。
|
|
80
|
+
assert rc == 1 and out["report_type"] == "plaza_open_preview" and out["opened"] is False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_MCPのvisibility省略は組次第_明示すれば従う(tmp_path, monkeypatch):
|
|
84
|
+
_root, server = _tenant(tmp_path, monkeypatch)
|
|
85
|
+
# 組が無ければ従前どおり project(回帰固定)。
|
|
86
|
+
posted = json.loads(_text(_post(server)))
|
|
87
|
+
assert posted["scope"] == "project" and posted["visibility_default"] is None
|
|
88
|
+
|
|
89
|
+
plaza.set_owner("masaru", ["kopicha", "other"], by="operator")
|
|
90
|
+
# 省略すれば owner が既定。
|
|
91
|
+
defaulted = json.loads(_text(_post(server, title="省略で owner")))
|
|
92
|
+
assert defaulted["scope"] == "owner" and defaulted["visibility_default"] == "masaru"
|
|
93
|
+
|
|
94
|
+
# 明示すればそれに従う(組があっても project のまま)。
|
|
95
|
+
explicit = json.loads(_text(_post(server, title="明示は project", visibility="project")))
|
|
96
|
+
assert explicit["scope"] == "project" and explicit["visibility_default"] is None
|
|
97
|
+
|
|
98
|
+
# open は MCP の口に無い(既定にもならない・従前どおり)。
|
|
99
|
+
denied = _post(server, title="open は無い", visibility="open")
|
|
100
|
+
assert denied["isError"] and _text(denied) == "open_requires_cli"
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""3.8.2 件 2: `thth approve --confirm-file <file> --by <名前>`(複数の確定を 1 回で)。
|
|
2
|
+
|
|
3
|
+
- 1 行に `<原稿のパス> <digest>`。1 回の同期と 1 回のロックの中で行の順に確定する。
|
|
4
|
+
- digest が合わない本はその 1 本だけ断って他は進める(断った本と理由は最後に並べる)。
|
|
5
|
+
- commit は 1 本ずつ・push は最後に 1 回。
|
|
6
|
+
- 同期・push で止まったら、どこまで通ったかと残りをそのまま打てる命令を出す。
|
|
7
|
+
- 1 段目(複数のとき)に `--confirm-file` の中身の形を 1 行。
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import shlex
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from tests.conftest import run_git, run_thth, write_queue_file
|
|
18
|
+
from thth import cli
|
|
19
|
+
from thth import lock as lock_mod
|
|
20
|
+
from thth import writeback
|
|
21
|
+
|
|
22
|
+
NAMES = ["a.md", "b.md", "c.md"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _digest(stdout: str) -> str:
|
|
26
|
+
return [line.split(": ", 1)[1].strip() for line in stdout.splitlines()
|
|
27
|
+
if line.startswith("digest: ")][0]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def drafts(isolated_account, tmp_path):
|
|
32
|
+
"""3 本の draft(commit・push 済み)と、それぞれの 1 段目の digest。"""
|
|
33
|
+
paths = [write_queue_file(isolated_account["queue_dir"], name,
|
|
34
|
+
body=f"## threads\n\n{name} の本文です。\n",
|
|
35
|
+
fm_overrides={"account": isolated_account["name"], "status": "draft",
|
|
36
|
+
"approved_sha": None})
|
|
37
|
+
for name in NAMES]
|
|
38
|
+
digests = []
|
|
39
|
+
for path in paths:
|
|
40
|
+
first = run_thth(["approve", path])
|
|
41
|
+
assert first.returncode == 1, first.stderr
|
|
42
|
+
digests.append(_digest(first.stdout))
|
|
43
|
+
work = isolated_account["repo_dir"]
|
|
44
|
+
return {"paths": paths, "digests": digests, "work": work,
|
|
45
|
+
"bare": os.path.join(os.path.dirname(work), "origin.git"),
|
|
46
|
+
"seed": os.path.join(os.path.dirname(work), "seed"),
|
|
47
|
+
"file": str(tmp_path / "confirm.txt")}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _write_confirm_file(drafts, digests=None, extra_lines=()) -> str:
|
|
51
|
+
digests = digests or drafts["digests"]
|
|
52
|
+
lines = ["# 確定の一覧(masaru)", ""]
|
|
53
|
+
lines += [f"{path} {digest}" for path, digest in zip(drafts["paths"], digests)]
|
|
54
|
+
lines += list(extra_lines)
|
|
55
|
+
with open(drafts["file"], "w", encoding="utf-8") as stream:
|
|
56
|
+
stream.write("\n".join(lines) + "\n")
|
|
57
|
+
return drafts["file"]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _origin_status(drafts, name: str) -> str:
|
|
61
|
+
shown = run_git(drafts["bare"], ["show", f"main:docs/sns/queue/{name}"]).stdout
|
|
62
|
+
return [line.split(": ", 1)[1] for line in shown.splitlines() if line.startswith("status: ")][0]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _approval_commits(repo: str, ref: str = "HEAD") -> list:
|
|
66
|
+
log = run_git(repo, ["log", "--format=%s", ref]).stdout.splitlines()
|
|
67
|
+
return [subject for subject in log if subject.startswith("承認: ")]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_3本の組が全部通る_同期とロックとpushは1回ずつ_commitは1本ずつ(drafts, monkeypatch, capsys):
|
|
71
|
+
counts = {"sync": 0, "push": 0, "lock": 0}
|
|
72
|
+
real_sync, real_push, real_acquire = writeback.sync_repo, writeback.push_committed, lock_mod.acquire
|
|
73
|
+
|
|
74
|
+
def sync(repo_dir):
|
|
75
|
+
counts["sync"] += 1
|
|
76
|
+
return real_sync(repo_dir)
|
|
77
|
+
|
|
78
|
+
def push(repo_dir, **kwargs):
|
|
79
|
+
counts["push"] += 1
|
|
80
|
+
return real_push(repo_dir, **kwargs)
|
|
81
|
+
|
|
82
|
+
def acquire(lock, wait=0):
|
|
83
|
+
counts["lock"] += 1
|
|
84
|
+
return real_acquire(lock, wait)
|
|
85
|
+
|
|
86
|
+
monkeypatch.setattr(writeback, "sync_repo", sync)
|
|
87
|
+
monkeypatch.setattr(writeback, "push_committed", push)
|
|
88
|
+
monkeypatch.setattr(lock_mod, "acquire", acquire)
|
|
89
|
+
|
|
90
|
+
rc = cli.main(["approve", "--confirm-file", _write_confirm_file(drafts), "--by", "masaru"])
|
|
91
|
+
out, err = capsys.readouterr()
|
|
92
|
+
|
|
93
|
+
assert rc == 0, err
|
|
94
|
+
assert counts == {"sync": 1, "push": 1, "lock": 1}
|
|
95
|
+
assert out.splitlines()[0] == "承認しました: 3 本(masaru)"
|
|
96
|
+
for name in NAMES:
|
|
97
|
+
assert _origin_status(drafts, name) == "approved"
|
|
98
|
+
# commit は従前どおり 1 本ずつ(新しい順)。
|
|
99
|
+
assert _approval_commits(drafts["bare"], "main")[:3] == [
|
|
100
|
+
f"承認: {name}(nigamilab-threads・masaru)" for name in reversed(NAMES)]
|
|
101
|
+
assert "report_channel" not in err
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_1本だけdigestが違えばその1本だけ断り_他は進める(drafts, capsys):
|
|
105
|
+
digests = list(drafts["digests"])
|
|
106
|
+
digests[1] = "0" * 12
|
|
107
|
+
rc = cli.main(["approve", "--confirm-file", _write_confirm_file(drafts, digests), "--by", "masaru"])
|
|
108
|
+
out, err = capsys.readouterr()
|
|
109
|
+
|
|
110
|
+
assert rc == 1
|
|
111
|
+
assert out.splitlines()[0] == "承認しました: 2 本(masaru)"
|
|
112
|
+
assert _origin_status(drafts, "a.md") == "approved"
|
|
113
|
+
assert _origin_status(drafts, "b.md") == "draft"
|
|
114
|
+
assert _origin_status(drafts, "c.md") == "approved"
|
|
115
|
+
assert "断った原稿: 1 本(ほかは進めました)" in err
|
|
116
|
+
refused = [line for line in err.splitlines() if line.startswith(" 4 行目 ")]
|
|
117
|
+
assert len(refused) == 1 and drafts["paths"][1] in refused[0]
|
|
118
|
+
assert f"いまの digest は {drafts['digests'][1]} です" in refused[0]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_形の違う行とパスの無い行もその1行だけ断る(drafts, capsys):
|
|
122
|
+
path = _write_confirm_file(drafts, extra_lines=["digest-だけ", "/no/such/file.md " + "a" * 12])
|
|
123
|
+
rc = cli.main(["approve", "--confirm-file", path, "--by", "masaru", "--json"])
|
|
124
|
+
payload = json.loads(capsys.readouterr().out)
|
|
125
|
+
|
|
126
|
+
assert rc == 1
|
|
127
|
+
assert payload["count"] == 3 and payload["pushed"] is True
|
|
128
|
+
assert [row["line"] for row in payload["refused"]] == [6, 7]
|
|
129
|
+
assert payload["refused"][0]["reason"].startswith("行の形が違います")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_pushに失敗したら_commitまで済んだ本とそのまま打てるpushの命令を出す(drafts, capsys):
|
|
133
|
+
hook = os.path.join(drafts["bare"], "hooks", "pre-receive")
|
|
134
|
+
with open(hook, "w", encoding="utf-8") as stream:
|
|
135
|
+
stream.write("#!/bin/sh\necho 'rejected for test' >&2\nexit 1\n")
|
|
136
|
+
os.chmod(hook, 0o755)
|
|
137
|
+
|
|
138
|
+
rc = cli.main(["approve", "--confirm-file", _write_confirm_file(drafts), "--by", "masaru"])
|
|
139
|
+
out, err = capsys.readouterr()
|
|
140
|
+
|
|
141
|
+
assert rc == 1
|
|
142
|
+
assert "まだ push できていません" in out.splitlines()[0]
|
|
143
|
+
assert "承認を commit しましたが push できませんでした" in err
|
|
144
|
+
push_line = [line for line in err.splitlines() if "push が通れば出ます: " in line][0]
|
|
145
|
+
command = push_line.split("push が通れば出ます: ", 1)[1]
|
|
146
|
+
# 次の一手は thth の命令(3.8.2 の裁定 2・VM で生の git を打たせない)。
|
|
147
|
+
assert command == "thth pull nigamilab-threads --push-pending --by masaru"
|
|
148
|
+
# 3 本とも commit はローカルに残り、origin にはまだ無い。
|
|
149
|
+
assert len(_approval_commits(drafts["work"])) == 3
|
|
150
|
+
assert _approval_commits(drafts["bare"], "main") == []
|
|
151
|
+
# 出した命令をそのまま打てば届く(hook を外してから)。
|
|
152
|
+
os.unlink(hook)
|
|
153
|
+
pushed = run_thth(shlex.split(command)[1:])
|
|
154
|
+
assert pushed.returncode == 0, pushed.stderr
|
|
155
|
+
assert pushed.stdout.splitlines()[0] == "nigamilab-threads: 押しました: 3 commit(masaru)"
|
|
156
|
+
for name in NAMES:
|
|
157
|
+
assert _origin_status(drafts, name) == "approved"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_同期で止まったら_残りをそのまま打てる確定の命令で出す(drafts, capsys):
|
|
161
|
+
# 手元に push していない commit があり、origin も進んでいる(ff できない)。
|
|
162
|
+
work = drafts["work"]
|
|
163
|
+
with open(os.path.join(work, "local.txt"), "w", encoding="utf-8") as stream:
|
|
164
|
+
stream.write("local\n")
|
|
165
|
+
run_git(work, ["add", "local.txt"])
|
|
166
|
+
run_git(work, ["commit", "-q", "-m", "local only"])
|
|
167
|
+
run_git(drafts["seed"], ["pull", "-q", "--ff-only"])
|
|
168
|
+
with open(os.path.join(drafts["seed"], "elsewhere.txt"), "w", encoding="utf-8") as stream:
|
|
169
|
+
stream.write("elsewhere\n")
|
|
170
|
+
run_git(drafts["seed"], ["add", "elsewhere.txt"])
|
|
171
|
+
run_git(drafts["seed"], ["commit", "-q", "-m", "elsewhere"])
|
|
172
|
+
run_git(drafts["seed"], ["push", "-q"])
|
|
173
|
+
|
|
174
|
+
rc = cli.main(["approve", "--confirm-file", _write_confirm_file(drafts), "--by", "masaru"])
|
|
175
|
+
out, err = capsys.readouterr()
|
|
176
|
+
|
|
177
|
+
assert rc == 1 and out == ""
|
|
178
|
+
assert err.splitlines()[0].startswith("repo を同期できないので承認しません: ")
|
|
179
|
+
assert "通ったもの: 0 本。残り 3 本はそのまま打てます:" in err
|
|
180
|
+
commands = [line.strip() for line in err.splitlines() if line.startswith(" thth approve ")]
|
|
181
|
+
assert commands == [f"thth approve {shlex.quote(path)} --confirm {digest} --by masaru"
|
|
182
|
+
for path, digest in zip(drafts["paths"], drafts["digests"])]
|
|
183
|
+
for path in drafts["paths"]:
|
|
184
|
+
assert "status: draft" in open(path, encoding="utf-8").read()
|
|
185
|
+
# 直したあと、出た命令をそのまま打てば通る。
|
|
186
|
+
run_git(work, ["reset", "-q", "--hard", "origin/main"])
|
|
187
|
+
result = run_thth(shlex.split(commands[0])[1:])
|
|
188
|
+
assert result.returncode == 0, result.stderr
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_confirm_fileにハイフンを渡すと標準入力から読む(drafts):
|
|
192
|
+
"""手元(Mac)のファイルは VM に無いので、`--confirm-file -` で標準入力から渡せる。"""
|
|
193
|
+
lines = "".join(f"{path} {digest}\n" for path, digest in zip(drafts["paths"], drafts["digests"]))
|
|
194
|
+
result = run_thth(["approve", "--confirm-file", "-", "--by", "masaru"], stdin=lines)
|
|
195
|
+
assert result.returncode == 0, result.stderr
|
|
196
|
+
assert result.stdout.splitlines()[0] == "承認しました: 3 本(masaru)"
|
|
197
|
+
missing = run_thth(["approve", "--confirm-file", "/Users/someone/confirm.txt", "--by", "masaru"])
|
|
198
|
+
assert missing.returncode == 2 and "手元のパス" in missing.stderr
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_1段目が複数のときconfirm_fileの形を1行添える(drafts):
|
|
202
|
+
first = run_thth(["approve", drafts["paths"][0], drafts["paths"][1]])
|
|
203
|
+
assert first.returncode == 1
|
|
204
|
+
lines = [line for line in first.stdout.splitlines() if "--confirm-file" in line]
|
|
205
|
+
assert lines == ["1 本ずつの digest で確定するなら: thth approve --confirm-file <file> --by <名前>"
|
|
206
|
+
"(<file> は 1 行に「<原稿のパス> <digest>」)"]
|
|
207
|
+
single = run_thth(["approve", drafts["paths"][0]])
|
|
208
|
+
assert "--confirm-file" not in single.stdout
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_confirm_fileは名指しや_confirmと一緒に使えない(drafts, capsys):
|
|
212
|
+
path = _write_confirm_file(drafts)
|
|
213
|
+
assert cli.main(["approve", drafts["paths"][0], "--confirm-file", path, "--by", "m"]) == 2
|
|
214
|
+
assert cli.main(["approve", "--confirm-file", path, "--confirm", "a" * 12, "--by", "m"]) == 2
|
|
215
|
+
assert cli.main(["approve"]) == 2
|
|
216
|
+
capsys.readouterr()
|
|
217
|
+
for path in drafts["paths"]:
|
|
218
|
+
assert "status: draft" in open(path, encoding="utf-8").read()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""3.8.2 件 3: 二段確認の 1 段目を「断り」に数えない(報告 r20260924-9f07f36b)。
|
|
2
|
+
|
|
3
|
+
実測: `thth approve` の 1 段目(digest を出すだけ・rc 1)の末尾に
|
|
4
|
+
`report_channel: … --title "exit_1 で断られた"` が付き、3.3.0 の直前の断りの控え
|
|
5
|
+
(`last_refusals`)にも入っていた。
|
|
6
|
+
|
|
7
|
+
- approve の 1 段目と広場の open の 1 段目は、rc 1 のまま(呼び出し側を壊さない)、
|
|
8
|
+
断りの行を出さず、`last_refusals` にも記録しない。
|
|
9
|
+
- つまずきの年表の材料にもならない(`--from-last-refusal` が拾うものが無い)。
|
|
10
|
+
- 本当の断り(digest 違い)では従前どおり出る・記録する。
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from tests.conftest import write_queue_file
|
|
20
|
+
from thth import accounts, cli, plaza, refusals, report_timeline
|
|
21
|
+
from tests.test_v340_plaza_open import ledger, join # noqa: F401 (fixture)
|
|
22
|
+
from tests.test_v340_plaza_store import owners # noqa: F401 (fixture)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _draft(account):
|
|
26
|
+
return write_queue_file(account["queue_dir"], "a.md", body="## threads\n\n1 段目の本文。\n",
|
|
27
|
+
fm_overrides={"account": account["name"], "status": "draft",
|
|
28
|
+
"approved_sha": None})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _refusal_file(account_name):
|
|
32
|
+
return os.path.join(accounts.state_dir_for(account_name), refusals.FILE)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_approveの1段目はreport_channelを出さず_last_refusalsにも入らない(isolated_account, tmp_path, capsys):
|
|
36
|
+
name = isolated_account["name"]
|
|
37
|
+
path = _draft(isolated_account)
|
|
38
|
+
|
|
39
|
+
assert cli.main(["approve", path, "--account", name]) == 1
|
|
40
|
+
out, err = capsys.readouterr()
|
|
41
|
+
|
|
42
|
+
assert "digest: " in out # 1 段目は出ている(rc も従前どおり 1)
|
|
43
|
+
assert "report_channel" not in err and "report_channel" not in out
|
|
44
|
+
assert "で断られた" not in err
|
|
45
|
+
assert refusals.read(name) == []
|
|
46
|
+
assert not os.path.exists(_refusal_file(name))
|
|
47
|
+
# 年表の材料にもならない(直前の断りの控えが無い)。
|
|
48
|
+
assert cli.main(["report", "file", name, "--kind", "friction", "--from-last-refusal",
|
|
49
|
+
"--title", "1 段目", "--by", "テスト"]) != 0
|
|
50
|
+
assert "no_last_refusal" in capsys.readouterr().err
|
|
51
|
+
scope = report_timeline.report_inbox.Scope([name], ["nigamilab"])
|
|
52
|
+
assert report_timeline.timeline(scope, notes_dir=tmp_path)["rows"] == []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_approveのjsonの1段目も断りに数えない(isolated_account, capsys):
|
|
56
|
+
name = isolated_account["name"]
|
|
57
|
+
path = _draft(isolated_account)
|
|
58
|
+
assert cli.main(["approve", path, "--account", name, "--json"]) == 1
|
|
59
|
+
out, err = capsys.readouterr()
|
|
60
|
+
assert json.loads(out)["not_until_confirmed"] is True
|
|
61
|
+
assert "report_channel" not in err
|
|
62
|
+
assert refusals.read(name) == []
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_digest違いの本当の断りでは従前どおり出て記録する(isolated_account, capsys):
|
|
66
|
+
name = isolated_account["name"]
|
|
67
|
+
path = _draft(isolated_account)
|
|
68
|
+
assert cli.main(["approve", path, "--account", name]) == 1
|
|
69
|
+
capsys.readouterr()
|
|
70
|
+
|
|
71
|
+
rc = cli.main(["approve", path, "--account", name, "--confirm", "0" * 12, "--by", "テスト"])
|
|
72
|
+
err = capsys.readouterr().err
|
|
73
|
+
|
|
74
|
+
assert rc == 1
|
|
75
|
+
assert err.splitlines()[0].startswith("digest が一致しないので承認しません")
|
|
76
|
+
assert err.splitlines()[-1].startswith(f"report_channel: thth report file {name} ")
|
|
77
|
+
[row] = refusals.read(name)
|
|
78
|
+
assert row["command"] == "approve" and row["reason_code"] == "exit_1"
|
|
79
|
+
# 次の cli.main で印は持ち越さない(1 段目の直後の本当の断りも数える)。
|
|
80
|
+
assert not refusals.is_first_stage()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_広場のopenの1段目も断りに数えない(owners, ledger, tmp_path, capsys):
|
|
84
|
+
join("kopicha")
|
|
85
|
+
body = tmp_path / "b.txt"
|
|
86
|
+
body.write_text("朝の問いを続けた", encoding="utf-8")
|
|
87
|
+
argv = ["plaza", "post", "kopicha-threads", "--kind", "finding", "--title", "朝の問い",
|
|
88
|
+
"--body-file", str(body), "--scope", "Threads の朝", "--open", "--by", "s"]
|
|
89
|
+
|
|
90
|
+
assert cli.main(argv) == 1
|
|
91
|
+
out, err = capsys.readouterr()
|
|
92
|
+
assert out.startswith("一段目: まだ open にしていません")
|
|
93
|
+
assert "report_channel" not in err
|
|
94
|
+
assert refusals.read("kopicha-threads") == []
|
|
95
|
+
|
|
96
|
+
# 二段目の digest 違いは本当の断り(従前どおり)。
|
|
97
|
+
assert cli.main(argv + ["--confirm", "x" * 20]) == 2
|
|
98
|
+
assert "report_channel" in capsys.readouterr().err
|
|
99
|
+
[row] = refusals.read("kopicha-threads")
|
|
100
|
+
assert row["command"] == "plaza post"
|
|
101
|
+
assert plaza.admin_list()["n"] == 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.parametrize("json_flag", [[], ["--json"]], ids=["text", "json"])
|
|
105
|
+
def test_広場のupdateでopenにする1段目も断りに数えない(owners, capsys, json_flag):
|
|
106
|
+
join("kopicha")
|
|
107
|
+
inside = plaza.post("kopicha-bsky", kind="question", title="内輪の問い", body="b",
|
|
108
|
+
scope_note="Bluesky", by="b")
|
|
109
|
+
argv = ["plaza", "update", inside["plaza_id"], "--as", "kopicha-bsky", "--visibility", "open",
|
|
110
|
+
"--by", "b", *json_flag]
|
|
111
|
+
assert cli.main(argv) == 1
|
|
112
|
+
assert "report_channel" not in capsys.readouterr().err
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""3.8.2 の裁定 2 点(主セッション・2026-09-24)。
|
|
2
|
+
|
|
3
|
+
1. `writeback.behind_remote()`(読むだけの口の遅れの確かめ)は repo のロックを待たずに
|
|
4
|
+
取りに行き、取れなければ fetch せず、前回取り込んだ remote の姿で数えて「いま確かめ
|
|
5
|
+
られない(他の実行が repo を使用中)」と言う。取れたら fetch する(FETCH_HEAD は書かない)。
|
|
6
|
+
承認の手元パスの読み替え(`_stale_clone_line()`)と queue 等の案内(`_behind_notices()`)も同じ。
|
|
7
|
+
2. `thth pull <account> --push-pending --by <名前>`: repo のロックの中で fetch し、手元が
|
|
8
|
+
upstream より先にいて遅れが 0 のときだけ push する。遅れがあれば押さずに理由を言う
|
|
9
|
+
(rebase はしない)。押した commit の件数と題を出す。
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
from tests.conftest import run_git
|
|
17
|
+
from thth import accounts, cli, writeback
|
|
18
|
+
from thth import lock as lock_mod
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _paths(account):
|
|
22
|
+
work = account["repo_dir"]
|
|
23
|
+
return work, os.path.join(os.path.dirname(work), "seed"), os.path.join(os.path.dirname(work), "origin.git")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _advance(repo: str, name: str, *, push: bool = True) -> None:
|
|
27
|
+
with open(os.path.join(repo, name), "w", encoding="utf-8") as stream:
|
|
28
|
+
stream.write(name + "\n")
|
|
29
|
+
run_git(repo, ["add", name])
|
|
30
|
+
run_git(repo, ["commit", "-q", "-m", f"commit {name}"])
|
|
31
|
+
if push:
|
|
32
|
+
run_git(repo, ["push", "-q"])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ------------------------------------------------------------ 1. ロック中の読み手は fetch しない
|
|
36
|
+
|
|
37
|
+
def test_repoのロック中はfetchせず前回の姿で数えて確かめられないと言う_空けばfetchする(isolated_account):
|
|
38
|
+
work, seed, _bare = _paths(isolated_account)
|
|
39
|
+
tracked_before = run_git(work, ["rev-parse", "origin/main"]).stdout.strip()
|
|
40
|
+
_advance(seed, "elsewhere.txt") # origin が 1 commit 進む(work はまだ知らない)
|
|
41
|
+
|
|
42
|
+
held = lock_mod.AccountLock(accounts.repo_lock_path_for(work))
|
|
43
|
+
held.acquire()
|
|
44
|
+
try:
|
|
45
|
+
info = writeback.behind_remote(work)
|
|
46
|
+
notices, _by_account = cli._behind_notices([isolated_account["name"]])
|
|
47
|
+
stale = cli._stale_clone_line(work, isolated_account["name"])
|
|
48
|
+
finally:
|
|
49
|
+
held.release()
|
|
50
|
+
|
|
51
|
+
# fetch していない: 追跡 ref は動かず、前回の姿で数えて 0・確かめた時刻は無い。
|
|
52
|
+
assert run_git(work, ["rev-parse", "origin/main"]).stdout.strip() == tracked_before
|
|
53
|
+
assert info["reason"] == writeback.BEHIND_UNCHECKED
|
|
54
|
+
assert info["behind"] == 0 and info["ahead"] == 0 and info["fetched_at"] is None
|
|
55
|
+
assert any("いま確かめられません(他の実行が repo を使用中)" in line for line in notices)
|
|
56
|
+
assert stale is not None and "いま確かめられません(他の実行が repo を使用中)" in stale
|
|
57
|
+
|
|
58
|
+
# 空けば従前どおり fetch して数える(FETCH_HEAD は書かない)。
|
|
59
|
+
fetch_head = os.path.join(work, ".git", "FETCH_HEAD")
|
|
60
|
+
before = os.path.getmtime(fetch_head) if os.path.exists(fetch_head) else None
|
|
61
|
+
info = writeback.behind_remote(work)
|
|
62
|
+
assert info["reason"] is None and info["behind"] == 1 and info["fetched_at"]
|
|
63
|
+
after = os.path.getmtime(fetch_head) if os.path.exists(fetch_head) else None
|
|
64
|
+
assert after == before
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ------------------------------------------------------------ 2. --push-pending
|
|
68
|
+
|
|
69
|
+
def test_push_pendingは手元が先にいるだけなら押して件数と題を出す(isolated_account, capsys):
|
|
70
|
+
work, _seed, bare = _paths(isolated_account)
|
|
71
|
+
_advance(work, "a.txt", push=False)
|
|
72
|
+
_advance(work, "b.txt", push=False)
|
|
73
|
+
|
|
74
|
+
rc = cli.main(["pull", isolated_account["name"], "--push-pending", "--by", "masaru"])
|
|
75
|
+
out, err = capsys.readouterr()
|
|
76
|
+
|
|
77
|
+
assert rc == 0, err
|
|
78
|
+
assert out.splitlines() == [f"{isolated_account['name']}: 押しました: 2 commit(masaru)",
|
|
79
|
+
" commit b.txt", " commit a.txt"]
|
|
80
|
+
assert run_git(bare, ["rev-parse", "main"]).stdout.strip() == \
|
|
81
|
+
run_git(work, ["rev-parse", "HEAD"]).stdout.strip()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_push_pendingはupstreamに遅れていれば押さずに理由を言う_rebaseもしない(isolated_account, capsys):
|
|
85
|
+
work, seed, bare = _paths(isolated_account)
|
|
86
|
+
_advance(work, "a.txt", push=False)
|
|
87
|
+
local_head = run_git(work, ["rev-parse", "HEAD"]).stdout.strip()
|
|
88
|
+
_advance(seed, "elsewhere.txt")
|
|
89
|
+
origin_head = run_git(bare, ["rev-parse", "main"]).stdout.strip()
|
|
90
|
+
|
|
91
|
+
rc = cli.main(["pull", isolated_account["name"], "--push-pending", "--by", "masaru", "--json"])
|
|
92
|
+
[row] = json.loads(capsys.readouterr().out)
|
|
93
|
+
|
|
94
|
+
assert rc == 1
|
|
95
|
+
assert row["pushed"] is False and row["ahead"] == 1 and row["behind"] == 1
|
|
96
|
+
assert row["error"].startswith("upstream に 1 commit 遅れているので押しません")
|
|
97
|
+
# 手元も origin も動かさない(rebase しない・押さない)。
|
|
98
|
+
assert run_git(work, ["rev-parse", "HEAD"]).stdout.strip() == local_head
|
|
99
|
+
assert run_git(bare, ["rev-parse", "main"]).stdout.strip() == origin_head
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_push_pendingは名乗りが要る(isolated_account, capsys, monkeypatch):
|
|
103
|
+
monkeypatch.delenv("THTH_ACTOR", raising=False)
|
|
104
|
+
assert cli.main(["pull", isolated_account["name"], "--push-pending"]) == 1
|
|
105
|
+
assert "--by を付けてください" in capsys.readouterr().err
|