comate-cli 0.4.0__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {comate_cli-0.4.0 → comate_cli-0.4.1}/PKG-INFO +1 -1
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/mention_completer.py +69 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/pyproject.toml +1 -1
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_mention_completer.py +34 -0
- comate_cli-0.4.1/uv.lock +2231 -0
- comate_cli-0.4.0/uv.lock +0 -2215
- {comate_cli-0.4.0 → comate_cli-0.4.1}/.gitignore +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/README.md +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/__init__.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/__main__.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/main.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/app.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/event_renderer.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/rewind_store.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/session_view.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tui.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/conftest.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_context_command.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_format_error.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_handle_error.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_history_printer.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_history_sync.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_input_history.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_logo.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_main_args.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_preflight.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_question_view.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_rewind_store.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_status_bar.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_task_poll.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_tool_view.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.4.0 → comate_cli-0.4.1}/tests/test_update_check.py +0 -0
|
@@ -113,6 +113,7 @@ class LocalFileMentionCompleter(Completer):
|
|
|
113
113
|
|
|
114
114
|
self._deep_cache_time: float = 0.0
|
|
115
115
|
self._deep_cached_paths: list[str] = []
|
|
116
|
+
self._directory_cached_paths: dict[str, tuple[float, list[str]]] = {}
|
|
116
117
|
|
|
117
118
|
@classmethod
|
|
118
119
|
def _is_ignored(cls, name: str) -> bool:
|
|
@@ -146,6 +147,18 @@ class LocalFileMentionCompleter(Completer):
|
|
|
146
147
|
return None
|
|
147
148
|
|
|
148
149
|
fragment_lower = context.fragment.lower()
|
|
150
|
+
directory_source = self._get_directory_candidates(context.fragment)
|
|
151
|
+
if directory_source is not None:
|
|
152
|
+
matched = [
|
|
153
|
+
path
|
|
154
|
+
for path in directory_source
|
|
155
|
+
if not fragment_lower or path.lower().startswith(fragment_lower)
|
|
156
|
+
]
|
|
157
|
+
if not matched:
|
|
158
|
+
return None
|
|
159
|
+
matched.sort(key=str.lower)
|
|
160
|
+
return context, matched[: max(max_items, 1)]
|
|
161
|
+
|
|
149
162
|
source = self._candidate_paths(context.fragment)
|
|
150
163
|
if not source:
|
|
151
164
|
return None
|
|
@@ -203,6 +216,62 @@ class LocalFileMentionCompleter(Completer):
|
|
|
203
216
|
return self._get_top_level_paths()
|
|
204
217
|
return self._get_deep_paths()
|
|
205
218
|
|
|
219
|
+
def _get_directory_candidates(self, fragment: str) -> list[str] | None:
|
|
220
|
+
directory_fragment = self._directory_fragment(fragment)
|
|
221
|
+
if directory_fragment is None:
|
|
222
|
+
return None
|
|
223
|
+
return self._get_directory_paths(directory_fragment)
|
|
224
|
+
|
|
225
|
+
def _directory_fragment(self, fragment: str) -> str | None:
|
|
226
|
+
if "/" not in fragment:
|
|
227
|
+
return None
|
|
228
|
+
if fragment.startswith(("/", "~")):
|
|
229
|
+
return None
|
|
230
|
+
directory_fragment = fragment if fragment.endswith("/") else fragment.rpartition("/")[0]
|
|
231
|
+
if directory_fragment in ("", "."):
|
|
232
|
+
return ""
|
|
233
|
+
return directory_fragment
|
|
234
|
+
|
|
235
|
+
def _get_directory_paths(self, directory_fragment: str) -> list[str]:
|
|
236
|
+
cache_key = directory_fragment or "."
|
|
237
|
+
now = time.monotonic()
|
|
238
|
+
cached = self._directory_cached_paths.get(cache_key)
|
|
239
|
+
if cached is not None:
|
|
240
|
+
cached_at, cached_paths = cached
|
|
241
|
+
if now - cached_at <= self._refresh_interval:
|
|
242
|
+
return cached_paths
|
|
243
|
+
|
|
244
|
+
directory = self._resolve_directory(directory_fragment)
|
|
245
|
+
if directory is None:
|
|
246
|
+
return []
|
|
247
|
+
|
|
248
|
+
prefix = f"{directory_fragment.rstrip('/')}/" if directory_fragment else ""
|
|
249
|
+
entries: list[str] = []
|
|
250
|
+
try:
|
|
251
|
+
for entry in sorted(directory.iterdir(), key=lambda path: path.name):
|
|
252
|
+
name = entry.name
|
|
253
|
+
if self._is_ignored(name):
|
|
254
|
+
continue
|
|
255
|
+
entries.append(f"{prefix}{name}/" if entry.is_dir() else f"{prefix}{name}")
|
|
256
|
+
if len(entries) >= self._limit:
|
|
257
|
+
break
|
|
258
|
+
except OSError as exc:
|
|
259
|
+
logger.debug("directory mention scan failed for %s: %s", directory_fragment, exc)
|
|
260
|
+
return cached[1] if cached is not None else []
|
|
261
|
+
|
|
262
|
+
self._directory_cached_paths[cache_key] = (now, entries)
|
|
263
|
+
return entries
|
|
264
|
+
|
|
265
|
+
def _resolve_directory(self, directory_fragment: str) -> Path | None:
|
|
266
|
+
try:
|
|
267
|
+
candidate = (self._root / directory_fragment).resolve()
|
|
268
|
+
candidate.relative_to(self._root.resolve())
|
|
269
|
+
except (OSError, ValueError):
|
|
270
|
+
return None
|
|
271
|
+
if not candidate.is_dir():
|
|
272
|
+
return None
|
|
273
|
+
return candidate
|
|
274
|
+
|
|
206
275
|
def _get_top_level_paths(self) -> list[str]:
|
|
207
276
|
now = time.monotonic()
|
|
208
277
|
if now - self._top_cache_time <= self._refresh_interval:
|
|
@@ -64,6 +64,40 @@ class TestLocalFileMentionCompleter(unittest.TestCase):
|
|
|
64
64
|
texts = [completion.text for completion in completions]
|
|
65
65
|
self.assertIn("main.py ", texts)
|
|
66
66
|
|
|
67
|
+
def test_directory_prefix_completion_is_not_lost_after_large_tree_scan(self) -> None:
|
|
68
|
+
for index in range(1200):
|
|
69
|
+
nested = self.root / "bulk" / f"dir{index}"
|
|
70
|
+
nested.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
(nested / f"file{index}.txt").write_text("x", encoding="utf-8")
|
|
72
|
+
|
|
73
|
+
target_dir = self.root / "target" / "sub"
|
|
74
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
(target_dir / "wanted.py").write_text("pass", encoding="utf-8")
|
|
76
|
+
|
|
77
|
+
result = self.completer.suggest("@target/", max_items=8)
|
|
78
|
+
|
|
79
|
+
self.assertIsNotNone(result)
|
|
80
|
+
assert result is not None
|
|
81
|
+
_, candidates = result
|
|
82
|
+
self.assertIn("target/sub/", candidates)
|
|
83
|
+
|
|
84
|
+
def test_existing_directory_prefix_continues_to_suggest_children(self) -> None:
|
|
85
|
+
for index in range(1200):
|
|
86
|
+
nested = self.root / "bulk" / f"dir{index}"
|
|
87
|
+
nested.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
(nested / f"file{index}.txt").write_text("x", encoding="utf-8")
|
|
89
|
+
|
|
90
|
+
target_dir = self.root / "target" / "sub"
|
|
91
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
(target_dir / "wanted.py").write_text("pass", encoding="utf-8")
|
|
93
|
+
|
|
94
|
+
result = self.completer.suggest("@target/s", max_items=8)
|
|
95
|
+
|
|
96
|
+
self.assertIsNotNone(result)
|
|
97
|
+
assert result is not None
|
|
98
|
+
_, candidates = result
|
|
99
|
+
self.assertIn("target/sub/", candidates)
|
|
100
|
+
|
|
67
101
|
|
|
68
102
|
if __name__ == "__main__":
|
|
69
103
|
unittest.main(verbosity=2)
|