comate-cli 0.7.0a2__tar.gz → 0.7.0a4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/PKG-INFO +1 -1
  2. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/mention_completer.py +65 -9
  3. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui.py +43 -0
  4. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/pyproject.toml +1 -1
  5. comate_cli-0.7.0a4/tests/test_completion_context_activation.py +119 -0
  6. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_mention_completer.py +94 -0
  7. comate_cli-0.7.0a2/tests/test_completion_context_activation.py +0 -56
  8. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/.gitignore +0 -0
  9. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/CHANGELOG.md +0 -0
  10. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/README.md +0 -0
  11. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/bash-exit-code-green-dot-bug.md +0 -0
  12. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/__init__.py +0 -0
  13. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/__main__.py +0 -0
  14. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/main.py +0 -0
  15. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/mcp_cli.py +0 -0
  16. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/__init__.py +0 -0
  17. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/animations.py +0 -0
  18. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/app.py +0 -0
  19. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/assistant_render.py +0 -0
  20. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/codenames.py +0 -0
  21. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  22. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/env_utils.py +0 -0
  23. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/error_display.py +0 -0
  24. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/event_renderer.py +0 -0
  25. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/figures.py +0 -0
  26. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  27. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/history_printer.py +0 -0
  28. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/input_geometry.py +0 -0
  29. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  30. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  31. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/logo.py +0 -0
  32. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/markdown_render.py +0 -0
  33. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/message_style.py +0 -0
  34. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/models.py +0 -0
  35. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  36. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  37. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  38. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  39. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  40. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  41. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  42. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
  43. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
  44. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  45. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
  46. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  47. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
  48. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
  49. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/preflight.py +0 -0
  50. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/question_view.py +0 -0
  51. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/resume_picker.py +0 -0
  52. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/resume_preview.py +0 -0
  53. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/resume_selector.py +0 -0
  54. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  55. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  56. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/selection_menu.py +0 -0
  57. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/slash_commands.py +0 -0
  58. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/startup.py +0 -0
  59. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/status_bar.py +0 -0
  60. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/text_effects.py +0 -0
  61. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tips.py +0 -0
  62. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
  63. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  64. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  65. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tool_view.py +0 -0
  66. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
  67. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  68. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  69. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
  70. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
  71. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  72. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
  73. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  74. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
  75. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  76. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  77. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/docs/hooks.md +0 -0
  78. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/docs/superpowers/plans/2026-04-03-phrase-shuffle.md +0 -0
  79. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -0
  80. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md +0 -0
  81. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/conftest.py +0 -0
  82. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_animator_shuffle.py +0 -0
  83. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_app_mcp_preload.py +0 -0
  84. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_app_preflight_gate.py +0 -0
  85. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_app_print_mode.py +0 -0
  86. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_app_shutdown.py +0 -0
  87. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_app_usage_line.py +0 -0
  88. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_btw_slash_command.py +0 -0
  89. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_cli_project_root.py +0 -0
  90. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_compact_command_semantics.py +0 -0
  91. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_completion_status_panel.py +0 -0
  92. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_context_command.py +0 -0
  93. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_custom_slash_commands.py +0 -0
  94. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_discover_tab.py +0 -0
  95. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_errors_tab.py +0 -0
  96. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_event_renderer.py +0 -0
  97. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_event_renderer_boundary.py +0 -0
  98. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_event_renderer_e2e.py +0 -0
  99. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_event_renderer_log_boundary.py +0 -0
  100. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_event_renderer_log_queue.py +0 -0
  101. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_event_renderer_streaming.py +0 -0
  102. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_format_error.py +0 -0
  103. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_handle_error.py +0 -0
  104. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_history_printer.py +0 -0
  105. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_history_printer_log.py +0 -0
  106. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_history_sync.py +0 -0
  107. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_input_behavior.py +0 -0
  108. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_input_history.py +0 -0
  109. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_installed_tab.py +0 -0
  110. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_interrupt_exit_semantics.py +0 -0
  111. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_layout_coordinator.py +0 -0
  112. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_logging_adapter.py +0 -0
  113. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_logo.py +0 -0
  114. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_main_args.py +0 -0
  115. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_markdown_render.py +0 -0
  116. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_marketplaces_tab.py +0 -0
  117. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_mcp_cli.py +0 -0
  118. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_mcp_slash_command.py +0 -0
  119. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_path_context_hint.py +0 -0
  120. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_plugin_slash_commands.py +0 -0
  121. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_plugin_tui_components.py +0 -0
  122. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_preflight.py +0 -0
  123. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_preflight_copilot.py +0 -0
  124. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_question_key_bindings.py +0 -0
  125. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_question_view.py +0 -0
  126. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_resume_picker.py +0 -0
  127. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_resume_preview.py +0 -0
  128. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_resume_selector.py +0 -0
  129. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_rewind_command_semantics.py +0 -0
  130. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_rpc_protocol.py +0 -0
  131. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_rpc_stdio_bridge.py +0 -0
  132. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_selection_menu.py +0 -0
  133. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_session_query_token_summary.py +0 -0
  134. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_skills_slash_command.py +0 -0
  135. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_slash_argument_hint.py +0 -0
  136. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_slash_completer.py +0 -0
  137. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_slash_registry.py +0 -0
  138. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_status_bar.py +0 -0
  139. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_status_bar_transient.py +0 -0
  140. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_task_panel_format.py +0 -0
  141. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_task_panel_key_bindings.py +0 -0
  142. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_task_panel_rendering.py +0 -0
  143. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_task_poll.py +0 -0
  144. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tool_result_formatters.py +0 -0
  145. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tool_result_store.py +0 -0
  146. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tool_result_viewer.py +0 -0
  147. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  148. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tool_view.py +0 -0
  149. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_transcript_viewer.py +0 -0
  150. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tui_elapsed_status.py +0 -0
  151. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tui_esc_queue.py +0 -0
  152. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tui_mcp_init_gate.py +0 -0
  153. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tui_paste_placeholder.py +0 -0
  154. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tui_queue_preview.py +0 -0
  155. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tui_queue_sdk_source.py +0 -0
  156. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tui_split_invariance.py +0 -0
  157. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tui_team_messages.py +0 -0
  158. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
  159. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/tests/test_update_check.py +0 -0
  160. {comate_cli-0.7.0a2 → comate_cli-0.7.0a4}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comate-cli
