tcw-cli 2.6.1__tar.gz → 2.6.3__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 (205) hide show
  1. {tcw_cli-2.6.1/tcw_cli.egg-info → tcw_cli-2.6.3}/PKG-INFO +3 -3
  2. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/README.md +2 -2
  3. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/pyproject.toml +1 -1
  4. tcw_cli-2.6.3/tcw/__init__.py +1 -0
  5. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/cli.py +3 -1
  6. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/__init__.py +66 -11
  7. tcw_cli-2.6.3/tcw/serve/dist/client/assets/index-C5_pngMt.css +1 -0
  8. tcw_cli-2.6.3/tcw/serve/dist/client/assets/index-Dss8_ZG_.js +128 -0
  9. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/dist/client/index.html +2 -2
  10. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/dist/server.cjs +29856 -27944
  11. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/base.py +289 -39
  12. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/fs.py +514 -72
  13. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/project.py +54 -21
  14. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/intake.py +7 -5
  15. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/jira.py +61 -16
  16. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/sync.py +41 -8
  17. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/validate.py +148 -39
  18. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/cli.py +214 -55
  19. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/plan.md +2 -1
  20. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/recursion.py +99 -36
  21. {tcw_cli-2.6.1 → tcw_cli-2.6.3/tcw_cli.egg-info}/PKG-INFO +3 -3
  22. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/SOURCES.txt +19 -3
  23. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_config_edit_writers.py +37 -0
  24. tcw_cli-2.6.3/tests/test_cross_node_blockers.py +230 -0
  25. tcw_cli-2.6.3/tests/test_edit_refusal.py +176 -0
  26. tcw_cli-2.6.3/tests/test_epic_completability_across_checkouts.py +272 -0
  27. tcw_cli-2.6.3/tests/test_interrupted_claim.py +219 -0
  28. tcw_cli-2.6.3/tests/test_item_tags_read.py +139 -0
  29. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_legacy_store_config.py +5 -5
  30. tcw_cli-2.6.3/tests/test_lifecycle_config_tags.py +188 -0
  31. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_hooks.py +3 -2
  32. tcw_cli-2.6.3/tests/test_literal_store_names.py +114 -0
  33. tcw_cli-2.6.3/tests/test_routing_nodes.py +144 -0
  34. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_write.py +4 -0
  35. tcw_cli-2.6.3/tests/test_sidecar_paths_in_validate.py +112 -0
  36. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_stage_verb.py +18 -4
  37. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_provisioning.py +62 -0
  38. tcw_cli-2.6.3/tests/test_taxonomy_rm_gaps.py +194 -0
  39. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_claim.py +1 -1
  40. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_cli.py +32 -1
  41. tcw_cli-2.6.3/tests/test_tracker_hardening.py +187 -0
  42. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_strict.py +1 -0
  43. tcw_cli-2.6.3/tests/test_tracker_strict_gate.py +275 -0
  44. tcw_cli-2.6.3/tests/test_unplanned_start.py +163 -0
  45. tcw_cli-2.6.3/tests/test_unreadable_capabilities_sidecar.py +201 -0
  46. tcw_cli-2.6.3/tests/test_validate_moved_stores.py +199 -0
  47. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work.py +2 -1
  48. tcw_cli-2.6.3/tests/test_worktree_sibling_nodes.py +152 -0
  49. tcw_cli-2.6.1/tcw/__init__.py +0 -1
  50. tcw_cli-2.6.1/tcw/serve/dist/client/assets/index-CNxlcI8A.js +0 -181
  51. tcw_cli-2.6.1/tcw/serve/dist/client/assets/index-CXNiOIeg.css +0 -1
  52. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/LICENSE +0 -0
  53. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/setup.cfg +0 -0
  54. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/capabilities/__init__.py +0 -0
  55. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/capabilities/cli.py +0 -0
  56. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/harness.py +0 -0
  57. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/refs.py +0 -0
  58. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/dist/client/theme-init.js +0 -0
  59. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/runtime.py +0 -0
  60. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/stdin.py +0 -0
  61. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/__init__.py +0 -0
  62. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/checkouts.py +0 -0
  63. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/config_edit.py +0 -0
  64. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/taxonomy/__init__.py +0 -0
  65. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/taxonomy/cli.py +0 -0
  66. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/__init__.py +0 -0
  67. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/claim.py +0 -0
  68. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/create.py +0 -0
  69. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/ownership.py +0 -0
  70. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/progress.py +0 -0
  71. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/__init__.py +0 -0
  72. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/generate.py +0 -0
  73. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/hooks.py +0 -0
  74. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/audit-backlog.md +0 -0
  75. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/consolidate-plans.md +0 -0
  76. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/create-work.md +0 -0
  77. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/decompose.md +0 -0
  78. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/delegation.md +0 -0
  79. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/documentation-sync.md +0 -0
  80. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/post-mortem.md +0 -0
  81. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/search.md +0 -0
  82. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/triage-issues.md +0 -0
  83. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/unattended-work.md +0 -0
  84. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/projection.py +0 -0
  85. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/implement.md +0 -0
  86. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/inbox.md +0 -0
  87. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/postmortem.md +0 -0
  88. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/request.md +0 -0
  89. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/spec.md +0 -0
  90. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/verify.md +0 -0
  91. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/resolve.py +0 -0
  92. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/templates.py +0 -0
  93. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/dependency_links.txt +0 -0
  94. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/entry_points.txt +0 -0
  95. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/requires.txt +0 -0
  96. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/top_level.txt +0 -0
  97. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_body_prompt.py +0 -0
  98. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities.py +0 -0
  99. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities_federation.py +0 -0
  100. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities_reset.py +0 -0
  101. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities_rm.py +0 -0
  102. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities_sidecar.py +0 -0
  103. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capability_gate_children.py +0 -0
  104. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capability_ref_wording.py +0 -0
  105. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_check_versions.py +0 -0
  106. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_child_status.py +0 -0
  107. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_cli_help_coverage.py +0 -0
  108. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_committed_paths.py +0 -0
  109. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_config_edit.py +0 -0
  110. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_configuration_text_home.py +0 -0
  111. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_cut_version.py +0 -0
  112. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_documentation_config.py +0 -0
  113. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_documentation_prompt.py +0 -0
  114. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_documentation_sync_wiring.py +0 -0
  115. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_documented_cli_surface.py +0 -0
  116. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_dynamic_skill_marker.py +0 -0
  117. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_edit_type.py +0 -0
  118. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_environment_hardness.py +0 -0
  119. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_epic_completable.py +0 -0
  120. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_coverage.py +0 -0
  121. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_files_changed.py +0 -0
  122. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_fixture.py +0 -0
  123. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_grading.py +0 -0
  124. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_runner.py +0 -0
  125. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_external_work_store.py +0 -0
  126. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_falsification_rule.py +0 -0
  127. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_generate_hook.py +0 -0
  128. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_harness.py +0 -0
  129. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_inbox_title.py +0 -0
  130. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_baseline.py +0 -0
  131. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_inert.py +0 -0
  132. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_policy.py +0 -0
  133. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_validation.py +0 -0
  134. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_multiproject.py +0 -0
  135. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_non_git_writes.py +0 -0
  136. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_plugin_manifests.py +0 -0
  137. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_procedure_config.py +0 -0
  138. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_procedure_verb.py +0 -0
  139. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_project_overrides.py +0 -0
  140. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_project_registry.py +0 -0
  141. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_projection.py +0 -0
  142. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_prompt_fallback.py +0 -0
  143. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_qualified_ref.py +0 -0
  144. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_recursion.py +0 -0
  145. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_refs.py +0 -0
  146. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_remote_session_setup.py +0 -0
  147. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_repo_lifecycle.py +0 -0
  148. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_resolve.py +0 -0
  149. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_resolve_procedure.py +0 -0
  150. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_retention.py +0 -0
  151. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_scaffold.py +0 -0
  152. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve.py +0 -0
  153. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_descendants.py +0 -0
  154. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_projection.py +0 -0
  155. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_resolve.py +0 -0
  156. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_runtime.py +0 -0
  157. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_session_bootstrap.py +0 -0
  158. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_shipped_procedures.py +0 -0
  159. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_shipped_prompts.py +0 -0
  160. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_show_json.py +0 -0
  161. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_skill_flow.py +0 -0
  162. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_skill_lifecycle_parity.py +0 -0
  163. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_skill_path_pointers.py +0 -0
  164. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_smoke.py +0 -0
  165. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_stage_validate.py +0 -0
  166. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_status_parity.py +0 -0
  167. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_stdin.py +0 -0
  168. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_stdin_cli.py +0 -0
  169. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_bounds.py +0 -0
  170. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_editor.py +0 -0
  171. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_nodes.py +0 -0
  172. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_publication.py +0 -0
  173. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_subprocess_stdin.py +0 -0
  174. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_taxonomy.py +0 -0
  175. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tombstone.py +0 -0
  176. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_absent.py +0 -0
  177. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_binding.py +0 -0
  178. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_claimability.py +0 -0
  179. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_client.py +0 -0
  180. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_comment.py +0 -0
  181. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_config.py +0 -0
  182. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_create.py +0 -0
  183. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_gate.py +0 -0
  184. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_help.py +0 -0
  185. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_hold.py +0 -0
  186. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_import.py +0 -0
  187. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_import_returns.py +0 -0
  188. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_inheritance.py +0 -0
  189. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_link.py +0 -0
  190. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_ownership.py +0 -0
  191. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_pre_backlog.py +0 -0
  192. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_replay.py +0 -0
  193. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_surface.py +0 -0
  194. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_sync.py +0 -0
  195. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_sync_config.py +0 -0
  196. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_validate.py +0 -0
  197. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_unattended_work_skill.py +0 -0
  198. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_unpushed_version_script.py +0 -0
  199. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_validate.py +0 -0
  200. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_validate_target.py +0 -0
  201. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work_autocommit.py +0 -0
  202. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work_review.py +0 -0
  203. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work_start.py +0 -0
  204. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work_tags.py +0 -0
  205. {tcw_cli-2.6.1 → tcw_cli-2.6.3}/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.1
