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.
Files changed (204) hide show
  1. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/PKG-INFO +6 -3
  2. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/README.md +5 -2
  3. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/pyproject.toml +1 -1
  4. tcw_cli-2.6.4/tcw/__init__.py +1 -0
  5. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/documentation-sync.md +4 -4
  6. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/unattended-work.md +1 -1
  7. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/PKG-INFO +6 -3
  8. tcw_cli-2.6.4/tests/test_cut_version.py +236 -0
  9. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_repo_lifecycle.py +2 -2
  10. tcw_cli-2.6.3/tcw/__init__.py +0 -1
  11. tcw_cli-2.6.3/tests/test_cut_version.py +0 -116
  12. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/LICENSE +0 -0
  13. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/setup.cfg +0 -0
  14. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/capabilities/__init__.py +0 -0
  15. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/capabilities/cli.py +0 -0
  16. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/cli.py +0 -0
  17. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/harness.py +0 -0
  18. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/refs.py +0 -0
  19. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/__init__.py +0 -0
  20. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/client/assets/index-C5_pngMt.css +0 -0
  21. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/client/assets/index-Dss8_ZG_.js +0 -0
  22. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/client/index.html +0 -0
  23. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/client/theme-init.js +0 -0
  24. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/dist/server.cjs +0 -0
  25. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/serve/runtime.py +0 -0
  26. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/stdin.py +0 -0
  27. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/__init__.py +0 -0
  28. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/base.py +0 -0
  29. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/checkouts.py +0 -0
  30. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/config_edit.py +0 -0
  31. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/fs.py +0 -0
  32. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/store/project.py +0 -0
  33. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/taxonomy/__init__.py +0 -0
  34. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/taxonomy/cli.py +0 -0
  35. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/__init__.py +0 -0
  36. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/claim.py +0 -0
  37. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/create.py +0 -0
  38. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/intake.py +0 -0
  39. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/jira.py +0 -0
  40. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/ownership.py +0 -0
  41. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/progress.py +0 -0
  42. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/tracker/sync.py +0 -0
  43. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/validate.py +0 -0
  44. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/__init__.py +0 -0
  45. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/cli.py +0 -0
  46. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/generate.py +0 -0
  47. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/hooks.py +0 -0
  48. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/audit-backlog.md +0 -0
  49. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/consolidate-plans.md +0 -0
  50. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/create-work.md +0 -0
  51. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/decompose.md +0 -0
  52. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/delegation.md +0 -0
  53. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/post-mortem.md +0 -0
  54. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/search.md +0 -0
  55. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/procedures/triage-issues.md +0 -0
  56. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/projection.py +0 -0
  57. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/implement.md +0 -0
  58. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/inbox.md +0 -0
  59. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/plan.md +0 -0
  60. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/postmortem.md +0 -0
  61. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/request.md +0 -0
  62. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/spec.md +0 -0
  63. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/prompts/verify.md +0 -0
  64. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/recursion.py +0 -0
  65. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/resolve.py +0 -0
  66. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw/work/templates.py +0 -0
  67. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/SOURCES.txt +0 -0
  68. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/dependency_links.txt +0 -0
  69. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/entry_points.txt +0 -0
  70. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/requires.txt +0 -0
  71. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tcw_cli.egg-info/top_level.txt +0 -0
  72. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_body_prompt.py +0 -0
  73. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities.py +0 -0
  74. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities_federation.py +0 -0
  75. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities_reset.py +0 -0
  76. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities_rm.py +0 -0
  77. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capabilities_sidecar.py +0 -0
  78. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capability_gate_children.py +0 -0
  79. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_capability_ref_wording.py +0 -0
  80. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_check_versions.py +0 -0
  81. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_child_status.py +0 -0
  82. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_cli_help_coverage.py +0 -0
  83. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_committed_paths.py +0 -0
  84. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_config_edit.py +0 -0
  85. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_config_edit_writers.py +0 -0
  86. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_configuration_text_home.py +0 -0
  87. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_cross_node_blockers.py +0 -0
  88. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_documentation_config.py +0 -0
  89. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_documentation_prompt.py +0 -0
  90. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_documentation_sync_wiring.py +0 -0
  91. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_documented_cli_surface.py +0 -0
  92. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_dynamic_skill_marker.py +0 -0
  93. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_edit_refusal.py +0 -0
  94. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_edit_type.py +0 -0
  95. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_environment_hardness.py +0 -0
  96. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_epic_completability_across_checkouts.py +0 -0
  97. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_epic_completable.py +0 -0
  98. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_coverage.py +0 -0
  99. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_files_changed.py +0 -0
  100. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_fixture.py +0 -0
  101. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_grading.py +0 -0
  102. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_eval_runner.py +0 -0
  103. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_external_work_store.py +0 -0
  104. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_falsification_rule.py +0 -0
  105. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_generate_hook.py +0 -0
  106. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_harness.py +0 -0
  107. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_inbox_title.py +0 -0
  108. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_interrupted_claim.py +0 -0
  109. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_item_tags_read.py +0 -0
  110. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_legacy_store_config.py +0 -0
  111. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_baseline.py +0 -0
  112. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_config_tags.py +0 -0
  113. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_hooks.py +0 -0
  114. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_inert.py +0 -0
  115. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_policy.py +0 -0
  116. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_lifecycle_validation.py +0 -0
  117. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_literal_store_names.py +0 -0
  118. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_multiproject.py +0 -0
  119. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_non_git_writes.py +0 -0
  120. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_plugin_manifests.py +0 -0
  121. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_procedure_config.py +0 -0
  122. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_procedure_verb.py +0 -0
  123. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_project_overrides.py +0 -0
  124. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_project_registry.py +0 -0
  125. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_projection.py +0 -0
  126. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_prompt_fallback.py +0 -0
  127. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_qualified_ref.py +0 -0
  128. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_recursion.py +0 -0
  129. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_refs.py +0 -0
  130. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_remote_session_setup.py +0 -0
  131. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_resolve.py +0 -0
  132. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_resolve_procedure.py +0 -0
  133. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_retention.py +0 -0
  134. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_routing_nodes.py +0 -0
  135. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_scaffold.py +0 -0
  136. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve.py +0 -0
  137. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_descendants.py +0 -0
  138. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_projection.py +0 -0
  139. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_resolve.py +0 -0
  140. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_runtime.py +0 -0
  141. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_serve_write.py +0 -0
  142. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_session_bootstrap.py +0 -0
  143. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_shipped_procedures.py +0 -0
  144. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_shipped_prompts.py +0 -0
  145. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_show_json.py +0 -0
  146. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_sidecar_paths_in_validate.py +0 -0
  147. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_skill_flow.py +0 -0
  148. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_skill_lifecycle_parity.py +0 -0
  149. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_skill_path_pointers.py +0 -0
  150. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_smoke.py +0 -0
  151. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_stage_validate.py +0 -0
  152. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_stage_verb.py +0 -0
  153. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_status_parity.py +0 -0
  154. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_stdin.py +0 -0
  155. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_stdin_cli.py +0 -0
  156. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_bounds.py +0 -0
  157. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_editor.py +0 -0
  158. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_nodes.py +0 -0
  159. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_provisioning.py +0 -0
  160. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_store_publication.py +0 -0
  161. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_subprocess_stdin.py +0 -0
  162. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_taxonomy.py +0 -0
  163. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_taxonomy_rm_gaps.py +0 -0
  164. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tombstone.py +0 -0
  165. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_absent.py +0 -0
  166. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_binding.py +0 -0
  167. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_claim.py +0 -0
  168. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_claimability.py +0 -0
  169. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_cli.py +0 -0
  170. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_client.py +0 -0
  171. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_comment.py +0 -0
  172. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_config.py +0 -0
  173. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_create.py +0 -0
  174. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_gate.py +0 -0
  175. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_hardening.py +0 -0
  176. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_help.py +0 -0
  177. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_hold.py +0 -0
  178. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_import.py +0 -0
  179. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_import_returns.py +0 -0
  180. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_inheritance.py +0 -0
  181. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_link.py +0 -0
  182. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_ownership.py +0 -0
  183. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_pre_backlog.py +0 -0
  184. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_replay.py +0 -0
  185. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_strict.py +0 -0
  186. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_strict_gate.py +0 -0
  187. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_surface.py +0 -0
  188. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_sync.py +0 -0
  189. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_sync_config.py +0 -0
  190. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_tracker_validate.py +0 -0
  191. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_unattended_work_skill.py +0 -0
  192. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_unplanned_start.py +0 -0
  193. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_unpushed_version_script.py +0 -0
  194. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_unreadable_capabilities_sidecar.py +0 -0
  195. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_validate.py +0 -0
  196. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_validate_moved_stores.py +0 -0
  197. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_validate_target.py +0 -0
  198. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work.py +0 -0
  199. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work_autocommit.py +0 -0
  200. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work_review.py +0 -0
  201. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work_start.py +0 -0
  202. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_work_tags.py +0 -0
  203. {tcw_cli-2.6.3 → tcw_cli-2.6.4}/tests/test_worktree_completion.py +0 -0
  204. {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
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, turns the `upcoming.md` changelog and release notes into
1106
- that version's files, commits and tags. Pushing the tag publishes the release to
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, turns the `upcoming.md` changelog and release notes into
890
- that version's files, commits and tags. Pushing the tag publishes the release to
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.3"
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 writing entries, rotating `upcoming.md`, running the version cross-check, or migrating an existing `CHANGELOG.md`. |
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, rotating, committing, tagging. See "When the user asks to cut a version" below. |
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.md`
88
- and `docs/changelogs/upcoming.md` — are answered by the documentation gate at
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.md` and move on. |
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
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, turns the `upcoming.md` changelog and release notes into
1106
- that version's files, commits and tags. Pushing the tag publishes the release to
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.md",
106
- "docs/changelogs/upcoming.md",
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
  ]
@@ -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