3
- Version: 0.7.0a2
3
+ Version: 0.7.0a4
4
4
  Summary: Comate terminal CLI built on comate-agent-sdk
5
5
  Project-URL: Homepage, https://github.com/AndyLee1024/agent-sdk
6
6
  Project-URL: Repository, https://github.com/AndyLee1024/agent-sdk
@@ -3,8 +3,9 @@ from __future__ import annotations
3
3
  import os
4
4
  import re
5
5
  import subprocess
6
+ import threading
6
7
  import time
7
- from collections.abc import Iterable
8
+ from collections.abc import Callable, Iterable
8
9
  from dataclasses import dataclass
9
10
  from pathlib import Path, PurePosixPath
10
11
 
@@ -112,6 +113,9 @@ class LocalFileMentionCompleter(Completer):
112
113
 
113
114
  self._deep_cache_time: float = 0.0
114
115
  self._deep_cached_paths: list[str] = []
116
+ self._deep_cache_lock = threading.RLock()
117
+ self._deep_warmup_lock = threading.Lock()
118
+ self._deep_warmup_thread: threading.Thread | None = None
115
119
  self._directory_cached_paths: dict[str, tuple[float, list[str]]] = {}
116
120
 
117
121
  @classmethod
@@ -300,16 +304,68 @@ class LocalFileMentionCompleter(Completer):
300
304
  return self._top_cached_paths
301
305
 
302
306
  def _get_deep_paths(self) -> list[str]:
303
- now = time.monotonic()
304
- if now - self._deep_cache_time <= self._refresh_interval:
305
- return self._deep_cached_paths
307
+ with self._deep_cache_lock:
308
+ now = time.monotonic()
309
+ if now - self._deep_cache_time <= self._refresh_interval:
310
+ return self._deep_cached_paths
306
311
 
307
- file_paths = self._load_indexed_file_paths()
308
- paths = self._build_deep_paths(file_paths)
312
+ file_paths = self._load_indexed_file_paths()
313
+ paths = self._build_deep_paths(file_paths)
309
314
 
