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.
- {tcw_cli-2.6.1/tcw_cli.egg-info → tcw_cli-2.6.3}/PKG-INFO +3 -3
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/README.md +2 -2
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/pyproject.toml +1 -1
- tcw_cli-2.6.3/tcw/__init__.py +1 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/cli.py +3 -1
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/__init__.py +66 -11
- tcw_cli-2.6.3/tcw/serve/dist/client/assets/index-C5_pngMt.css +1 -0
- tcw_cli-2.6.3/tcw/serve/dist/client/assets/index-Dss8_ZG_.js +128 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/dist/client/index.html +2 -2
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/dist/server.cjs +29856 -27944
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/base.py +289 -39
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/fs.py +514 -72
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/project.py +54 -21
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/intake.py +7 -5
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/jira.py +61 -16
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/sync.py +41 -8
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/validate.py +148 -39
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/cli.py +214 -55
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/plan.md +2 -1
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/recursion.py +99 -36
- {tcw_cli-2.6.1 → tcw_cli-2.6.3/tcw_cli.egg-info}/PKG-INFO +3 -3
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/SOURCES.txt +19 -3
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_config_edit_writers.py +37 -0
- tcw_cli-2.6.3/tests/test_cross_node_blockers.py +230 -0
- tcw_cli-2.6.3/tests/test_edit_refusal.py +176 -0
- tcw_cli-2.6.3/tests/test_epic_completability_across_checkouts.py +272 -0
- tcw_cli-2.6.3/tests/test_interrupted_claim.py +219 -0
- tcw_cli-2.6.3/tests/test_item_tags_read.py +139 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_legacy_store_config.py +5 -5
- tcw_cli-2.6.3/tests/test_lifecycle_config_tags.py +188 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_hooks.py +3 -2
- tcw_cli-2.6.3/tests/test_literal_store_names.py +114 -0
- tcw_cli-2.6.3/tests/test_routing_nodes.py +144 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_write.py +4 -0
- tcw_cli-2.6.3/tests/test_sidecar_paths_in_validate.py +112 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_stage_verb.py +18 -4
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_provisioning.py +62 -0
- tcw_cli-2.6.3/tests/test_taxonomy_rm_gaps.py +194 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_claim.py +1 -1
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_cli.py +32 -1
- tcw_cli-2.6.3/tests/test_tracker_hardening.py +187 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_strict.py +1 -0
- tcw_cli-2.6.3/tests/test_tracker_strict_gate.py +275 -0
- tcw_cli-2.6.3/tests/test_unplanned_start.py +163 -0
- tcw_cli-2.6.3/tests/test_unreadable_capabilities_sidecar.py +201 -0
- tcw_cli-2.6.3/tests/test_validate_moved_stores.py +199 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work.py +2 -1
- tcw_cli-2.6.3/tests/test_worktree_sibling_nodes.py +152 -0
- tcw_cli-2.6.1/tcw/__init__.py +0 -1
- tcw_cli-2.6.1/tcw/serve/dist/client/assets/index-CNxlcI8A.js +0 -181
- tcw_cli-2.6.1/tcw/serve/dist/client/assets/index-CXNiOIeg.css +0 -1
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/LICENSE +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/setup.cfg +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/capabilities/__init__.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/capabilities/cli.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/harness.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/refs.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/dist/client/theme-init.js +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/serve/runtime.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/stdin.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/__init__.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/checkouts.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/store/config_edit.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/taxonomy/__init__.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/taxonomy/cli.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/__init__.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/claim.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/create.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/ownership.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/tracker/progress.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/__init__.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/generate.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/hooks.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/audit-backlog.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/consolidate-plans.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/create-work.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/decompose.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/delegation.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/documentation-sync.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/post-mortem.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/search.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/triage-issues.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/procedures/unattended-work.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/projection.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/implement.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/inbox.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/postmortem.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/request.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/spec.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/prompts/verify.md +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/resolve.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw/work/templates.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/dependency_links.txt +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/entry_points.txt +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/requires.txt +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tcw_cli.egg-info/top_level.txt +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_body_prompt.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities_federation.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities_reset.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities_rm.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capabilities_sidecar.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capability_gate_children.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_capability_ref_wording.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_check_versions.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_child_status.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_cli_help_coverage.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_committed_paths.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_config_edit.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_configuration_text_home.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_cut_version.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_documentation_config.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_documentation_prompt.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_documentation_sync_wiring.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_documented_cli_surface.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_dynamic_skill_marker.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_edit_type.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_environment_hardness.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_epic_completable.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_coverage.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_files_changed.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_fixture.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_grading.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_eval_runner.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_external_work_store.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_falsification_rule.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_generate_hook.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_harness.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_inbox_title.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_baseline.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_inert.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_policy.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_lifecycle_validation.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_multiproject.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_non_git_writes.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_plugin_manifests.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_procedure_config.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_procedure_verb.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_project_overrides.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_project_registry.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_projection.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_prompt_fallback.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_qualified_ref.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_recursion.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_refs.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_remote_session_setup.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_repo_lifecycle.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_resolve.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_resolve_procedure.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_retention.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_scaffold.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_descendants.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_projection.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_resolve.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_serve_runtime.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_session_bootstrap.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_shipped_procedures.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_shipped_prompts.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_show_json.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_skill_flow.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_skill_lifecycle_parity.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_skill_path_pointers.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_smoke.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_stage_validate.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_status_parity.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_stdin.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_stdin_cli.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_bounds.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_editor.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_nodes.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_store_publication.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_subprocess_stdin.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_taxonomy.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tombstone.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_absent.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_binding.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_claimability.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_client.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_comment.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_config.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_create.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_gate.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_help.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_hold.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_import.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_import_returns.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_inheritance.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_link.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_ownership.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_pre_backlog.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_replay.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_surface.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_sync.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_sync_config.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_tracker_validate.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_unattended_work_skill.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_unpushed_version_script.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_validate.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_validate_target.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work_autocommit.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work_review.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work_start.py +0 -0
- {tcw_cli-2.6.1 → tcw_cli-2.6.3}/tests/test_work_tags.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|