3
+ Version: 2.6.3
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
@@ -908,9 +908,9 @@ driving it to completion, verifying it, processing the inbox) described in
908
908
  | -------------------- | ----------------------------------------------------------------- |
909
909
  | `tcw work inbox` | `inbox list`, `inbox show` and `inbox accept` raw requests, and Jira tickets awaiting triage |
910
910
  | `tcw work nodes` | lists this project's parent and child projects |
911
- | `tcw work delegate` | writes a request into a child project's inbox |
911
+ | `tcw work delegate` | writes a request into the nearest child project with a board |
912
912
  | `tcw work escalate` | writes a request into the parent project's inbox |
913
- | `tcw work reconcile` | reads child projects' items and writes an epic's rolled-up status |
913
+ | `tcw work reconcile` | reads every project below with a board; writes the epic's roll-up |
914
914
 
915
915
  **Jira and housekeeping**
916
916
 
@@ -692,9 +692,9 @@ driving it to completion, verifying it, processing the inbox) described in
692
692
  | -------------------- | ----------------------------------------------------------------- |
693
693
  | `tcw work inbox` | `inbox list`, `inbox show` and `inbox accept` raw requests, and Jira tickets awaiting triage |
694
694
  | `tcw work nodes` | lists this project's parent and child projects |
695
- | `tcw work delegate` | writes a request into a child project's inbox |
695
+ | `tcw work delegate` | writes a request into the nearest child project with a board |
696
696
  | `tcw work escalate` | writes a request into the parent project's inbox |