310
- self._deep_cached_paths = paths
311
- self._deep_cache_time = now
312
- return self._deep_cached_paths
315
+ self._deep_cached_paths = paths
316
+ self._deep_cache_time = time.monotonic()
317
+ return self._deep_cached_paths
318
+
319
+ def start_deep_cache_warmup(
320
+ self,
321
+ *,
322
+ on_complete: Callable[[], None] | None = None,
323
+ ) -> bool:
324
+ """Build the deep path cache in a daemon thread if it is stale."""
325
+ with self._deep_warmup_lock:
326
+ if (
327
+ self._deep_warmup_thread is not None
328
+ and self._deep_warmup_thread.is_alive()
329
+ ):
330
+ return False
331
+
332
+ with self._deep_cache_lock:
333
+ now = time.monotonic()
334
+ if now - self._deep_cache_time <= self._refresh_interval:
335
+ return False
336
+
337
+ def _warm() -> None:
338
+ try:
339
+ self._get_deep_paths()
340
+ except Exception:
341
+ logger.debug("deep mention cache warmup failed", exc_info=True)
342
+ finally:
343
+ with self._deep_warmup_lock:
344
+ if self._deep_warmup_thread is threading.current_thread():
345
+ self._deep_warmup_thread = None
346
+ if on_complete is not None:
347
+ try:
348
+ on_complete()
349
+ except Exception:
350
+ logger.debug(
351
+ "deep mention cache warmup callback failed",
352
+ exc_info=True,
353
+ )
354
+
355
+ thread = threading.Thread(
356
+ target=_warm,
357
+ name="comate-mention-index-warmup",
358
+ daemon=True,
359
+ )
360
+ with self._deep_warmup_lock:
361
+ if (
362
+ self._deep_warmup_thread is not None
363
+ and self._deep_warmup_thread.is_alive()
364
+ ):
365
+ return False
366
+ self._deep_warmup_thread = thread
367
+ thread.start()
368
+ return True
313
369
 
314
370
  def _load_indexed_file_paths(self) -> list[str]:
315
371
  git_paths = self._get_paths_from_git()
