tcw-cli 2.6.3__tar.gz → 2.6.4__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.
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/PKG-INFO +6 -3
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/README.md +5 -2
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/pyproject.toml +1 -1
- tcw_cli-2.6.4/tcw/__init__.py +1 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/documentation-sync.md +4 -4
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/unattended-work.md +1 -1
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/PKG-INFO +6 -3
- tcw_cli-2.6.4/tests/test_cut_version.py +236 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_repo_lifecycle.py +2 -2
- tcw_cli-2.6.3/tcw/__init__.py +0 -1
- tcw_cli-2.6.3/tests/test_cut_version.py +0 -116
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/LICENSE +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/setup.cfg +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/capabilities/__init__.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/capabilities/cli.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/cli.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/harness.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/refs.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/__init__.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/client/assets/index-C5_pngMt.css +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/client/assets/index-Dss8_ZG_.js +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/client/index.html +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/client/theme-init.js +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/server.cjs +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/runtime.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/stdin.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/__init__.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/base.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/checkouts.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/config_edit.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/fs.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/project.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/taxonomy/__init__.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/taxonomy/cli.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/__init__.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/claim.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/create.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/intake.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/jira.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/ownership.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/progress.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/sync.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/validate.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/__init__.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/cli.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/generate.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/hooks.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/audit-backlog.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/consolidate-plans.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/create-work.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/decompose.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/delegation.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/post-mortem.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/search.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/triage-issues.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/projection.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/implement.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/inbox.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/plan.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/postmortem.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/request.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/spec.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/verify.md +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/recursion.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/resolve.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/templates.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/SOURCES.txt +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/dependency_links.txt +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/entry_points.txt +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/requires.txt +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/top_level.txt +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_body_prompt.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities_federation.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities_reset.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities_rm.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities_sidecar.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capability_gate_children.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capability_ref_wording.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_check_versions.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_child_status.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_cli_help_coverage.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_committed_paths.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_config_edit.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_config_edit_writers.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_configuration_text_home.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_cross_node_blockers.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_documentation_config.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_documentation_prompt.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_documentation_sync_wiring.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_documented_cli_surface.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_dynamic_skill_marker.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_edit_refusal.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_edit_type.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_environment_hardness.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_epic_completability_across_checkouts.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_epic_completable.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_coverage.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_files_changed.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_fixture.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_grading.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_runner.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_external_work_store.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_falsification_rule.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_generate_hook.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_harness.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_inbox_title.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_interrupted_claim.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_item_tags_read.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_legacy_store_config.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_baseline.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_config_tags.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_hooks.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_inert.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_policy.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_validation.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_literal_store_names.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_multiproject.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_non_git_writes.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_plugin_manifests.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_procedure_config.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_procedure_verb.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_project_overrides.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_project_registry.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_projection.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_prompt_fallback.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_qualified_ref.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_recursion.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_refs.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_remote_session_setup.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_resolve.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_resolve_procedure.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_retention.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_routing_nodes.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_scaffold.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_descendants.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_projection.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_resolve.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_runtime.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_write.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_session_bootstrap.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_shipped_procedures.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_shipped_prompts.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_show_json.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_sidecar_paths_in_validate.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_skill_flow.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_skill_lifecycle_parity.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_skill_path_pointers.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_smoke.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_stage_validate.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_stage_verb.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_status_parity.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_stdin.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_stdin_cli.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_bounds.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_editor.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_nodes.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_provisioning.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_publication.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_subprocess_stdin.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_taxonomy.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_taxonomy_rm_gaps.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tombstone.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_absent.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_binding.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_claim.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_claimability.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_cli.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_client.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_comment.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_config.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_create.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_gate.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_hardening.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_help.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_hold.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_import.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_import_returns.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_inheritance.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_link.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_ownership.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_pre_backlog.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_replay.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_strict.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_strict_gate.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_surface.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_sync.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_sync_config.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_validate.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_unattended_work_skill.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_unplanned_start.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_unpushed_version_script.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_unreadable_capabilities_sidecar.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_validate.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_validate_moved_stores.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_validate_target.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work_autocommit.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work_review.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work_start.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work_tags.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_worktree_completion.py +0 -0
- {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_worktree_sibling_nodes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tcw-cli
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.4
|
|
4
4
|
Summary: Taxonomy · Capabilities · Work — a storage-abstracted framework for describing and evolving a software project.
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -1102,8 +1102,11 @@ that an agent read them. Running it starts real agent sessions and costs money;
|
|
|
1102
1102
|
### Releasing
|
|
1103
1103
|
|
|
1104
1104
|
`python scripts/cut_version.py <patch|minor|major|X.Y.Z>` bumps the version in all
|
|
1105
|
-
five files that carry it,
|
|
1106
|
-
that version's files, commits and tags.
|
|
1105
|
+
five files that carry it, combines the waiting changelog and release-note entries
|
|
1106
|
+
into that version's files, commits and tags. Each change adds its entries as its
|
|
1107
|
+
own file in `docs/changelogs/upcoming/` and `docs/release-notes/upcoming/`, named
|
|
1108
|
+
after its work item, so changes finished on separate branches never edit the same
|
|
1109
|
+
file. Pushing the tag publishes the release to
|
|
1107
1110
|
PyPI. Details, including the one-time PyPI setup, are in
|
|
1108
1111
|
[`docs/releasing.md`](docs/releasing.md).
|
|
1109
1112
|
|
|
@@ -886,8 +886,11 @@ that an agent read them. Running it starts real agent sessions and costs money;
|
|
|
886
886
|
### Releasing
|
|
887
887
|
|
|
888
888
|
`python scripts/cut_version.py <patch|minor|major|X.Y.Z>` bumps the version in all
|
|
889
|
-
five files that carry it,
|
|
890
|
-
that version's files, commits and tags.
|
|
889
|
+
five files that carry it, combines the waiting changelog and release-note entries
|
|
890
|
+
into that version's files, commits and tags. Each change adds its entries as its
|
|
891
|
+
own file in `docs/changelogs/upcoming/` and `docs/release-notes/upcoming/`, named
|
|
892
|
+
after its work item, so changes finished on separate branches never edit the same
|
|
893
|
+
file. Pushing the tag publishes the release to
|
|
891
894
|
PyPI. Details, including the one-time PyPI setup, are in
|
|
892
895
|
[`docs/releasing.md`](docs/releasing.md).
|
|
893
896
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tcw-cli"
|
|
7
|
-
version = "2.6.
|
|
7
|
+
version = "2.6.4"
|
|
8
8
|
description = "Taxonomy · Capabilities · Work — a storage-abstracted framework for describing and evolving a software project."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.6.4"
|
|
@@ -43,9 +43,9 @@ These workflows are deeper than the core trigger-evaluation loop and live as ref
|
|
|
43
43
|
|
|
44
44
|
| Reference | Load when |
|
|
45
45
|
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
-
| `references/release-notes-and-changelogs.md` | The project uses the opt-in `docs/release-notes/` + `docs/changelogs/` structure AND you're
|
|
46
|
+
| `references/release-notes-and-changelogs.md` | The project uses the opt-in `docs/release-notes/` + `docs/changelogs/` structure AND you're adding an entry file to `upcoming/`, combining entries at a cut, running the version cross-check, or migrating an existing `CHANGELOG.md`. |
|
|
47
47
|
| the `configure` skill's `docs-sync.md` | The project's `CLAUDE.md` has no `## Documentation Sync` section and the user wants to add one, or you need to create tracked files that don't exist yet. |
|
|
48
|
-
| `references/cut-version.md` | The user asked to cut a version and you're running it — choosing the bump size, bumping every version-bearing file,
|
|
48
|
+
| `references/cut-version.md` | The user asked to cut a version and you're running it — choosing the bump size, bumping every version-bearing file, combining the `upcoming/` entries, committing, tagging. See "When the user asks to cut a version" below. |
|
|
49
49
|
|
|
50
50
|
## When the user asks to cut a version
|
|
51
51
|
|
|
@@ -84,8 +84,8 @@ ritual only when the project has none.
|
|
|
84
84
|
Updating the changelog files is **not** part of a version cut and does not wait
|
|
85
85
|
for one. The release-note and developer-changelog working files among the
|
|
86
86
|
project's documentation entries (`tcw work docs`, or its `## Documentation Sync`
|
|
87
|
-
section when `source` is `agent-guide`) — such as `docs/release-notes/upcoming
|
|
88
|
-
and `docs/changelogs/upcoming
|
|
87
|
+
section when `source` is `agent-guide`) — such as `docs/release-notes/upcoming/<slug>.md`
|
|
88
|
+
and `docs/changelogs/upcoming/<slug>.md`, one file per change — are answered by the documentation gate at
|
|
89
89
|
the end of `implement`, like any other entry whose trigger fired. Work
|
|
90
90
|
accumulates there until a cut is asked for.
|
|
91
91
|
|
|
@@ -34,5 +34,5 @@ not let the run wait on the subagent alone.
|
|
|
34
34
|
| Verify decision | Yours. Green → `tcw work submit`. Red → rework and loop; the same criterion failing three loops is a hard blocker. |
|
|
35
35
|
| Hands-on QA | Drive the real thing yourself, however this project is run — see the `run` skill, or whatever the repo's own guidance says. Agent reports and green suites are testimony, not evidence. |
|
|
36
36
|
| Capability reconciliation | The `capabilities` sub-skill, unchanged. |
|
|
37
|
-
| Version cut | Not yours to make. Nobody is present to ask for one, so let changelog entries accumulate in `upcoming
|
|
37
|
+
| Version cut | Not yours to make. Nobody is present to ask for one, so let changelog entries accumulate in `upcoming/` and move on. |
|
|
38
38
|
| Closeout route | `tcw work complete`, then merge the feature branch into main **locally**. Never `git push`. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tcw-cli
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.4
|
|
4
4
|
Summary: Taxonomy · Capabilities · Work — a storage-abstracted framework for describing and evolving a software project.
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -1102,8 +1102,11 @@ that an agent read them. Running it starts real agent sessions and costs money;
|
|
|
1102
1102
|
### Releasing
|
|
1103
1103
|
|
|
1104
1104
|
`python scripts/cut_version.py <patch|minor|major|X.Y.Z>` bumps the version in all
|
|
1105
|
-
five files that carry it,
|
|
1106
|
-
that version's files, commits and tags.
|
|
1105
|
+
five files that carry it, combines the waiting changelog and release-note entries
|
|
1106
|
+
into that version's files, commits and tags. Each change adds its entries as its
|
|
1107
|
+
own file in `docs/changelogs/upcoming/` and `docs/release-notes/upcoming/`, named
|
|
1108
|
+
after its work item, so changes finished on separate branches never edit the same
|
|
1109
|
+
file. Pushing the tag publishes the release to
|
|
1107
1110
|
PyPI. Details, including the one-time PyPI setup, are in
|
|
1108
1111
|
[`docs/releasing.md`](docs/releasing.md).
|
|
1109
1112
|
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "cut_version.py"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _load():
|
|
11
|
+
spec = importlib.util.spec_from_file_location("cut_version", SCRIPT)
|
|
12
|
+
mod = importlib.util.module_from_spec(spec)
|
|
13
|
+
spec.loader.exec_module(mod)
|
|
14
|
+
return mod
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
cv = _load()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
README = "# Upcoming entries\n\nDrafting guidance for whoever writes an entry.\n"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def make_repo(tmp_path: Path, version: str = "0.2.2") -> Path:
|
|
24
|
+
root = tmp_path / "repo"
|
|
25
|
+
(root / ".claude-plugin").mkdir(parents=True)
|
|
26
|
+
(root / ".codex-plugin").mkdir(parents=True)
|
|
27
|
+
(root / "tcw").mkdir()
|
|
28
|
+
(root / "docs" / "changelogs" / "upcoming").mkdir(parents=True)
|
|
29
|
+
(root / "docs" / "release-notes" / "upcoming").mkdir(parents=True)
|
|
30
|
+
(root / "pyproject.toml").write_text(f'[project]\nname = "tcw"\nversion = "{version}"\n')
|
|
31
|
+
(root / "tcw" / "__init__.py").write_text(f'__version__ = "{version}"\n')
|
|
32
|
+
(root / ".claude-plugin" / "plugin.json").write_text(f'{{\n "version": "{version}"\n}}\n')
|
|
33
|
+
(root / ".claude-plugin" / "marketplace.json").write_text(
|
|
34
|
+
f'{{\n "plugins": [\n {{\n "version": "{version}"\n }}\n ]\n}}\n')
|
|
35
|
+
(root / ".codex-plugin" / "plugin.json").write_text(f'{{\n "version": "{version}"\n}}\n')
|
|
36
|
+
for kind in ("changelogs", "release-notes"):
|
|
37
|
+
(root / f"docs/{kind}/upcoming/README.md").write_text(README)
|
|
38
|
+
(root / "docs/changelogs/upcoming/a.md").write_text("## Added\n\n- changelog entry\n")
|
|
39
|
+
(root / "docs/release-notes/upcoming/a.md").write_text("## Improvements\n\n- release note\n")
|
|
40
|
+
subprocess.run(["git", "init", "-q", str(root)], check=True)
|
|
41
|
+
subprocess.run(["git", "-C", str(root), "config", "user.email", "t@t"], check=True)
|
|
42
|
+
subprocess.run(["git", "-C", str(root), "config", "user.name", "t"], check=True)
|
|
43
|
+
subprocess.run(["git", "-C", str(root), "add", "-A"], check=True)
|
|
44
|
+
subprocess.run(["git", "-C", str(root), "commit", "-qm", "init"], check=True)
|
|
45
|
+
return root
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_next_version_increments():
|
|
49
|
+
assert cv.next_version("0.2.2", "patch") == "0.2.3"
|
|
50
|
+
assert cv.next_version("0.2.2", "minor") == "0.3.0"
|
|
51
|
+
assert cv.next_version("0.2.2", "major") == "1.0.0"
|
|
52
|
+
assert cv.next_version("0.2.2", "1.5.0") == "1.5.0" # explicit passthrough
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_next_version_invalid():
|
|
56
|
+
with pytest.raises(SystemExit):
|
|
57
|
+
cv.next_version("0.2.2", "bogus")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_current_version_reads_and_detects_drift(tmp_path):
|
|
61
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
62
|
+
assert cv.current_version(root) == "0.2.2"
|
|
63
|
+
(root / "tcw" / "__init__.py").write_text('__version__ = "9.9.9"\n') # introduce drift
|
|
64
|
+
with pytest.raises(SystemExit):
|
|
65
|
+
cv.current_version(root)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_bump_files_updates_all_five(tmp_path):
|
|
69
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
70
|
+
cv.bump_files(root, "0.2.2", "0.2.3")
|
|
71
|
+
assert cv.current_version(root) == "0.2.3"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _git_out(root: Path, *args: str) -> str:
|
|
75
|
+
return subprocess.run(["git", "-C", str(root), *args],
|
|
76
|
+
capture_output=True, text=True, check=True).stdout
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _entries(root: Path, kind: str, files: dict[str, str]) -> None:
|
|
80
|
+
"""Replace the fixture's entry files in `docs/<kind>/upcoming/` with `files`."""
|
|
81
|
+
folder = root / "docs" / kind / "upcoming"
|
|
82
|
+
for p in folder.glob("*.md"):
|
|
83
|
+
if p.name != "README.md":
|
|
84
|
+
p.unlink()
|
|
85
|
+
for name, text in files.items():
|
|
86
|
+
(folder / name).write_text(text, encoding="utf-8")
|
|
87
|
+
subprocess.run(["git", "-C", str(root), "add", "-A"], check=True)
|
|
88
|
+
subprocess.run(["git", "-C", str(root), "commit", "-qm", "entries"], check=True)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _headings(text: str) -> list[str]:
|
|
92
|
+
return [line for line in text.splitlines() if line.startswith("## ")]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _assert_cut_is_clean(root: Path, version: str) -> None:
|
|
96
|
+
"""Every entry file is gone, the guidance stays, and the cut is committed and tagged."""
|
|
97
|
+
for kind in ("changelogs", "release-notes"):
|
|
98
|
+
folder = root / "docs" / kind / "upcoming"
|
|
99
|
+
assert sorted(p.name for p in folder.iterdir()) == ["README.md"]
|
|
100
|
+
assert (folder / "README.md").read_text() == README
|
|
101
|
+
assert f"v{version}" in _git_out(root, "tag").split()
|
|
102
|
+
assert _git_out(root, "log", "-1", "--pretty=%s").strip() == f"chore(release): cut v{version}"
|
|
103
|
+
assert _git_out(root, "status", "--porcelain") == ""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_main_end_to_end(tmp_path):
|
|
107
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
108
|
+
cv.main(["patch"], root=root)
|
|
109
|
+
assert cv.current_version(root) == "0.2.3"
|
|
110
|
+
assert (root / "docs/changelogs/v0.2.3.md").read_text() == "# v0.2.3\n\n## Added\n\n- changelog entry\n"
|
|
111
|
+
assert (root / "docs/release-notes/v0.2.3.md").read_text() == "# v0.2.3\n\n## Improvements\n\n- release note\n"
|
|
112
|
+
_assert_cut_is_clean(root, "0.2.3")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_combine_merges_sections_by_heading(tmp_path):
|
|
116
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
117
|
+
_entries(root, "changelogs", {
|
|
118
|
+
"a.md": "## Fixed\n\n- a fixed\n\n## Added\n\n- a added\n",
|
|
119
|
+
"b.md": "## Added\n\n- b added\n\n## Security\n\n- b security\n",
|
|
120
|
+
})
|
|
121
|
+
cv.main(["patch"], root=root)
|
|
122
|
+
shipped = (root / "docs/changelogs/v0.2.3.md").read_text()
|
|
123
|
+
assert shipped.startswith("# v0.2.3\n")
|
|
124
|
+
assert _headings(shipped) == ["## Added", "## Fixed", "## Security"]
|
|
125
|
+
assert shipped.index("- a added") < shipped.index("- b added") < shipped.index("## Fixed")
|
|
126
|
+
assert "Drafting guidance" not in shipped
|
|
127
|
+
_assert_cut_is_clean(root, "0.2.3")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_combine_with_no_entries_ships_only_the_title(tmp_path):
|
|
131
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
132
|
+
_entries(root, "changelogs", {})
|
|
133
|
+
_entries(root, "release-notes", {})
|
|
134
|
+
cv.main(["patch"], root=root)
|
|
135
|
+
for kind in ("changelogs", "release-notes"):
|
|
136
|
+
assert (root / f"docs/{kind}/v0.2.3.md").read_text() == "# v0.2.3\n"
|
|
137
|
+
_assert_cut_is_clean(root, "0.2.3")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_subheadings_stay_under_their_section(tmp_path):
|
|
141
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
142
|
+
_entries(root, "changelogs", {
|
|
143
|
+
"a.md": "## Added\n\n- a added\n",
|
|
144
|
+
"b.md": "## Added\n\n- b added\n\n### Detail\n\n- b detail\n\n## Fixed\n\n- b fixed\n",
|
|
145
|
+
})
|
|
146
|
+
cv.main(["patch"], root=root)
|
|
147
|
+
shipped = (root / "docs/changelogs/v0.2.3.md").read_text()
|
|
148
|
+
assert shipped.index("- b added") < shipped.index("### Detail") < shipped.index("## Fixed")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_release_notes_keep_first_appearance_order(tmp_path):
|
|
152
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
153
|
+
_entries(root, "release-notes", {
|
|
154
|
+
"a.md": "## Zeta\n\n- a zeta\n",
|
|
155
|
+
"b.md": "## Alpha\n\n- b alpha\n\n## Zeta\n\n- b zeta\n",
|
|
156
|
+
})
|
|
157
|
+
cv.main(["patch"], root=root)
|
|
158
|
+
shipped = (root / "docs/release-notes/v0.2.3.md").read_text()
|
|
159
|
+
assert _headings(shipped) == ["## Zeta", "## Alpha"]
|
|
160
|
+
assert shipped.index("- a zeta") < shipped.index("- b zeta") < shipped.index("## Alpha")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_text_before_the_first_heading_follows_the_title(tmp_path):
|
|
164
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
165
|
+
_entries(root, "changelogs", {
|
|
166
|
+
"a.md": "## Added\n\n- a added\n",
|
|
167
|
+
"b.md": "A loose paragraph.\n",
|
|
168
|
+
})
|
|
169
|
+
cv.main(["patch"], root=root)
|
|
170
|
+
shipped = (root / "docs/changelogs/v0.2.3.md").read_text()
|
|
171
|
+
assert shipped.startswith("# v0.2.3\n\nA loose paragraph.\n")
|
|
172
|
+
assert shipped.index("A loose paragraph.") < shipped.index("## Added")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_readme_guidance_never_ships(tmp_path):
|
|
176
|
+
"""The folder's README.md is addressed to whoever writes an entry, not to
|
|
177
|
+
whoever reads the release, so the cut must neither ship nor consume it —
|
|
178
|
+
the same reason the old rotation dropped the `upcoming.md` preamble."""
|
|
179
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
180
|
+
cv.main(["minor"], root=root)
|
|
181
|
+
for kind in ("changelogs", "release-notes"):
|
|
182
|
+
assert "Drafting guidance" not in (root / f"docs/{kind}/v0.3.0.md").read_text()
|
|
183
|
+
_assert_cut_is_clean(root, "0.3.0")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_a_missing_upcoming_folder_aborts_before_anything_changes(tmp_path):
|
|
187
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
188
|
+
subprocess.run(["git", "-C", str(root), "rm", "-rqf", "docs/release-notes/upcoming"], check=True)
|
|
189
|
+
subprocess.run(["git", "-C", str(root), "commit", "-qm", "drop"], check=True)
|
|
190
|
+
with pytest.raises(SystemExit, match="docs/release-notes/upcoming"):
|
|
191
|
+
cv.main(["patch"], root=root)
|
|
192
|
+
assert cv.current_version(root) == "0.2.2"
|
|
193
|
+
assert _git_out(root, "status", "--porcelain") == ""
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@pytest.mark.parametrize("state", ["edited", "untracked"])
|
|
197
|
+
def test_an_uncommitted_entry_ships_rather_than_breaking_the_cut(tmp_path, state):
|
|
198
|
+
"""`git rm` refuses a tracked file with local edits and an untracked one;
|
|
199
|
+
the cut used to fail after rewriting the version files, leaving no commit."""
|
|
200
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
201
|
+
folder = root / "docs/changelogs/upcoming"
|
|
202
|
+
if state == "edited":
|
|
203
|
+
(folder / "a.md").write_text("## Added\n\n- changelog entry\n- edited late\n")
|
|
204
|
+
else:
|
|
205
|
+
(folder / "b.md").write_text("## Fixed\n\n- written late\n")
|
|
206
|
+
cv.main(["patch"], root=root)
|
|
207
|
+
shipped = (root / "docs/changelogs/v0.2.3.md").read_text()
|
|
208
|
+
assert ("- edited late" if state == "edited" else "- written late") in shipped
|
|
209
|
+
_assert_cut_is_clean(root, "0.2.3")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_a_heading_with_no_entries_is_dropped(tmp_path):
|
|
213
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
214
|
+
_entries(root, "changelogs", {
|
|
215
|
+
"a.md": "## Added\n\n- a added\n\n## Changed\n\n## Fixed\n",
|
|
216
|
+
"b.md": "## Fixed\n",
|
|
217
|
+
})
|
|
218
|
+
cv.main(["patch"], root=root)
|
|
219
|
+
shipped = (root / "docs/changelogs/v0.2.3.md").read_text()
|
|
220
|
+
assert _headings(shipped) == ["## Added"]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_files_the_cut_does_not_combine_are_named(tmp_path, capsys):
|
|
224
|
+
root = make_repo(tmp_path, "0.2.2")
|
|
225
|
+
folder = root / "docs/changelogs/upcoming"
|
|
226
|
+
(folder / "note.txt").write_text("stray\n")
|
|
227
|
+
(folder / "sub").mkdir()
|
|
228
|
+
(folder / "sub" / "x.md").write_text("## Added\n\n- nested\n")
|
|
229
|
+
subprocess.run(["git", "-C", str(root), "add", "-A"], check=True)
|
|
230
|
+
subprocess.run(["git", "-C", str(root), "commit", "-qm", "strays"], check=True)
|
|
231
|
+
cv.main(["patch"], root=root)
|
|
232
|
+
err = capsys.readouterr().err
|
|
233
|
+
assert "docs/changelogs/upcoming/note.txt" in err
|
|
234
|
+
assert "docs/changelogs/upcoming/sub" in err
|
|
235
|
+
assert "- nested" not in (root / "docs/changelogs/v0.2.3.md").read_text()
|
|
236
|
+
assert (folder / "note.txt").exists() and (folder / "sub" / "x.md").exists()
|
|
@@ -102,8 +102,8 @@ def test_this_repos_documentation_entries_parse():
|
|
|
102
102
|
"README.md",
|
|
103
103
|
"docs/guide/jira.md",
|
|
104
104
|
"docs/guide/<topic>.md",
|
|
105
|
-
"docs/release-notes/upcoming
|
|
106
|
-
"docs/changelogs/upcoming
|
|
105
|
+
"docs/release-notes/upcoming/<slug>.md",
|
|
106
|
+
"docs/changelogs/upcoming/<slug>.md",
|
|
107
107
|
"skills/<component>/SKILL.md",
|
|
108
108
|
"skills/configure/references/<document>.md",
|
|
109
109
|
]
|
tcw_cli-2.6.3/tcw/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.6.3"
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import importlib.util
|
|
2
|
-
import subprocess
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "cut_version.py"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _load():
|
|
11
|
-
spec = importlib.util.spec_from_file_location("cut_version", SCRIPT)
|
|
12
|
-
mod = importlib.util.module_from_spec(spec)
|
|
13
|
-
spec.loader.exec_module(mod)
|
|
14
|
-
return mod
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
cv = _load()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def make_repo(tmp_path: Path, version: str = "0.2.2") -> Path:
|
|
21
|
-
root = tmp_path / "repo"
|
|
22
|
-
(root / ".claude-plugin").mkdir(parents=True)
|
|
23
|
-
(root / ".codex-plugin").mkdir(parents=True)
|
|
24
|
-
(root / "tcw").mkdir()
|
|
25
|
-
(root / "docs" / "changelogs").mkdir(parents=True)
|
|
26
|
-
(root / "docs" / "release-notes").mkdir(parents=True)
|
|
27
|
-
(root / "pyproject.toml").write_text(f'[project]\nname = "tcw"\nversion = "{version}"\n')
|
|
28
|
-
(root / "tcw" / "__init__.py").write_text(f'__version__ = "{version}"\n')
|
|
29
|
-
(root / ".claude-plugin" / "plugin.json").write_text(f'{{\n "version": "{version}"\n}}\n')
|
|
30
|
-
(root / ".claude-plugin" / "marketplace.json").write_text(
|
|
31
|
-
f'{{\n "plugins": [\n {{\n "version": "{version}"\n }}\n ]\n}}\n')
|
|
32
|
-
(root / ".codex-plugin" / "plugin.json").write_text(f'{{\n "version": "{version}"\n}}\n')
|
|
33
|
-
(root / "docs/changelogs/upcoming.md").write_text("# Upcoming\n\nchangelog entries here\n")
|
|
34
|
-
(root / "docs/release-notes/upcoming.md").write_text("# Upcoming\n\nrelease notes here\n")
|
|
35
|
-
subprocess.run(["git", "init", "-q", str(root)], check=True)
|
|
36
|
-
subprocess.run(["git", "-C", str(root), "config", "user.email", "t@t"], check=True)
|
|
37
|
-
subprocess.run(["git", "-C", str(root), "config", "user.name", "t"], check=True)
|
|
38
|
-
subprocess.run(["git", "-C", str(root), "add", "-A"], check=True)
|
|
39
|
-
subprocess.run(["git", "-C", str(root), "commit", "-qm", "init"], check=True)
|
|
40
|
-
return root
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_next_version_increments():
|
|
44
|
-
assert cv.next_version("0.2.2", "patch") == "0.2.3"
|
|
45
|
-
assert cv.next_version("0.2.2", "minor") == "0.3.0"
|
|
46
|
-
assert cv.next_version("0.2.2", "major") == "1.0.0"
|
|
47
|
-
assert cv.next_version("0.2.2", "1.5.0") == "1.5.0" # explicit passthrough
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def test_next_version_invalid():
|
|
51
|
-
with pytest.raises(SystemExit):
|
|
52
|
-
cv.next_version("0.2.2", "bogus")
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def test_current_version_reads_and_detects_drift(tmp_path):
|
|
56
|
-
root = make_repo(tmp_path, "0.2.2")
|
|
57
|
-
assert cv.current_version(root) == "0.2.2"
|
|
58
|
-
(root / "tcw" / "__init__.py").write_text('__version__ = "9.9.9"\n') # introduce drift
|
|
59
|
-
with pytest.raises(SystemExit):
|
|
60
|
-
cv.current_version(root)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def test_bump_files_updates_all_five(tmp_path):
|
|
64
|
-
root = make_repo(tmp_path, "0.2.2")
|
|
65
|
-
cv.bump_files(root, "0.2.2", "0.2.3")
|
|
66
|
-
assert cv.current_version(root) == "0.2.3"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def test_main_end_to_end(tmp_path):
|
|
70
|
-
root = make_repo(tmp_path, "0.2.2")
|
|
71
|
-
cv.main(["patch"], root=root)
|
|
72
|
-
assert cv.current_version(root) == "0.2.3"
|
|
73
|
-
# old upcoming content rotated into the versioned files, retitled to the
|
|
74
|
-
# version it shipped as. Rotation used to be a bare `git mv`, so a released
|
|
75
|
-
# document kept the `# Upcoming` placeholder it was drafted under.
|
|
76
|
-
assert (root / "docs/changelogs/v0.2.3.md").read_text() == "# v0.2.3\n\nchangelog entries here\n"
|
|
77
|
-
assert (root / "docs/release-notes/v0.2.3.md").read_text() == "# v0.2.3\n\nrelease notes here\n"
|
|
78
|
-
# fresh upcoming.md reset (header kept, old entries gone)
|
|
79
|
-
fresh = (root / "docs/changelogs/upcoming.md").read_text()
|
|
80
|
-
assert "# Upcoming" in fresh and "changelog entries here" not in fresh
|
|
81
|
-
# commit + tag
|
|
82
|
-
tags = subprocess.run(["git", "-C", str(root), "tag"], capture_output=True, text=True).stdout
|
|
83
|
-
assert "v0.2.3" in tags
|
|
84
|
-
msg = subprocess.run(["git", "-C", str(root), "log", "-1", "--pretty=%s"],
|
|
85
|
-
capture_output=True, text=True).stdout.strip()
|
|
86
|
-
assert msg == "chore(release): cut v0.2.3"
|
|
87
|
-
# everything the cut touched is *in* the commit — the retitle above used to
|
|
88
|
-
# be left unstaged, so the tag pointed at a file still titled "# Upcoming".
|
|
89
|
-
dirty = subprocess.run(["git", "-C", str(root), "status", "--porcelain"],
|
|
90
|
-
capture_output=True, text=True).stdout
|
|
91
|
-
assert dirty == "", f"cut left uncommitted changes:\n{dirty}"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def test_rotation_drops_the_working_file_preamble(tmp_path):
|
|
95
|
-
"""A shipped release note must not carry the drafting instructions.
|
|
96
|
-
|
|
97
|
-
The `upcoming.md` header says the file is "for the next version" and tells
|
|
98
|
-
its *author* to use plain language — text addressed to whoever writes the
|
|
99
|
-
notes, not to whoever reads the release. Retitling `# Upcoming` alone left
|
|
100
|
-
that preamble in every published file from v0.6.2 through v1.0.0.
|
|
101
|
-
"""
|
|
102
|
-
root = make_repo(tmp_path, "0.2.2")
|
|
103
|
-
# The real templates the script recreates, not the fixture's short stand-in.
|
|
104
|
-
for rel, header in cv.UPCOMING.items():
|
|
105
|
-
(root / rel).write_text(header + "\n## Added\n\n- a thing\n", encoding="utf-8")
|
|
106
|
-
subprocess.run(["git", "-C", str(root), "commit", "-aqm", "real headers"], check=True)
|
|
107
|
-
|
|
108
|
-
cv.main(["minor"], root=root)
|
|
109
|
-
|
|
110
|
-
for rel in cv.UPCOMING:
|
|
111
|
-
shipped = (root / rel).with_name("v0.3.0.md").read_text(encoding="utf-8")
|
|
112
|
-
assert shipped.startswith("# v0.3.0\n"), shipped[:40]
|
|
113
|
-
assert "for the next version" not in shipped
|
|
114
|
-
assert "## Added" in shipped and "- a thing" in shipped
|
|
115
|
-
# the fresh working file keeps its preamble — it is drafting guidance
|
|
116
|
-
assert "for the next version" in (root / rel).read_text(encoding="utf-8")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|