tcw-cli 2.0.2__tar.gz → 2.1.2__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 (139) hide show
  1. {tcw_cli-2.0.2/tcw_cli.egg-info → tcw_cli-2.1.2}/PKG-INFO +201 -18
  2. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/README.md +200 -17
  3. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/pyproject.toml +8 -1
  4. tcw_cli-2.1.2/tcw/__init__.py +1 -0
  5. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/capabilities/cli.py +19 -1
  6. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/cli.py +9 -0
  7. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/store/base.py +205 -9
  8. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/store/fs.py +244 -31
  9. tcw_cli-2.1.2/tcw/tracker/__init__.py +12 -0
  10. tcw_cli-2.1.2/tcw/tracker/claim.py +183 -0
  11. tcw_cli-2.1.2/tcw/tracker/intake.py +373 -0
  12. tcw_cli-2.1.2/tcw/tracker/jira.py +315 -0
  13. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/validate.py +43 -9
  14. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/cli.py +408 -15
  15. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/recursion.py +16 -7
  16. {tcw_cli-2.0.2 → tcw_cli-2.1.2/tcw_cli.egg-info}/PKG-INFO +201 -18
  17. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw_cli.egg-info/SOURCES.txt +22 -0
  18. tcw_cli-2.1.2/tests/test_capabilities_rm.py +406 -0
  19. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_capabilities_sidecar.py +18 -5
  20. tcw_cli-2.1.2/tests/test_committed_paths.py +38 -0
  21. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_documented_cli_surface.py +18 -3
  22. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_environment_hardness.py +130 -0
  23. tcw_cli-2.1.2/tests/test_eval_coverage.py +48 -0
  24. tcw_cli-2.1.2/tests/test_eval_files_changed.py +177 -0
  25. tcw_cli-2.1.2/tests/test_eval_fixture.py +316 -0
  26. tcw_cli-2.1.2/tests/test_eval_grading.py +234 -0
  27. tcw_cli-2.1.2/tests/test_eval_runner.py +273 -0
  28. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_external_work_store.py +109 -0
  29. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_non_git_writes.py +73 -3
  30. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_plugin_manifests.py +1 -1
  31. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_recursion.py +10 -0
  32. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_remote_session_setup.py +65 -11
  33. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_scaffold.py +6 -0
  34. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_session_bootstrap.py +1 -1
  35. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_skill_lifecycle_parity.py +5 -1
  36. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_store_editor.py +30 -22
  37. tcw_cli-2.1.2/tests/test_tracker_absent.py +163 -0
  38. tcw_cli-2.1.2/tests/test_tracker_binding.py +250 -0
  39. tcw_cli-2.1.2/tests/test_tracker_claim.py +247 -0
  40. tcw_cli-2.1.2/tests/test_tracker_claimability.py +220 -0
  41. tcw_cli-2.1.2/tests/test_tracker_cli.py +258 -0
  42. tcw_cli-2.1.2/tests/test_tracker_client.py +460 -0
  43. tcw_cli-2.1.2/tests/test_tracker_config.py +191 -0
  44. tcw_cli-2.1.2/tests/test_tracker_import.py +354 -0
  45. tcw_cli-2.1.2/tests/test_tracker_link.py +192 -0
  46. tcw_cli-2.1.2/tests/test_tracker_replay.py +147 -0
  47. tcw_cli-2.1.2/tests/test_tracker_validate.py +178 -0
  48. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_validate.py +99 -0
  49. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_work.py +23 -0
  50. tcw_cli-2.1.2/tests/test_work_tags.py +398 -0
  51. tcw_cli-2.0.2/tcw/__init__.py +0 -1
  52. tcw_cli-2.0.2/tests/test_work_tags.py +0 -182
  53. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/LICENSE +0 -0
  54. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/setup.cfg +0 -0
  55. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/capabilities/__init__.py +0 -0
  56. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/refs.py +0 -0
  57. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/serve/__init__.py +0 -0
  58. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/serve/dist/client/assets/index-CXNiOIeg.css +0 -0
  59. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/serve/dist/client/assets/index-Dz5B7M8G.js +0 -0
  60. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/serve/dist/client/index.html +0 -0
  61. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/serve/dist/client/theme-init.js +0 -0
  62. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/serve/dist/server.cjs +0 -0
  63. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/serve/runtime.py +0 -0
  64. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/stdin.py +0 -0
  65. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/store/__init__.py +0 -0
  66. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/store/checkouts.py +0 -0
  67. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/store/project.py +0 -0
  68. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/taxonomy/__init__.py +0 -0
  69. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/taxonomy/cli.py +0 -0
  70. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/__init__.py +0 -0
  71. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/generate.py +0 -0
  72. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/hooks.py +0 -0
  73. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/projection.py +0 -0
  74. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/prompts/implement.md +0 -0
  75. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/prompts/inbox.md +0 -0
  76. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/prompts/plan.md +0 -0
  77. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/prompts/postmortem.md +0 -0
  78. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/prompts/request.md +0 -0
  79. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/prompts/spec.md +0 -0
  80. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/prompts/verify.md +0 -0
  81. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/resolve.py +0 -0
  82. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw/work/templates.py +0 -0
  83. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw_cli.egg-info/dependency_links.txt +0 -0
  84. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw_cli.egg-info/entry_points.txt +0 -0
  85. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw_cli.egg-info/requires.txt +0 -0
  86. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tcw_cli.egg-info/top_level.txt +0 -0
  87. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_body_prompt.py +0 -0
  88. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_capabilities.py +0 -0
  89. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_capabilities_federation.py +0 -0
  90. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_capabilities_reset.py +0 -0
  91. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_capability_ref_wording.py +0 -0
  92. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_cut_version.py +0 -0
  93. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_documentation_config.py +0 -0
  94. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_documentation_prompt.py +0 -0
  95. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_documentation_sync_wiring.py +0 -0
  96. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_epic_completable.py +0 -0
  97. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_falsification_rule.py +0 -0
  98. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_generate_hook.py +0 -0
  99. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_inbox_title.py +0 -0
  100. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_lifecycle_baseline.py +0 -0
  101. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_lifecycle_hooks.py +0 -0
  102. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_lifecycle_inert.py +0 -0
  103. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_lifecycle_policy.py +0 -0
  104. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_lifecycle_validation.py +0 -0
  105. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_multiproject.py +0 -0
  106. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_project_overrides.py +0 -0
  107. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_project_registry.py +0 -0
  108. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_projection.py +0 -0
  109. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_prompt_fallback.py +0 -0
  110. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_qualified_ref.py +0 -0
  111. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_refs.py +0 -0
  112. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_repo_lifecycle.py +0 -0
  113. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_resolve.py +0 -0
  114. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_retention.py +0 -0
  115. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_serve.py +0 -0
  116. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_serve_descendants.py +0 -0
  117. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_serve_projection.py +0 -0
  118. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_serve_resolve.py +0 -0
  119. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_serve_runtime.py +0 -0
  120. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_serve_write.py +0 -0
  121. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_shipped_prompts.py +0 -0
  122. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_show_json.py +0 -0
  123. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_skill_flow.py +0 -0
  124. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_smoke.py +0 -0
  125. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_stage_verb.py +0 -0
  126. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_status_parity.py +0 -0
  127. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_stdin.py +0 -0
  128. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_stdin_cli.py +0 -0
  129. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_store_bounds.py +0 -0
  130. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_store_nodes.py +0 -0
  131. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_store_provisioning.py +0 -0
  132. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_store_publication.py +0 -0
  133. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_subprocess_stdin.py +0 -0
  134. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_taxonomy.py +0 -0
  135. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_tombstone.py +0 -0
  136. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_unpushed_version_script.py +0 -0
  137. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_validate_target.py +0 -0
  138. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_work_autocommit.py +0 -0
  139. {tcw_cli-2.0.2 → tcw_cli-2.1.2}/tests/test_work_review.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tcw-cli