@@ -329,6 +329,11 @@ class TerminalAgentTUI(
329
329
  if self._busy:
330
330
  return
331
331
  doc = self._input_area.buffer.document
332
+ mention_context = self._mention_completer.extract_context(
333
+ doc.text_before_cursor
334
+ )
335
+ if mention_context is not None:
336
+ self._start_mention_cache_warmup()
332
337
  if self._completion_context_active(
333
338
  doc.text_before_cursor,
334
339
  doc.text_after_cursor,
@@ -1325,6 +1330,43 @@ class TerminalAgentTUI(
1325
1330
 
1326
1331
  task.add_done_callback(_done)
1327
1332
 
1333
+ def _start_mention_cache_warmup(self) -> None:
1334
+ mention_completer = getattr(self, "_mention_completer", None)
1335
+ start_warmup = getattr(mention_completer, "start_deep_cache_warmup", None)
1336
+ if not callable(start_warmup):
1337
+ return
1338
+ start_warmup(on_complete=self._on_mention_cache_warmed)
1339
+
1340
+ def _on_mention_cache_warmed(self) -> None:
1341
+ app = self._app
1342
+ if app is None or self._closing:
1343
+ return
1344
+ loop = getattr(app, "loop", None)
1345
+ if loop is None or loop.is_closed():
1346
+ return
1347
+ loop.call_soon_threadsafe(self._restart_active_mention_completion)
1348
+ app.invalidate()
1349
+
1350
+ def _restart_active_mention_completion(self) -> None:
1351
+ if self._closing or self._busy or self._ui_mode != UIMode.NORMAL:
1352
+ return
1353
+ input_area = getattr(self, "_input_area", None)
1354
+ if input_area is None:
1355
+ return
1356
+ buffer = input_area.buffer
1357
+ doc = buffer.document
1358
+ if not self._completion_context_active(
1359
+ doc.text_before_cursor,
1360
+ doc.text_after_cursor,
1361
+ ):
1362
+ return
1363
+ complete_state = buffer.complete_state
1364
+ if complete_state is not None:
1365
+ self._invalidate()
1366
+ return
1367
+ buffer.start_completion(select_first=False)
1368
+ self._invalidate()
1369
+
1328
1370
  def _refresh_layers(self) -> None:
1329
1371
  self._sync_focus_for_mode()
1330
1372
  self._render_dirty = True
@@ -1546,6 +1588,7 @@ class TerminalAgentTUI(
1546
1588
  if self._app is None:
1547
1589
  return
1548
1590
 
1591
+ self._start_mention_cache_warmup()
1549
1592
  self._refresh_layers()
1550
1593
 
1551
1594
  try:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "comate-cli"
7
- version = "0.7.0a2"
7
+ version = "0.7.0a4"
8
8
  description = "Comate terminal CLI built on comate-agent-sdk"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import tempfile
4
+ import unittest
5
+ from pathlib import Path
6
+ from types import SimpleNamespace
7
+
8
+ from prompt_toolkit.document import Document
9
+
10
+ from comate_cli.terminal_agent.mention_completer import LocalFileMentionCompleter
11
+ from comate_cli.terminal_agent.tui import TerminalAgentTUI
12
+ from comate_cli.terminal_agent.tui_parts import UIMode
13
+
14
+
15
+ class TestCompletionContextActivation(unittest.TestCase):
16
+ def setUp(self) -> None:
17
+ self._tmpdir = tempfile.TemporaryDirectory()
18
+ self.tui = TerminalAgentTUI.__new__(TerminalAgentTUI)
19
+ self.tui._mention_completer = LocalFileMentionCompleter(
20
+ Path(self._tmpdir.name),
21
+ refresh_interval=0.0,
22
+ limit=200,
23
+ )
24
+
25
+ def tearDown(self) -> None:
26
+ self._tmpdir.cleanup()
27
+
28
+ def test_slash_completion_requires_cursor_at_end(self) -> None:
29
+ self.assertTrue(self.tui._completion_context_active("/model", ""))
30
+ self.assertFalse(self.tui._completion_context_active("/model", " trailing"))
31
+
32
+ def test_mention_completion_triggers_at_line_end(self) -> None:
33
+ self.assertTrue(self.tui._completion_context_active("message @file", ""))
34
+
35
+ def test_mention_completion_triggers_at_line_start(self) -> None:
36
+ self.assertTrue(self.tui._completion_context_active("@file", " message"))
37
+
38
+ def test_mention_completion_triggers_in_middle_with_space_boundaries(self) -> None:
39
+ self.assertTrue(
40
+ self.tui._completion_context_active("message @file", " message")
41
+ )
42
+
43
+ def test_mention_completion_rejects_middle_without_right_space(self) -> None:
44
+ self.assertFalse(
45
+ self.tui._completion_context_active("message @file", "message")
46
+ )
47
+
48
+ def test_mention_completion_rejects_middle_without_left_space(self) -> None:
49
+ self.assertFalse(
50
+ self.tui._completion_context_active("message@file", " message")
51
+ )
52
+
53
+ def test_mention_completion_rejects_inside_token_editing(self) -> None:
54
+ self.assertFalse(
55
+ self.tui._completion_context_active("message @fi", "le message")
56
+ )
57
+
58
+ def test_mention_cache_warmup_registers_completion_callback(self) -> None:
59
+ callbacks = []
60
+
61
+ class _FakeMentionCompleter:
62
+ def start_deep_cache_warmup(self, *, on_complete):
63
+ callbacks.append(on_complete)
64
+ return True
65
+
66
+ self.tui._mention_completer = _FakeMentionCompleter()
67
+
68
+ self.tui._start_mention_cache_warmup()
69
+
70
+ self.assertEqual(len(callbacks), 1)
71
+ self.assertTrue(callable(callbacks[0]))
72
+
73
+ def test_warmup_callback_does_not_cancel_in_flight_completion(self) -> None:
74
+ class _FakeBuffer:
75
+ def __init__(self) -> None:
76
+ self.document = Document("@测", cursor_position=2)
77
+ self.complete_state = SimpleNamespace(completions=[])
78
+ self.start_calls = 0
79
+
80
+ def start_completion(self, *, select_first: bool) -> None:
81
+ del select_first
82
+ self.start_calls += 1
83
+
84
+ buffer = _FakeBuffer()
85
+ self.tui._closing = False
86
+ self.tui._busy = False
87
+ self.tui._ui_mode = UIMode.NORMAL
88
+ self.tui._app = SimpleNamespace(invalidate=lambda: None)
89
+ self.tui._input_area = SimpleNamespace(buffer=buffer)
90
+
91
+ self.tui._restart_active_mention_completion()
92
+
93
+ self.assertEqual(buffer.start_calls, 0)
94
+
95
+ def test_warmup_callback_restarts_when_no_completion_is_active(self) -> None:
96
+ class _FakeBuffer:
97
+ def __init__(self) -> None:
98
+ self.document = Document("@测", cursor_position=2)
99
+ self.complete_state = None
100
+ self.start_calls = 0
101
+
102
+ def start_completion(self, *, select_first: bool) -> None:
103
+ del select_first
104
+ self.start_calls += 1
105
+
106
+ buffer = _FakeBuffer()
107
+ self.tui._closing = False
108
+ self.tui._busy = False
109
+ self.tui._ui_mode = UIMode.NORMAL
110
+ self.tui._app = SimpleNamespace(invalidate=lambda: None)
111
+ self.tui._input_area = SimpleNamespace(buffer=buffer)
112
+
113
+ self.tui._restart_active_mention_completion()
114
+
115
+ self.assertEqual(buffer.start_calls, 1)
116
+
117
+
118
+ if __name__ == "__main__":
119
+ unittest.main(verbosity=2)
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import shutil
4
4
  import subprocess
5
5
  import tempfile
6
+ import threading
6
7
  import unittest
7
8
  from pathlib import Path
8
9
  from unittest.mock import patch
@@ -430,6 +431,77 @@ class TestLocalFileMentionCompleter(unittest.TestCase):
430
431
  self.assertEqual(first, second)
431
432
  mock_run.assert_called_once()
432
433
 
434
+ def test_deep_cache_warmup_populates_chinese_path_cache(self) -> None:
435
+ completer = LocalFileMentionCompleter(
436
+ self.root,
437
+ refresh_interval=60.0,
438
+ limit=200,
439
+ )
440
+ completed = threading.Event()
441
+ with patch(
442
+ "comate_cli.terminal_agent.mention_completer.subprocess.run",
443
+ return_value=subprocess.CompletedProcess(
444
+ args=["git"],
445
+ returncode=0,
446
+ stdout="Wukong/测试用户.pdf\n",
447
+ stderr="",
448
+ ),
449
+ ) as mock_run:
450
+ started = completer.start_deep_cache_warmup(
451
+ on_complete=completed.set
452
+ )
453
+ self.assertTrue(started)
454
+ self.assertTrue(completed.wait(timeout=2.0))
455
+
456
+ result = completer.suggest("@测", max_items=16)
457
+
458
+ self.assertIsNotNone(result)
459
+ assert result is not None
460
+ _, candidates = result
461
+ self.assertIn("Wukong/测试用户.pdf", candidates)
462
+ mock_run.assert_called_once()
463
+
464
+ def test_deep_cache_warmup_coalesces_concurrent_requests(self) -> None:
465
+ completer = LocalFileMentionCompleter(
466
+ self.root,
467
+ refresh_interval=60.0,
468
+ limit=200,
469
+ )
470
+ entered = threading.Event()
471
+ release = threading.Event()
472
+ completed = threading.Event()
473
+
474
+ def _run_index_command(
475
+ *args: object,
476
+ **kwargs: object,
477
+ ) -> subprocess.CompletedProcess[str]:
478
+ del args, kwargs
479
+ entered.set()
480
+ self.assertTrue(release.wait(timeout=2.0))
481
+ return subprocess.CompletedProcess(
482
+ args=["git"],
483
+ returncode=0,
484
+ stdout="Wukong/测试用户.pdf\n",
485
+ stderr="",
486
+ )
487
+
488
+ with patch(
489
+ "comate_cli.terminal_agent.mention_completer.subprocess.run",
490
+ side_effect=_run_index_command,
491
+ ) as mock_run:
492
+ first_started = completer.start_deep_cache_warmup(
493
+ on_complete=completed.set
494
+ )
495
+ self.assertTrue(first_started)
496
+ self.assertTrue(entered.wait(timeout=2.0))
497
+
498
+ second_started = completer.start_deep_cache_warmup()
499
+ release.set()
500
+ self.assertTrue(completed.wait(timeout=2.0))
501
+
502
+ self.assertFalse(second_started)
503
+ mock_run.assert_called_once()
504
+
433
505
  def test_context_query_prefers_matching_directory_over_deeper_files(self) -> None:
434
506
  (self.root / "comate_agent_sdk" / "context").mkdir(parents=True, exist_ok=True)
435
507
  (self.root / "comate_agent_sdk" / "context" / "ir.py").write_text("x = 1\n", encoding="utf-8")
@@ -534,6 +606,28 @@ class TestLocalFileMentionCompleter(unittest.TestCase):
534
606
  _, candidates = result
535
607
  self.assertIn("docs/中文目录/技术文档.md", candidates)
536
608
 
609
+ def test_cjk_person_name_prefixes_find_deep_file_from_first_character(self) -> None:
610
+ """Claude Code 对齐:中文人名/文件名从第一个字符开始就应该进深度 fuzzy。
611
+
612
+ 固定三字符闸门会让 `@测`、`@测试` 只查顶层,从而漏掉
613
+ `Wukong/测试用户.pdf`。这个测试锁住真实用户输入路径。
614
+ """
615
+ with patch(
616
+ "comate_cli.terminal_agent.mention_completer.subprocess.run",
617
+ return_value=subprocess.CompletedProcess(
618
+ args=["git"],
619
+ returncode=0,
620
+ stdout="Wukong/测试用户.pdf\n",
621
+ stderr="",
622
+ ),
623
+ ):
624
+ for query in ("@测", "@测试", "@测试用户"):
625
+ result = self.completer.suggest(query, max_items=16)
626
+ self.assertIsNotNone(result, f"suggest({query!r}) returned None")
627
+ assert result is not None
628
+ _, candidates = result
629
+ self.assertIn("Wukong/测试用户.pdf", candidates)
630
+
537
631
  def test_short_ascii_query_still_restricted_to_top_level(self) -> None:
538
632
  """Bug #2 反向回归:纯 ASCII 短查询仍然走顶层,避免大仓库性能退化。
539
633
  即便深度索引里有 src/io_helper.py,2 字符 ASCII 输入也不应进深度。"""
@@ -1,56 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import tempfile
4
- import unittest
5
- from pathlib import Path
6
-
7
- from comate_cli.terminal_agent.mention_completer import LocalFileMentionCompleter
8
- from comate_cli.terminal_agent.tui import TerminalAgentTUI
9
-
10
-
11
- class TestCompletionContextActivation(unittest.TestCase):
12
- def setUp(self) -> None:
13
- self._tmpdir = tempfile.TemporaryDirectory()
14
- self.tui = TerminalAgentTUI.__new__(TerminalAgentTUI)
15
- self.tui._mention_completer = LocalFileMentionCompleter(
16
- Path(self._tmpdir.name),
17
- refresh_interval=0.0,
18
- limit=200,
19
- )
20
-
21
- def tearDown(self) -> None:
22
- self._tmpdir.cleanup()
23
-
24
- def test_slash_completion_requires_cursor_at_end(self) -> None:
25
- self.assertTrue(self.tui._completion_context_active("/model", ""))
26
- self.assertFalse(self.tui._completion_context_active("/model", " trailing"))
27
-
28
- def test_mention_completion_triggers_at_line_end(self) -> None:
29
- self.assertTrue(self.tui._completion_context_active("message @file", ""))
30
-
31
- def test_mention_completion_triggers_at_line_start(self) -> None:
32
- self.assertTrue(self.tui._completion_context_active("@file", " message"))
33
-
34
- def test_mention_completion_triggers_in_middle_with_space_boundaries(self) -> None:
35
- self.assertTrue(
36
- self.tui._completion_context_active("message @file", " message")
37
- )
38
-
39
- def test_mention_completion_rejects_middle_without_right_space(self) -> None:
40
- self.assertFalse(
41
- self.tui._completion_context_active("message @file", "message")
42
- )
43
-
44
- def test_mention_completion_rejects_middle_without_left_space(self) -> None:
45
- self.assertFalse(
46
- self.tui._completion_context_active("message@file", " message")
47
- )
48
-
49
- def test_mention_completion_rejects_inside_token_editing(self) -> None:
50
- self.assertFalse(
51
- self.tui._completion_context_active("message @fi", "le message")
52
- )
53
-
54
-
55
- if __name__ == "__main__":
56
- unittest.main(verbosity=2)
File without changes
File without changes
File without changes
File without changes
File without changes