697
- | `tcw work reconcile` | reads child projects' items and writes an epic's rolled-up status |
697
+ | `tcw work reconcile` | reads every project below with a board; writes the epic's roll-up |
698
698
 
699
699
  **Jira and housekeeping**
700
700
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tcw-cli"
7
- version = "2.6.1"
7
+ version = "2.6.3"
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.3"
@@ -71,7 +71,9 @@ def run_init(components: list[str], project_id: str | None = None,
71
71
  components, root, project_id,
72
72
  Path(work_path).expanduser() if work_path is not None else None,
73
73
  {c: Path(p).expanduser() for c, p in (paths or {}).items()})
74
- except (ValueError, OSError) as error:
74
+ # `RuntimeError` too: `Path.expanduser()` raises it for a `~name` naming no
75
+ # user, whether that came from `--path` or from `work.path` in the config.
76
+ except (ValueError, OSError, RuntimeError) as error:
75
77
  print(f"tcw init: {error}", file=sys.stderr)
76
78
  return 1
77
79
  print(f"Scaffolded {len(created)} dir(s):")
@@ -230,7 +230,10 @@ def _strict_refuses(work, action: str, slug: str = "", body: dict | None = None)
230
230
  if not work.tracker_strict():
231
231
  return None
232
232
  body = body or {}
