tcw-cli 2.6.0__tar.gz → 2.6.1__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 (187) hide show
  1. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/PKG-INFO +4 -4
  2. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/README.md +3 -3
  3. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/pyproject.toml +1 -1
  4. tcw_cli-2.6.1/tcw/__init__.py +1 -0
  5. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/intake.py +49 -1
  6. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/cli.py +23 -1
  7. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/PKG-INFO +4 -4
  8. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/SOURCES.txt +1 -0
  9. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_claim.py +10 -0
  10. tcw_cli-2.6.1/tests/test_tracker_import_returns.py +162 -0
  11. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_pre_backlog.py +8 -1
  12. tcw_cli-2.6.0/tcw/__init__.py +0 -1
  13. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/LICENSE +0 -0
  14. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/setup.cfg +0 -0
  15. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/capabilities/__init__.py +0 -0
  16. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/capabilities/cli.py +0 -0
  17. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/cli.py +0 -0
  18. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/harness.py +0 -0
  19. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/refs.py +0 -0
  20. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/__init__.py +0 -0
  21. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/client/assets/index-CNxlcI8A.js +0 -0
  22. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/client/assets/index-CXNiOIeg.css +0 -0
  23. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/client/index.html +0 -0
  24. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/client/theme-init.js +0 -0
  25. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/server.cjs +0 -0
  26. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/runtime.py +0 -0
  27. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/stdin.py +0 -0
  28. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/__init__.py +0 -0
  29. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/base.py +0 -0
  30. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/checkouts.py +0 -0
  31. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/config_edit.py +0 -0
  32. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/fs.py +0 -0
  33. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/project.py +0 -0
  34. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/taxonomy/__init__.py +0 -0
  35. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/taxonomy/cli.py +0 -0
  36. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/__init__.py +0 -0
  37. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/claim.py +0 -0
  38. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/create.py +0 -0
  39. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/jira.py +0 -0
  40. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/ownership.py +0 -0
  41. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/progress.py +0 -0
  42. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/sync.py +0 -0
  43. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/validate.py +0 -0
  44. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/__init__.py +0 -0
  45. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/generate.py +0 -0
  46. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/hooks.py +0 -0
  47. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/audit-backlog.md +0 -0
  48. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/consolidate-plans.md +0 -0
  49. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/create-work.md +0 -0
  50. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/decompose.md +0 -0
  51. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/delegation.md +0 -0
  52. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/documentation-sync.md +0 -0
  53. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/post-mortem.md +0 -0
  54. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/search.md +0 -0
  55. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/triage-issues.md +0 -0
  56. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/unattended-work.md +0 -0
  57. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/projection.py +0 -0
  58. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/implement.md +0 -0
  59. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/inbox.md +0 -0
  60. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/plan.md +0 -0
  61. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/postmortem.md +0 -0
  62. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/request.md +0 -0
  63. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/spec.md +0 -0
  64. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/verify.md +0 -0
  65. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/recursion.py +0 -0
  66. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/resolve.py +0 -0
  67. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/templates.py +0 -0
  68. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/dependency_links.txt +0 -0
  69. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/entry_points.txt +0 -0
  70. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/requires.txt +0 -0
  71. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/top_level.txt +0 -0
  72. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_body_prompt.py +0 -0
  73. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities.py +0 -0
  74. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities_federation.py +0 -0
  75. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities_reset.py +0 -0
  76. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities_rm.py +0 -0
  77. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities_sidecar.py +0 -0
  78. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capability_gate_children.py +0 -0
  79. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capability_ref_wording.py +0 -0
  80. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_check_versions.py +0 -0
  81. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_child_status.py +0 -0
  82. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_cli_help_coverage.py +0 -0
  83. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_committed_paths.py +0 -0
  84. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_config_edit.py +0 -0
  85. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_config_edit_writers.py +0 -0
  86. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_configuration_text_home.py +0 -0
  87. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_cut_version.py +0 -0
  88. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_documentation_config.py +0 -0
  89. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_documentation_prompt.py +0 -0
  90. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_documentation_sync_wiring.py +0 -0
  91. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_documented_cli_surface.py +0 -0
  92. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_dynamic_skill_marker.py +0 -0
  93. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_edit_type.py +0 -0
  94. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_environment_hardness.py +0 -0
  95. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_epic_completable.py +0 -0
  96. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_coverage.py +0 -0
  97. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_files_changed.py +0 -0
  98. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_fixture.py +0 -0
  99. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_grading.py +0 -0
  100. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_runner.py +0 -0
  101. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_external_work_store.py +0 -0
  102. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_falsification_rule.py +0 -0
  103. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_generate_hook.py +0 -0
  104. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_harness.py +0 -0
  105. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_inbox_title.py +0 -0
  106. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_legacy_store_config.py +0 -0
  107. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_baseline.py +0 -0
  108. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_hooks.py +0 -0
  109. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_inert.py +0 -0
  110. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_policy.py +0 -0
  111. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_validation.py +0 -0
  112. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_multiproject.py +0 -0
  113. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_non_git_writes.py +0 -0
  114. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_plugin_manifests.py +0 -0
  115. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_procedure_config.py +0 -0
  116. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_procedure_verb.py +0 -0
  117. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_project_overrides.py +0 -0
  118. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_project_registry.py +0 -0
  119. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_projection.py +0 -0
  120. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_prompt_fallback.py +0 -0
  121. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_qualified_ref.py +0 -0
  122. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_recursion.py +0 -0
  123. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_refs.py +0 -0
  124. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_remote_session_setup.py +0 -0
  125. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_repo_lifecycle.py +0 -0
  126. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_resolve.py +0 -0
  127. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_resolve_procedure.py +0 -0
  128. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_retention.py +0 -0
  129. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_scaffold.py +0 -0
  130. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve.py +0 -0
  131. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_descendants.py +0 -0
  132. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_projection.py +0 -0
  133. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_resolve.py +0 -0
  134. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_runtime.py +0 -0
  135. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_write.py +0 -0
  136. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_session_bootstrap.py +0 -0
  137. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_shipped_procedures.py +0 -0
  138. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_shipped_prompts.py +0 -0
  139. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_show_json.py +0 -0
  140. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_skill_flow.py +0 -0
  141. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_skill_lifecycle_parity.py +0 -0
  142. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_skill_path_pointers.py +0 -0
  143. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_smoke.py +0 -0
  144. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_stage_validate.py +0 -0
  145. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_stage_verb.py +0 -0
  146. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_status_parity.py +0 -0
  147. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_stdin.py +0 -0
  148. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_stdin_cli.py +0 -0
  149. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_bounds.py +0 -0
  150. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_editor.py +0 -0
  151. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_nodes.py +0 -0
  152. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_provisioning.py +0 -0
  153. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_publication.py +0 -0
  154. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_subprocess_stdin.py +0 -0
  155. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_taxonomy.py +0 -0
  156. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tombstone.py +0 -0
  157. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_absent.py +0 -0
  158. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_binding.py +0 -0
  159. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_claimability.py +0 -0
  160. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_cli.py +0 -0
  161. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_client.py +0 -0
  162. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_comment.py +0 -0
  163. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_config.py +0 -0
  164. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_create.py +0 -0
  165. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_gate.py +0 -0
  166. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_help.py +0 -0
  167. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_hold.py +0 -0
  168. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_import.py +0 -0
  169. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_inheritance.py +0 -0
  170. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_link.py +0 -0
  171. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_ownership.py +0 -0
  172. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_replay.py +0 -0
  173. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_strict.py +0 -0
  174. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_surface.py +0 -0
  175. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_sync.py +0 -0
  176. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_sync_config.py +0 -0
  177. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_validate.py +0 -0
  178. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_unattended_work_skill.py +0 -0
  179. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_unpushed_version_script.py +0 -0
  180. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_validate.py +0 -0
  181. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_validate_target.py +0 -0
  182. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work.py +0 -0
  183. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work_autocommit.py +0 -0
  184. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work_review.py +0 -0
  185. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work_start.py +0 -0
  186. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work_tags.py +0 -0
  187. {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_worktree_completion.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tcw-cli
3
- Version: 2.6.0
3
+ Version: 2.6.1
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
@@ -727,7 +727,7 @@ to the transitions.
727
727
 
728
728
  | Lifecycle step | Without Jira | With Jira configured |
729
729
  | --------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
730
- | an item is created | `tcw work inbox accept`, `new` | `tcw work tracker import <ticket>` claims the ticket (starts it and assigns it to you), then creates a backlog item whose intake is the ticket. With `inbox-query` set, `tcw work inbox accept <ticket>` does the same |
730
+ | an item is created | `tcw work inbox accept`, `new` | `tcw work tracker import <ticket>` claims the ticket (starts it and assigns it to you), then creates a backlog item whose intake is the ticket and puts the ticket back where it was, still assigned to you. With `inbox-query` set, `tcw work inbox accept <ticket>` does the same |
731
731
  | `request`, `spec`, `plan` stages | unchanged | unchanged |
732
732
  | at any time | — | `tcw work tracker link` / `unlink` records or removes the link between an existing item and a ticket; Jira itself is not touched |
733
733
  | an item has no ticket | — | `tcw work tracker create <slug>` makes one for it and binds it; `--all` sweeps every open item without one. Needs `work.tracker.create` and `statuses.backlog`. With `create.on-new`, filing an item makes its ticket |
@@ -797,9 +797,9 @@ e-mail address or token themselves.
797
797
 
798
798
  ```sh
799
799
  tcw work tracker list # TCW: lists tickets the query selects. Jira: unchanged
800
- tcw work tracker import ENG-482 # TCW: creates a backlog item. Jira: ENG-482 → In Progress, assigned to you
800
+ tcw work tracker import ENG-482 # TCW: creates a backlog item. Jira: ENG-482 stays in To Do, assigned to you
801
801
  # …the request, spec and plan stages run as usual…
802
- tcw work start <slug> # TCW: backlog → active. Jira: confirms the claim import made
802
+ tcw work start <slug> # TCW: backlog → active. Jira: ENG-482 → In Progress
803
803
  tcw work submit <slug> # TCW: active → review. Jira: ENG-482 → In Review
804
804
  tcw work complete <slug> --resolution done --confirm
805
805
  # TCW: review → completed. Jira: ENG-482 → Done
@@ -511,7 +511,7 @@ to the transitions.
511
511
 
512
512
  | Lifecycle step | Without Jira | With Jira configured |
513
513
  | --------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
514
- | an item is created | `tcw work inbox accept`, `new` | `tcw work tracker import <ticket>` claims the ticket (starts it and assigns it to you), then creates a backlog item whose intake is the ticket. With `inbox-query` set, `tcw work inbox accept <ticket>` does the same |
514
+ | an item is created | `tcw work inbox accept`, `new` | `tcw work tracker import <ticket>` claims the ticket (starts it and assigns it to you), then creates a backlog item whose intake is the ticket and puts the ticket back where it was, still assigned to you. With `inbox-query` set, `tcw work inbox accept <ticket>` does the same |
515
515
  | `request`, `spec`, `plan` stages | unchanged | unchanged |
516
516
  | at any time | — | `tcw work tracker link` / `unlink` records or removes the link between an existing item and a ticket; Jira itself is not touched |
517
517
  | an item has no ticket | — | `tcw work tracker create <slug>` makes one for it and binds it; `--all` sweeps every open item without one. Needs `work.tracker.create` and `statuses.backlog`. With `create.on-new`, filing an item makes its ticket |
@@ -581,9 +581,9 @@ e-mail address or token themselves.
581
581
 
582
582
  ```sh
583
583
  tcw work tracker list # TCW: lists tickets the query selects. Jira: unchanged
584
- tcw work tracker import ENG-482 # TCW: creates a backlog item. Jira: ENG-482 → In Progress, assigned to you
584
+ tcw work tracker import ENG-482 # TCW: creates a backlog item. Jira: ENG-482 stays in To Do, assigned to you
585
585
  # …the request, spec and plan stages run as usual…
586
- tcw work start <slug> # TCW: backlog → active. Jira: confirms the claim import made
586
+ tcw work start <slug> # TCW: backlog → active. Jira: ENG-482 → In Progress
587
587
  tcw work submit <slug> # TCW: active → review. Jira: ENG-482 → In Review
588
588
  tcw work complete <slug> --resolution done --confirm
589
589
  # TCW: review → completed. Jira: ENG-482 → Done
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tcw-cli"
7
- version = "2.6.0"
7
+ version = "2.6.1"
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.1"
@@ -445,6 +445,9 @@ class ClaimOutcome:
445
445
  # when no such step ran. Set on success and on a refusal alike: either way the
446
446
  # ticket has left triage, and the caller has to say so (`moved_out`).
447
447
  left_status: str = ""
448
+ # The status the claim found the ticket in, after any `pre-backlog` step: where
449
+ # `import` puts a ticket its claim moved (`put_back`). Set only on a claim.
450
+ claimed_from: str = ""
448
451
 
449
452
 
450
453
  def _fields(issue: dict) -> tuple[str, str, str, str]:
@@ -625,7 +628,8 @@ def claim(client, ticket: TicketRead) -> ClaimOutcome:
625
628
  if left:
626
629
  error.left_status = left
627
630
  raise
628
- return replace(outcome, left_status=left)
631
+ return replace(outcome, left_status=left,
632
+ claimed_from=ticket.status if outcome.claimed else "")
629
633
 
630
634
 
631
635
  def _claim_from(client, ticket: TicketRead) -> ClaimOutcome:
@@ -745,3 +749,47 @@ def _claim_from(client, ticket: TicketRead) -> ClaimOutcome:
745
749
  return refused("3f", f"could not tell whether this run's claim applied: {key} is "
746
750
  f"in '{now_status}', unassigned. Check the ticket's history "
747
751
  f"in the tracker before assigning it.", detail)
752
+
753
+
754
+ def put_back(client, outcome: ClaimOutcome) -> tuple[str, str]:
755
+ """Move a ticket this run's claim took back to `outcome.claimed_from`, where the
756
+ claim found it. `import` does this once its backlog item exists, so the ticket
757
+ does not say the work has started while the item says it has not.
758
+
759
+ The claim's own transition is what kept a second claimant out, so it is taken
760
+ first and only undone here, after the item is bound. The way back is the one
761
+ transition the ticket offers to that status, chosen as a lifecycle move with no
762
+ named transition chooses it (`assess_move`); none, or more than one, sends
763
+ nothing.
764
+
765
+ Returns `(the status the ticket is in, why it is not back or "")`. Never raises a
766
+ tracker error: by now the item exists, so a ticket left where the claim put it is
767
+ something to report, not a reason to fail.
768
+ """
769
+ from tcw.tracker.claim import _normalize
770
+ from tcw.tracker.jira import TrackerError
771
+ from tcw.tracker.sync import CURRENT, assess_move
772
+
773
+ target = outcome.claimed_from
774
+ try:
775
+ ticket = read_ticket(client, outcome.issue_id)
776
+ state, found = assess_move(ticket, target=target, expected=(), move=None)
777
+ if state == CURRENT:
778
+ return ticket.status, ""
779
+ if state != "apply":
780
+ return ticket.status, found
781
+ client.apply_transition(ticket.issue_id, found.id)
782
+ status = _fields(client.issue(ticket.issue_id))[0]
783
+ except TrackerError as error:
784
+ # The transition may have landed with its answer lost, so where the ticket
785
+ # is has to be read rather than assumed.
786
+ try:
787
+ status = _fields(client.issue(outcome.issue_id))[0]
788
+ except TrackerError:
789
+ return outcome.status, f"{error}; where it is now could not be read"
790
+ if _normalize(status) == _normalize(target):
791
+ return status, ""
792
+ return status, str(error)
793
+ if _normalize(status) != _normalize(target):
794
+ return status, f"it is in '{status}' after '{found.name}'"
795
+ return status, ""
@@ -2600,11 +2600,13 @@ def _tracker_import(args: argparse.Namespace, label: str = "tracker import",
2600
2600
  locally is finished by running the command again: the ticket is then already
2601
2601
  assigned to this account, which the claim accepts without a second transition.
2602
2602
  """
2603
+ from dataclasses import replace
2603
2604
  from datetime import date
2604
2605
 
2606
+ from tcw.tracker.claim import _normalize
2605
2607
  from tcw.tracker.intake import (BINDING_SIDECAR, BindingProblem, claim,
2606
2608
  find_binding, moved_out, pre_backlog_hint,
2607
- read_ticket, validate_part)
2609
+ put_back, read_ticket, validate_part)
2608
2610
  from tcw.tracker.jira import TrackerError
2609
2611
 
2610
2612
  client = _tracker_client(label)
@@ -2706,8 +2708,28 @@ def _tracker_import(args: argparse.Namespace, label: str = "tracker import",
2706
2708
  print(f"tcw work {label}: claimed {outcome.key}, but the binding could "
2707
2709
  f"not be written: {e}. Run this command again.", file=sys.stderr)
2708
2710
  return 1
2711
+ # The item is in the backlog, so a ticket this run's claim moved goes back where
2712
+ # the claim found it. Only now: every failure above is finished by running the
2713
+ # command again, which finds the ticket already assigned (row `1e`) and moves
2714
+ # nothing — so a ticket put back earlier would be left claimed with no item.
2715
+ # Not under strict mode, whose proof that nobody else can claim the ticket is
2716
+ # that it sits where the exclusive transition led. Only for a ticket this run's
2717
+ # transition moved: a claim can also count when somebody else moved a ticket
2718
+ # already yours (row `3a` with a refused transition), and that is work under way,
2719
+ # not a move of ours to undo. And not for a ticket still where the claim found
2720
+ # it, which saves an already-held ticket (row `1e`) the tracker reads — a failure
2721
+ # in them would warn about work nobody moved.
2722
+ put_back_failed = ""
2723
+ if (outcome.transitioned and outcome.claimed_from and not client.config.strict
2724
+ and _normalize(outcome.claimed_from) != _normalize(outcome.status)):
2725
+ status, put_back_failed = put_back(client, outcome)
2726
+ outcome = replace(outcome, status=status)
2709
2727
  print(slug)
2710
2728
  print(f"→ {_claim_summary(outcome)}; bound to {slug}", file=sys.stderr)
2729
+ if put_back_failed:
2730
+ print(f"warning: {outcome.key} stays in '{outcome.status}', although its item "
2731
+ f"is in the backlog: {put_back_failed.rstrip('.')}. Move it back in the "
2732
+ f"tracker, or leave it until the item starts.", file=sys.stderr)
2711
2733
  if not outcome.transitioned:
2712
2734
  print(f"→ {outcome.message}", file=sys.stderr)
2713
2735
  if outcome.row == "1e" and (hint := pre_backlog_hint(client.config, outcome.status,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tcw-cli
3
- Version: 2.6.0
3
+ Version: 2.6.1
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
@@ -727,7 +727,7 @@ to the transitions.
727
727
 
728
728
  | Lifecycle step | Without Jira | With Jira configured |
729
729
  | --------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
730
- | an item is created | `tcw work inbox accept`, `new` | `tcw work tracker import <ticket>` claims the ticket (starts it and assigns it to you), then creates a backlog item whose intake is the ticket. With `inbox-query` set, `tcw work inbox accept <ticket>` does the same |
730
+ | an item is created | `tcw work inbox accept`, `new` | `tcw work tracker import <ticket>` claims the ticket (starts it and assigns it to you), then creates a backlog item whose intake is the ticket and puts the ticket back where it was, still assigned to you. With `inbox-query` set, `tcw work inbox accept <ticket>` does the same |
731
731
  | `request`, `spec`, `plan` stages | unchanged | unchanged |
732
732
  | at any time | — | `tcw work tracker link` / `unlink` records or removes the link between an existing item and a ticket; Jira itself is not touched |
733
733
  | an item has no ticket | — | `tcw work tracker create <slug>` makes one for it and binds it; `--all` sweeps every open item without one. Needs `work.tracker.create` and `statuses.backlog`. With `create.on-new`, filing an item makes its ticket |
@@ -797,9 +797,9 @@ e-mail address or token themselves.
797
797
 
798
798
  ```sh
799
799
  tcw work tracker list # TCW: lists tickets the query selects. Jira: unchanged
800
- tcw work tracker import ENG-482 # TCW: creates a backlog item. Jira: ENG-482 → In Progress, assigned to you
800
+ tcw work tracker import ENG-482 # TCW: creates a backlog item. Jira: ENG-482 stays in To Do, assigned to you
801
801
  # …the request, spec and plan stages run as usual…
802
- tcw work start <slug> # TCW: backlog → active. Jira: confirms the claim import made
802
+ tcw work start <slug> # TCW: backlog → active. Jira: ENG-482 → In Progress
803
803
  tcw work submit <slug> # TCW: active → review. Jira: ENG-482 → In Review
804
804
  tcw work complete <slug> --resolution done --confirm
805
805
  # TCW: review → completed. Jira: ENG-482 → Done
@@ -161,6 +161,7 @@ tests/test_tracker_gate.py
161
161
  tests/test_tracker_help.py
162
162
  tests/test_tracker_hold.py
163
163
  tests/test_tracker_import.py
164
+ tests/test_tracker_import_returns.py
164
165
  tests/test_tracker_inheritance.py
165
166
  tests/test_tracker_link.py
166
167
  tests/test_tracker_ownership.py
@@ -74,6 +74,16 @@ def test_a_ready_unassigned_ticket_is_transitioned_then_assigned(fake, alice):
74
74
  assert outcome.url == f"{BASE_URL}/browse/TCWCLAIM-6"
75
75
 
76
76
 
77
+ def test_a_claim_records_the_status_it_found_the_ticket_in(fake, alice):
78
+ """What `import` puts the ticket back to once its item exists."""
79
+ assert _claim(alice).claimed_from == "To Do"
80
+
81
+
82
+ def test_a_refused_claim_records_no_status(fake, alice):
83
+ fake.tickets["10052"].assignee = B
84
+ assert _claim(alice).claimed_from == ""
85
+
86
+
77
87
  def test_the_key_the_user_typed_resolves_to_the_canonical_one(fake, alice):
78
88
  assert _claim(alice, "tcwclaim-6").key == "TCWCLAIM-6"
79
89
 
@@ -0,0 +1,162 @@
1
+ """`import` and `inbox accept` put a ticket their claim moved back where the claim
2
+ found it, so it reads as not started, like the backlog item they create.
3
+
4
+ The claim itself still moves the ticket first: that transition is what keeps two
5
+ people importing the same ticket apart. Only once the item exists and is bound is
6
+ the ticket put back — and only when this run moved it, never under strict mode, and
7
+ never at the cost of the import: a ticket that cannot go back is a warning.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import pytest
13
+
14
+ from tcw.store.fs import FsWorkStore
15
+ from tcw.tracker.jira import TrackerUnavailable
16
+ from test_tracker_pre_backlog import TRIAGE, post_fails, set_pre_backlog
17
+ from test_tracker_strict import set_tracker_key, strict_node
18
+ from test_tracker_sync import A, KEY, SENTINEL, TICKET_ID, cli, ladder_node
19
+ from tracker_fake import SYNC, FakeJira
20
+
21
+ STOP = ("71", "Stop", "To Do")
22
+ # `SYNC` with a way back from the working statuses to the backlog, as the TCW Jira
23
+ # workflow has had since 2026-09-24.
24
+ WITH_STOP = {**SYNC, "In Progress": [*SYNC["In Progress"], STOP],
25
+ "In Review": [*SYNC["In Review"], STOP]}
26
+ START, BACK = "21", "71"
27
+ TRANSITIONS = f"/rest/api/3/issue/{TICKET_ID}/transitions"
28
+ ASSIGNEE = f"/rest/api/3/issue/{TICKET_ID}/assignee"
29
+
30
+
31
+ def items(root):
32
+ return FsWorkStore.open(root).board()
33
+
34
+
35
+ def ticket(fake_):
36
+ t = fake_.tickets[TICKET_ID]
37
+ return t.status, t.assignee
38
+
39
+
40
+ def warnings(err: str) -> list[str]:
41
+ return [line for line in err.splitlines() if line.startswith("warning:")]
42
+
43
+
44
+ @pytest.mark.parametrize("verb", [("tracker", "import"), ("inbox", "accept")])
45
+ def test_the_ticket_goes_back_to_where_the_claim_found_it(tmp_path, monkeypatch, verb):
46
+ root, fake_ = ladder_node(tmp_path, monkeypatch, dict(WITH_STOP))
47
+ set_tracker_key(root, "inbox-query", "project = SYNC")
48
+ code, out, err = cli(root, "work", *verb, KEY)
49
+ assert code == 0, err
50
+ [item] = items(root)
51
+ assert item.status == "backlog" and item.tracker is not None
52
+ assert ticket(fake_) == ("To Do", A)
53
+ assert fake_.applied == [START, BACK]
54
+ assert fake_.writes() == [("POST", TRANSITIONS), ("PUT", ASSIGNEE),
55
+ ("POST", TRANSITIONS)]
56
+ assert f"{KEY} is in 'To Do', claimed by this run" in err
57
+ assert warnings(err) == []
58
+
59
+
60
+ def test_a_ticket_taken_out_of_triage_goes_back_to_the_backlog_not_triage(
61
+ tmp_path, monkeypatch):
62
+ workflow = {**TRIAGE, "In Progress": [*TRIAGE["In Progress"], STOP]}
63
+ root, fake_ = ladder_node(tmp_path, monkeypatch, workflow, status="Triage")
64
+ set_pre_backlog(root, {"Triage": "Accept"})
65
+ code, _out, err = cli(root, "work", "tracker", "import", KEY)
66
+ assert code == 0, err
67
+ assert ticket(fake_) == ("To Do", A)
68
+ assert fake_.applied == ["11", START, BACK]
69
+
70
+
71
+ def test_a_ticket_already_yours_and_under_way_is_left_alone(tmp_path, monkeypatch):
72
+ root, fake_ = ladder_node(tmp_path, monkeypatch, dict(WITH_STOP),
73
+ status="In Progress", assignee=A)
74
+ code, _out, err = cli(root, "work", "tracker", "import", KEY)
75
+ assert code == 0, err
76
+ assert len(items(root)) == 1
77
+ assert ticket(fake_) == ("In Progress", A)
78
+ assert fake_.writes() == []
79
+ # Not even asked about: the claim's own read is the only one, so a tracker that
80
+ # fails afterwards cannot turn work already under way into a warning.
81
+ assert [p for m, p, _a in fake_.requests
82
+ if m == "GET" and p == TRANSITIONS] == [TRANSITIONS]
83
+
84
+
85
+ def test_a_workflow_with_no_way_back_warns_and_still_imports(tmp_path, monkeypatch):
86
+ root, fake_ = ladder_node(tmp_path, monkeypatch, dict(SYNC))
87
+ code, _out, err = cli(root, "work", "tracker", "import", KEY)
88
+ assert code == 0, err
89
+ [item] = items(root)
90
+ assert item.tracker is not None
91
+ assert ticket(fake_) == ("In Progress", A)
92
+ assert fake_.applied == [START]
93
+ [warning] = warnings(err)
94
+ assert KEY in warning and "'In Progress'" in warning and "backlog" in warning
95
+ assert ".." not in warning
96
+ assert f"{KEY} is in 'In Progress', claimed by this run" in err
97
+
98
+
99
+ def test_a_tracker_error_on_the_way_back_warns_and_still_imports(tmp_path, monkeypatch):
100
+ root, fake_ = ladder_node(tmp_path, monkeypatch, dict(WITH_STOP))
101
+ post_fails(fake_, 2, TrackerUnavailable("the tracker went away (fake)"))
102
+ code, _out, err = cli(root, "work", "tracker", "import", KEY)
103
+ assert code == 0, err
104
+ [item] = items(root)
105
+ assert item.tracker is not None
106
+ assert ticket(fake_) == ("In Progress", A)
107
+ [warning] = warnings(err)
108
+ assert KEY in warning and "went away" in warning
109
+
110
+
111
+ def test_strict_mode_leaves_the_ticket_where_the_claim_put_it(tmp_path, monkeypatch):
112
+ monkeypatch.setenv("TCW_A_EMAIL", "a@example.test")
113
+ monkeypatch.setenv("TCW_PROBE_TOKEN", SENTINEL)
114
+ monkeypatch.setenv("TCW_WORK_OWNER", "a@example.test")
115
+ fake_ = FakeJira(workflow=dict(WITH_STOP))
116
+ fake_.account("a@example.test", A, "Alice")
117
+ fake_.ticket(id=TICKET_ID, key=KEY, summary="t")
118
+ fake_.install(monkeypatch)
119
+ root = strict_node(tmp_path, strict=True, claim_transition="Start Progress")
120
+ code, _out, err = cli(root, "work", "tracker", "import", KEY)
121
+ assert code == 0, err
122
+ assert len(items(root)) == 1
123
+ assert ticket(fake_) == ("In Progress", A)
124
+ assert fake_.applied == [START]
125
+
126
+
127
+ def test_a_ticket_moved_by_someone_else_during_the_claim_is_left_alone(
128
+ tmp_path, monkeypatch):
129
+ """The claim still counts — the ticket is yours and where the claim leads — but
130
+ this run's transition did not move it: somebody else did. That is work under
131
+ way, not a move of ours to undo."""
132
+ root, fake_ = ladder_node(tmp_path, monkeypatch, dict(WITH_STOP), assignee=A)
133
+
134
+ def moved_first():
135
+ fake_.tickets[TICKET_ID].status = "In Progress"
136
+
137
+ fake_.before("POST", "/transitions", moved_first)
138
+ code, _out, err = cli(root, "work", "tracker", "import", KEY)
139
+ assert code == 0, err
140
+ assert len(items(root)) == 1
141
+ assert ticket(fake_) == ("In Progress", A)
142
+ assert fake_.applied == []
143
+
144
+
145
+ def test_an_unanswered_way_back_reports_where_the_ticket_really_is(tmp_path, monkeypatch):
146
+ root, fake_ = ladder_node(tmp_path, monkeypatch, dict(WITH_STOP))
147
+ post_fails(fake_, 2, TrackerUnavailable("no answer (fake)"), apply_first=True)
148
+ code, _out, err = cli(root, "work", "tracker", "import", KEY)
149
+ assert code == 0, err
150
+ assert ticket(fake_) == ("To Do", A)
151
+ assert f"{KEY} is in 'To Do', claimed by this run" in err
152
+ assert warnings(err) == []
153
+
154
+
155
+ def test_starting_an_imported_item_moves_its_ticket_on(tmp_path, monkeypatch):
156
+ root, fake_ = ladder_node(tmp_path, monkeypatch, dict(WITH_STOP))
157
+ code, out, err = cli(root, "work", "tracker", "import", KEY)
158
+ assert code == 0, err
159
+ code, _out, err = cli(root, "work", "start", out.strip())
160
+ assert code == 0, err
161
+ assert ticket(fake_) == ("In Progress", A)
162
+ assert fake_.applied == [START, BACK, START]
@@ -157,6 +157,11 @@ def test_an_unassigned_triage_ticket_is_accepted_then_claimed(fake):
157
157
  assert (fake.tickets[ID].status, fake.tickets[ID].assignee) == ("In Progress", A)
158
158
 
159
159
 
160
+ def test_a_triage_ticket_is_recorded_as_found_in_the_backlog_status(fake):
161
+ """The step's landing, not Triage: an import puts the ticket back there."""
162
+ assert _claim().claimed_from == "To Do"
163
+
164
+
160
165
  def test_a_triage_ticket_already_yours_is_accepted_then_claimed(fake):
161
166
  """The reporter's case: a creator is often the ticket's assignee, and today that
162
167
  is row 1e — "already held" — with the ticket left in Triage."""
@@ -661,7 +666,9 @@ def test_import_of_a_triage_ticket_already_yours(tmp_path, monkeypatch):
661
666
  code, _out, err = cli(root, "work", "tracker", "import", KEY)
662
667
  assert code == 0, err
663
668
  assert fake_.applied == ACCEPT_THEN_START
664
- assert "warning:" not in err
669
+ # No "stays in Triage" warning. `TRIAGE` offers no way back from In Progress,
670
+ # so the ticket's own put-back warning is expected and is not what this is about.
671
+ assert "stays in 'Triage'" not in err and HINT not in err
665
672
 
666
673
 
667
674
  def test_without_the_setting_import_of_a_triage_ticket_already_yours_warns(tmp_path,
@@ -1 +0,0 @@
1
- __version__ = "2.6.0"
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