tcw-cli 2.4.0__tar.gz → 2.6.0__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.4.0/tcw_cli.egg-info → tcw_cli-2.6.0}/PKG-INFO +45 -12
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/README.md +44 -11
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/pyproject.toml +1 -1
- tcw_cli-2.6.0/tcw/__init__.py +1 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/serve/__init__.py +50 -0
- tcw_cli-2.4.0/tcw/serve/dist/client/assets/index-CCjF_dHr.js → tcw_cli-2.6.0/tcw/serve/dist/client/assets/index-CNxlcI8A.js +1 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/serve/dist/client/index.html +1 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/store/base.py +478 -29
- tcw_cli-2.6.0/tcw/store/config_edit.py +641 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/store/fs.py +617 -168
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/taxonomy/cli.py +2 -2
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/tracker/claim.py +18 -0
- tcw_cli-2.6.0/tcw/tracker/create.py +207 -0
- tcw_cli-2.6.0/tcw/tracker/intake.py +747 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/tracker/jira.py +20 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/tracker/ownership.py +51 -14
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/tracker/progress.py +2 -2
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/tracker/sync.py +476 -174
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/validate.py +23 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/cli.py +1018 -102
- tcw_cli-2.6.0/tcw/work/procedures/decompose.md +23 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/projection.py +21 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/recursion.py +195 -35
- {tcw_cli-2.4.0 → tcw_cli-2.6.0/tcw_cli.egg-info}/PKG-INFO +45 -12
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw_cli.egg-info/SOURCES.txt +13 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_capabilities.py +39 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_capabilities_federation.py +56 -14
- tcw_cli-2.6.0/tests/test_capability_gate_children.py +623 -0
- tcw_cli-2.6.0/tests/test_check_versions.py +308 -0
- tcw_cli-2.6.0/tests/test_child_status.py +649 -0
- tcw_cli-2.6.0/tests/test_config_edit.py +466 -0
- tcw_cli-2.6.0/tests/test_config_edit_writers.py +384 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_environment_hardness.py +10 -5
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_epic_completable.py +36 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_external_work_store.py +56 -0
- tcw_cli-2.6.0/tests/test_legacy_store_config.py +346 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_multiproject.py +4 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_projection.py +18 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_repo_lifecycle.py +9 -4
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_retention.py +125 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_serve.py +4 -2
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_serve_write.py +122 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_session_bootstrap.py +179 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_store_editor.py +23 -2
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_store_provisioning.py +129 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_taxonomy.py +238 -2
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_claimability.py +18 -1
- tcw_cli-2.6.0/tests/test_tracker_cli.py +1683 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_client.py +7 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_comment.py +14 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_config.py +42 -19
- tcw_cli-2.6.0/tests/test_tracker_create.py +250 -0
- tcw_cli-2.6.0/tests/test_tracker_gate.py +125 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_help.py +24 -7
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_import.py +67 -3
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_inheritance.py +61 -12
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_ownership.py +74 -0
- tcw_cli-2.6.0/tests/test_tracker_pre_backlog.py +852 -0
- tcw_cli-2.6.0/tests/test_tracker_strict.py +1687 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_surface.py +42 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_sync.py +1028 -170
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_sync_config.py +4 -1
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_validate.py +147 -2
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_validate.py +14 -3
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_work.py +160 -47
- tcw_cli-2.6.0/tests/test_work_start.py +48 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_worktree_completion.py +66 -0
- tcw_cli-2.4.0/tcw/__init__.py +0 -1
- tcw_cli-2.4.0/tcw/tracker/intake.py +0 -411
- tcw_cli-2.4.0/tcw/work/procedures/decompose.md +0 -18
- tcw_cli-2.4.0/tests/test_tracker_cli.py +0 -468
- tcw_cli-2.4.0/tests/test_tracker_strict.py +0 -834
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/LICENSE +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/setup.cfg +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/capabilities/__init__.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/capabilities/cli.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/cli.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/harness.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/refs.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/serve/dist/client/assets/index-CXNiOIeg.css +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/serve/dist/client/theme-init.js +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/serve/dist/server.cjs +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/serve/runtime.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/stdin.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/store/__init__.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/store/checkouts.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/store/project.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/taxonomy/__init__.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/tracker/__init__.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/__init__.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/generate.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/hooks.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/procedures/audit-backlog.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/procedures/consolidate-plans.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/procedures/create-work.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/procedures/delegation.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/procedures/documentation-sync.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/procedures/post-mortem.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/procedures/search.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/procedures/triage-issues.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/procedures/unattended-work.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/prompts/implement.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/prompts/inbox.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/prompts/plan.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/prompts/postmortem.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/prompts/request.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/prompts/spec.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/prompts/verify.md +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/resolve.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw/work/templates.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw_cli.egg-info/dependency_links.txt +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw_cli.egg-info/entry_points.txt +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw_cli.egg-info/requires.txt +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tcw_cli.egg-info/top_level.txt +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_body_prompt.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_capabilities_reset.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_capabilities_rm.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_capabilities_sidecar.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_capability_ref_wording.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_cli_help_coverage.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_committed_paths.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_configuration_text_home.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_cut_version.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_documentation_config.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_documentation_prompt.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_documentation_sync_wiring.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_documented_cli_surface.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_dynamic_skill_marker.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_edit_type.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_eval_coverage.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_eval_files_changed.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_eval_fixture.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_eval_grading.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_eval_runner.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_falsification_rule.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_generate_hook.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_harness.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_inbox_title.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_lifecycle_baseline.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_lifecycle_hooks.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_lifecycle_inert.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_lifecycle_policy.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_lifecycle_validation.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_non_git_writes.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_plugin_manifests.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_procedure_config.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_procedure_verb.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_project_overrides.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_project_registry.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_prompt_fallback.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_qualified_ref.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_recursion.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_refs.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_remote_session_setup.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_resolve.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_resolve_procedure.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_scaffold.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_serve_descendants.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_serve_projection.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_serve_resolve.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_serve_runtime.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_shipped_procedures.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_shipped_prompts.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_show_json.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_skill_flow.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_skill_lifecycle_parity.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_skill_path_pointers.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_smoke.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_stage_validate.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_stage_verb.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_status_parity.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_stdin.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_stdin_cli.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_store_bounds.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_store_nodes.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_store_publication.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_subprocess_stdin.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tombstone.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_absent.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_binding.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_claim.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_hold.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_link.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_tracker_replay.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_unattended_work_skill.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_unpushed_version_script.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_validate_target.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_work_autocommit.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_work_review.py +0 -0
- {tcw_cli-2.4.0 → tcw_cli-2.6.0}/tests/test_work_tags.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tcw-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
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
|
|
@@ -293,7 +293,9 @@ cannot run until the next one begins. That first session needs network access. I
|
|
|
293
293
|
installs over an existing `pipx install tcw-cli` rather than beside it, and
|
|
294
294
|
leaves a development checkout (`pip install -e .`) alone. If `tcw` goes missing
|
|
295
295
|
anyway, `pipx install tcw-cli` is the whole fix — the **`setup`** skill
|
|
296
|
-
carries the cases where it is not.
|
|
296
|
+
carries the cases where it is not. When the `tcw` CLI and the plugin's skills
|
|
297
|
+
come from different releases, the session tells you which versions they are and
|
|
298
|
+
what to run to line them up; it never stops your work.
|
|
297
299
|
|
|
298
300
|
In **Codex**:
|
|
299
301
|
|
|
@@ -303,7 +305,9 @@ codex plugin add tcw@tcw
|
|
|
303
305
|
```
|
|
304
306
|
|
|
305
307
|
Codex has no session-start hook, so ask the agent to run the **`setup`**
|
|
306
|
-
skill — it runs the same install script Claude runs automatically.
|
|
308
|
+
skill — it runs the same install script Claude runs automatically. Every tcw
|
|
309
|
+
skill also asks the agent to run the plugin's version check once per session,
|
|
310
|
+
which warns when the CLI and the skills come from different releases.
|
|
307
311
|
|
|
308
312
|
The plugin ships the skills and read-only review agents described in
|
|
309
313
|
[Skills and Agents](#skills-and-agents).
|
|
@@ -609,8 +613,8 @@ behavior, changes to its internals, or changes to how the project itself runs.
|
|
|
609
613
|
- **Blockers** are recorded on an item (`tcw work edit --blocked-by`). An item
|
|
610
614
|
with an unresolved blocker cannot start, or be completed as done, without
|
|
611
615
|
`--force`; there is no separate "blocked" status.
|
|
612
|
-
- **Large items split.** A child item
|
|
613
|
-
|
|
616
|
+
- **Large items split.** A child item has its own status and keeps a link to its
|
|
617
|
+
parent, which cannot be closed while the child is open. An **epic** groups related items, including items in other
|
|
614
618
|
repositories, and reports their combined status.
|
|
615
619
|
- **Completing is checked.** `tcw work complete` prints the project's Definition
|
|
616
620
|
of Done (a list you set in `docs/work/dod.yaml`) and refuses until you confirm
|
|
@@ -726,10 +730,12 @@ to the transitions.
|
|
|
726
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 |
|
|
727
731
|
| `request`, `spec`, `plan` stages | unchanged | unchanged |
|
|
728
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 |
|
|
729
|
-
|
|
|
730
|
-
| `
|
|
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 |
|
|
734
|
+
| `start` | moves the item | also claims a linked ticket (assigns it to you) and moves it to the status mapped for `active`; a ticket already past that stays where it is. Posts a comment if `comments: true` |
|
|
735
|
+
| `submit`, `rework` | moves the item | refused before the item moves unless a linked ticket is assigned to you (`tcw work tracker claim` takes it). Then moves the ticket to the Jira status mapped under `statuses`, and posts a comment if `comments: true` |
|
|
736
|
+
| `complete`, discard | moves the item | moves a linked ticket to its mapped status whoever holds it — finishing work needs no claim — and posts a comment if `comments: true` |
|
|
731
737
|
| the ticket could not follow | — | the item still moves; the command exits 1 and records the ticket as pending or conflicting; `tcw work tracker sync` retries |
|
|
732
|
-
| the ticket is behind its item | — |
|
|
738
|
+
| the ticket is behind its item | — | for a ticket linked to work already under way: `tcw work tracker claim <slug>`, then `tcw work tracker sync <slug>` |
|
|
733
739
|
| the ticket does not match its item | — | `tcw work tracker sync <slug>` puts it where the item's status says, whether the ticket is ahead of the item or behind it; a backwards move says so |
|
|
734
740
|
|
|
735
741
|
Setting `strict: true` changes several steps, so that no work happens without a
|
|
@@ -739,9 +745,17 @@ claimed ticket:
|
|
|
739
745
|
and point you at `tcw work tracker import`. Where `inbox-query` is set, `inbox accept`
|
|
740
746
|
of a ticket is allowed, because it claims the ticket.
|
|
741
747
|
- `start` refuses an item with no ticket. For a linked item it claims the ticket
|
|
742
|
-
first,
|
|
743
|
-
|
|
744
|
-
|
|
748
|
+
first, through the transition `exclusive-claim-transition` names, and moves the
|
|
749
|
+
item only if the claim worked and the workflow would refuse a second person —
|
|
750
|
+
that is, it does not offer that transition again once the ticket has it. Every
|
|
751
|
+
strict claim checks this, `tracker claim` and `tracker import` included.
|
|
752
|
+
- Strict mode needs `exclusive-claim-transition` and `transitions.start` set, and
|
|
753
|
+
`tcw validate` reports either one missing: `tracker import`, the way work is
|
|
754
|
+
created, claims through `transitions.start`.
|
|
755
|
+
- `submit` and `rework` read the ticket first, and refuse unless it is assigned to
|
|
756
|
+
you and where the item's last move left it. `complete --resolution done` refuses
|
|
757
|
+
only if the ticket is not where the item's last move left it: who holds it does
|
|
758
|
+
not matter for finishing work.
|
|
745
759
|
- Discarding is always allowed. `drop` refuses an item that was ever linked, so
|
|
746
760
|
the record stays.
|
|
747
761
|
- While Jira cannot be reached, those commands refuse, and `tcw serve` refuses
|
|
@@ -770,7 +784,11 @@ triaged. When it is set, `tcw work inbox list` prints two sections, `raw intake:
|
|
|
770
784
|
`inbox accept` then take a ticket key as well as an inbox entry: an inbox entry with
|
|
771
785
|
the same name wins, and `--ticket` reads the name as a ticket anyway. Accepting a
|
|
772
786
|
ticket claims it, exactly as `tcw work tracker import` does. Write the query so it
|
|
773
|
-
leaves out tickets that already have a work item; TCW does not filter them.
|
|
787
|
+
leaves out tickets that already have a work item; TCW does not filter them. A
|
|
788
|
+
triage status does not offer the transition that starts work, so to take tickets
|
|
789
|
+
straight from it, name the status and its way out under `pre-backlog` (for example
|
|
790
|
+
`pre-backlog: { Triage: Accept }`, with `statuses.backlog` set): every claim then
|
|
791
|
+
moves the ticket out of Triage to the backlog first, and says so.
|
|
774
792
|
|
|
775
793
|
The credentials entries hold the **names** of environment variables, never the
|
|
776
794
|
e-mail address or token themselves.
|
|
@@ -795,6 +813,21 @@ tcw work tracker link <slug> ENG-517 # TCW: records the link.
|
|
|
795
813
|
tcw work start <slug> # TCW: backlog → active. Jira: if unassigned or yours, ENG-517 → In Progress, assigned to you
|
|
796
814
|
```
|
|
797
815
|
|
|
816
|
+
**Example 2a: making a ticket for an item that has none.**
|
|
817
|
+
|
|
818
|
+
```sh
|
|
819
|
+
tcw work new "Speed up the checkout page" # TCW: creates a backlog item. Jira: unchanged
|
|
820
|
+
tcw work tracker create <slug> --dry-run # TCW: says what it would do. Jira: unchanged
|
|
821
|
+
tcw work tracker create <slug> # TCW: binds the new ticket. Jira: creates it, then moves it to statuses.backlog
|
|
822
|
+
tcw work tracker create --all # TCW: the same for every open item. Jira: one new ticket per item without one
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
The move out of whatever status Jira starts issues in is part of creating the
|
|
826
|
+
ticket, not a courtesy: in a project with a triage column, that starting status
|
|
827
|
+
is usually the one `inbox-query` selects, so a ticket left there would come back
|
|
828
|
+
through `tcw work inbox` as new inbound work. Run `--all` with `--dry-run`
|
|
829
|
+
first — nothing here can delete a ticket.
|
|
830
|
+
|
|
798
831
|
**Example 3: taking a ticket without starting it, and handing it back.**
|
|
799
832
|
|
|
800
833
|
```sh
|
|
@@ -77,7 +77,9 @@ cannot run until the next one begins. That first session needs network access. I
|
|
|
77
77
|
installs over an existing `pipx install tcw-cli` rather than beside it, and
|
|
78
78
|
leaves a development checkout (`pip install -e .`) alone. If `tcw` goes missing
|
|
79
79
|
anyway, `pipx install tcw-cli` is the whole fix — the **`setup`** skill
|
|
80
|
-
carries the cases where it is not.
|
|
80
|
+
carries the cases where it is not. When the `tcw` CLI and the plugin's skills
|
|
81
|
+
come from different releases, the session tells you which versions they are and
|
|
82
|
+
what to run to line them up; it never stops your work.
|
|
81
83
|
|
|
82
84
|
In **Codex**:
|
|
83
85
|
|
|
@@ -87,7 +89,9 @@ codex plugin add tcw@tcw
|
|
|
87
89
|
```
|
|
88
90
|
|
|
89
91
|
Codex has no session-start hook, so ask the agent to run the **`setup`**
|
|
90
|
-
skill — it runs the same install script Claude runs automatically.
|
|
92
|
+
skill — it runs the same install script Claude runs automatically. Every tcw
|
|
93
|
+
skill also asks the agent to run the plugin's version check once per session,
|
|
94
|
+
which warns when the CLI and the skills come from different releases.
|
|
91
95
|
|
|
92
96
|
The plugin ships the skills and read-only review agents described in
|
|
93
97
|
[Skills and Agents](#skills-and-agents).
|
|
@@ -393,8 +397,8 @@ behavior, changes to its internals, or changes to how the project itself runs.
|
|
|
393
397
|
- **Blockers** are recorded on an item (`tcw work edit --blocked-by`). An item
|
|
394
398
|
with an unresolved blocker cannot start, or be completed as done, without
|
|
395
399
|
`--force`; there is no separate "blocked" status.
|
|
396
|
-
- **Large items split.** A child item
|
|
397
|
-
|
|
400
|
+
- **Large items split.** A child item has its own status and keeps a link to its
|
|
401
|
+
parent, which cannot be closed while the child is open. An **epic** groups related items, including items in other
|
|
398
402
|
repositories, and reports their combined status.
|
|
399
403
|
- **Completing is checked.** `tcw work complete` prints the project's Definition
|
|
400
404
|
of Done (a list you set in `docs/work/dod.yaml`) and refuses until you confirm
|
|
@@ -510,10 +514,12 @@ to the transitions.
|
|
|
510
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 |
|
|
511
515
|
| `request`, `spec`, `plan` stages | unchanged | unchanged |
|
|
512
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 |
|
|
513
|
-
|
|
|
514
|
-
| `
|
|
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 |
|
|
518
|
+
| `start` | moves the item | also claims a linked ticket (assigns it to you) and moves it to the status mapped for `active`; a ticket already past that stays where it is. Posts a comment if `comments: true` |
|
|
519
|
+
| `submit`, `rework` | moves the item | refused before the item moves unless a linked ticket is assigned to you (`tcw work tracker claim` takes it). Then moves the ticket to the Jira status mapped under `statuses`, and posts a comment if `comments: true` |
|
|
520
|
+
| `complete`, discard | moves the item | moves a linked ticket to its mapped status whoever holds it — finishing work needs no claim — and posts a comment if `comments: true` |
|
|
515
521
|
| the ticket could not follow | — | the item still moves; the command exits 1 and records the ticket as pending or conflicting; `tcw work tracker sync` retries |
|
|
516
|
-
| the ticket is behind its item | — |
|
|
522
|
+
| the ticket is behind its item | — | for a ticket linked to work already under way: `tcw work tracker claim <slug>`, then `tcw work tracker sync <slug>` |
|
|
517
523
|
| the ticket does not match its item | — | `tcw work tracker sync <slug>` puts it where the item's status says, whether the ticket is ahead of the item or behind it; a backwards move says so |
|
|
518
524
|
|
|
519
525
|
Setting `strict: true` changes several steps, so that no work happens without a
|
|
@@ -523,9 +529,17 @@ claimed ticket:
|
|
|
523
529
|
and point you at `tcw work tracker import`. Where `inbox-query` is set, `inbox accept`
|
|
524
530
|
of a ticket is allowed, because it claims the ticket.
|
|
525
531
|
- `start` refuses an item with no ticket. For a linked item it claims the ticket
|
|
526
|
-
first,
|
|
527
|
-
|
|
528
|
-
|
|
532
|
+
first, through the transition `exclusive-claim-transition` names, and moves the
|
|
533
|
+
item only if the claim worked and the workflow would refuse a second person —
|
|
534
|
+
that is, it does not offer that transition again once the ticket has it. Every
|
|
535
|
+
strict claim checks this, `tracker claim` and `tracker import` included.
|
|
536
|
+
- Strict mode needs `exclusive-claim-transition` and `transitions.start` set, and
|
|
537
|
+
`tcw validate` reports either one missing: `tracker import`, the way work is
|
|
538
|
+
created, claims through `transitions.start`.
|
|
539
|
+
- `submit` and `rework` read the ticket first, and refuse unless it is assigned to
|
|
540
|
+
you and where the item's last move left it. `complete --resolution done` refuses
|
|
541
|
+
only if the ticket is not where the item's last move left it: who holds it does
|
|
542
|
+
not matter for finishing work.
|
|
529
543
|
- Discarding is always allowed. `drop` refuses an item that was ever linked, so
|
|
530
544
|
the record stays.
|
|
531
545
|
- While Jira cannot be reached, those commands refuse, and `tcw serve` refuses
|
|
@@ -554,7 +568,11 @@ triaged. When it is set, `tcw work inbox list` prints two sections, `raw intake:
|
|
|
554
568
|
`inbox accept` then take a ticket key as well as an inbox entry: an inbox entry with
|
|
555
569
|
the same name wins, and `--ticket` reads the name as a ticket anyway. Accepting a
|
|
556
570
|
ticket claims it, exactly as `tcw work tracker import` does. Write the query so it
|
|
557
|
-
leaves out tickets that already have a work item; TCW does not filter them.
|
|
571
|
+
leaves out tickets that already have a work item; TCW does not filter them. A
|
|
572
|
+
triage status does not offer the transition that starts work, so to take tickets
|
|
573
|
+
straight from it, name the status and its way out under `pre-backlog` (for example
|
|
574
|
+
`pre-backlog: { Triage: Accept }`, with `statuses.backlog` set): every claim then
|
|
575
|
+
moves the ticket out of Triage to the backlog first, and says so.
|
|
558
576
|
|
|
559
577
|
The credentials entries hold the **names** of environment variables, never the
|
|
560
578
|
e-mail address or token themselves.
|
|
@@ -579,6 +597,21 @@ tcw work tracker link <slug> ENG-517 # TCW: records the link.
|
|
|
579
597
|
tcw work start <slug> # TCW: backlog → active. Jira: if unassigned or yours, ENG-517 → In Progress, assigned to you
|
|
580
598
|
```
|
|
581
599
|
|
|
600
|
+
**Example 2a: making a ticket for an item that has none.**
|
|
601
|
+
|
|
602
|
+
```sh
|
|
603
|
+
tcw work new "Speed up the checkout page" # TCW: creates a backlog item. Jira: unchanged
|
|
604
|
+
tcw work tracker create <slug> --dry-run # TCW: says what it would do. Jira: unchanged
|
|
605
|
+
tcw work tracker create <slug> # TCW: binds the new ticket. Jira: creates it, then moves it to statuses.backlog
|
|
606
|
+
tcw work tracker create --all # TCW: the same for every open item. Jira: one new ticket per item without one
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
The move out of whatever status Jira starts issues in is part of creating the
|
|
610
|
+
ticket, not a courtesy: in a project with a triage column, that starting status
|
|
611
|
+
is usually the one `inbox-query` selects, so a ticket left there would come back
|
|
612
|
+
through `tcw work inbox` as new inbound work. Run `--all` with `--dry-run`
|
|
613
|
+
first — nothing here can delete a ticket.
|
|
614
|
+
|
|
582
615
|
**Example 3: taking a ticket without starting it, and handing it back.**
|
|
583
616
|
|
|
584
617
|
```sh
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tcw-cli"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.6.0"
|
|
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.0"
|
|
@@ -219,6 +219,14 @@ def _strict_refuses(work, action: str, slug: str = "", body: dict | None = None)
|
|
|
219
219
|
strict mode each change that needs one is sent to the `tcw work` command that
|
|
220
220
|
does. Epics and discards are not gated, as on the command line.
|
|
221
221
|
"""
|
|
222
|
+
# Asked first, and not a strict-mode rule: see `created_but_unbound_refusal`.
|
|
223
|
+
# The CLI offers two ways out of this and the web app used to offer one — the
|
|
224
|
+
# one that makes the item permanently undroppable — so both now say the same
|
|
225
|
+
# words, from the same place.
|
|
226
|
+
from tcw.tracker.intake import created_but_unbound_refusal
|
|
227
|
+
if action == "drop" and slug:
|
|
228
|
+
if refusal := created_but_unbound_refusal(work, slug):
|
|
229
|
+
return refusal
|
|
222
230
|
if not work.tracker_strict():
|
|
223
231
|
return None
|
|
224
232
|
body = body or {}
|
|
@@ -239,6 +247,41 @@ def _strict_refuses(work, action: str, slug: str = "", body: dict | None = None)
|
|
|
239
247
|
return None
|
|
240
248
|
|
|
241
249
|
|
|
250
|
+
def _owe_ticket_if_configured(work, slug: str) -> bool:
|
|
251
|
+
"""Record that a web-filed item is owed a ticket, when the project wants one.
|
|
252
|
+
|
|
253
|
+
Returns whether anything was written, so the caller knows to re-read.
|
|
254
|
+
|
|
255
|
+
`work.tracker.create.on-new` says filing an item makes its ticket. The web
|
|
256
|
+
app deliberately runs no tracker code — no credentials, no network, no page
|
|
257
|
+
render waiting on Jira — so it cannot make it. What it can do is leave the
|
|
258
|
+
debt where the board and `tcw work tracker create --all` will find it, which
|
|
259
|
+
is what the owed record is for.
|
|
260
|
+
|
|
261
|
+
Nothing here may fail the creation that just succeeded. The item exists and
|
|
262
|
+
the response is about to be sent; an item the user never sees because
|
|
263
|
+
recording a note failed is worse than a note that is missing.
|
|
264
|
+
"""
|
|
265
|
+
from contextlib import suppress
|
|
266
|
+
from datetime import date
|
|
267
|
+
|
|
268
|
+
from tcw.tracker.intake import record_owed
|
|
269
|
+
|
|
270
|
+
# Reading the configuration is inside the guard too. It fails closed today,
|
|
271
|
+
# returning None rather than raising, but the promise above is "nothing here
|
|
272
|
+
# may fail the creation that just succeeded" — and a promise that holds only
|
|
273
|
+
# because of what another function happens to do is not the promise.
|
|
274
|
+
with suppress(Exception): # see the docstring
|
|
275
|
+
config = work.tracker_config()
|
|
276
|
+
if config is None or config.create is None or not config.create.on_new:
|
|
277
|
+
return False
|
|
278
|
+
record_owed(work, slug, since=date.today().isoformat(),
|
|
279
|
+
reason="filed in the web app, which does not reach the "
|
|
280
|
+
"tracker; `tcw work tracker create` makes it")
|
|
281
|
+
return True
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
|
|
242
285
|
def _map_store_error(e: Exception) -> tuple[int, bytes]:
|
|
243
286
|
"""Map store-level exceptions to HTTP status codes and JSON error bodies.
|
|
244
287
|
|
|
@@ -867,6 +910,13 @@ class TcwHandler(BaseHTTPRequestHandler):
|
|
|
867
910
|
type=type_val if type_val else "",
|
|
868
911
|
tags=tags,
|
|
869
912
|
)
|
|
913
|
+
if _owe_ticket_if_configured(work, detail.item.slug):
|
|
914
|
+
# Re-read: `detail` predates the owed record, so the
|
|
915
|
+
# response would say `tracker: null` for an item that just
|
|
916
|
+
# got one, and carry a `tracker.yaml` revision already
|
|
917
|
+
# stale — which the next sidecar write from the page would
|
|
918
|
+
# be rejected on.
|
|
919
|
+
detail = work.get_detail(detail.item.slug) or detail
|
|
870
920
|
response = {
|
|
871
921
|
"item": _item_payload(work, detail.item.slug, detail.item),
|
|
872
922
|
"coreRevision": detail.core_revision,
|
|
@@ -164,7 +164,7 @@ ${n}</tr>
|
|
|
164
164
|
`}tablecell(n){let r=this.parser.parseInline(n.tokens),l=n.header?"th":"td";return(n.align?`<${l} align="${n.align}">`:`<${l}>`)+r+`</${l}>
|
|
165
165
|
`}strong({tokens:n}){return`<strong>${this.parser.parseInline(n)}</strong>`}em({tokens:n}){return`<em>${this.parser.parseInline(n)}</em>`}codespan({text:n}){return`<code>${lr(n,!0)}</code>`}br(n){return"<br>"}del({tokens:n}){return`<del>${this.parser.parseInline(n)}</del>`}link({href:n,title:r,tokens:l}){let o=this.parser.parseInline(l),s=Fb(n);if(s===null)return o;n=s;let c='<a href="'+n+'"';return r&&(c+=' title="'+lr(r)+'"'),c+=">"+o+"</a>",c}image({href:n,title:r,text:l,tokens:o}){o&&(l=this.parser.parseInline(o,this.parser.textRenderer));let s=Fb(n);if(s===null)return lr(l);n=s;let c=`<img src="${n}" alt="${l}"`;return r&&(c+=` title="${lr(r)}"`),c+=">",c}text(n){return"tokens"in n&&n.tokens?this.parser.parseInline(n.tokens):"escaped"in n&&n.escaped?n.text:lr(n.text)}},Jp=class{strong({text:n}){return n}em({text:n}){return n}codespan({text:n}){return n}del({text:n}){return n}html({text:n}){return n}text({text:n}){return n}link({text:n}){return""+n}image({text:n}){return""+n}br(){return""}},Vr=class kh{options;renderer;textRenderer;constructor(r){this.options=r||Tl,this.options.renderer=this.options.renderer||new eu,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new Jp}static parse(r,l){return new kh(l).parse(r)}static parseInline(r,l){return new kh(l).parseInline(r)}parse(r,l=!0){let o="";for(let s=0;s<r.length;s++){let c=r[s];if(this.options.extensions?.renderers?.[c.type]){let d=c,h=this.options.extensions.renderers[d.type].call({parser:this},d);if(h!==!1||!["space","hr","heading","code","table","blockquote","list","html","def","paragraph","text"].includes(d.type)){o+=h||"";continue}}let u=c;switch(u.type){case"space":{o+=this.renderer.space(u);continue}case"hr":{o+=this.renderer.hr(u);continue}case"heading":{o+=this.renderer.heading(u);continue}case"code":{o+=this.renderer.code(u);continue}case"table":{o+=this.renderer.table(u);continue}case"blockquote":{o+=this.renderer.blockquote(u);continue}case"list":{o+=this.renderer.list(u);continue}case"html":{o+=this.renderer.html(u);continue}case"def":{o+=this.renderer.def(u);continue}case"paragraph":{o+=this.renderer.paragraph(u);continue}case"text":{let d=u,h=this.renderer.text(d);for(;s+1<r.length&&r[s+1].type==="text";)d=r[++s],h+=`
|
|
166
166
|
`+this.renderer.text(d);l?o+=this.renderer.paragraph({type:"paragraph",raw:h,text:h,tokens:[{type:"text",raw:h,text:h,escaped:!0}]}):o+=h;continue}default:{let d='Token with "'+u.type+'" type was not found.';if(this.options.silent)return console.error(d),"";throw new Error(d)}}}return o}parseInline(r,l=this.renderer){let o="";for(let s=0;s<r.length;s++){let c=r[s];if(this.options.extensions?.renderers?.[c.type]){let d=this.options.extensions.renderers[c.type].call({parser:this},c);if(d!==!1||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(c.type)){o+=d||"";continue}}let u=c;switch(u.type){case"escape":{o+=l.text(u);break}case"html":{o+=l.html(u);break}case"link":{o+=l.link(u);break}case"image":{o+=l.image(u);break}case"strong":{o+=l.strong(u);break}case"em":{o+=l.em(u);break}case"codespan":{o+=l.codespan(u);break}case"br":{o+=l.br(u);break}case"del":{o+=l.del(u);break}case"text":{o+=l.text(u);break}default:{let d='Token with "'+u.type+'" type was not found.';if(this.options.silent)return console.error(d),"";throw new Error(d)}}}return o}},Pi=class{options;block;constructor(n){this.options=n||Tl}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens","emStrongMask"]);static passThroughHooksRespectAsync=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(n){return n}postprocess(n){return n}processAllTokens(n){return n}emStrongMask(n){return n}provideLexer(){return this.block?qr.lex:qr.lexInline}provideParser(){return this.block?Vr.parse:Vr.parseInline}},Gk=class{defaults=Yp();options=this.setOptions;parse=this.parseMarkdown(!0);parseInline=this.parseMarkdown(!1);Parser=Vr;Renderer=eu;TextRenderer=Jp;Lexer=qr;Tokenizer=Jc;Hooks=Pi;constructor(...n){this.use(...n)}walkTokens(n,r){let l=[];for(let o of n)switch(l=l.concat(r.call(this,o)),o.type){case"table":{let s=o;for(let c of s.header)l=l.concat(this.walkTokens(c.tokens,r));for(let c of s.rows)for(let u of c)l=l.concat(this.walkTokens(u.tokens,r));break}case"list":{let s=o;l=l.concat(this.walkTokens(s.items,r));break}default:{let s=o;this.defaults.extensions?.childTokens?.[s.type]?this.defaults.extensions.childTokens[s.type].forEach(c=>{let u=s[c].flat(1/0);l=l.concat(this.walkTokens(u,r))}):s.tokens&&(l=l.concat(this.walkTokens(s.tokens,r)))}}return l}use(...n){let r=this.defaults.extensions||{renderers:{},childTokens:{}};return n.forEach(l=>{let o={...l};if(o.async=this.defaults.async||o.async||!1,l.extensions&&(l.extensions.forEach(s=>{if(!s.name)throw new Error("extension name required");if("renderer"in s){let c=r.renderers[s.name];c?r.renderers[s.name]=function(...u){let d=s.renderer.apply(this,u);return d===!1&&(d=c.apply(this,u)),d}:r.renderers[s.name]=s.renderer}if("tokenizer"in s){if(!s.level||s.level!=="block"&&s.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let c=r[s.level];c?c.unshift(s.tokenizer):r[s.level]=[s.tokenizer],s.start&&(s.level==="block"?r.startBlock?r.startBlock.push(s.start):r.startBlock=[s.start]:s.level==="inline"&&(r.startInline?r.startInline.push(s.start):r.startInline=[s.start]))}"childTokens"in s&&s.childTokens&&(r.childTokens[s.name]=s.childTokens)}),o.extensions=r),l.renderer){let s=this.defaults.renderer||new eu(this.defaults);for(let c in l.renderer){if(!(c in s))throw new Error(`renderer '${c}' does not exist`);if(["options","parser"].includes(c))continue;let u=c,d=l.renderer[u],h=s[u];s[u]=(...g)=>{let v=d.apply(s,g);return v===!1&&(v=h.apply(s,g)),v||""}}o.renderer=s}if(l.tokenizer){let s=this.defaults.tokenizer||new Jc(this.defaults);for(let c in l.tokenizer){if(!(c in s))throw new Error(`tokenizer '${c}' does not exist`);if(["options","rules","lexer"].includes(c))continue;let u=c,d=l.tokenizer[u],h=s[u];s[u]=(...g)=>{let v=d.apply(s,g);return v===!1&&(v=h.apply(s,g)),v}}o.tokenizer=s}if(l.hooks){let s=this.defaults.hooks||new Pi;for(let c in l.hooks){if(!(c in s))throw new Error(`hook '${c}' does not exist`);if(["options","block"].includes(c))continue;let u=c,d=l.hooks[u],h=s[u];Pi.passThroughHooks.has(c)?s[u]=g=>{if(this.defaults.async&&Pi.passThroughHooksRespectAsync.has(c))return Promise.resolve(d.call(s,g)).then(y=>h.call(s,y));let v=d.call(s,g);return h.call(s,v)}:s[u]=(...g)=>{let v=d.apply(s,g);return v===!1&&(v=h.apply(s,g)),v}}o.hooks=s}if(l.walkTokens){let s=this.defaults.walkTokens,c=l.walkTokens;o.walkTokens=function(u){let d=[];return d.push(c.call(this,u)),s&&(d=d.concat(s.call(this,u))),d}}this.defaults={...this.defaults,...o}}),this}setOptions(n){return this.defaults={...this.defaults,...n},this}lexer(n,r){return qr.lex(n,r??this.defaults)}parser(n,r){return Vr.parse(n,r??this.defaults)}parseMarkdown(n){return(r,l)=>{let o={...l},s={...this.defaults,...o},c=this.onError(!!s.silent,!!s.async);if(this.defaults.async===!0&&o.async===!1)return c(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof r>"u"||r===null)return c(new Error("marked(): input parameter is undefined or null"));if(typeof r!="string")return c(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(r)+", string expected"));s.hooks&&(s.hooks.options=s,s.hooks.block=n);let u=s.hooks?s.hooks.provideLexer():n?qr.lex:qr.lexInline,d=s.hooks?s.hooks.provideParser():n?Vr.parse:Vr.parseInline;if(s.async)return Promise.resolve(s.hooks?s.hooks.preprocess(r):r).then(h=>u(h,s)).then(h=>s.hooks?s.hooks.processAllTokens(h):h).then(h=>s.walkTokens?Promise.all(this.walkTokens(h,s.walkTokens)).then(()=>h):h).then(h=>d(h,s)).then(h=>s.hooks?s.hooks.postprocess(h):h).catch(c);try{s.hooks&&(r=s.hooks.preprocess(r));let h=u(r,s);s.hooks&&(h=s.hooks.processAllTokens(h)),s.walkTokens&&this.walkTokens(h,s.walkTokens);let g=d(h,s);return s.hooks&&(g=s.hooks.postprocess(g)),g}catch(h){return c(h)}}}onError(n,r){return l=>{if(l.message+=`
|
|
167
|
-
Please report this to https://github.com/markedjs/marked.`,n){let o="<p>An error occurred:</p><pre>"+lr(l.message+"",!0)+"</pre>";return r?Promise.resolve(o):o}if(r)return Promise.reject(l);throw l}}},xl=new Gk;function st(n,r){return xl.parse(n,r)}st.options=st.setOptions=function(n){return xl.setOptions(n),st.defaults=xl.defaults,uw(st.defaults),st};st.getDefaults=Yp;st.defaults=Tl;st.use=function(...n){return xl.use(...n),st.defaults=xl.defaults,uw(st.defaults),st};st.walkTokens=function(n,r){return xl.walkTokens(n,r)};st.parseInline=xl.parseInline;st.Parser=Vr;st.parser=Vr.parse;st.Renderer=eu;st.TextRenderer=Jp;st.Lexer=qr;st.lexer=qr.lex;st.Tokenizer=Jc;st.Hooks=Pi;st.parse=st;st.options;st.setOptions;st.use;st.walkTokens;st.parseInline;Vr.parse;qr.lex;function Sw(n,r,l,o,s){n.preventDefault();const c=d=>{const h=r.getBoundingClientRect();s(Math.min(o,Math.max(l,(d.clientX-h.left)/h.width)))},u=()=>{window.removeEventListener("pointermove",c),window.removeEventListener("pointerup",u)};window.addEventListener("pointermove",c),window.addEventListener("pointerup",u)}function Yk(){const n=()=>({work:new Set,taxonomy:new Set,capabilities:new Set});try{const r=JSON.parse(localStorage.getItem("tcw.treeExpanded")??"{}");return{work:new Set(r.work),taxonomy:new Set(r.taxonomy),capabilities:new Set(r.capabilities)}}catch{return n()}}function Ik(){let n=0;for(;document.getElementById(`tcw-off-board-${n}`);)n+=1;return`tcw-off-board-${n}`}function Zb(n,r){n.dataset.tcwRef=r,n.removeAttribute("href")}function Fk(n,r){n.classList.add("tcw-unhosted"),n.title=`Project ${r} is not included in this board`;const l=document.createElement("span");l.className="tcw-project-badge",l.textContent=r;const o=document.createElement("span");o.className="tcw-sr-only",o.id=Ik(),o.textContent=n.title,n.setAttribute("aria-describedby",o.id),n.after(l),l.after(o)}function Qi({source:n,resolveLinks:r=!1}){const l=p.useRef(null),o=p.useMemo(()=>st.parse(n||""),[n]);return p.useEffect(()=>{if(!r||!l.current)return;const s=[...l.current.querySelectorAll('a[href^="tcw://"]')];if(!s.length)return;const c=s.map(d=>d.getAttribute("href"));let u=!1;return yo("/api/resolve","POST",{uris:c}).then(d=>{if(u)return;const h=l.current?.querySelectorAll('a[href^="tcw://"]')??[];for(const g of h){const v=g.getAttribute("href"),y=d.data?.[v];y?.ok&&y.axis&&y.key?(g.href=Nh(y.axis,y.key),g.dataset.navAxis=y.axis,g.dataset.navKey=y.key):y?.reason==="unhosted-project"&&y.project?(Zb(g,v),Fk(g,y.project)):(Zb(g,v),g.classList.add("tcw-inert"),g.title=y?.detail||v)}}),()=>{u=!0}},[o,r]),b.jsx("article",{ref:l,className:"body",dangerouslySetInnerHTML:{__html:o}})}function nh({children:n}){return b.jsx("div",{className:"fields",children:n})}function un({name:n,value:r}){return b.jsxs(cs,{className:"field",size:"1",children:[b.jsx(qe,{as:"div",color:"gray",size:"1",children:n}),b.jsx(qe,{as:"div",size:"2",children:String(r??"-")})]})}function Xk({binding:n}){return b.jsxs(cs,{className:"field",size:"1",children:[b.jsx(qe,{as:"div",color:"gray",size:"1",children:"Ticket"}),b.jsx(qe,{as:"div",size:"2",children:"problem"in n?`tracker.yaml cannot be read: ${n.problem}`:b.jsxs(b.Fragment,{children:[n.ticket.url?b.jsx("a",{href:n.ticket.url,target:"_blank",rel:"noreferrer",children:n.ticket.key}):n.ticket.key,` · ${n.provider} · part ${n.part}`,n.sync&&("problem"in n.sync?` · sync record cannot be read: ${n.sync.problem}`:` · ${n.sync.state}: ${n.sync.reason}`)]})})]})}function tu({errors:n}){return n.length?b.jsx(Br,{color:"red",role:"alert",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"Validation errors"}),b.jsx("ul",{children:n.map(r=>b.jsx("li",{children:r},r))})]})}):null}function ww({title:n,children:r,onClose:l}){return b.jsx(IS,{open:!0,onOpenChange:o=>{o||l()},children:b.jsxs(FS,{className:"modal-box",maxWidth:"560px",children:[b.jsxs(We,{justify:"between",align:"center",mb:"3",children:[b.jsx(XS,{children:n}),b.jsx(KS,{children:b.jsx(Qr,{className:"modal-dismiss",variant:"ghost",type:"button","aria-label":"Close",children:"×"})})]}),r]})})}function Kk({nodes:n,axis:r,selected:l,expanded:o,onToggle:s,onSelect:c,visible:u}){const d=(h,g)=>b.jsx("div",{className:"tree-children",role:g===0?"none":"group",children:h.map(v=>{const y=v.children.length>0,x=o.has(v.path),w=v.item?Dh(r,v.item):v.path;return b.jsxs("div",{className:"tree-node",role:"none",children:[b.jsxs("div",{className:"tree-row",role:"none",children:[Array.from({length:g},(E,T)=>b.jsx("span",{className:"tree-indent"},T)),y?b.jsx(Qr,{className:"tree-toggle",variant:"ghost",type:"button",tabIndex:-1,"aria-label":x?`Collapse ${v.name}`:`Expand ${v.name}`,"aria-expanded":x,onClick:()=>s(v.path),children:x?"▾":"▸"}):b.jsx("span",{className:"tree-spacer"}),v.item?b.jsxs("div",{className:"tree-item-content",children:[b.jsxs(et,{type:"button",variant:"soft",role:"treeitem","aria-level":g+1,"data-tree-path":v.path,"aria-expanded":y?x:void 0,"aria-selected":l===w,className:`item item-${r}${r==="work"?` st-${v.item.status}`:""}${l===w?" active":""}${u(v.item)?"":" ancestor-dim"}`,onClick:()=>c(w),children:[b.jsx("div",{className:"item-title",children:lk(r,v.item)}),b.jsx("div",{className:"item-meta",children:b.jsx(Zk,{axis:r,item:v.item})})]}),r==="work"&&b.jsx(Gp,{content:b.jsx("span",{className:"copy-slug-tooltip",children:"Copy slug"}),children:b.jsx(Qr,{className:"copy-slug",variant:"ghost",type:"button","aria-label":"Copy slug to clipboard",onClick:()=>void navigator.clipboard.writeText(w),children:"⎘"})})]}):b.jsx(et,{className:"tree-folder",variant:"ghost",type:"button",role:"treeitem","aria-level":g+1,"data-tree-path":v.path,"aria-expanded":x,onClick:()=>s(v.path),children:v.name})]}),y&&x?d(v.children,g+1):null]},v.path)})});return d(n,0)}function Zk({axis:n,item:r}){if(n==="work"){const o=r;return b.jsxs("div",{children:[b.jsx("span",{className:"work-status",children:o.status})," · ",[o.effort&&`effort ${o.effort}`,o.complexity&&`complexity ${o.complexity}`,o.tags?.length&&`tags ${o.tags.join(", ")}`].filter(Boolean).join(" · "),b.jsx(Eo,{value:o.modified})]})}if(n==="taxonomy"){const o=r;return b.jsxs("div",{children:[b.jsx("div",{children:[o.kind,o.origin].filter(Boolean).join(" · ")}),b.jsx(Eo,{value:o.modified})]})}const l=r;return b.jsxs("div",{children:[[l.status,l.origin!=="local"&&l.origin].filter(Boolean).join(" · "),b.jsx(Eo,{value:l.modified})]})}function Qb({value:n,onChange:r,placeholder:l="Write Markdown..."}){const[o,s]=p.useState(.5);return b.jsxs("div",{className:"md-editor",style:{"--md-split":`${o*100}%`},children:[b.jsx(aw,{className:"md-input","aria-label":"Markdown",value:n,placeholder:l,onChange:c=>r(c.target.value)}),b.jsx("div",{className:"md-resizer",role:"separator","aria-orientation":"vertical","aria-label":"Resize Markdown editor",onPointerDown:c=>Sw(c,c.currentTarget.parentElement,.2,.8,s)}),b.jsx("div",{className:"md-preview",children:b.jsx(Qi,{source:n})})]})}function pl({label:n,value:r,onChange:l,type:o="text",placeholder:s}){return b.jsxs("label",{className:"field-group",children:[b.jsx(qe,{color:"gray",size:"1",weight:"bold",children:n}),b.jsx(Eu,{className:"field-input","aria-label":n,type:o,value:r,placeholder:s,onChange:c=>l(c.target.value)})]})}function Ro({label:n,value:r,options:l,onChange:o}){return b.jsxs("label",{className:"field-group",children:[b.jsx(qe,{color:"gray",size:"1",weight:"bold",children:n}),b.jsxs(Hp,{value:r||"__unset__",onValueChange:s=>o(s==="__unset__"?"":s),children:[b.jsx(qp,{"aria-label":n,className:"field-select"}),b.jsx(Vp,{children:l.map(s=>b.jsx(Qc,{value:s||"__unset__",children:s||"(unset)"},s||"__unset__"))})]})]})}function Qk(){const{preference:n,setPreference:r}=IO();return b.jsxs(Lp,{children:[b.jsx(Gp,{content:"Settings",children:b.jsx(Pp,{children:b.jsx(Qr,{"aria-label":"Settings",variant:"soft",children:b.jsx(sk,{})})})}),b.jsx(Up,{align:"end",width:"220px",children:b.jsxs(We,{direction:"column",gap:"3",children:[b.jsx(Dn,{as:"h2",size:"3",children:"Appearance"}),b.jsx(JS,{value:n,onValueChange:l=>r(iw(l)),children:b.jsx(We,{direction:"column",gap:"2",children:["light","dark","system"].map(l=>b.jsx(qe,{as:"label",size:"2",children:b.jsxs(We,{align:"center",gap:"2",children:[b.jsx(ew,{value:l}),l[0].toUpperCase()+l.slice(1)]})},l))})})]})})]})}const qc=["backlog","active","review","completed","discarded"],Wk=[{name:"initial-request",label:"Initial Request"},{name:"spec",label:"Spec"},{name:"plan",label:"Implementation Plan"}];function Jk({item:n,artifacts:r,onEditInitialRequest:l,onEditArtifact:o,onReadArtifact:s}){const[c,u]=p.useState("initial-request"),[d,h]=p.useState({});p.useEffect(()=>{u("initial-request"),h({})},[n.slug]);const g=E=>{const T=r.find(A=>A.name===E),R=d[E];!T?.present||R?.content!==void 0||R?.loading||(h(A=>({...A,[E]:{loading:!0}})),s(n.slug,E).then(A=>{h(O=>({...O,[E]:{content:A.content}}))}).catch(A=>{h(O=>({...O,[E]:{error:A instanceof Error?A.message:String(A)}}))}))},v=r.find(E=>E.name==="initial-request")?.present??!1,y=c==="initial-request"?void 0:r.find(E=>E.name===c),x=c==="initial-request"?void 0:d[c],w=()=>{c!=="initial-request"&&g(c)};return b.jsxs(tw,{className:"work-document-tabs",value:c,onValueChange:E=>{(E==="initial-request"||E==="spec"||E==="plan")&&(u(E),E!=="initial-request"&&g(E))},children:[b.jsx(nw,{"aria-label":"Work content",children:Wk.map(E=>b.jsx(rw,{value:E.name,"aria-label":E.label,children:E.label},E.name))}),b.jsx("div",{className:"work-document-panel",children:c==="initial-request"?b.jsxs(b.Fragment,{children:[b.jsx(We,{justify:"end",children:b.jsx(et,{size:"1",variant:"soft",type:"button",onClick:l,children:"Edit Initial Request"})}),v?b.jsx(Qi,{source:n.body??"",resolveLinks:!0}):b.jsx(qe,{color:"gray",children:"Initial Request is not yet present."})]}):y?.present?x?.error?b.jsxs(We,{direction:"column",align:"start",gap:"2",children:[b.jsxs(qe,{color:"red",children:["Could not load this document: ",x.error]}),b.jsx(et,{size:"1",variant:"soft",type:"button",onClick:w,children:"Retry"})]}):x?.content===void 0?b.jsx(qe,{color:"gray",children:"Loading document..."}):b.jsxs(b.Fragment,{children:[b.jsx(We,{justify:"end",children:b.jsxs(et,{size:"1",variant:"soft",type:"button",onClick:()=>o(n.slug,c),children:["Edit"," ",c==="spec"?"Spec":"Implementation Plan"]})}),b.jsx(Qi,{source:x.content,resolveLinks:!0})]}):b.jsx(qe,{color:"gray",children:c==="spec"?"Spec is not yet present.":"Implementation Plan is not yet present."})})]})}const eM=[["Status",["","Supported","Partial","Missing","Blocked","Omitted"]],["Priority",["","P0","P1","P2","P3"]],["Lifecycle",["","Experimental","Stable","Deprecated"]],["Feature",null],["Subject",null],["Roles",null],["When",null],["Gaps",null],["Blocked by",null],["Tracker",null],["Planning doc",null],["Superseded by",null]];function tM({axis:n,registeredTags:r,statusFilter:l,setStatusFilter:o,kindFilter:s,setKindFilter:c,tagFilter:u,setTagFilter:d,workSortKey:h,setWorkSortKey:g,workSortDirection:v,setWorkSortDirection:y}){if(n==="capabilities")return null;const x=qc.filter(w=>l[w]);return b.jsxs(We,{className:"status-filters",role:"group","aria-label":"Filters",align:"center",gap:"2",wrap:"wrap",children:[n==="work"&&b.jsxs(b.Fragment,{children:[b.jsx(Wb,{label:"Status",options:qc,value:x,capitalize:!0,onChange:w=>o(Object.fromEntries(qc.map(E=>[E,w.includes(E)])))}),b.jsx(nM,{sortKey:h,direction:v,onSortKeyChange:g,onDirectionChange:y})]}),b.jsx(Wb,{label:n==="taxonomy"?"Kind":"Tags",options:n==="taxonomy"?["Feature","Vocabulary"]:r,value:n==="taxonomy"?s:u,onChange:n==="taxonomy"?c:d})]})}function nM({sortKey:n,direction:r,onSortKeyChange:l,onDirectionChange:o}){const s=r==="ascending"?"descending":"ascending";return b.jsxs(We,{className:"sort-control",align:"center",gap:"1",children:[b.jsxs(Hp,{value:n,onValueChange:c=>{(c==="name"||c==="modified")&&l(c)},children:[b.jsx(qp,{"aria-label":"Sort work items"}),b.jsxs(Vp,{children:[b.jsx(Qc,{value:"name",children:"Name"}),b.jsx(Qc,{value:"modified",children:"Modified"})]})]}),b.jsx(Qr,{type:"button",size:"1",variant:"soft","aria-label":`Sort ${s}`,title:`Sort ${s}`,onClick:()=>o(s),children:r==="ascending"?"↑":"↓"})]})}function Wb({label:n,options:r,value:l,onChange:o,capitalize:s=!1}){return b.jsxs(Lp,{children:[b.jsx(Pp,{children:b.jsxs(et,{size:"1",variant:"soft",children:[n,l.length?` (${l.length})`:""]})}),b.jsx(Up,{align:"start",className:"facet-panel",width:"180px",children:b.jsx(We,{direction:"column",gap:"2",children:r.length?r.map(c=>b.jsx(qe,{as:"label",className:"facet-option",size:"2",children:b.jsxs(We,{align:"center",gap:"2",children:[b.jsx(wu,{checked:l.includes(c),onCheckedChange:u=>o(u?[...l,c]:l.filter(d=>d!==c))})," ",s?c[0].toUpperCase()+c.slice(1):c]})},c)):b.jsx(qe,{className:"facet-empty",color:"gray",size:"1",children:"none available"})})})]})}function rM({axis:n,detail:r,onEdit:l,onResource:o,onOpen:s,onReadArtifact:c,onDeletePlanStage:u,onAction:d}){if(n==="work"){const g=r,v=g.item;return b.jsxs(b.Fragment,{children:[b.jsxs("div",{className:"detail-head",children:[b.jsxs("div",{children:[b.jsx(Dn,{as:"h2",size:"5",children:v.title??v.slug}),b.jsx(qe,{className:"item-meta",color:"gray",size:"2",children:v.slug}),b.jsx(Eo,{value:v.modified})]}),b.jsxs(We,{className:"detail-actions",align:"center",gap:"2",children:[b.jsx($i,{children:v.status}),b.jsx(et,{className:"edit-btn",variant:"soft",type:"button",onClick:l,children:"Edit"})]})]}),b.jsxs(We,{className:"action-buttons",gap:"2",wrap:"wrap",children:[v.status==="backlog"&&b.jsxs(b.Fragment,{children:[b.jsx(et,{className:"action-btn start",color:"green",variant:"soft",type:"button",onClick:()=>d("start"),children:"Start"}),b.jsx(et,{className:"action-btn discard",color:"amber",variant:"soft",type:"button",onClick:()=>d("discard"),children:"Discard"}),b.jsx(et,{className:"action-btn drop",color:"red",variant:"soft",type:"button",onClick:()=>d("drop"),children:"Drop"})]}),v.status==="active"&&b.jsx(et,{className:"action-btn complete",color:"green",variant:"soft",type:"button",onClick:()=>d("complete"),children:"Complete"})]}),b.jsxs(nh,{children:[b.jsx(un,{name:"Priority",value:v.priority}),b.jsx(un,{name:"Effort",value:v.effort}),b.jsx(un,{name:"Complexity",value:v.complexity}),b.jsx(un,{name:"Tags",value:v.tags?.join(", ")||"-"}),b.jsx(un,{name:"Resolution",value:v.resolution}),b.jsx(un,{name:"Blocked by",value:v.blocked_by?.map(y=>y.slug??y.external).join(", ")||"-"}),v.parent&&b.jsx(un,{name:"Parent",value:v.parent}),v.initiative&&b.jsx(un,{name:"Initiative",value:v.initiative}),v.tracker&&b.jsx(Xk,{binding:v.tracker})]}),b.jsx(Jk,{item:v,artifacts:g.artifacts,onEditInitialRequest:l,onEditArtifact:(y,x)=>o("artifacts",y,x),onReadArtifact:c},`${v.slug}:${g.coreRevision}:${g.artifacts.filter(y=>["initial-request","spec","plan"].includes(y.name)).map(y=>y.revision??"missing").join(":")}`),b.jsx("div",{className:"artifacts",children:g.artifacts.filter(y=>y.present&&!["initial-request","spec","plan"].includes(y.name)).map(y=>b.jsxs(We,{className:"artifact-group",children:[b.jsx(et,{className:"artifact",variant:"soft",type:"button",onClick:()=>s(v.slug,y.name),children:y.name}),b.jsx(Qr,{className:"artifact-edit",variant:"ghost",type:"button","aria-label":`Edit ${y.name}`,onClick:()=>o("artifacts",v.slug,y.name),children:"✎"})]},y.name))}),g.planStages?.length>0&&b.jsxs("div",{className:"plan-stages",children:[b.jsx(Dn,{as:"h3",size:"3",children:"Plan stages"}),g.planStages.map(y=>b.jsxs(cs,{className:"plan-stage",children:[b.jsxs("div",{children:[b.jsx("strong",{children:y.title}),b.jsxs("span",{className:"item-meta",children:[" ",y.id]})]}),b.jsxs("div",{className:"item-meta",children:["Depends on:"," ",y.depends_on.length?y.depends_on.join(", "):"none",y.effort&&` · effort ${y.effort}`,y.complexity&&` · complexity ${y.complexity}`,y.priority!=null&&` · priority ${y.priority}`,y.tags.length>0&&` · ${y.tags.join(", ")}`]}),g.planStages.filter(x=>x.id!==y.id&&JSON.stringify(x.depends_on)===JSON.stringify(y.depends_on)).length>0&&b.jsxs("div",{className:"item-meta",children:["Parallel with:"," ",g.planStages.filter(x=>x.id!==y.id&&JSON.stringify(x.depends_on)===JSON.stringify(y.depends_on)).map(x=>x.id).join(", ")]}),b.jsxs(We,{className:"artifact-group",align:"center",gap:"2",children:[b.jsx(qe,{size:"2",children:y.present?"Document present":"Document missing"}),y.present&&b.jsx(et,{size:"1",variant:"soft",type:"button",onClick:()=>s(v.slug,y.id,"plan-stages"),children:"Open"}),b.jsx(et,{size:"1",variant:"soft",type:"button",onClick:()=>o("plan-stages",v.slug,y.id),children:y.present?"Edit":"Create"}),y.present&&b.jsx(et,{size:"1",color:"red",variant:"soft",type:"button",onClick:()=>u(v.slug,y.id,y.revision),children:"Delete"})]})]},y.id))]}),g.sidecars?.some(y=>y.present)&&b.jsxs("div",{className:"sidecars-section",children:[b.jsx(Dn,{as:"h3",size:"3",children:"Sidecars"}),g.sidecars.filter(y=>y.present).map(y=>b.jsxs(We,{className:"sidecar-item",align:"center",gap:"2",children:[b.jsx(qe,{className:"sidecar-name",color:"gray",size:"2",children:y.name}),y.generated?b.jsx(qe,{color:"gray",size:"1",children:"generated"}):b.jsx(et,{className:"sidecar-edit-btn",size:"1",variant:"soft",type:"button",onClick:()=>o("sidecars",v.slug,y.name),children:"Edit"})]},y.name))]})]})}if(n==="taxonomy"){const g=r.term;return b.jsxs(b.Fragment,{children:[b.jsxs("div",{className:"detail-head",children:[b.jsxs("div",{children:[b.jsx(Dn,{as:"h2",size:"5",children:g.name??g.slug}),b.jsx(qe,{className:"item-meta",color:"gray",size:"2",children:g.qualified??g.slug}),b.jsx(Eo,{value:g.modified})]}),b.jsxs(We,{className:"detail-actions",align:"center",gap:"2",children:[b.jsx($i,{children:g.kind}),b.jsx(et,{className:"edit-btn",variant:"soft",type:"button",onClick:l,children:"Edit"})]})]}),b.jsxs(nh,{children:[b.jsx(un,{name:"Kind",value:g.kind}),b.jsx(un,{name:"Origin",value:g.origin}),b.jsx(un,{name:"Parent",value:g.parent}),b.jsx(un,{name:"Relates to",value:g.relates_to?.join(", ")||"-"}),b.jsx(un,{name:"Vocabulary",value:g.vocabulary?.join(", ")||"-"})]}),b.jsx(Qi,{source:g.description??"",resolveLinks:!0})]})}const h=r.capability;return b.jsxs(b.Fragment,{children:[b.jsxs("div",{className:"detail-head",children:[b.jsxs("div",{children:[b.jsx(Dn,{as:"h2",size:"5",children:h.name??h.path}),b.jsx(qe,{className:"item-meta",color:"gray",size:"2",children:h.path}),b.jsx(Eo,{value:h.modified})]}),b.jsxs(We,{className:"detail-actions",align:"center",gap:"2",children:[b.jsx($i,{children:h.status}),b.jsx(et,{className:"edit-btn",variant:"soft",type:"button",onClick:l,children:"Edit"})]})]}),b.jsx(nh,{children:Object.entries(h.fields??{}).map(([g,v])=>b.jsx(un,{name:g,value:Array.isArray(v)?v.join(", "):v},g))}),b.jsx(Qi,{source:h.body??"",resolveLinks:!0})]})}function aM({editor:n,setDraft:r,saving:l,errors:o,conflict:s,registeredTags:c,data:u,onSave:d,onCancel:h,onRefreshConflict:g}){if(n.mode==="resource")return b.jsxs("div",{className:`editor-container${l?" saving":""}`,children:[b.jsx(Jb,{title:`Edit ${n.kind==="artifacts"?"Artifact":n.kind==="sidecars"?"Sidecar":"Plan stage"}: ${n.name}`,saving:l,onSave:d,onCancel:h}),b.jsx(tu,{errors:o}),!!s&&b.jsx(e0,{onRefresh:g,onDiscard:h}),b.jsx(Qb,{value:n.draft,onChange:y=>r("draft",y)})]});const v=n.draft;return b.jsxs("div",{className:`editor-container${l?" saving":""}`,children:[b.jsx(Jb,{title:`${n.mode==="create"?"Create":"Edit"} ${bo[n.axis]}`,saving:l,onSave:d,onCancel:h}),b.jsx(tu,{errors:o}),!!s&&b.jsx(e0,{onRefresh:g,onDiscard:h}),b.jsx("div",{className:"editor-section",children:"Fields"}),b.jsxs("div",{className:"editor-fields",children:[n.axis==="work"&&b.jsx(lM,{draft:v,setDraft:r,registeredTags:c,data:u,current:n.mode==="core"?n.ref:void 0}),n.axis==="taxonomy"&&b.jsx(oM,{draft:v,setDraft:r,creating:n.mode==="create",data:u,current:n.mode==="core"?n.ref:void 0}),n.axis==="capabilities"&&b.jsx(iM,{draft:v,setDraft:r,creating:n.mode==="create",data:u,current:n.mode==="core"?n.ref:void 0})]}),b.jsx("div",{className:"editor-section",children:"Body"}),b.jsx(Qb,{value:String(v.body??""),onChange:y=>r("body",y)})]})}function Jb({title:n,saving:r,onSave:l,onCancel:o}){return b.jsxs(We,{className:"editor-header",justify:"between",align:"center",children:[b.jsx(Dn,{as:"h2",className:"editor-title",size:"3",children:n}),b.jsxs(We,{className:"editor-actions",gap:"2",children:[b.jsx(et,{variant:"soft",color:"gray",type:"button",onClick:o,children:"Cancel"}),b.jsx(et,{type:"button",disabled:r,onClick:l,children:r?"Saving...":"Save"})]})]})}function e0({onRefresh:n,onDiscard:r}){return b.jsx(Br,{className:"conflict-banner",color:"amber",role:"alert",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"Stale write detected"}),b.jsx(qe,{as:"p",size:"2",children:"The server version changed. Your draft is preserved."}),b.jsxs(We,{gap:"2",mt:"2",children:[b.jsx(et,{size:"1",variant:"soft",onClick:n,children:"Refresh from server"}),b.jsx(et,{size:"1",color:"red",variant:"soft",onClick:r,children:"Discard draft"})]})]})})}function ml(n,r,l,o){return rk(n,{...r,currentIdentifier:l,selected:o})}function lM({draft:n,setDraft:r,registeredTags:l,data:o,current:s}){const c=n.tags??[];return b.jsxs(b.Fragment,{children:[b.jsx(pl,{label:"Title",value:String(n.title??""),onChange:u=>r("title",u)}),b.jsx(pl,{label:"Priority",type:"number",value:String(n.priority??""),onChange:u=>r("priority",u)}),b.jsx(Ro,{label:"Effort",value:String(n.effort??""),options:["","low","medium","high","very-high"],onChange:u=>r("effort",u)}),b.jsx(Ro,{label:"Complexity",value:String(n.complexity??""),options:["","low","medium","high","very-high"],onChange:u=>r("complexity",u)}),b.jsx(hl,{label:"Parent",value:String(n.parent??""),options:ml("work-parent",o,s),onChange:u=>r("parent",u)}),b.jsx(hl,{label:"Initiative",value:String(n.initiative??""),options:ml("work-initiative",o,s),onChange:u=>r("initiative",u)}),b.jsx(hl,{label:"Blockers",multiple:!0,value:n.blockers??[],options:ml("work-blockers",o,s,n.blockers??[]),onChange:u=>r("blockers",u)}),b.jsxs("div",{className:"field-group",children:[b.jsx(qe,{color:"gray",size:"1",weight:"bold",children:"Tags"}),b.jsx(We,{className:"tag-checkboxes",gap:"3",wrap:"wrap",children:[...new Set([...l,...c])].map(u=>b.jsx(qe,{as:"label",className:`tag-checkbox${l.includes(u)?"":" tag-stale"}`,size:"2",children:b.jsxs(We,{align:"center",gap:"1",children:[b.jsx(wu,{checked:c.includes(u),onCheckedChange:d=>r("tags",d?[...c,u]:c.filter(h=>h!==u))})," ",u,l.includes(u)?"":" (unregistered)"]})},u))})]})]})}function oM({draft:n,setDraft:r,creating:l,data:o,current:s}){return b.jsxs(b.Fragment,{children:[b.jsx(pl,{label:"Name",value:String(n.name??""),onChange:c=>r("name",c)}),b.jsx(Ro,{label:"Kind",value:String(n.kind??"Vocabulary"),options:["Vocabulary","Feature"],onChange:c=>r("kind",c)}),l&&b.jsxs(b.Fragment,{children:[b.jsx(pl,{label:"Slug",value:String(n.slug??""),onChange:c=>r("slug",c)}),b.jsx(hl,{label:"Parent",value:String(n.parent??""),options:ml("taxonomy-parent",o,s),onChange:c=>r("parent",c)})]}),!l&&b.jsx(hl,{label:"Relates to",multiple:!0,value:n.relates_to??[],options:ml("taxonomy-relates",o,s,n.relates_to??[]),onChange:c=>r("relates_to",c)}),b.jsx(hl,{label:"Vocabulary",multiple:!0,value:n.vocabulary??[],options:ml("taxonomy-vocabulary",o,s,n.vocabulary??[]),onChange:c=>r("vocabulary",c)})]})}function iM({draft:n,setDraft:r,creating:l,data:o,current:s}){if(l)return b.jsxs(b.Fragment,{children:[b.jsx(pl,{label:"Path",value:String(n.path??""),placeholder:"e.g. web/editing",onChange:u=>r("path",u)}),b.jsx(pl,{label:"Name",value:String(n.name??""),onChange:u=>r("name",u)}),b.jsx(Ro,{label:"Status",value:String(n.status??"Missing"),options:["Supported","Partial","Missing","Blocked","Omitted"],onChange:u=>r("status",u)})]});const c={Feature:{field:"capability-feature"},Subject:{field:"capability-subject",multiple:!0},Roles:{field:"capability-roles",multiple:!0},When:{field:"capability-when",multiple:!0,negated:!0},"Blocked by":{field:"capability-blocked-by"},"Planning doc":{field:"capability-planning-doc"},"Superseded by":{field:"capability-superseded-by"}};return b.jsx(b.Fragment,{children:eM.map(([u,d])=>{if(d)return b.jsx(Ro,{label:u,value:String(n[u]??""),options:d,onChange:v=>r(u,v)},u);const h=c[u];if(!h)return b.jsx(pl,{label:u,value:String(n[u]??""),onChange:v=>r(u,v)},u);const g=h.multiple?Array.isArray(n[u])?n[u]:String(n[u]??"").split(",").map(v=>v.trim()).filter(Boolean):String(n[u]??"");return b.jsx(hl,{label:u,value:g,multiple:h.multiple,negated:h.negated,options:ml(h.field,o,s,Array.isArray(g)?g.map(v=>v.replace(/^!/,"")):void 0),onChange:v=>r(u,v)},u)})})}function sM({onClose:n,onStart:r,errors:l}){const[o,s]=p.useState(!1);return b.jsxs(ww,{title:"Start Work Item",onClose:n,children:[b.jsx(tu,{errors:l}),o&&b.jsx(Br,{color:"amber",children:b.jsx(Hr,{children:"This item may have unresolved blockers. You can force-start it."})}),b.jsxs(We,{className:"modal-actions",gap:"2",justify:"end",children:[b.jsx(et,{variant:"soft",color:"gray",type:"button",onClick:n,children:"Cancel"}),b.jsx(et,{type:"button",onClick:()=>void r(o).then(c=>{c||s(!0)}),children:o?"Start (force)":"Start"})]})]})}function cM({detail:n,onClose:r,onComplete:l,errors:o,discardOnly:s=!1}){const c=n.dodChecklist?.length?n.dodChecklist:["tests pass","docs synced","capabilities reconciled","reviewed","version offered"],[u,d]=p.useState([]),[h,g]=p.useState(s?"wontfix":""),[v,y]=p.useState(!1),x=h===""||h==="done";return b.jsxs(ww,{title:x?"Complete Work Item":"Close Work Item",onClose:r,children:[b.jsx(tu,{errors:o}),b.jsx(Br,{className:"hooks-not-run",color:"gray",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"Configured hooks do not run here"}),b.jsx("br",{}),"The web app performs and commits the transition, but does not run any commands bound to it in"," ",b.jsx("code",{children:"tcw-config.yaml"}),". Use"," ",b.jsx("code",{children:"tcw work complete"})," if you need them. If the commit itself is refused, the item still moves and the reason is printed by ",b.jsx("code",{children:"tcw serve"})," in your terminal."]})}),x?b.jsx(Br,{className:"reconciliation-reminder",color:"blue",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"Reconciliation reminder"}),b.jsx("br",{}),"Reconcile the capabilities ledger before completing."]})}):h&&b.jsx(Br,{className:"discard-warning",color:"amber",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"This item will be discarded"}),b.jsx("br",{}),"It lands in ",b.jsx("code",{children:"discarded/"}),", not"," ",b.jsx("code",{children:"completed/"}),", and this is permanent. Mark any capability it declared as ",b.jsx("code",{children:"Omitted"})," ","if it will never be built."]})}),b.jsx(Ro,{label:"Resolution",value:h,options:s?["wontfix","duplicate","superseded"]:["","done","wontfix","duplicate","superseded"],onChange:g}),x&&b.jsxs(b.Fragment,{children:[b.jsx(Dn,{as:"h3",size:"3",children:"Definition of Done"}),b.jsx(We,{className:"dod-list",direction:"column",children:c.map(w=>b.jsx(qe,{as:"label",className:"dod-item",size:"2",children:b.jsxs(We,{align:"center",gap:"2",children:[b.jsx(wu,{checked:u.includes(w),onCheckedChange:E=>d(E?[...u,w]:u.filter(T=>T!==w))})," ",w]})},w))})]}),v&&b.jsx(Br,{className:"modal-error",color:"red",children:b.jsx(Hr,{children:"Completion was blocked. You can retry while ignoring blockers."})}),b.jsxs(We,{className:"modal-actions",gap:"2",justify:"end",children:[b.jsx(et,{variant:"soft",color:"gray",type:"button",onClick:r,children:"Cancel"}),b.jsx(et,{type:"button",disabled:!h||x&&u.length!==c.length,onClick:()=>void l({resolution:h,dod_ack:x?u:[],force:v}).then(w=>{w||y(!0)}),children:v?x?"Complete (ignore blockers)":"Discard (ignore blockers)":x?"Complete":"Discard"})]})]})}async function uM(n,r,l,o){try{const s=await fetch(`/api/work/${qt(n)}/${l}/${qt(r)}/open`,{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"});if(!s.ok)throw new Error(`${s.status} ${s.statusText}`);if(s.status===204){o("Opened resource");return}const c=await s.json();c.url&&window.open(c.url,"_blank","noopener")}catch(s){o(`Could not open resource: ${s instanceof Error?s.message:String(s)}`)}}function fM(){const n=La(),r=x0(),l=p.useRef(Gb(n.pathname)).current,[o,s]=p.useState(l.axis),[c,u]=p.useState(l.key),[d,h]=p.useState({work:[],taxonomy:[],capabilities:[]}),[g,v]=p.useState([]),[y,x]=p.useState(""),[w,E]=p.useState({backlog:!0,active:!0,completed:!1,discarded:!1}),[T,R]=p.useState([]),[A,O]=p.useState([]),[N,M]=p.useState("name"),[L,D]=p.useState("ascending"),[F,te]=p.useState(Yk),[J,ae]=p.useState(()=>({work:new Set,taxonomy:new Set,capabilities:new Set})),[ce,oe]=p.useState(()=>{const q=Number.parseFloat(localStorage.getItem("tcw.listWidth")??"");return Number.isFinite(q)&&q>=.12&&q<=.6?q:.28}),[se,de]=p.useState(null),[Ce,P]=p.useState(!1),[G,Z]=p.useState(null),[ue,_]=p.useState(!1),[X,re]=p.useState(!1),[ne,ie]=p.useState([]),[_e,pe]=p.useState(null),[pt,he]=p.useState([]),[Ge,Ue]=p.useState(""),[Me,Ye]=p.useState(null),[lt,gt]=p.useState(""),vt=p.useCallback(q=>{Ue(q),window.setTimeout(()=>Ue(""),2800)},[]),Gn=p.useCallback((q,fe)=>{const me=hM(fe);he(me),vt(me.length?`${q} with ${me.length} validation issue${me.length===1?"":"s"}`:q)},[vt]),At=p.useCallback(()=>!ue||window.confirm("You have unsaved changes. Leave without saving?"),[ue]);IR(p.useCallback(q=>{ue&&q.preventDefault()},[ue]));const Wt=p.useCallback(async(q=!0)=>{try{const[fe,me,ye,Le]=await Promise.all([fl("/api/work"),fl("/api/taxonomy"),fl("/api/capabilities"),fl("/api/work/tags").catch(()=>({tags:[]}))]);h({work:fe,taxonomy:me,capabilities:ye}),v(Le.tags??[]),q||u(null),gt("")}catch(fe){gt(fe instanceof Error?fe.message:String(fe))}},[]);p.useEffect(()=>{Wt()},[Wt]),p.useEffect(()=>{localStorage.setItem("tcw.treeExpanded",JSON.stringify(Object.fromEntries(Ah.map(q=>[q,[...F[q]]]))))},[F]),p.useEffect(()=>{localStorage.setItem("tcw.listWidth",String(ce))},[ce]),p.useEffect(()=>{const q=Gb(n.pathname);if(q.axis!==o||q.key!==c){if(!At()){r(Nh(o,c),{replace:!0});return}Z(null),_(!1),de(null),s(q.axis),u(q.key)}},[n.pathname]);const Ha=d[o];p.useEffect(()=>{if(!c||!d[o].some(fe=>Dh(o,fe)===c)){de(null);return}P(!0);const q=o==="work"?`/api/work/${qt(c)}`:o==="taxonomy"?`/api/taxonomy/${qt(c)}`:`/api/capabilities/${qt(c)}`;fl(q).then(de).catch(fe=>gt(String(fe))).finally(()=>P(!1))},[o,c,d]);const Wn=(q,fe)=>{if(At()&&(Z(null),_(!1),ie([]),pe(null),de(null),s(q),u(fe),r(Nh(q,fe)),fe)){const me=q==="work"?ek(fe,d.work):JO(fe);te(ye=>({...ye,[q]:new Set([...ye[q],...me])}))}},yr=q=>{G&&!At()||(Z(null),_(!1),x(q),Wn(o,null))},gn=p.useCallback(q=>{if(o==="work"){const fe=q;if(fe.status&&w[fe.status]===!1||A.length&&!A.some(me=>fe.tags?.includes(me)))return!1}return o==="taxonomy"&&T.length&&!T.includes(q.kind??"")?!1:!y||JSON.stringify(q).toLowerCase().includes(y.toLowerCase())},[o,y,T,w,A]),Jt=p.useMemo(()=>{const q=o==="work"?cw(QO(d.work),N,L):ZO(Ha,ye=>Dh(o,ye));if(!!!(y||T.length||A.length||o==="work"&&qc.some(ye=>!w[ye])))return{nodes:q,forced:new Set};const me=WO(q,gn);return{nodes:me.nodes,forced:y?me.forceExpand:new Set}},[o,Ha,d.work,y,T,w,A,gn,L,N]),Al=p.useMemo(()=>new Set([...F[o],...Jt.forced]),[o,F,Jt.forced]);p.useEffect(()=>{const q=[],fe=ye=>ye.forEach(Le=>{Le.children.length&&(q.push(Le.path),fe(Le.children))});fe(Jt.nodes);const me=q.filter(ye=>!J[o].has(ye));me.length&&(ae(ye=>({...ye,[o]:new Set([...ye[o],...me])})),te(ye=>({...ye,[o]:new Set([...ye[o],...me])})))},[o,J,Jt.nodes]);const qa=(q,fe)=>{Z(me=>me&&(me.mode==="resource"?q==="draft"?{...me,draft:String(fe)}:me:{...me,draft:{...me.draft,[q]:fe}})),_(!0)},Kt=()=>{Z({mode:"create",axis:o,draft:o==="work"?{title:"",priority:"",effort:"",complexity:"",parent:"",initiative:"",blockers:[],tags:[],body:""}:o==="taxonomy"?{name:"",kind:"Vocabulary",slug:"",parent:"",vocabulary:[],description:"",body:""}:{path:"",name:"",status:"Missing",body:""}}),u(null),_(!1),ie([]),pe(null)},yt=()=>{if(!se)return;let q,fe,me;if(o==="work"){const ye=se,Le=ye.item,Yn=ye.artifacts.find(xr=>xr.name==="initial-request")?.present??!1;q={title:Le.title??"",priority:Le.priority??"",effort:Le.effort??"",complexity:Le.complexity??"",parent:Le.parent??"",initiative:Le.initiative??"",blockers:(Le.blocked_by??[]).map(xr=>xr.slug??xr.external??"").filter(Boolean),tags:Le.tags??[],body:Yn?Le.body??"":""},fe=Le.slug,me=ye.coreRevision}else if(o==="taxonomy"){const ye=se,Le=ye.term;q={name:Le.name??"",kind:Le.kind??"Vocabulary",relates_to:Le.relates_to??[],vocabulary:Le.vocabulary??[],body:Le.description??""},fe=Le.qualified??Le.slug,me=ye.coreRevision}else{const ye=se,Le=ye.capability;q={...Le.fields??{},body:Le.body??""},fe=Le.path,me=ye.coreRevision}Z({mode:"core",axis:o,ref:fe,revision:me,draft:q,original:structuredClone(q)}),_(!1),ie([]),pe(null)},vn=async(q,fe,me)=>{try{const ye=await fl(`/api/work/${qt(fe)}/${q}/${qt(me)}`);Z({mode:"resource",axis:"work",kind:q,slug:fe,name:me,revision:ye.revision,mediaType:ye.mediaType??(q==="artifacts"?"text/markdown":"application/yaml"),draft:ye.content,original:ye.content}),_(!1),ie([]),pe(null)}catch(ye){if(q==="plan-stages"){Z({mode:"resource",axis:"work",kind:q,slug:fe,name:me,revision:"",mediaType:"text/markdown",draft:`## Objective
|
|
167
|
+
Please report this to https://github.com/markedjs/marked.`,n){let o="<p>An error occurred:</p><pre>"+lr(l.message+"",!0)+"</pre>";return r?Promise.resolve(o):o}if(r)return Promise.reject(l);throw l}}},xl=new Gk;function st(n,r){return xl.parse(n,r)}st.options=st.setOptions=function(n){return xl.setOptions(n),st.defaults=xl.defaults,uw(st.defaults),st};st.getDefaults=Yp;st.defaults=Tl;st.use=function(...n){return xl.use(...n),st.defaults=xl.defaults,uw(st.defaults),st};st.walkTokens=function(n,r){return xl.walkTokens(n,r)};st.parseInline=xl.parseInline;st.Parser=Vr;st.parser=Vr.parse;st.Renderer=eu;st.TextRenderer=Jp;st.Lexer=qr;st.lexer=qr.lex;st.Tokenizer=Jc;st.Hooks=Pi;st.parse=st;st.options;st.setOptions;st.use;st.walkTokens;st.parseInline;Vr.parse;qr.lex;function Sw(n,r,l,o,s){n.preventDefault();const c=d=>{const h=r.getBoundingClientRect();s(Math.min(o,Math.max(l,(d.clientX-h.left)/h.width)))},u=()=>{window.removeEventListener("pointermove",c),window.removeEventListener("pointerup",u)};window.addEventListener("pointermove",c),window.addEventListener("pointerup",u)}function Yk(){const n=()=>({work:new Set,taxonomy:new Set,capabilities:new Set});try{const r=JSON.parse(localStorage.getItem("tcw.treeExpanded")??"{}");return{work:new Set(r.work),taxonomy:new Set(r.taxonomy),capabilities:new Set(r.capabilities)}}catch{return n()}}function Ik(){let n=0;for(;document.getElementById(`tcw-off-board-${n}`);)n+=1;return`tcw-off-board-${n}`}function Zb(n,r){n.dataset.tcwRef=r,n.removeAttribute("href")}function Fk(n,r){n.classList.add("tcw-unhosted"),n.title=`Project ${r} is not included in this board`;const l=document.createElement("span");l.className="tcw-project-badge",l.textContent=r;const o=document.createElement("span");o.className="tcw-sr-only",o.id=Ik(),o.textContent=n.title,n.setAttribute("aria-describedby",o.id),n.after(l),l.after(o)}function Qi({source:n,resolveLinks:r=!1}){const l=p.useRef(null),o=p.useMemo(()=>st.parse(n||""),[n]);return p.useEffect(()=>{if(!r||!l.current)return;const s=[...l.current.querySelectorAll('a[href^="tcw://"]')];if(!s.length)return;const c=s.map(d=>d.getAttribute("href"));let u=!1;return yo("/api/resolve","POST",{uris:c}).then(d=>{if(u)return;const h=l.current?.querySelectorAll('a[href^="tcw://"]')??[];for(const g of h){const v=g.getAttribute("href"),y=d.data?.[v];y?.ok&&y.axis&&y.key?(g.href=Nh(y.axis,y.key),g.dataset.navAxis=y.axis,g.dataset.navKey=y.key):y?.reason==="unhosted-project"&&y.project?(Zb(g,v),Fk(g,y.project)):(Zb(g,v),g.classList.add("tcw-inert"),g.title=y?.detail||v)}}),()=>{u=!0}},[o,r]),b.jsx("article",{ref:l,className:"body",dangerouslySetInnerHTML:{__html:o}})}function nh({children:n}){return b.jsx("div",{className:"fields",children:n})}function un({name:n,value:r}){return b.jsxs(cs,{className:"field",size:"1",children:[b.jsx(qe,{as:"div",color:"gray",size:"1",children:n}),b.jsx(qe,{as:"div",size:"2",children:String(r??"-")})]})}function Xk({binding:n}){return b.jsxs(cs,{className:"field",size:"1",children:[b.jsx(qe,{as:"div",color:"gray",size:"1",children:"Ticket"}),b.jsx(qe,{as:"div",size:"2",children:"ticket"in n?b.jsxs(b.Fragment,{children:[n.ticket.url?b.jsx("a",{href:n.ticket.url,target:"_blank",rel:"noreferrer",children:n.ticket.key}):n.ticket.key,` · ${n.provider} · part ${n.part}`,n.sync&&("problem"in n.sync?` · sync record cannot be read: ${n.sync.problem}`:` · ${n.sync.state}: ${n.sync.reason}`)]}):"created"in n?`none yet — ${n.created.key} was created for it and the binding did not follow; \`tcw work tracker create\` binds it`:"owed"in n?`none yet — one was to be created on filing and was not (${n.owed.reason}), owed since ${n.owed.since}`:`tracker.yaml cannot be read: ${n.problem}`})]})}function tu({errors:n}){return n.length?b.jsx(Br,{color:"red",role:"alert",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"Validation errors"}),b.jsx("ul",{children:n.map(r=>b.jsx("li",{children:r},r))})]})}):null}function ww({title:n,children:r,onClose:l}){return b.jsx(IS,{open:!0,onOpenChange:o=>{o||l()},children:b.jsxs(FS,{className:"modal-box",maxWidth:"560px",children:[b.jsxs(We,{justify:"between",align:"center",mb:"3",children:[b.jsx(XS,{children:n}),b.jsx(KS,{children:b.jsx(Qr,{className:"modal-dismiss",variant:"ghost",type:"button","aria-label":"Close",children:"×"})})]}),r]})})}function Kk({nodes:n,axis:r,selected:l,expanded:o,onToggle:s,onSelect:c,visible:u}){const d=(h,g)=>b.jsx("div",{className:"tree-children",role:g===0?"none":"group",children:h.map(v=>{const y=v.children.length>0,x=o.has(v.path),w=v.item?Dh(r,v.item):v.path;return b.jsxs("div",{className:"tree-node",role:"none",children:[b.jsxs("div",{className:"tree-row",role:"none",children:[Array.from({length:g},(E,T)=>b.jsx("span",{className:"tree-indent"},T)),y?b.jsx(Qr,{className:"tree-toggle",variant:"ghost",type:"button",tabIndex:-1,"aria-label":x?`Collapse ${v.name}`:`Expand ${v.name}`,"aria-expanded":x,onClick:()=>s(v.path),children:x?"▾":"▸"}):b.jsx("span",{className:"tree-spacer"}),v.item?b.jsxs("div",{className:"tree-item-content",children:[b.jsxs(et,{type:"button",variant:"soft",role:"treeitem","aria-level":g+1,"data-tree-path":v.path,"aria-expanded":y?x:void 0,"aria-selected":l===w,className:`item item-${r}${r==="work"?` st-${v.item.status}`:""}${l===w?" active":""}${u(v.item)?"":" ancestor-dim"}`,onClick:()=>c(w),children:[b.jsx("div",{className:"item-title",children:lk(r,v.item)}),b.jsx("div",{className:"item-meta",children:b.jsx(Zk,{axis:r,item:v.item})})]}),r==="work"&&b.jsx(Gp,{content:b.jsx("span",{className:"copy-slug-tooltip",children:"Copy slug"}),children:b.jsx(Qr,{className:"copy-slug",variant:"ghost",type:"button","aria-label":"Copy slug to clipboard",onClick:()=>void navigator.clipboard.writeText(w),children:"⎘"})})]}):b.jsx(et,{className:"tree-folder",variant:"ghost",type:"button",role:"treeitem","aria-level":g+1,"data-tree-path":v.path,"aria-expanded":x,onClick:()=>s(v.path),children:v.name})]}),y&&x?d(v.children,g+1):null]},v.path)})});return d(n,0)}function Zk({axis:n,item:r}){if(n==="work"){const o=r;return b.jsxs("div",{children:[b.jsx("span",{className:"work-status",children:o.status})," · ",[o.effort&&`effort ${o.effort}`,o.complexity&&`complexity ${o.complexity}`,o.tags?.length&&`tags ${o.tags.join(", ")}`].filter(Boolean).join(" · "),b.jsx(Eo,{value:o.modified})]})}if(n==="taxonomy"){const o=r;return b.jsxs("div",{children:[b.jsx("div",{children:[o.kind,o.origin].filter(Boolean).join(" · ")}),b.jsx(Eo,{value:o.modified})]})}const l=r;return b.jsxs("div",{children:[[l.status,l.origin!=="local"&&l.origin].filter(Boolean).join(" · "),b.jsx(Eo,{value:l.modified})]})}function Qb({value:n,onChange:r,placeholder:l="Write Markdown..."}){const[o,s]=p.useState(.5);return b.jsxs("div",{className:"md-editor",style:{"--md-split":`${o*100}%`},children:[b.jsx(aw,{className:"md-input","aria-label":"Markdown",value:n,placeholder:l,onChange:c=>r(c.target.value)}),b.jsx("div",{className:"md-resizer",role:"separator","aria-orientation":"vertical","aria-label":"Resize Markdown editor",onPointerDown:c=>Sw(c,c.currentTarget.parentElement,.2,.8,s)}),b.jsx("div",{className:"md-preview",children:b.jsx(Qi,{source:n})})]})}function pl({label:n,value:r,onChange:l,type:o="text",placeholder:s}){return b.jsxs("label",{className:"field-group",children:[b.jsx(qe,{color:"gray",size:"1",weight:"bold",children:n}),b.jsx(Eu,{className:"field-input","aria-label":n,type:o,value:r,placeholder:s,onChange:c=>l(c.target.value)})]})}function Ro({label:n,value:r,options:l,onChange:o}){return b.jsxs("label",{className:"field-group",children:[b.jsx(qe,{color:"gray",size:"1",weight:"bold",children:n}),b.jsxs(Hp,{value:r||"__unset__",onValueChange:s=>o(s==="__unset__"?"":s),children:[b.jsx(qp,{"aria-label":n,className:"field-select"}),b.jsx(Vp,{children:l.map(s=>b.jsx(Qc,{value:s||"__unset__",children:s||"(unset)"},s||"__unset__"))})]})]})}function Qk(){const{preference:n,setPreference:r}=IO();return b.jsxs(Lp,{children:[b.jsx(Gp,{content:"Settings",children:b.jsx(Pp,{children:b.jsx(Qr,{"aria-label":"Settings",variant:"soft",children:b.jsx(sk,{})})})}),b.jsx(Up,{align:"end",width:"220px",children:b.jsxs(We,{direction:"column",gap:"3",children:[b.jsx(Dn,{as:"h2",size:"3",children:"Appearance"}),b.jsx(JS,{value:n,onValueChange:l=>r(iw(l)),children:b.jsx(We,{direction:"column",gap:"2",children:["light","dark","system"].map(l=>b.jsx(qe,{as:"label",size:"2",children:b.jsxs(We,{align:"center",gap:"2",children:[b.jsx(ew,{value:l}),l[0].toUpperCase()+l.slice(1)]})},l))})})]})})]})}const qc=["backlog","active","review","completed","discarded"],Wk=[{name:"initial-request",label:"Initial Request"},{name:"spec",label:"Spec"},{name:"plan",label:"Implementation Plan"}];function Jk({item:n,artifacts:r,onEditInitialRequest:l,onEditArtifact:o,onReadArtifact:s}){const[c,u]=p.useState("initial-request"),[d,h]=p.useState({});p.useEffect(()=>{u("initial-request"),h({})},[n.slug]);const g=E=>{const T=r.find(A=>A.name===E),R=d[E];!T?.present||R?.content!==void 0||R?.loading||(h(A=>({...A,[E]:{loading:!0}})),s(n.slug,E).then(A=>{h(O=>({...O,[E]:{content:A.content}}))}).catch(A=>{h(O=>({...O,[E]:{error:A instanceof Error?A.message:String(A)}}))}))},v=r.find(E=>E.name==="initial-request")?.present??!1,y=c==="initial-request"?void 0:r.find(E=>E.name===c),x=c==="initial-request"?void 0:d[c],w=()=>{c!=="initial-request"&&g(c)};return b.jsxs(tw,{className:"work-document-tabs",value:c,onValueChange:E=>{(E==="initial-request"||E==="spec"||E==="plan")&&(u(E),E!=="initial-request"&&g(E))},children:[b.jsx(nw,{"aria-label":"Work content",children:Wk.map(E=>b.jsx(rw,{value:E.name,"aria-label":E.label,children:E.label},E.name))}),b.jsx("div",{className:"work-document-panel",children:c==="initial-request"?b.jsxs(b.Fragment,{children:[b.jsx(We,{justify:"end",children:b.jsx(et,{size:"1",variant:"soft",type:"button",onClick:l,children:"Edit Initial Request"})}),v?b.jsx(Qi,{source:n.body??"",resolveLinks:!0}):b.jsx(qe,{color:"gray",children:"Initial Request is not yet present."})]}):y?.present?x?.error?b.jsxs(We,{direction:"column",align:"start",gap:"2",children:[b.jsxs(qe,{color:"red",children:["Could not load this document: ",x.error]}),b.jsx(et,{size:"1",variant:"soft",type:"button",onClick:w,children:"Retry"})]}):x?.content===void 0?b.jsx(qe,{color:"gray",children:"Loading document..."}):b.jsxs(b.Fragment,{children:[b.jsx(We,{justify:"end",children:b.jsxs(et,{size:"1",variant:"soft",type:"button",onClick:()=>o(n.slug,c),children:["Edit"," ",c==="spec"?"Spec":"Implementation Plan"]})}),b.jsx(Qi,{source:x.content,resolveLinks:!0})]}):b.jsx(qe,{color:"gray",children:c==="spec"?"Spec is not yet present.":"Implementation Plan is not yet present."})})]})}const eM=[["Status",["","Supported","Partial","Missing","Blocked","Omitted"]],["Priority",["","P0","P1","P2","P3"]],["Lifecycle",["","Experimental","Stable","Deprecated"]],["Feature",null],["Subject",null],["Roles",null],["When",null],["Gaps",null],["Blocked by",null],["Tracker",null],["Planning doc",null],["Superseded by",null]];function tM({axis:n,registeredTags:r,statusFilter:l,setStatusFilter:o,kindFilter:s,setKindFilter:c,tagFilter:u,setTagFilter:d,workSortKey:h,setWorkSortKey:g,workSortDirection:v,setWorkSortDirection:y}){if(n==="capabilities")return null;const x=qc.filter(w=>l[w]);return b.jsxs(We,{className:"status-filters",role:"group","aria-label":"Filters",align:"center",gap:"2",wrap:"wrap",children:[n==="work"&&b.jsxs(b.Fragment,{children:[b.jsx(Wb,{label:"Status",options:qc,value:x,capitalize:!0,onChange:w=>o(Object.fromEntries(qc.map(E=>[E,w.includes(E)])))}),b.jsx(nM,{sortKey:h,direction:v,onSortKeyChange:g,onDirectionChange:y})]}),b.jsx(Wb,{label:n==="taxonomy"?"Kind":"Tags",options:n==="taxonomy"?["Feature","Vocabulary"]:r,value:n==="taxonomy"?s:u,onChange:n==="taxonomy"?c:d})]})}function nM({sortKey:n,direction:r,onSortKeyChange:l,onDirectionChange:o}){const s=r==="ascending"?"descending":"ascending";return b.jsxs(We,{className:"sort-control",align:"center",gap:"1",children:[b.jsxs(Hp,{value:n,onValueChange:c=>{(c==="name"||c==="modified")&&l(c)},children:[b.jsx(qp,{"aria-label":"Sort work items"}),b.jsxs(Vp,{children:[b.jsx(Qc,{value:"name",children:"Name"}),b.jsx(Qc,{value:"modified",children:"Modified"})]})]}),b.jsx(Qr,{type:"button",size:"1",variant:"soft","aria-label":`Sort ${s}`,title:`Sort ${s}`,onClick:()=>o(s),children:r==="ascending"?"↑":"↓"})]})}function Wb({label:n,options:r,value:l,onChange:o,capitalize:s=!1}){return b.jsxs(Lp,{children:[b.jsx(Pp,{children:b.jsxs(et,{size:"1",variant:"soft",children:[n,l.length?` (${l.length})`:""]})}),b.jsx(Up,{align:"start",className:"facet-panel",width:"180px",children:b.jsx(We,{direction:"column",gap:"2",children:r.length?r.map(c=>b.jsx(qe,{as:"label",className:"facet-option",size:"2",children:b.jsxs(We,{align:"center",gap:"2",children:[b.jsx(wu,{checked:l.includes(c),onCheckedChange:u=>o(u?[...l,c]:l.filter(d=>d!==c))})," ",s?c[0].toUpperCase()+c.slice(1):c]})},c)):b.jsx(qe,{className:"facet-empty",color:"gray",size:"1",children:"none available"})})})]})}function rM({axis:n,detail:r,onEdit:l,onResource:o,onOpen:s,onReadArtifact:c,onDeletePlanStage:u,onAction:d}){if(n==="work"){const g=r,v=g.item;return b.jsxs(b.Fragment,{children:[b.jsxs("div",{className:"detail-head",children:[b.jsxs("div",{children:[b.jsx(Dn,{as:"h2",size:"5",children:v.title??v.slug}),b.jsx(qe,{className:"item-meta",color:"gray",size:"2",children:v.slug}),b.jsx(Eo,{value:v.modified})]}),b.jsxs(We,{className:"detail-actions",align:"center",gap:"2",children:[b.jsx($i,{children:v.status}),b.jsx(et,{className:"edit-btn",variant:"soft",type:"button",onClick:l,children:"Edit"})]})]}),b.jsxs(We,{className:"action-buttons",gap:"2",wrap:"wrap",children:[v.status==="backlog"&&b.jsxs(b.Fragment,{children:[b.jsx(et,{className:"action-btn start",color:"green",variant:"soft",type:"button",onClick:()=>d("start"),children:"Start"}),b.jsx(et,{className:"action-btn discard",color:"amber",variant:"soft",type:"button",onClick:()=>d("discard"),children:"Discard"}),b.jsx(et,{className:"action-btn drop",color:"red",variant:"soft",type:"button",onClick:()=>d("drop"),children:"Drop"})]}),v.status==="active"&&b.jsx(et,{className:"action-btn complete",color:"green",variant:"soft",type:"button",onClick:()=>d("complete"),children:"Complete"})]}),b.jsxs(nh,{children:[b.jsx(un,{name:"Priority",value:v.priority}),b.jsx(un,{name:"Effort",value:v.effort}),b.jsx(un,{name:"Complexity",value:v.complexity}),b.jsx(un,{name:"Tags",value:v.tags?.join(", ")||"-"}),b.jsx(un,{name:"Resolution",value:v.resolution}),b.jsx(un,{name:"Blocked by",value:v.blocked_by?.map(y=>y.slug??y.external).join(", ")||"-"}),v.parent&&b.jsx(un,{name:"Parent",value:v.parent}),v.initiative&&b.jsx(un,{name:"Initiative",value:v.initiative}),v.tracker&&b.jsx(Xk,{binding:v.tracker})]}),b.jsx(Jk,{item:v,artifacts:g.artifacts,onEditInitialRequest:l,onEditArtifact:(y,x)=>o("artifacts",y,x),onReadArtifact:c},`${v.slug}:${g.coreRevision}:${g.artifacts.filter(y=>["initial-request","spec","plan"].includes(y.name)).map(y=>y.revision??"missing").join(":")}`),b.jsx("div",{className:"artifacts",children:g.artifacts.filter(y=>y.present&&!["initial-request","spec","plan"].includes(y.name)).map(y=>b.jsxs(We,{className:"artifact-group",children:[b.jsx(et,{className:"artifact",variant:"soft",type:"button",onClick:()=>s(v.slug,y.name),children:y.name}),b.jsx(Qr,{className:"artifact-edit",variant:"ghost",type:"button","aria-label":`Edit ${y.name}`,onClick:()=>o("artifacts",v.slug,y.name),children:"✎"})]},y.name))}),g.planStages?.length>0&&b.jsxs("div",{className:"plan-stages",children:[b.jsx(Dn,{as:"h3",size:"3",children:"Plan stages"}),g.planStages.map(y=>b.jsxs(cs,{className:"plan-stage",children:[b.jsxs("div",{children:[b.jsx("strong",{children:y.title}),b.jsxs("span",{className:"item-meta",children:[" ",y.id]})]}),b.jsxs("div",{className:"item-meta",children:["Depends on:"," ",y.depends_on.length?y.depends_on.join(", "):"none",y.effort&&` · effort ${y.effort}`,y.complexity&&` · complexity ${y.complexity}`,y.priority!=null&&` · priority ${y.priority}`,y.tags.length>0&&` · ${y.tags.join(", ")}`]}),g.planStages.filter(x=>x.id!==y.id&&JSON.stringify(x.depends_on)===JSON.stringify(y.depends_on)).length>0&&b.jsxs("div",{className:"item-meta",children:["Parallel with:"," ",g.planStages.filter(x=>x.id!==y.id&&JSON.stringify(x.depends_on)===JSON.stringify(y.depends_on)).map(x=>x.id).join(", ")]}),b.jsxs(We,{className:"artifact-group",align:"center",gap:"2",children:[b.jsx(qe,{size:"2",children:y.present?"Document present":"Document missing"}),y.present&&b.jsx(et,{size:"1",variant:"soft",type:"button",onClick:()=>s(v.slug,y.id,"plan-stages"),children:"Open"}),b.jsx(et,{size:"1",variant:"soft",type:"button",onClick:()=>o("plan-stages",v.slug,y.id),children:y.present?"Edit":"Create"}),y.present&&b.jsx(et,{size:"1",color:"red",variant:"soft",type:"button",onClick:()=>u(v.slug,y.id,y.revision),children:"Delete"})]})]},y.id))]}),g.sidecars?.some(y=>y.present)&&b.jsxs("div",{className:"sidecars-section",children:[b.jsx(Dn,{as:"h3",size:"3",children:"Sidecars"}),g.sidecars.filter(y=>y.present).map(y=>b.jsxs(We,{className:"sidecar-item",align:"center",gap:"2",children:[b.jsx(qe,{className:"sidecar-name",color:"gray",size:"2",children:y.name}),y.generated?b.jsx(qe,{color:"gray",size:"1",children:"generated"}):b.jsx(et,{className:"sidecar-edit-btn",size:"1",variant:"soft",type:"button",onClick:()=>o("sidecars",v.slug,y.name),children:"Edit"})]},y.name))]})]})}if(n==="taxonomy"){const g=r.term;return b.jsxs(b.Fragment,{children:[b.jsxs("div",{className:"detail-head",children:[b.jsxs("div",{children:[b.jsx(Dn,{as:"h2",size:"5",children:g.name??g.slug}),b.jsx(qe,{className:"item-meta",color:"gray",size:"2",children:g.qualified??g.slug}),b.jsx(Eo,{value:g.modified})]}),b.jsxs(We,{className:"detail-actions",align:"center",gap:"2",children:[b.jsx($i,{children:g.kind}),b.jsx(et,{className:"edit-btn",variant:"soft",type:"button",onClick:l,children:"Edit"})]})]}),b.jsxs(nh,{children:[b.jsx(un,{name:"Kind",value:g.kind}),b.jsx(un,{name:"Origin",value:g.origin}),b.jsx(un,{name:"Parent",value:g.parent}),b.jsx(un,{name:"Relates to",value:g.relates_to?.join(", ")||"-"}),b.jsx(un,{name:"Vocabulary",value:g.vocabulary?.join(", ")||"-"})]}),b.jsx(Qi,{source:g.description??"",resolveLinks:!0})]})}const h=r.capability;return b.jsxs(b.Fragment,{children:[b.jsxs("div",{className:"detail-head",children:[b.jsxs("div",{children:[b.jsx(Dn,{as:"h2",size:"5",children:h.name??h.path}),b.jsx(qe,{className:"item-meta",color:"gray",size:"2",children:h.path}),b.jsx(Eo,{value:h.modified})]}),b.jsxs(We,{className:"detail-actions",align:"center",gap:"2",children:[b.jsx($i,{children:h.status}),b.jsx(et,{className:"edit-btn",variant:"soft",type:"button",onClick:l,children:"Edit"})]})]}),b.jsx(nh,{children:Object.entries(h.fields??{}).map(([g,v])=>b.jsx(un,{name:g,value:Array.isArray(v)?v.join(", "):v},g))}),b.jsx(Qi,{source:h.body??"",resolveLinks:!0})]})}function aM({editor:n,setDraft:r,saving:l,errors:o,conflict:s,registeredTags:c,data:u,onSave:d,onCancel:h,onRefreshConflict:g}){if(n.mode==="resource")return b.jsxs("div",{className:`editor-container${l?" saving":""}`,children:[b.jsx(Jb,{title:`Edit ${n.kind==="artifacts"?"Artifact":n.kind==="sidecars"?"Sidecar":"Plan stage"}: ${n.name}`,saving:l,onSave:d,onCancel:h}),b.jsx(tu,{errors:o}),!!s&&b.jsx(e0,{onRefresh:g,onDiscard:h}),b.jsx(Qb,{value:n.draft,onChange:y=>r("draft",y)})]});const v=n.draft;return b.jsxs("div",{className:`editor-container${l?" saving":""}`,children:[b.jsx(Jb,{title:`${n.mode==="create"?"Create":"Edit"} ${bo[n.axis]}`,saving:l,onSave:d,onCancel:h}),b.jsx(tu,{errors:o}),!!s&&b.jsx(e0,{onRefresh:g,onDiscard:h}),b.jsx("div",{className:"editor-section",children:"Fields"}),b.jsxs("div",{className:"editor-fields",children:[n.axis==="work"&&b.jsx(lM,{draft:v,setDraft:r,registeredTags:c,data:u,current:n.mode==="core"?n.ref:void 0}),n.axis==="taxonomy"&&b.jsx(oM,{draft:v,setDraft:r,creating:n.mode==="create",data:u,current:n.mode==="core"?n.ref:void 0}),n.axis==="capabilities"&&b.jsx(iM,{draft:v,setDraft:r,creating:n.mode==="create",data:u,current:n.mode==="core"?n.ref:void 0})]}),b.jsx("div",{className:"editor-section",children:"Body"}),b.jsx(Qb,{value:String(v.body??""),onChange:y=>r("body",y)})]})}function Jb({title:n,saving:r,onSave:l,onCancel:o}){return b.jsxs(We,{className:"editor-header",justify:"between",align:"center",children:[b.jsx(Dn,{as:"h2",className:"editor-title",size:"3",children:n}),b.jsxs(We,{className:"editor-actions",gap:"2",children:[b.jsx(et,{variant:"soft",color:"gray",type:"button",onClick:o,children:"Cancel"}),b.jsx(et,{type:"button",disabled:r,onClick:l,children:r?"Saving...":"Save"})]})]})}function e0({onRefresh:n,onDiscard:r}){return b.jsx(Br,{className:"conflict-banner",color:"amber",role:"alert",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"Stale write detected"}),b.jsx(qe,{as:"p",size:"2",children:"The server version changed. Your draft is preserved."}),b.jsxs(We,{gap:"2",mt:"2",children:[b.jsx(et,{size:"1",variant:"soft",onClick:n,children:"Refresh from server"}),b.jsx(et,{size:"1",color:"red",variant:"soft",onClick:r,children:"Discard draft"})]})]})})}function ml(n,r,l,o){return rk(n,{...r,currentIdentifier:l,selected:o})}function lM({draft:n,setDraft:r,registeredTags:l,data:o,current:s}){const c=n.tags??[];return b.jsxs(b.Fragment,{children:[b.jsx(pl,{label:"Title",value:String(n.title??""),onChange:u=>r("title",u)}),b.jsx(pl,{label:"Priority",type:"number",value:String(n.priority??""),onChange:u=>r("priority",u)}),b.jsx(Ro,{label:"Effort",value:String(n.effort??""),options:["","low","medium","high","very-high"],onChange:u=>r("effort",u)}),b.jsx(Ro,{label:"Complexity",value:String(n.complexity??""),options:["","low","medium","high","very-high"],onChange:u=>r("complexity",u)}),b.jsx(hl,{label:"Parent",value:String(n.parent??""),options:ml("work-parent",o,s),onChange:u=>r("parent",u)}),b.jsx(hl,{label:"Initiative",value:String(n.initiative??""),options:ml("work-initiative",o,s),onChange:u=>r("initiative",u)}),b.jsx(hl,{label:"Blockers",multiple:!0,value:n.blockers??[],options:ml("work-blockers",o,s,n.blockers??[]),onChange:u=>r("blockers",u)}),b.jsxs("div",{className:"field-group",children:[b.jsx(qe,{color:"gray",size:"1",weight:"bold",children:"Tags"}),b.jsx(We,{className:"tag-checkboxes",gap:"3",wrap:"wrap",children:[...new Set([...l,...c])].map(u=>b.jsx(qe,{as:"label",className:`tag-checkbox${l.includes(u)?"":" tag-stale"}`,size:"2",children:b.jsxs(We,{align:"center",gap:"1",children:[b.jsx(wu,{checked:c.includes(u),onCheckedChange:d=>r("tags",d?[...c,u]:c.filter(h=>h!==u))})," ",u,l.includes(u)?"":" (unregistered)"]})},u))})]})]})}function oM({draft:n,setDraft:r,creating:l,data:o,current:s}){return b.jsxs(b.Fragment,{children:[b.jsx(pl,{label:"Name",value:String(n.name??""),onChange:c=>r("name",c)}),b.jsx(Ro,{label:"Kind",value:String(n.kind??"Vocabulary"),options:["Vocabulary","Feature"],onChange:c=>r("kind",c)}),l&&b.jsxs(b.Fragment,{children:[b.jsx(pl,{label:"Slug",value:String(n.slug??""),onChange:c=>r("slug",c)}),b.jsx(hl,{label:"Parent",value:String(n.parent??""),options:ml("taxonomy-parent",o,s),onChange:c=>r("parent",c)})]}),!l&&b.jsx(hl,{label:"Relates to",multiple:!0,value:n.relates_to??[],options:ml("taxonomy-relates",o,s,n.relates_to??[]),onChange:c=>r("relates_to",c)}),b.jsx(hl,{label:"Vocabulary",multiple:!0,value:n.vocabulary??[],options:ml("taxonomy-vocabulary",o,s,n.vocabulary??[]),onChange:c=>r("vocabulary",c)})]})}function iM({draft:n,setDraft:r,creating:l,data:o,current:s}){if(l)return b.jsxs(b.Fragment,{children:[b.jsx(pl,{label:"Path",value:String(n.path??""),placeholder:"e.g. web/editing",onChange:u=>r("path",u)}),b.jsx(pl,{label:"Name",value:String(n.name??""),onChange:u=>r("name",u)}),b.jsx(Ro,{label:"Status",value:String(n.status??"Missing"),options:["Supported","Partial","Missing","Blocked","Omitted"],onChange:u=>r("status",u)})]});const c={Feature:{field:"capability-feature"},Subject:{field:"capability-subject",multiple:!0},Roles:{field:"capability-roles",multiple:!0},When:{field:"capability-when",multiple:!0,negated:!0},"Blocked by":{field:"capability-blocked-by"},"Planning doc":{field:"capability-planning-doc"},"Superseded by":{field:"capability-superseded-by"}};return b.jsx(b.Fragment,{children:eM.map(([u,d])=>{if(d)return b.jsx(Ro,{label:u,value:String(n[u]??""),options:d,onChange:v=>r(u,v)},u);const h=c[u];if(!h)return b.jsx(pl,{label:u,value:String(n[u]??""),onChange:v=>r(u,v)},u);const g=h.multiple?Array.isArray(n[u])?n[u]:String(n[u]??"").split(",").map(v=>v.trim()).filter(Boolean):String(n[u]??"");return b.jsx(hl,{label:u,value:g,multiple:h.multiple,negated:h.negated,options:ml(h.field,o,s,Array.isArray(g)?g.map(v=>v.replace(/^!/,"")):void 0),onChange:v=>r(u,v)},u)})})}function sM({onClose:n,onStart:r,errors:l}){const[o,s]=p.useState(!1);return b.jsxs(ww,{title:"Start Work Item",onClose:n,children:[b.jsx(tu,{errors:l}),o&&b.jsx(Br,{color:"amber",children:b.jsx(Hr,{children:"This item may have unresolved blockers. You can force-start it."})}),b.jsxs(We,{className:"modal-actions",gap:"2",justify:"end",children:[b.jsx(et,{variant:"soft",color:"gray",type:"button",onClick:n,children:"Cancel"}),b.jsx(et,{type:"button",onClick:()=>void r(o).then(c=>{c||s(!0)}),children:o?"Start (force)":"Start"})]})]})}function cM({detail:n,onClose:r,onComplete:l,errors:o,discardOnly:s=!1}){const c=n.dodChecklist?.length?n.dodChecklist:["tests pass","docs synced","capabilities reconciled","reviewed"],[u,d]=p.useState([]),[h,g]=p.useState(s?"wontfix":""),[v,y]=p.useState(!1),x=h===""||h==="done";return b.jsxs(ww,{title:x?"Complete Work Item":"Close Work Item",onClose:r,children:[b.jsx(tu,{errors:o}),b.jsx(Br,{className:"hooks-not-run",color:"gray",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"Configured hooks do not run here"}),b.jsx("br",{}),"The web app performs and commits the transition, but does not run any commands bound to it in"," ",b.jsx("code",{children:"tcw-config.yaml"}),". Use"," ",b.jsx("code",{children:"tcw work complete"})," if you need them. If the commit itself is refused, the item still moves and the reason is printed by ",b.jsx("code",{children:"tcw serve"})," in your terminal."]})}),x?b.jsx(Br,{className:"reconciliation-reminder",color:"blue",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"Reconciliation reminder"}),b.jsx("br",{}),"Reconcile the capabilities ledger before completing."]})}):h&&b.jsx(Br,{className:"discard-warning",color:"amber",children:b.jsxs(Hr,{children:[b.jsx("strong",{children:"This item will be discarded"}),b.jsx("br",{}),"It lands in ",b.jsx("code",{children:"discarded/"}),", not"," ",b.jsx("code",{children:"completed/"}),", and this is permanent. Mark any capability it declared as ",b.jsx("code",{children:"Omitted"})," ","if it will never be built."]})}),b.jsx(Ro,{label:"Resolution",value:h,options:s?["wontfix","duplicate","superseded"]:["","done","wontfix","duplicate","superseded"],onChange:g}),x&&b.jsxs(b.Fragment,{children:[b.jsx(Dn,{as:"h3",size:"3",children:"Definition of Done"}),b.jsx(We,{className:"dod-list",direction:"column",children:c.map(w=>b.jsx(qe,{as:"label",className:"dod-item",size:"2",children:b.jsxs(We,{align:"center",gap:"2",children:[b.jsx(wu,{checked:u.includes(w),onCheckedChange:E=>d(E?[...u,w]:u.filter(T=>T!==w))})," ",w]})},w))})]}),v&&b.jsx(Br,{className:"modal-error",color:"red",children:b.jsx(Hr,{children:"Completion was blocked. You can retry while ignoring blockers."})}),b.jsxs(We,{className:"modal-actions",gap:"2",justify:"end",children:[b.jsx(et,{variant:"soft",color:"gray",type:"button",onClick:r,children:"Cancel"}),b.jsx(et,{type:"button",disabled:!h||x&&u.length!==c.length,onClick:()=>void l({resolution:h,dod_ack:x?u:[],force:v}).then(w=>{w||y(!0)}),children:v?x?"Complete (ignore blockers)":"Discard (ignore blockers)":x?"Complete":"Discard"})]})]})}async function uM(n,r,l,o){try{const s=await fetch(`/api/work/${qt(n)}/${l}/${qt(r)}/open`,{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"});if(!s.ok)throw new Error(`${s.status} ${s.statusText}`);if(s.status===204){o("Opened resource");return}const c=await s.json();c.url&&window.open(c.url,"_blank","noopener")}catch(s){o(`Could not open resource: ${s instanceof Error?s.message:String(s)}`)}}function fM(){const n=La(),r=x0(),l=p.useRef(Gb(n.pathname)).current,[o,s]=p.useState(l.axis),[c,u]=p.useState(l.key),[d,h]=p.useState({work:[],taxonomy:[],capabilities:[]}),[g,v]=p.useState([]),[y,x]=p.useState(""),[w,E]=p.useState({backlog:!0,active:!0,completed:!1,discarded:!1}),[T,R]=p.useState([]),[A,O]=p.useState([]),[N,M]=p.useState("name"),[L,D]=p.useState("ascending"),[F,te]=p.useState(Yk),[J,ae]=p.useState(()=>({work:new Set,taxonomy:new Set,capabilities:new Set})),[ce,oe]=p.useState(()=>{const q=Number.parseFloat(localStorage.getItem("tcw.listWidth")??"");return Number.isFinite(q)&&q>=.12&&q<=.6?q:.28}),[se,de]=p.useState(null),[Ce,P]=p.useState(!1),[G,Z]=p.useState(null),[ue,_]=p.useState(!1),[X,re]=p.useState(!1),[ne,ie]=p.useState([]),[_e,pe]=p.useState(null),[pt,he]=p.useState([]),[Ge,Ue]=p.useState(""),[Me,Ye]=p.useState(null),[lt,gt]=p.useState(""),vt=p.useCallback(q=>{Ue(q),window.setTimeout(()=>Ue(""),2800)},[]),Gn=p.useCallback((q,fe)=>{const me=hM(fe);he(me),vt(me.length?`${q} with ${me.length} validation issue${me.length===1?"":"s"}`:q)},[vt]),At=p.useCallback(()=>!ue||window.confirm("You have unsaved changes. Leave without saving?"),[ue]);IR(p.useCallback(q=>{ue&&q.preventDefault()},[ue]));const Wt=p.useCallback(async(q=!0)=>{try{const[fe,me,ye,Le]=await Promise.all([fl("/api/work"),fl("/api/taxonomy"),fl("/api/capabilities"),fl("/api/work/tags").catch(()=>({tags:[]}))]);h({work:fe,taxonomy:me,capabilities:ye}),v(Le.tags??[]),q||u(null),gt("")}catch(fe){gt(fe instanceof Error?fe.message:String(fe))}},[]);p.useEffect(()=>{Wt()},[Wt]),p.useEffect(()=>{localStorage.setItem("tcw.treeExpanded",JSON.stringify(Object.fromEntries(Ah.map(q=>[q,[...F[q]]]))))},[F]),p.useEffect(()=>{localStorage.setItem("tcw.listWidth",String(ce))},[ce]),p.useEffect(()=>{const q=Gb(n.pathname);if(q.axis!==o||q.key!==c){if(!At()){r(Nh(o,c),{replace:!0});return}Z(null),_(!1),de(null),s(q.axis),u(q.key)}},[n.pathname]);const Ha=d[o];p.useEffect(()=>{if(!c||!d[o].some(fe=>Dh(o,fe)===c)){de(null);return}P(!0);const q=o==="work"?`/api/work/${qt(c)}`:o==="taxonomy"?`/api/taxonomy/${qt(c)}`:`/api/capabilities/${qt(c)}`;fl(q).then(de).catch(fe=>gt(String(fe))).finally(()=>P(!1))},[o,c,d]);const Wn=(q,fe)=>{if(At()&&(Z(null),_(!1),ie([]),pe(null),de(null),s(q),u(fe),r(Nh(q,fe)),fe)){const me=q==="work"?ek(fe,d.work):JO(fe);te(ye=>({...ye,[q]:new Set([...ye[q],...me])}))}},yr=q=>{G&&!At()||(Z(null),_(!1),x(q),Wn(o,null))},gn=p.useCallback(q=>{if(o==="work"){const fe=q;if(fe.status&&w[fe.status]===!1||A.length&&!A.some(me=>fe.tags?.includes(me)))return!1}return o==="taxonomy"&&T.length&&!T.includes(q.kind??"")?!1:!y||JSON.stringify(q).toLowerCase().includes(y.toLowerCase())},[o,y,T,w,A]),Jt=p.useMemo(()=>{const q=o==="work"?cw(QO(d.work),N,L):ZO(Ha,ye=>Dh(o,ye));if(!!!(y||T.length||A.length||o==="work"&&qc.some(ye=>!w[ye])))return{nodes:q,forced:new Set};const me=WO(q,gn);return{nodes:me.nodes,forced:y?me.forceExpand:new Set}},[o,Ha,d.work,y,T,w,A,gn,L,N]),Al=p.useMemo(()=>new Set([...F[o],...Jt.forced]),[o,F,Jt.forced]);p.useEffect(()=>{const q=[],fe=ye=>ye.forEach(Le=>{Le.children.length&&(q.push(Le.path),fe(Le.children))});fe(Jt.nodes);const me=q.filter(ye=>!J[o].has(ye));me.length&&(ae(ye=>({...ye,[o]:new Set([...ye[o],...me])})),te(ye=>({...ye,[o]:new Set([...ye[o],...me])})))},[o,J,Jt.nodes]);const qa=(q,fe)=>{Z(me=>me&&(me.mode==="resource"?q==="draft"?{...me,draft:String(fe)}:me:{...me,draft:{...me.draft,[q]:fe}})),_(!0)},Kt=()=>{Z({mode:"create",axis:o,draft:o==="work"?{title:"",priority:"",effort:"",complexity:"",parent:"",initiative:"",blockers:[],tags:[],body:""}:o==="taxonomy"?{name:"",kind:"Vocabulary",slug:"",parent:"",vocabulary:[],description:"",body:""}:{path:"",name:"",status:"Missing",body:""}}),u(null),_(!1),ie([]),pe(null)},yt=()=>{if(!se)return;let q,fe,me;if(o==="work"){const ye=se,Le=ye.item,Yn=ye.artifacts.find(xr=>xr.name==="initial-request")?.present??!1;q={title:Le.title??"",priority:Le.priority??"",effort:Le.effort??"",complexity:Le.complexity??"",parent:Le.parent??"",initiative:Le.initiative??"",blockers:(Le.blocked_by??[]).map(xr=>xr.slug??xr.external??"").filter(Boolean),tags:Le.tags??[],body:Yn?Le.body??"":""},fe=Le.slug,me=ye.coreRevision}else if(o==="taxonomy"){const ye=se,Le=ye.term;q={name:Le.name??"",kind:Le.kind??"Vocabulary",relates_to:Le.relates_to??[],vocabulary:Le.vocabulary??[],body:Le.description??""},fe=Le.qualified??Le.slug,me=ye.coreRevision}else{const ye=se,Le=ye.capability;q={...Le.fields??{},body:Le.body??""},fe=Le.path,me=ye.coreRevision}Z({mode:"core",axis:o,ref:fe,revision:me,draft:q,original:structuredClone(q)}),_(!1),ie([]),pe(null)},vn=async(q,fe,me)=>{try{const ye=await fl(`/api/work/${qt(fe)}/${q}/${qt(me)}`);Z({mode:"resource",axis:"work",kind:q,slug:fe,name:me,revision:ye.revision,mediaType:ye.mediaType??(q==="artifacts"?"text/markdown":"application/yaml"),draft:ye.content,original:ye.content}),_(!1),ie([]),pe(null)}catch(ye){if(q==="plan-stages"){Z({mode:"resource",axis:"work",kind:q,slug:fe,name:me,revision:"",mediaType:"text/markdown",draft:`## Objective
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<script src="/theme-init.js"></script>
|
|
7
7
|
<title>TCW</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CNxlcI8A.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-CXNiOIeg.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|