233
- item = work.get(slug) if slug else None
233
+ # An interrupted claim is read as the item it was: an ordinary read of one
234
+ # raises, and this runs before any route's error handling.
235
+ item = (next((c for c in work.interrupted_claims() if c.slug == slug), None)
236
+ or work.get(slug)) if slug else None
234
237
  epic = (body.get("type") == "epic") if action == "create" else (
235
238
  item is not None and item.type == "epic")
236
239
  from tcw.tracker.intake import ever_bound
@@ -238,7 +241,8 @@ def _strict_refuses(work, action: str, slug: str = "", body: dict | None = None)
238
241
  if action == "create" and not epic:
239
242
  return lead + "create work from a ticket with `tcw work tracker import <ticket>`."
240
243
  if action == "start" and not epic:
241
- return lead + f"use `tcw work start {slug}`, which claims the ticket."
244
+ take_over = " --take-over" if body.get("recover") else ""
245
+ return lead + f"use `tcw work start {slug}{take_over}`, which claims the ticket."
242
246
  if action == "complete" and not epic and body.get("resolution") == "done":
243
247
  return lead + f"use `tcw work complete {slug}`, which checks the ticket."
244
248
  if action == "drop" and ever_bound(work, slug):
@@ -286,13 +290,16 @@ def _map_store_error(e: Exception) -> tuple[int, bytes]:
286
290
  """Map store-level exceptions to HTTP status codes and JSON error bodies.
287
291
 
288
292
  "no such" / "no heading" ValueError messages → 404.
289
- StaleRevision → 409.
293
+ StaleRevision → 409, with `"code": "stale-revision"`.
290
294
  IllegalTransition / validation ValueError / RefError → 422.
291
295
  Everything else → 500.
292
296
  """
293
297
  msg = str(e).lower()
294
298
  if isinstance(e, StaleRevision):
295
- return _err(HTTPStatus.CONFLICT, str(e))
299
+ # The one 409 the web app answers with its conflict banner. Marked,
300
+ # because strict-tracker and generated-sidecar refusals are 409 too and
301
+ # must show their own message instead.
302
+ return _err(HTTPStatus.CONFLICT, str(e), code="stale-revision")
296
303
  if isinstance(e, IllegalTransition):
297
304
  return _err(HTTPStatus.UNPROCESSABLE_ENTITY, str(e))
298
305
  if isinstance(e, RefError):
@@ -525,19 +532,32 @@ class TcwHandler(BaseHTTPRequestHandler):
525
532
  items are shaped differently from the items every other route returns,
526
533
  which is the drift the projection exists to prevent.