3
- Version: 2.0.2
3
+ Version: 2.1.2
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
@@ -229,6 +229,28 @@ They link by one-directional pointers — a capability names the taxonomy terms
229
229
  involves, a work item names the capability it changes — and never copy each
230
230
  other's content.
231
231
 
232
+ ## Contents
233
+
234
+ - [The problem](#the-problem)
235
+ - [What it looks like](#what-it-looks-like)
236
+ - [Why many repositories is the case it is built for](#why-many-repositories-is-the-case-it-is-built-for)
237
+ - [What it deliberately refuses](#what-it-deliberately-refuses)
238
+ - [What adopting it costs](#what-adopting-it-costs)
239
+ - [Who it's for](#who-its-for)
240
+ - [Storage abstraction (the prime directive)](#storage-abstraction-the-prime-directive)
241
+ - [Install](#install)
242
+ - [As a plugin (recommended)](#as-a-plugin-recommended)
243
+ - [As a Python package](#as-a-python-package)
244
+ - [In a cloud environment](#in-a-cloud-environment)
245
+ - [Quickstart](#quickstart)
246
+ - [Working from your Jira tickets](#working-from-your-jira-tickets)
247
+ - [Documentation](#documentation)
248
+ - [Skills — the judgment layer](#skills--the-judgment-layer)
249
+ - [Reading a lifecycle stage](#reading-a-lifecycle-stage)
250
+ - [Review agents and slash commands](#review-agents-and-slash-commands)
251
+ - [Status](#status)
252
+ - [Further reading](#further-reading)
253
+
232
254
  ---
233
255
 
234
256
  ## The problem
@@ -407,8 +429,9 @@ Or, in the Claude **web app** or **desktop app**, open the plugin directory, add
407
429
  by installing `tcw-cli` from PyPI with `pipx`, so one installed mid-session
408
430
  cannot run until the next one begins. That first session needs network access. It
409
431
  installs over an existing `pipx install tcw-cli` rather than beside it, and
410
- leaves a development checkout (`pip install -e .`) alone. Run `/tcw-doctor` any
411
- time `tcw` goes missing or looks wrong.
432
+ leaves a development checkout (`pip install -e .`) alone. If `tcw` goes missing
433
+ anyway, `pipx install tcw-cli` is the whole fix — the **`tcw-plugin`** skill
434
+ carries the cases where it is not.
412
435
 
413
436
  In **Codex** (skills only, no slash commands):
414
437
 
@@ -439,6 +462,89 @@ additionally requires **Node.js ≥ 22.12**; every other command is Python-only.
439
462
  Released wheels carry the prebuilt web assets, so there is no frontend build step
440
463
  and no network needed after install.
441
464
 
465
+ ### In a cloud environment
466
+
467
+ A cloud agent session — Claude Code on the web, a Codex container, a CI job —
468
+ starts from a clean image and is thrown away when it ends, so a `tcw` you install
469
+ by hand is gone by the next one. Install it **from your repository**, with a
470
+ session-start hook, and every session gets it without anyone remembering to.
471
+
472
+ Add a script to your repository:
473
+
474
+ ```sh
475
+ #!/usr/bin/env bash
476
+ # Make `tcw` available in a disposable agent container.
477
+ # Exit 0 on every path — a session must start even when this cannot finish.
478
+ set -u
479
+
480
+ command -v tcw >/dev/null 2>&1 && exit 0
481
+
482
+ # pipx where it exists; otherwise install into the container's own interpreter.
483
+ # That is the right answer *here* and nowhere else: the container is disposable
484
+ # and single-purpose, so there is no user environment to damage.
485
+ if command -v pipx >/dev/null 2>&1; then
486
+ pipx install tcw-cli >/dev/null 2>&1 || echo "tcw: pipx install tcw-cli failed"
487
+ elif ! python3 -m pip install tcw-cli >/dev/null 2>&1 &&
488
+ ! python3 -m pip install tcw-cli --break-system-packages >/dev/null 2>&1; then
489
+ echo "tcw: pip install tcw-cli failed — the tcw CLI is not available."
490
+ fi
491
+
492
+ # An install that landed outside PATH is installed and unusable. $CLAUDE_ENV_FILE
493
+ # is the harness's own channel for repairing that.
494
+ if ! command -v tcw >/dev/null 2>&1 && [ -n "${CLAUDE_ENV_FILE:-}" ]; then
495
+ userbin="$(python3 -m site --user-base 2>/dev/null)/bin"
496
+ [ -x "$userbin/tcw" ] && echo "export PATH=\"$userbin:\$PATH\"" >>"$CLAUDE_ENV_FILE"
497
+ fi
498
+
499
+ exit 0
500
+ ```
501
+
502
+ Then wire it to `SessionStart` in your repository's `.claude/settings.json`:
503
+
504
+ ```json
505
+ {
506
+ "hooks": {
507
+ "SessionStart": [
508
+ {
509
+ "hooks": [
510
+ {
511
+ "type": "command",
512
+ "command": "\"${CLAUDE_PROJECT_DIR}\"/scripts/tcw_session_setup.sh"
513
+ }
514
+ ]
515
+ }
516
+ ]
517
+ }
518
+ }
519
+ ```
520
+
521
+ Three things make the difference between a hook that helps and one that wastes a
522
+ session:
523
+
524
+ - **Exit 0 on every path, and print only on failure.** A hook that fails the
525
+ session start over a missing CLI has cost more than the CLI was worth.
526
+ - **Print to stdout.** Claude Code adds a `SessionStart` hook's stdout to the
527
+ agent's context, where it will be read; stderr becomes a transcript notice
528
+ nobody sees.
529
+ - **Check for `tcw` first.** The hook runs every session, including the ones
530
+ where a previous install is still there.
531
+
532
+ **If your work store lives in another repository**, the container holds only the
533
+ repository it cloned, so the board is declared but absent. Run `tcw provision`
534
+ after the install — it obtains what the checkout does not have, and does nothing
535
+ on a machine that already holds it. See
536
+ [Working across repositories](docs/guide/multi-repo.md).
537
+
538
+ Installing the **plugin** in such a session is a separate step from installing the
539
+ CLI, and only needed where the harness does not carry your plugins in: append
540
+ `claude plugin marketplace add brocef/TCW` and `claude plugin install tcw@tcw` to
541
+ the same script, guarded on `command -v claude`.
542
+
543
+ This repository's own [`scripts/remote_session_setup.sh`](scripts/remote_session_setup.sh)
544
+ is the same pattern written for a _contributor_ rather than a user — it installs
545
+ the checkout with `pip install -e` instead of the release from PyPI, and installs
546
+ the plugin from the checkout too. Read it as the worked example.
547
+
442
548
  ## Quickstart
443
549
 
444
550
  ```sh
@@ -464,6 +570,79 @@ studies your code, proposes a first draft, refines it with you, and writes it.
464
570
 
465
571
  ---
466
572
 
573
+ ## Working from your Jira tickets
574
+
575
+ A project can name the Jira Cloud site it uses, read its tickets, and take one as
576
+ a work item.
577
+
578
+ ```sh
579
+ tcw work tracker list # tickets the configured query selects
580
+ tcw work tracker show ENG-482 # one ticket, and whether it is yours to take
581
+ tcw work tracker import ENG-482 # take the ticket and get a work item for it
582
+ tcw work tracker link <slug> ENG-482 # take it for an item you already have
583
+ tcw work tracker unlink <slug> --reason "wrong ticket"
584
+ ```
585
+
586
+ Configuration goes in the node's `tcw-config.yaml`:
587
+
588
+ ```yaml
589
+ work:
590
+ tracker:
591
+ provider: jira-cloud
592
+ base-url: https://yourcompany.atlassian.net
593
+ candidate-query: assignee = currentUser() AND status = "To Do"
594
+ credentials:
595
+ email-env: TCW_JIRA_EMAIL
596
+ token-env: TCW_JIRA_API_TOKEN
597
+ transitions:
598
+ claim: Start Progress
599
+ timeout-seconds: 15 # optional
600
+ ```
601
+
602
+ **Credentials are named, never stored.** The file holds the names of two
603
+ environment variables; TCW reads them at the moment it makes a request, so a token
604
+ cannot be committed by accident.
605
+
606
+ `tracker show` reports two different things, and the distinction matters:
607
+
608
+ - **claimable** — whether this ticket currently offers the transition you
609
+ configured as the claim.
610
+ - **exclusive** — whether your Jira workflow would actually refuse a second person
611
+ trying to take the same ticket.
612
+
613
+ Many Jira workflows allow a status change from any status, including the one it
614
+ leads to. On a workflow like that, two people who both take one ticket both
615
+ succeed and neither is told. So a ticket that has been started reports
616
+ `not exclusive` when that is the truth, and a ticket that has not been started
617
+ reports `not determined`, because it cannot show you what happens to the second
618
+ person. Making a workflow exclusive is a Jira administration change, not something
619
+ TCW can do for you.
620
+
621
+ **Taking a ticket.** `tracker import` moves the ticket through your configured
622
+ claim transition, assigns it to you if nobody had it, and then checks Jira again:
623
+ only when the ticket really is started and yours does it create a backlog item. The
624
+ ticket's text goes into the item as its raw input, with a link back, and the item's
625
+ request still gets written the usual way. A ticket assigned to someone else is
626
+ refused, and so is one that is already closed.
627
+
628
+ Running `import` again for the same ticket gives you the same item rather than a
629
+ second one, and if the first run took the ticket but stopped before creating the
630
+ item, the second run finishes the job. One ticket can deliberately become several
631
+ items with `--part api`, `--part web`, and so on. `unlink` removes a wrong binding
632
+ and keeps a record of it with your reason; it never changes the ticket in Jira.
633
+
634
+ Two limits to know. On a workflow that lets anyone start a ticket from any status,
635
+ two people can both take the same ticket, and TCW does not stop that. And two runs
636
+ by the same Jira account at the same moment can both create an item.
637
+
638
+ Two guarantees worth stating plainly. A project with no `tracker` block behaves
639
+ exactly as before, with no new required setting and no network access. And
640
+ `tcw validate` never contacts the tracker, which matters because projects commonly
641
+ run it when closing a work item — finishing your work must not depend on Jira being
642
+ reachable.
643
+
644
+ ---
645
+
467
646
  ## Documentation
468
647
 
469
648
  | Document | Covers |
@@ -483,21 +662,22 @@ Every command group also has `--help`, and a `check` that validates its tree.
483
662
 
484
663
  ## Skills — the judgment layer
485
664
 
486
- The CLI is the _mechanism_. Fourteen skills in [`skills/`](skills/) supply the
487
- _judgment_ that drives it — the parts a deterministic tool cannot decide. Eight
665
+ The CLI is the _mechanism_. Fifteen skills in [`skills/`](skills/) supply the
666
+ _judgment_ that drives it — the parts a deterministic tool cannot decide. Nine
488
667
  carry a distinct procedure; the other six all compose one lifecycle stage and
489
668
  are listed together at the end.
490
669
 
491
- | Skill | What it does |
492
- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
493
- | [`tcw-work`](skills/tcw-work/SKILL.md) | Plans a request through spec and plan, drives implementation and verification, triages the inbox, runs the lifecycle, decomposes epics |
494
- | [`tcw-capabilities`](skills/tcw-capabilities/SKILL.md) | The capability-delta planning check, contradiction detection, and the ledger flip at completion |
495
- | [`tcw-taxonomy`](skills/tcw-taxonomy/SKILL.md) | Declaring vocabulary and features, linking them, and federating shared vocabulary |
496
- | [`tcw-plugin`](skills/tcw-plugin/SKILL.md) | Installs and repairs the CLI; the source of the `/tcw-doctor` procedure |
497
- | [`tcw-report`](skills/tcw-report/SKILL.md) | Reporting a `tcw` bug or suggestion upstream to [this project's issues](https://github.com/brocef/TCW/issues) |
498
- | [`tcw-triage-issues`](skills/tcw-triage-issues/SKILL.md) | Sweeps **your** project's GitHub issues and turns the ones worth doing into work items |
499
- | [`documentation-sync`](skills/documentation-sync/SKILL.md) | Keeps README, changelogs, release notes, and driving skills moving with the code that changes them |
500
- | [`tcw-post-mortem`](skills/tcw-post-mortem/SKILL.md) | Finds which lifecycle stage could first have caught a problem, once one has surfaced |
670
+ | Skill | What it does |
671
+ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
672
+ | [`tcw-work`](skills/tcw-work/SKILL.md) | Plans a request through spec and plan, drives implementation and verification, triages the inbox, runs the lifecycle, decomposes epics, searches the board |
673
+ | [`tcw-capabilities`](skills/tcw-capabilities/SKILL.md) | The capability-delta planning check, contradiction detection, and the ledger flip at completion |
674
+ | [`tcw-taxonomy`](skills/tcw-taxonomy/SKILL.md) | Declaring vocabulary and features, linking them, and federating shared vocabulary |
675
+ | [`tcw-plugin`](skills/tcw-plugin/SKILL.md) | Installs the CLI from PyPI, and maps the other skills |
676
+ | [`tcw-report`](skills/tcw-report/SKILL.md) | Reporting a `tcw` bug or suggestion upstream to [this project's issues](https://github.com/brocef/TCW/issues) |
677
+ | [`tcw-triage-issues`](skills/tcw-triage-issues/SKILL.md) | Sweeps **your** project's GitHub issues and turns the ones worth doing into work items |
678
+ | [`documentation-sync`](skills/documentation-sync/SKILL.md) | Keeps README, changelogs, release notes, and driving skills moving with the code that changes them |
679
+ | [`tcw-post-mortem`](skills/tcw-post-mortem/SKILL.md) | Finds which lifecycle stage could first have caught a problem, once one has surfaced |
680
+ | [`autonomous-work`](skills/autonomous-work/SKILL.md) | Drives work items to completion unattended, consulting two read-only advisors wherever the lifecycle would ask you |
501
681
 
502
682
  They name `tcw` commands and never reimplement tool logic: mechanism stays in the
503
683
  binary, judgment stays in the skills.
@@ -515,13 +695,16 @@ The other five bake their stage in and ask only for the work item, which is
515
695
  optional: `tcw-work-stage-request`, `tcw-work-stage-spec`,
516
696
  `tcw-work-stage-plan`, `tcw-work-stage-implement`, `tcw-work-stage-verify`.
517
697
 
698
+ ### Review agents and slash commands
699
+
518
700
  Three read-only review agents ship alongside them — `tcw-verifier`,
519
701
  `tcw-backlog-auditor`, and `tcw-post-mortem`, which accelerates the skill of the
520
702
  same name — plus slash commands for each skill's main procedure
521
703
  (`/tcw-plan-work`, `/tcw-drive-work-to-completion`, `/tcw-verify-work`,
522
- `/tcw-process-inbox`, `/tcw-triage-issues`, `/tcw-audit-work-backlog`,
523
- `/tcw-taxonomy-init`, `/tcw-capabilities-init`, `/tcw-docs-sync-setup`,
524
- `/tcw-cut-version`, `/tcw-post-mortem`, `/tcw-doctor`).
704
+ `/tcw-process-inbox`, `/tcw-work-search`, `/tcw-triage-issues`,
705
+ `/tcw-audit-work-backlog`, `/tcw-consolidate-plans`, `/tcw-taxonomy-init`,
706
+ `/tcw-capabilities-init`, `/tcw-docs-sync-setup`, `/tcw-cut-version`,
707
+ `/tcw-post-mortem`).
525
708
 
526
709
  ---
527
710
 
@@ -13,6 +13,28 @@ They link by one-directional pointers — a capability names the taxonomy terms
13
13
  involves, a work item names the capability it changes — and never copy each
14
14
  other's content.
15
15
 
16
+ ## Contents
17
+
18
+ - [The problem](#the-problem)
19
+ - [What it looks like](#what-it-looks-like)
20
+ - [Why many repositories is the case it is built for](#why-many-repositories-is-the-case-it-is-built-for)
21
+ - [What it deliberately refuses](#what-it-deliberately-refuses)
22
+ - [What adopting it costs](#what-adopting-it-costs)
23
+ - [Who it's for](#who-its-for)
24
+ - [Storage abstraction (the prime directive)](#storage-abstraction-the-prime-directive)
25
+ - [Install](#install)
26
+ - [As a plugin (recommended)](#as-a-plugin-recommended)
27
+ - [As a Python package](#as-a-python-package)
28
+ - [In a cloud environment](#in-a-cloud-environment)
29
+ - [Quickstart](#quickstart)
30
+ - [Working from your Jira tickets](#working-from-your-jira-tickets)
31
+ - [Documentation](#documentation)
32
+ - [Skills — the judgment layer](#skills--the-judgment-layer)
33
+ - [Reading a lifecycle stage](#reading-a-lifecycle-stage)
34
+ - [Review agents and slash commands](#review-agents-and-slash-commands)
35
+ - [Status](#status)
36
+ - [Further reading](#further-reading)
37
+
16
38
  ---
17
39
 
18
40
  ## The problem
@@ -191,8 +213,9 @@ Or, in the Claude **web app** or **desktop app**, open the plugin directory, add
191
213
  by installing `tcw-cli` from PyPI with `pipx`, so one installed mid-session
192
214
  cannot run until the next one begins. That first session needs network access. It
193
215
  installs over an existing `pipx install tcw-cli` rather than beside it, and
194
- leaves a development checkout (`pip install -e .`) alone. Run `/tcw-doctor` any
195
- time `tcw` goes missing or looks wrong.
216
+ leaves a development checkout (`pip install -e .`) alone. If `tcw` goes missing
217
+ anyway, `pipx install tcw-cli` is the whole fix — the **`tcw-plugin`** skill
218
+ carries the cases where it is not.
196
219
 
197
220
  In **Codex** (skills only, no slash commands):
198
221
 
@@ -223,6 +246,89 @@ additionally requires **Node.js ≥ 22.12**; every other command is Python-only.
223
246
  Released wheels carry the prebuilt web assets, so there is no frontend build step
224
247
  and no network needed after install.
225
248
 
249
+ ### In a cloud environment
250
+
251
+ A cloud agent session — Claude Code on the web, a Codex container, a CI job —
252
+ starts from a clean image and is thrown away when it ends, so a `tcw` you install
253
+ by hand is gone by the next one. Install it **from your repository**, with a
254
+ session-start hook, and every session gets it without anyone remembering to.
255
+
256
+ Add a script to your repository:
257
+
258
+ ```sh
259
+ #!/usr/bin/env bash
260
+ # Make `tcw` available in a disposable agent container.
261
+ # Exit 0 on every path — a session must start even when this cannot finish.
262
+ set -u
263
+
264
+ command -v tcw >/dev/null 2>&1 && exit 0
265
+
266
+ # pipx where it exists; otherwise install into the container's own interpreter.
267
+ # That is the right answer *here* and nowhere else: the container is disposable
268
+ # and single-purpose, so there is no user environment to damage.
269
+ if command -v pipx >/dev/null 2>&1; then
270
+ pipx install tcw-cli >/dev/null 2>&1 || echo "tcw: pipx install tcw-cli failed"
271
+ elif ! python3 -m pip install tcw-cli >/dev/null 2>&1 &&
272
+ ! python3 -m pip install tcw-cli --break-system-packages >/dev/null 2>&1; then
273
+ echo "tcw: pip install tcw-cli failed — the tcw CLI is not available."
274
+ fi
275
+
276
+ # An install that landed outside PATH is installed and unusable. $CLAUDE_ENV_FILE
277
+ # is the harness's own channel for repairing that.
278
+ if ! command -v tcw >/dev/null 2>&1 && [ -n "${CLAUDE_ENV_FILE:-}" ]; then
279
+ userbin="$(python3 -m site --user-base 2>/dev/null)/bin"
280
+ [ -x "$userbin/tcw" ] && echo "export PATH=\"$userbin:\$PATH\"" >>"$CLAUDE_ENV_FILE"
281
+ fi
282
+
283
+ exit 0
284
+ ```
285
+
286
+ Then wire it to `SessionStart` in your repository's `.claude/settings.json`:
287
+
288
+ ```json
289
+ {
290
+ "hooks": {
291
+ "SessionStart": [
292
+ {
293
+ "hooks": [
294
+ {
295
+ "type": "command",
296
+ "command": "\"${CLAUDE_PROJECT_DIR}\"/scripts/tcw_session_setup.sh"
297
+ }
298
+ ]
299
+ }
300
+ ]
301
+ }
302
+ }
303
+ ```
304
+
305
+ Three things make the difference between a hook that helps and one that wastes a
306
+ session:
307
+
308
+ - **Exit 0 on every path, and print only on failure.** A hook that fails the
309
+ session start over a missing CLI has cost more than the CLI was worth.
310
+ - **Print to stdout.** Claude Code adds a `SessionStart` hook's stdout to the
311
+ agent's context, where it will be read; stderr becomes a transcript notice
312
+ nobody sees.
313
+ - **Check for `tcw` first.** The hook runs every session, including the ones
314
+ where a previous install is still there.
315
+
316
+ **If your work store lives in another repository**, the container holds only the
317
+ repository it cloned, so the board is declared but absent. Run `tcw provision`
318
+ after the install — it obtains what the checkout does not have, and does nothing
319
+ on a machine that already holds it. See
320
+ [Working across repositories](docs/guide/multi-repo.md).
321
+
322
+ Installing the **plugin** in such a session is a separate step from installing the
323
+ CLI, and only needed where the harness does not carry your plugins in: append
324
+ `claude plugin marketplace add brocef/TCW` and `claude plugin install tcw@tcw` to
325
+ the same script, guarded on `command -v claude`.
326
+
327
+ This repository's own [`scripts/remote_session_setup.sh`](scripts/remote_session_setup.sh)
328
+ is the same pattern written for a _contributor_ rather than a user — it installs
329
+ the checkout with `pip install -e` instead of the release from PyPI, and installs
330
+ the plugin from the checkout too. Read it as the worked example.
331
+
226
332
  ## Quickstart
227
333
 
228
334
  ```sh
@@ -248,6 +354,79 @@ studies your code, proposes a first draft, refines it with you, and writes it.
248
354
 
249
355
  ---
250
356
 
357
+ ## Working from your Jira tickets
358
+
359
+ A project can name the Jira Cloud site it uses, read its tickets, and take one as
360
+ a work item.
361
+
362
+ ```sh
363
+ tcw work tracker list # tickets the configured query selects
364
+ tcw work tracker show ENG-482 # one ticket, and whether it is yours to take
365
+ tcw work tracker import ENG-482 # take the ticket and get a work item for it
366
+ tcw work tracker link <slug> ENG-482 # take it for an item you already have
367
+ tcw work tracker unlink <slug> --reason "wrong ticket"
368
+ ```
369
+
370
+ Configuration goes in the node's `tcw-config.yaml`:
371
+
372
+ ```yaml
373
+ work:
374
+ tracker:
375
+ provider: jira-cloud
376
+ base-url: https://yourcompany.atlassian.net
377
+ candidate-query: assignee = currentUser() AND status = "To Do"
378
+ credentials:
379
+ email-env: TCW_JIRA_EMAIL
380
+ token-env: TCW_JIRA_API_TOKEN
381
+ transitions:
382
+ claim: Start Progress
383
+ timeout-seconds: 15 # optional
384
+ ```
385
+
386
+ **Credentials are named, never stored.** The file holds the names of two
387
+ environment variables; TCW reads them at the moment it makes a request, so a token
388
+ cannot be committed by accident.
389
+
390
+ `tracker show` reports two different things, and the distinction matters:
391
+
392
+ - **claimable** — whether this ticket currently offers the transition you
393
+ configured as the claim.
394
+ - **exclusive** — whether your Jira workflow would actually refuse a second person
395
+ trying to take the same ticket.
396
+
397
+ Many Jira workflows allow a status change from any status, including the one it
398
+ leads to. On a workflow like that, two people who both take one ticket both
399
+ succeed and neither is told. So a ticket that has been started reports
400
+ `not exclusive` when that is the truth, and a ticket that has not been started
401
+ reports `not determined`, because it cannot show you what happens to the second
402
+ person. Making a workflow exclusive is a Jira administration change, not something
403
+ TCW can do for you.
404
+
405
+ **Taking a ticket.** `tracker import` moves the ticket through your configured
406
+ claim transition, assigns it to you if nobody had it, and then checks Jira again:
407
+ only when the ticket really is started and yours does it create a backlog item. The
408
+ ticket's text goes into the item as its raw input, with a link back, and the item's
409
+ request still gets written the usual way. A ticket assigned to someone else is
410
+ refused, and so is one that is already closed.
411
+
412
+ Running `import` again for the same ticket gives you the same item rather than a
413
+ second one, and if the first run took the ticket but stopped before creating the
414
+ item, the second run finishes the job. One ticket can deliberately become several
415
+ items with `--part api`, `--part web`, and so on. `unlink` removes a wrong binding
416
+ and keeps a record of it with your reason; it never changes the ticket in Jira.
417
+
418
+ Two limits to know. On a workflow that lets anyone start a ticket from any status,
419
+ two people can both take the same ticket, and TCW does not stop that. And two runs
420
+ by the same Jira account at the same moment can both create an item.
421
+
422
+ Two guarantees worth stating plainly. A project with no `tracker` block behaves
423
+ exactly as before, with no new required setting and no network access. And
424
+ `tcw validate` never contacts the tracker, which matters because projects commonly
425
+ run it when closing a work item — finishing your work must not depend on Jira being
426
+ reachable.
427
+
428
+ ---
429
+
251
430
  ## Documentation
252
431
 
253
432
  | Document | Covers |
@@ -267,21 +446,22 @@ Every command group also has `--help`, and a `check` that validates its tree.
267
446
 
268
447
  ## Skills — the judgment layer
269
448
 
270
- The CLI is the _mechanism_. Fourteen skills in [`skills/`](skills/) supply the
271
- _judgment_ that drives it — the parts a deterministic tool cannot decide. Eight
449
+ The CLI is the _mechanism_. Fifteen skills in [`skills/`](skills/) supply the
450
+ _judgment_ that drives it — the parts a deterministic tool cannot decide. Nine
272
451
  carry a distinct procedure; the other six all compose one lifecycle stage and
273
452
  are listed together at the end.
274
453
 
275
- | Skill | What it does |
276
- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
277
- | [`tcw-work`](skills/tcw-work/SKILL.md) | Plans a request through spec and plan, drives implementation and verification, triages the inbox, runs the lifecycle, decomposes epics |
278
- | [`tcw-capabilities`](skills/tcw-capabilities/SKILL.md) | The capability-delta planning check, contradiction detection, and the ledger flip at completion |
279
- | [`tcw-taxonomy`](skills/tcw-taxonomy/SKILL.md) | Declaring vocabulary and features, linking them, and federating shared vocabulary |
280
- | [`tcw-plugin`](skills/tcw-plugin/SKILL.md) | Installs and repairs the CLI; the source of the `/tcw-doctor` procedure |
281
- | [`tcw-report`](skills/tcw-report/SKILL.md) | Reporting a `tcw` bug or suggestion upstream to [this project's issues](https://github.com/brocef/TCW/issues) |
282
- | [`tcw-triage-issues`](skills/tcw-triage-issues/SKILL.md) | Sweeps **your** project's GitHub issues and turns the ones worth doing into work items |
283
- | [`documentation-sync`](skills/documentation-sync/SKILL.md) | Keeps README, changelogs, release notes, and driving skills moving with the code that changes them |
284
- | [`tcw-post-mortem`](skills/tcw-post-mortem/SKILL.md) | Finds which lifecycle stage could first have caught a problem, once one has surfaced |
454
+ | Skill | What it does |
455
+ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
456
+ | [`tcw-work`](skills/tcw-work/SKILL.md) | Plans a request through spec and plan, drives implementation and verification, triages the inbox, runs the lifecycle, decomposes epics, searches the board |
457
+ | [`tcw-capabilities`](skills/tcw-capabilities/SKILL.md) | The capability-delta planning check, contradiction detection, and the ledger flip at completion |
458
+ | [`tcw-taxonomy`](skills/tcw-taxonomy/SKILL.md) | Declaring vocabulary and features, linking them, and federating shared vocabulary |
459
+ | [`tcw-plugin`](skills/tcw-plugin/SKILL.md) | Installs the CLI from PyPI, and maps the other skills |
460
+ | [`tcw-report`](skills/tcw-report/SKILL.md) | Reporting a `tcw` bug or suggestion upstream to [this project's issues](https://github.com/brocef/TCW/issues) |
461
+ | [`tcw-triage-issues`](skills/tcw-triage-issues/SKILL.md) | Sweeps **your** project's GitHub issues and turns the ones worth doing into work items |
462
+ | [`documentation-sync`](skills/documentation-sync/SKILL.md) | Keeps README, changelogs, release notes, and driving skills moving with the code that changes them |
463
+ | [`tcw-post-mortem`](skills/tcw-post-mortem/SKILL.md) | Finds which lifecycle stage could first have caught a problem, once one has surfaced |
464
+ | [`autonomous-work`](skills/autonomous-work/SKILL.md) | Drives work items to completion unattended, consulting two read-only advisors wherever the lifecycle would ask you |
285
465
 
286
466
  They name `tcw` commands and never reimplement tool logic: mechanism stays in the
287
467
  binary, judgment stays in the skills.
@@ -299,13 +479,16 @@ The other five bake their stage in and ask only for the work item, which is
299
479
  optional: `tcw-work-stage-request`, `tcw-work-stage-spec`,
300
480
  `tcw-work-stage-plan`, `tcw-work-stage-implement`, `tcw-work-stage-verify`.
301
481
 
482
+ ### Review agents and slash commands
483
+
302
484
  Three read-only review agents ship alongside them — `tcw-verifier`,
303
485
  `tcw-backlog-auditor`, and `tcw-post-mortem`, which accelerates the skill of the
304
486
  same name — plus slash commands for each skill's main procedure
305
487
  (`/tcw-plan-work`, `/tcw-drive-work-to-completion`, `/tcw-verify-work`,
306
- `/tcw-process-inbox`, `/tcw-triage-issues`, `/tcw-audit-work-backlog`,
307
- `/tcw-taxonomy-init`, `/tcw-capabilities-init`, `/tcw-docs-sync-setup`,
308
- `/tcw-cut-version`, `/tcw-post-mortem`, `/tcw-doctor`).
488
+ `/tcw-process-inbox`, `/tcw-work-search`, `/tcw-triage-issues`,
489
+ `/tcw-audit-work-backlog`, `/tcw-consolidate-plans`, `/tcw-taxonomy-init`,
490
+ `/tcw-capabilities-init`, `/tcw-docs-sync-setup`, `/tcw-cut-version`,
491
+ `/tcw-post-mortem`).
309
492
 
310
493
  ---
311
494
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tcw-cli"
7
- version = "2.0.2"
7
+ version = "2.1.2"
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"
@@ -35,4 +35,11 @@ include = ["tcw*"]
35
35
 
36
36
  [tool.pytest.ini_options]
37
37
  testpaths = ["tests"]
38
+ # The repository root on sys.path, so `from evals import ...` resolves. The
39
+ # eval-harness tests import it, and `evals` is deliberately not a published
40
+ # package (`packages.find` includes only `tcw*`), so the editable install does
41
+ # not put it there. `python -m pytest` happened to work anyway — that form puts
42
+ # the current directory on sys.path and bare `pytest` does not — which is why
43
+ # CI, which runs bare `pytest`, was the only place this failed.
44
+ pythonpath = ["."]
38
45
  norecursedirs = [".venv", "build", "dist", "*.egg-info"]
@@ -0,0 +1 @@
1
+ __version__ = "2.1.2"
@@ -9,7 +9,8 @@ from tcw.store.base import AmbiguousRef
9
9
  from tcw.store.fs import FsCapabilitiesStore, find_node, git_root
10
10
 
11
11
  NAME = "capabilities"
12
- SUBCOMMANDS = {"init", "list", "show", "path", "add", "search", "check", "set", "reset", "extends", "drift"}
12
+ SUBCOMMANDS = {"init", "list", "show", "path", "add", "search", "check", "set", "reset", "rm",
13
+ "extends", "drift"}
13
14
  DEFAULT_SUBCOMMAND = "show" # `tcw capabilities <path>` == `tcw capabilities show <path>`
14
15
 
15
16
 
@@ -129,6 +130,19 @@ def _reset(args: argparse.Namespace) -> int:
129
130
  return 0
130
131
 
131
132
 
133
+ def _rm(args: argparse.Namespace) -> int:
134
+ st = _store()
135
+ if st is None:
136
+ return 1
137
+ try:
138
+ st.remove(args.id)
139
+ except (ValueError, RefError) as e: # AmbiguousRef carries its own message
140
+ print(f"tcw capabilities rm: {e}", file=sys.stderr)
141
+ return 1
142
+ print(f"Removed capability {args.id}")
143
+ return 0
144
+
145
+
132
146
  def _search(args: argparse.Namespace) -> int:
133
147
  st = _store()
134
148
  if st is None:
@@ -294,6 +308,10 @@ def add_subparser(sub: argparse._SubParsersAction) -> None:
294
308
  prst.add_argument("id", metavar="path")
295
309
  prst.set_defaults(func=_reset)
296
310
 
311
+ prm = g.add_parser("rm", help="remove a local capability")
312
+ prm.add_argument("id", metavar="path")
313
+ prm.set_defaults(func=_rm)
314
+
297
315
  pse = g.add_parser("search", help="search names + bodies")
298
316
  pse.add_argument("query")
299
317
  pse.set_defaults(func=_search)
@@ -535,6 +535,15 @@ def main(argv: list[str] | None = None) -> int:
535
535
  except ValueError as error:
536
536
  print(f"tcw: {error}", file=sys.stderr)
537
537
  return 1
538
+ except yaml.YAMLError as error:
539
+ # A malformed YAML file is the user's, not a bug in TCW, so it reads as
540
+ # a message rather than a traceback. Here rather than at each of the
541
+ # twenty-odd `load_yaml` calls that do not catch it: refusing is already
542
+ # the right answer at every one of them — `init` must not overwrite a
543
+ # config it cannot read — and only the presentation was wrong. The
544
+ # loader's message names the file.
545
+ print(f"tcw: {error}", file=sys.stderr)
546
+ return 1
538
547
  except subprocess.CalledProcessError as error:
539
548
  # Deliberately generic: the only policy here is "a git subprocess
540
549
  # failed", which is true of every component. git's own diagnostic has