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.
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/PKG-INFO +4 -4
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/README.md +3 -3
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/pyproject.toml +1 -1
- tcw_cli-2.6.1/tcw/__init__.py +1 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/intake.py +49 -1
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/cli.py +23 -1
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/PKG-INFO +4 -4
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/SOURCES.txt +1 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_claim.py +10 -0
- tcw_cli-2.6.1/tests/test_tracker_import_returns.py +162 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_pre_backlog.py +8 -1
- tcw_cli-2.6.0/tcw/__init__.py +0 -1
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/LICENSE +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/setup.cfg +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/capabilities/__init__.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/capabilities/cli.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/cli.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/harness.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/refs.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/__init__.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/client/assets/index-CNxlcI8A.js +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/client/assets/index-CXNiOIeg.css +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/client/index.html +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/client/theme-init.js +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/dist/server.cjs +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/serve/runtime.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/stdin.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/__init__.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/base.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/checkouts.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/config_edit.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/fs.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/store/project.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/taxonomy/__init__.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/taxonomy/cli.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/__init__.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/claim.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/create.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/jira.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/ownership.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/progress.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/tracker/sync.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/validate.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/__init__.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/generate.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/hooks.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/audit-backlog.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/consolidate-plans.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/create-work.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/decompose.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/delegation.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/documentation-sync.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/post-mortem.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/search.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/triage-issues.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/procedures/unattended-work.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/projection.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/implement.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/inbox.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/plan.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/postmortem.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/request.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/spec.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/prompts/verify.md +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/recursion.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/resolve.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw/work/templates.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/dependency_links.txt +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/entry_points.txt +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/requires.txt +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tcw_cli.egg-info/top_level.txt +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_body_prompt.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities_federation.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities_reset.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities_rm.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capabilities_sidecar.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capability_gate_children.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_capability_ref_wording.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_check_versions.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_child_status.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_cli_help_coverage.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_committed_paths.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_config_edit.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_config_edit_writers.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_configuration_text_home.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_cut_version.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_documentation_config.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_documentation_prompt.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_documentation_sync_wiring.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_documented_cli_surface.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_dynamic_skill_marker.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_edit_type.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_environment_hardness.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_epic_completable.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_coverage.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_files_changed.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_fixture.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_grading.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_eval_runner.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_external_work_store.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_falsification_rule.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_generate_hook.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_harness.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_inbox_title.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_legacy_store_config.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_baseline.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_hooks.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_inert.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_policy.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_lifecycle_validation.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_multiproject.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_non_git_writes.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_plugin_manifests.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_procedure_config.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_procedure_verb.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_project_overrides.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_project_registry.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_projection.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_prompt_fallback.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_qualified_ref.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_recursion.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_refs.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_remote_session_setup.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_repo_lifecycle.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_resolve.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_resolve_procedure.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_retention.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_scaffold.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_descendants.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_projection.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_resolve.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_runtime.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_serve_write.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_session_bootstrap.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_shipped_procedures.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_shipped_prompts.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_show_json.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_skill_flow.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_skill_lifecycle_parity.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_skill_path_pointers.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_smoke.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_stage_validate.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_stage_verb.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_status_parity.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_stdin.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_stdin_cli.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_bounds.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_editor.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_nodes.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_provisioning.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_store_publication.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_subprocess_stdin.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_taxonomy.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tombstone.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_absent.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_binding.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_claimability.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_cli.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_client.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_comment.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_config.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_create.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_gate.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_help.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_hold.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_import.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_inheritance.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_link.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_ownership.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_replay.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_strict.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_surface.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_sync.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_sync_config.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_tracker_validate.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_unattended_work_skill.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_unpushed_version_script.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_validate.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_validate_target.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work_autocommit.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work_review.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work_start.py +0 -0
- {tcw_cli-2.6.0 → tcw_cli-2.6.1}/tests/test_work_tags.py +0 -0
- {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.
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
|
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:
|
|
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
|
-
|
|
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,
|
tcw_cli-2.6.0/tcw/__init__.py
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|