527
534
  """
528
- anchor = self.server.node_root.resolve()
529
- roots = [anchor]
530
- if self.server.include_descendants:
531
- roots += descendant_nodes(anchor)
532
535
  items = []
533
- for root in roots:
534
- prefix = "" if root == anchor else f"{registered_project_id(anchor, root)}/"
536
+ for root, prefix in self._board_roots():
535
537
  work = FsWorkStore.open(root)
536
538
  for it in work.board():
537
539
  items.append(_item_payload(work, it.slug, it,
538
540
  f"{prefix}{it.slug}" if prefix else None))
539
541
  return items
540
542
 
543
+ def _board_roots(self) -> list[tuple[Path, str]]:
544
+ """The nodes the board shows, each with the prefix its slugs carry."""
545
+ anchor = self.server.node_root.resolve()
546
+ roots = [anchor]
547
+ if self.server.include_descendants:
548
+ roots += descendant_nodes(anchor)
549
+ return [(root, "" if root == anchor
550
+ else f"{registered_project_id(anchor, root)}/") for root in roots]
551
+
552
+ def _interrupted_claims(self) -> list[dict]:
553
+ """Interrupted starts on every node the board shows, slugs qualified as
554
+ the board's are, so Recover addresses the node that holds the claim."""
555
+ claims = []
556
+ for root, prefix in self._board_roots():
557
+ claims += [{"slug": f"{prefix}{c.slug}", "title": c.title}
558
+ for c in FsWorkStore.open(root).interrupted_claims()]
559
+ return claims
560
+
541
561
  # ── HTTP method dispatchers ───────────────────────────────────────────
542
562
 
543
563
  def do_GET(self) -> None:
@@ -672,6 +692,19 @@ class TcwHandler(BaseHTTPRequestHandler):
672
692
  return
673
693
  try:
674
694
  resource = work.read_sidecar(slug, name)
695
+ except OSError as e:
696
+ # Something is at the name but it is not a readable file. Only a
697
+ # message TCW wrote (no errno) is shown: the system's own names
698
+ # an absolute path on the server.
699
+ reason = str(e) if e.errno is None else f"{name} cannot be read"
700
+ self._send(HTTPStatus.BAD_REQUEST, reason.encode("utf-8"))
701
+ return
702
+ except UnicodeDecodeError:
703
+ # Before ValueError, which it is: the decoder's own message names
704
+ # a byte offset, not the file.
705
+ self._send(HTTPStatus.BAD_REQUEST,
706
+ f"{name} is not valid UTF-8; fix or replace the file".encode("utf-8"))
707
+ return
675
708
  except ValueError as e:
676
709
  self._send(HTTPStatus.BAD_REQUEST, str(e).encode("utf-8"))
677
710
  return
@@ -724,6 +757,13 @@ class TcwHandler(BaseHTTPRequestHandler):
724
757
  self._send_json(HTTPStatus.OK, {"tags": work.registered_tags()})
725
758
  return
726
759
 
760
+ # GET /api/work/interrupted-claims — starts whose claimant died mid-move.
761
+ # They are on no board (they are in no status), so without this the web
762
+ # app could neither show one nor offer to recover it.
763
+ if path == "/api/work/interrupted-claims":
764
+ self._send_json(HTTPStatus.OK, self._interrupted_claims())
765
+ return
766
+
727
767
  # Catch-all work detail: /api/work/<slug>
728
768
  if path.startswith("/api/work/"):
729
769
  slug = _parse_ref_param(path, "/api/work/")
@@ -949,8 +989,23 @@ class TcwHandler(BaseHTTPRequestHandler):
949
989
  return
950
990
  if action == "start":
951
991
  force = bool(body.get("force", False))
992
+ start = lambda: work.start(slug, force=force) # noqa: E731
993
+ if body.get("recover"):
994
+ # Recovery only. `take_over` also takes an *active* item from
995
+ # its owner, which a browser must not do without the CLI's
996
+ # explicit `--take-over`; so the slug has to be an
997
+ # interrupted claim, and the claim goes to this server's own
998
+ # identity, found the way the CLI finds one.
999
+ from tcw.work.cli import _local_owner
1000
+ owner = _local_owner(work)
1001
+ if not owner:
1002
+ self._send_err(HTTPStatus.CONFLICT,
1003
+ "no claimant identity for this server; set "
1004
+ "TCW_WORK_OWNER or git user.email and restart it")
1005
+ return
1006
+ start = lambda: work.start(slug, owner=owner, recover=True) # noqa: E731
952
1007
  try:
953
- item = _transition_ok(work, slug, lambda: work.start(slug, force=force))
1008
+ item = _transition_ok(work, slug, start)
954
1009
  # Payload built from the bare slug the store knows, then
955
1010
  # relabelled with the qualified one the UI addresses by.
956
1011
  self._send_json(HTTPStatus.OK,