tunacode-cli 0.0.76.3__py3-none-any.whl → 0.0.76.6__py3-none-any.whl

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.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

@@ -5,7 +5,6 @@ CLAUDE_ANCHOR[command-registry]: Central command registration and execution
5
5
 
6
6
  import logging
7
7
  from dataclasses import dataclass
8
- from difflib import get_close_matches
9
8
  from pathlib import Path
10
9
  from typing import Any, Dict, List, Optional, Type
11
10
 
@@ -315,16 +314,12 @@ class CommandRegistry:
315
314
  self.discover_commands()
316
315
  partial = partial_command.lower()
317
316
 
318
- # 1) Prefer prefix matches (preserves current behavior)
317
+ # CLAUDE_ANCHOR[key=86cc1a41] Prefix-only command matching after removing fuzzy fallback
319
318
  prefix_matches = [cmd for cmd in self._commands.keys() if cmd.startswith(partial)]
320
319
  if prefix_matches:
321
320
  return prefix_matches
322
321
 
323
- # 2) Fuzzy fallback for typos and near-misses
324
- # CLAUDE_ANCHOR[fuzzy-command-matching]: Fuzzy fallback using difflib
325
- # Keep minimal change: reuse built-ins, avoid new deps.
326
- fuzzy = get_close_matches(partial, list(self._commands.keys()), n=3, cutoff=0.75)
327
- return fuzzy
322
+ return []
328
323
 
329
324
  def is_command(self, text: str) -> bool:
330
325
  """Check if text starts with a registered command (supports partial matching)."""
tunacode/cli/main.py CHANGED
@@ -19,7 +19,7 @@ from tunacode.ui import console as ui
19
19
  from tunacode.utils.system import check_for_updates
20
20
 
21
21
  app_settings = ApplicationSettings()
22
- app = typer.Typer(help="TunaCode - OS AI-powered CLI Coding Tool")
22
+ app = typer.Typer(help="TunaCode - OS AI-powered development assistant")
23
23
  state_manager = StateManager()
24
24
 
25
25
 
tunacode/constants.py CHANGED
@@ -9,7 +9,7 @@ from enum import Enum
9
9
 
10
10
  # Application info
11
11
  APP_NAME = "TunaCode"
12
- APP_VERSION = "0.0.76.3"
12
+ APP_VERSION = "0.0.76.6"
13
13
 
14
14
 
15
15
  # File patterns
tunacode/ui/completers.py CHANGED
@@ -1,17 +1,20 @@
1
1
  """Completers for file references and commands."""
2
2
 
3
3
  import os
4
- from typing import TYPE_CHECKING, Iterable, List, Optional
4
+ from typing import TYPE_CHECKING, Iterable, List, Optional, Sequence, Set, Tuple
5
5
 
6
6
  from prompt_toolkit.completion import (
7
7
  CompleteEvent,
8
8
  Completer,
9
9
  Completion,
10
+ FuzzyCompleter,
11
+ FuzzyWordCompleter,
12
+ PathCompleter,
10
13
  merge_completers,
11
14
  )
12
15
  from prompt_toolkit.document import Document
13
16
 
14
- from ..utils.fuzzy_utils import find_fuzzy_matches
17
+ from .path_heuristics import prioritize_roots, should_skip_directory
15
18
 
16
19
  if TYPE_CHECKING:
17
20
  from ..cli.commands import CommandRegistry
@@ -21,6 +24,17 @@ if TYPE_CHECKING:
21
24
  class CommandCompleter(Completer):
22
25
  """Completer for slash commands."""
23
26
 
27
+ _DEFAULT_COMMANDS: Sequence[str] = (
28
+ "/help",
29
+ "/clear",
30
+ "/dump",
31
+ "/yolo",
32
+ "/branch",
33
+ "/compact",
34
+ "/model",
35
+ )
36
+ _FUZZY_WORD_MODE = True
37
+
24
38
  def __init__(self, command_registry: Optional["CommandRegistry"] = None):
25
39
  self.command_registry = command_registry
26
40
 
@@ -52,33 +66,34 @@ class CommandCompleter(Completer):
52
66
  if self.command_registry:
53
67
  command_names = self.command_registry.get_command_names()
54
68
  else:
55
- # Fallback list of commands
56
- command_names = ["/help", "/clear", "/dump", "/yolo", "/branch", "/compact", "/model"]
69
+ command_names = list(self._DEFAULT_COMMANDS)
57
70
 
58
- # Get the partial command (without /)
59
- partial = word_before_cursor[1:].lower()
60
-
61
- # Yield completions for matching commands
62
- for cmd in command_names:
63
- if cmd.startswith("/") and cmd[1:].lower().startswith(partial):
64
- yield Completion(
65
- text=cmd,
66
- start_position=-len(word_before_cursor),
67
- display=cmd,
68
- display_meta="command",
69
- )
71
+ fuzzy_completer = FuzzyWordCompleter(command_names, WORD=self._FUZZY_WORD_MODE)
72
+ for completion in fuzzy_completer.get_completions(document, _complete_event):
73
+ yield Completion(
74
+ text=completion.text,
75
+ start_position=completion.start_position,
76
+ display=completion.display,
77
+ display_meta="command",
78
+ )
70
79
 
71
80
 
72
81
  class FileReferenceCompleter(Completer):
73
82
  """Completer for @file references that provides file path suggestions."""
74
83
 
84
+ _FUZZY_WORD_MODE = True
85
+ _FUZZY_RESULT_LIMIT = 10
86
+ _GLOBAL_ROOT_CACHE: Optional[List[str]] = None
87
+ _GLOBAL_ROOT_LIMIT = 128
88
+ _GLOBAL_MAX_DEPTH = 20
89
+
75
90
  def get_completions(
76
91
  self, document: Document, _complete_event: CompleteEvent
77
92
  ) -> Iterable[Completion]:
78
93
  """Get completions for @file references.
79
94
 
80
- Favors file matches before directory matches and supports fuzzy
81
- matching for near-miss filenames. Order:
95
+ Favors file matches before directory matches while allowing fuzzy
96
+ near-miss suggestions. Ordering:
82
97
  exact files > fuzzy files > exact dirs > fuzzy dirs
83
98
  """
84
99
  # Get the word before cursor
@@ -108,7 +123,7 @@ class FileReferenceCompleter(Completer):
108
123
  dir_path = candidate_dir
109
124
  prefix = ""
110
125
 
111
- # Get matching files with fuzzy support
126
+ # Get matching files using prefix matching
112
127
  try:
113
128
  if os.path.exists(dir_path) and os.path.isdir(dir_path):
114
129
  items = sorted(os.listdir(dir_path))
@@ -131,37 +146,44 @@ class FileReferenceCompleter(Completer):
131
146
  exact_files = [f for f in files if f.lower().startswith(prefix_lower)]
132
147
  exact_dirs = [d for d in dirs if d.lower().startswith(prefix_lower)]
133
148
 
134
- # Fuzzy matches (exclude items already matched exactly)
135
149
  fuzzy_file_candidates = [f for f in files if f not in exact_files]
136
150
  fuzzy_dir_candidates = [d for d in dirs if d not in exact_dirs]
137
151
 
138
- fuzzy_files = (
139
- find_fuzzy_matches(prefix, fuzzy_file_candidates, n=10, cutoff=0.75)
140
- if prefix
141
- else []
142
- )
143
- fuzzy_dirs = (
144
- find_fuzzy_matches(prefix, fuzzy_dir_candidates, n=10, cutoff=0.75)
145
- if prefix
146
- else []
152
+ fuzzy_files = self._collect_fuzzy_matches(prefix, fuzzy_file_candidates)
153
+ fuzzy_dirs = self._collect_fuzzy_matches(prefix, fuzzy_dir_candidates)
154
+
155
+ ordered: List[tuple[str, str, bool]] = (
156
+ [("file", name, False) for name in exact_files]
157
+ + [("file", name, False) for name in fuzzy_files]
158
+ + [("dir", name, False) for name in exact_dirs]
159
+ + [("dir", name, False) for name in fuzzy_dirs]
147
160
  )
148
161
 
149
- # Compose ordered results
150
- ordered: List[tuple[str, str]] = (
151
- [("file", name) for name in exact_files]
152
- + [("file", name) for name in fuzzy_files]
153
- + [("dir", name) for name in exact_dirs]
154
- + [("dir", name) for name in fuzzy_dirs]
162
+ local_seen: Set[str] = {
163
+ os.path.normpath(os.path.join(dir_path, name))
164
+ if dir_path != "."
165
+ else os.path.normpath(name)
166
+ for name in (*exact_files, *fuzzy_files, *exact_dirs, *fuzzy_dirs)
167
+ }
168
+
169
+ global_matches = self._collect_global_path_matches(
170
+ prefix,
171
+ dir_path,
172
+ local_seen,
155
173
  )
174
+ ordered += global_matches
156
175
 
157
176
  start_position = -len(path_part)
158
- for kind, name in ordered:
159
- full_path = os.path.join(dir_path, name) if dir_path != "." else name
177
+ for kind, name, is_global in ordered:
178
+ if is_global:
179
+ full_path = name
180
+ display = name + "/" if kind == "dir" else name
181
+ else:
182
+ full_path = os.path.join(dir_path, name) if dir_path != "." else name
183
+ display = name + "/" if kind == "dir" else name
160
184
  if kind == "dir":
161
- display = name + "/"
162
185
  completion_text = full_path + "/"
163
186
  else:
164
- display = name
165
187
  completion_text = full_path
166
188
 
167
189
  yield Completion(
@@ -174,6 +196,117 @@ class FileReferenceCompleter(Completer):
174
196
  # Silently ignore inaccessible directories
175
197
  pass
176
198
 
199
+ @classmethod
200
+ # CLAUDE_ANCHOR[key=1f0911c7] Prompt Toolkit fuzzy matching consolidates file and directory suggestions
201
+ def _collect_fuzzy_matches(cls, prefix: str, candidates: Sequence[str]) -> List[str]:
202
+ """Return fuzzy-ordered candidate names respecting configured limit."""
203
+
204
+ if not prefix or not candidates:
205
+ return []
206
+
207
+ fuzzy_completer = FuzzyWordCompleter(candidates, WORD=cls._FUZZY_WORD_MODE)
208
+ prefix_document = Document(text=prefix)
209
+ event = CompleteEvent(completion_requested=True)
210
+ matches: List[str] = []
211
+ for completion in fuzzy_completer.get_completions(prefix_document, event):
212
+ candidate = completion.text
213
+ if candidate in candidates and candidate not in matches:
214
+ matches.append(candidate)
215
+ if len(matches) >= cls._FUZZY_RESULT_LIMIT:
216
+ break
217
+ return matches
218
+
219
+ @classmethod
220
+ def _collect_global_path_matches(
221
+ cls,
222
+ prefix: str,
223
+ current_dir: str,
224
+ seen: Set[str],
225
+ ) -> List[Tuple[str, str, bool]]:
226
+ """Return global fuzzy matches outside the current directory."""
227
+
228
+ if not prefix:
229
+ return []
230
+
231
+ roots = cls._global_roots()
232
+ if not roots:
233
+ return []
234
+
235
+ event = CompleteEvent(completion_requested=True)
236
+ document = Document(text=prefix)
237
+ matches: List[Tuple[str, str, bool]] = []
238
+ normalized_current = os.path.normpath(current_dir or ".")
239
+
240
+ for root in roots:
241
+ normalized_root = os.path.normpath(root)
242
+ if normalized_root == normalized_current:
243
+ continue
244
+
245
+ completer = FuzzyCompleter(
246
+ PathCompleter(only_directories=False, get_paths=lambda root=normalized_root: [root])
247
+ )
248
+ for completion in completer.get_completions(document, event):
249
+ candidate_path = os.path.normpath(os.path.join(normalized_root, completion.text))
250
+ if candidate_path in seen:
251
+ continue
252
+
253
+ seen.add(candidate_path)
254
+ normalized_display = os.path.relpath(candidate_path, start=".").replace("\\", "/")
255
+ matches.append(
256
+ (
257
+ "dir" if os.path.isdir(candidate_path) else "file",
258
+ normalized_display,
259
+ True,
260
+ )
261
+ )
262
+ if len(matches) >= cls._FUZZY_RESULT_LIMIT:
263
+ return matches
264
+
265
+ return matches
266
+
267
+ @classmethod
268
+ def _global_roots(cls) -> List[str]:
269
+ """Compute cached directory list for global fuzzy lookups."""
270
+
271
+ if cls._GLOBAL_ROOT_CACHE is not None:
272
+ return cls._GLOBAL_ROOT_CACHE
273
+
274
+ roots: List[str] = []
275
+ limit = cls._GLOBAL_ROOT_LIMIT
276
+ max_depth = cls._GLOBAL_MAX_DEPTH
277
+
278
+ for root, dirs, _ in os.walk(".", topdown=True):
279
+ rel_root = os.path.relpath(root, ".")
280
+ normalized = "." if rel_root == "." else rel_root
281
+ depth = 0 if normalized == "." else normalized.count(os.sep) + 1
282
+ if depth > max_depth:
283
+ dirs[:] = []
284
+ continue
285
+
286
+ if should_skip_directory(normalized):
287
+ dirs[:] = []
288
+ continue
289
+
290
+ if dirs:
291
+ rel_dir = os.path.relpath(root, ".")
292
+ base = "." if rel_dir == "." else rel_dir
293
+ filtered_dirs = []
294
+ for directory in dirs:
295
+ candidate = directory if base == "." else f"{base}/{directory}"
296
+ if should_skip_directory(candidate):
297
+ continue
298
+ filtered_dirs.append(directory)
299
+ dirs[:] = filtered_dirs
300
+
301
+ if normalized not in roots:
302
+ roots.append(normalized)
303
+
304
+ if len(roots) >= limit:
305
+ break
306
+
307
+ cls._GLOBAL_ROOT_CACHE = prioritize_roots(roots)
308
+ return cls._GLOBAL_ROOT_CACHE
309
+
177
310
 
178
311
  class ModelCompleter(Completer):
179
312
  """Completer for model names in /model command."""
@@ -0,0 +1,89 @@
1
+ """Shared heuristics for prioritizing and skipping project paths."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterable, List, Sequence
6
+
7
+ # CLAUDE_ANCHOR[key=0b3f320e] Cross-ecosystem skip directory defaults for FileReferenceCompleter global roots
8
+ DEFAULT_SKIP_DIRECTORY_NAMES: Sequence[str] = (
9
+ ".git",
10
+ ".hg",
11
+ ".svn",
12
+ ".idea",
13
+ ".vscode",
14
+ ".venv",
15
+ "venv",
16
+ ".uv_cache",
17
+ "node_modules",
18
+ "dist",
19
+ "build",
20
+ "out",
21
+ "target",
22
+ # CLAUDE_ANCHOR[key=6fd59413] Skip list includes __pycache__ to avoid noisy Python build artifacts in suggestions
23
+ "vendor",
24
+ "__pycache__",
25
+ ".mypy_cache",
26
+ ".pytest_cache",
27
+ ".ruff_cache",
28
+ ".tox",
29
+ "coverage",
30
+ ".cache",
31
+ # CLAUDE_ANCHOR[key=2bcebd52] Skip heuristic checks every path component to prune nested junk directories such as __pycache__
32
+ )
33
+
34
+ DEFAULT_PRIORITY_PREFIXES: Sequence[str] = (
35
+ "src",
36
+ "app",
37
+ "lib",
38
+ "cmd",
39
+ "pkg",
40
+ "internal",
41
+ "include",
42
+ "components",
43
+ "tests",
44
+ "test",
45
+ "spec",
46
+ "examples",
47
+ "docs",
48
+ "documentation",
49
+ )
50
+
51
+
52
+ def prioritize_roots(roots: Iterable[str]) -> List[str]:
53
+ """Order project roots so high-signal directories surface first."""
54
+
55
+ ordered: List[str] = []
56
+ seen = set()
57
+
58
+ for root in roots:
59
+ if root == "." and root not in seen:
60
+ ordered.append(root)
61
+ seen.add(root)
62
+
63
+ for prefix in DEFAULT_PRIORITY_PREFIXES:
64
+ for root in roots:
65
+ if root in seen:
66
+ continue
67
+ normalized = root.replace("\\", "/")
68
+ if normalized == prefix or normalized.startswith(f"{prefix}/"):
69
+ ordered.append(root)
70
+ seen.add(root)
71
+
72
+ for root in roots:
73
+ if root in seen:
74
+ continue
75
+ ordered.append(root)
76
+ seen.add(root)
77
+
78
+ return ordered
79
+
80
+
81
+ def should_skip_directory(path: str) -> bool:
82
+ """Return True when any component of the path matches skip heuristics."""
83
+
84
+ if not path or path == ".":
85
+ return False
86
+
87
+ normalized = path.replace("\\", "/")
88
+ components = normalized.split("/")
89
+ return any(component in DEFAULT_SKIP_DIRECTORY_NAMES for component in components)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.76.3
3
+ Version: 0.0.76.6
4
4
  Summary: Your agentic CLI developer.
5
5
  Project-URL: Homepage, https://tunacode.xyz/
6
6
  Project-URL: Repository, https://github.com/alchemiststudiosDOTai/tunacode
@@ -1,16 +1,16 @@
1
1
  tunacode/__init__.py,sha256=yUul8igNYMfUrHnYfioIGAqvrH8b5BKiO_pt1wVnmd0,119
2
- tunacode/constants.py,sha256=bRNiKjbYi_2hRO7BlYIJ8z8jGPXe-ze4fSoKj5aomh4,6170
2
+ tunacode/constants.py,sha256=PN8RpED4DoJ5YbkRBo9payGmF448TLoROc9IFvc5HDA,6170
3
3
  tunacode/context.py,sha256=YtfRjUiqsSkk2k9Nn_pjb_m-AXyh6XcOBOJWtFI0wVw,2405
4
4
  tunacode/exceptions.py,sha256=m80njR-LqBXhFAEOPqCE7N2QPU4Fkjlf_f6CWKO0_Is,8479
5
5
  tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  tunacode/setup.py,sha256=F1E4zHVnbByu_Uo6AhCJ-W-lIGF_gV6kB84HLAGLmVY,2103
7
7
  tunacode/types.py,sha256=xNpDRjIRYg4qGNbl3EG8B13CWAWBoob9ekVm8_6dvnc,10496
8
8
  tunacode/cli/__init__.py,sha256=zgs0UbAck8hfvhYsWhWOfBe5oK09ug2De1r4RuQZREA,55
9
- tunacode/cli/main.py,sha256=ZzFjLnn6YTYRrcgt7DfyqrxIPM220kBF2xV9rwF1BmA,3439
9
+ tunacode/cli/main.py,sha256=MAKPFA4kGSFciqMdxnyp2r9XzVp8TfxvK6ztt7dvjwM,3445
10
10
  tunacode/cli/repl.py,sha256=QBw05NNypxuTYoV51FVoTfujJfiOTCIYzAr_1uoi0YA,23515
11
11
  tunacode/cli/commands/__init__.py,sha256=J7MZofTaSgspAKP64OavPukj4l53qvkv_-sCfYEUi10,1794
12
12
  tunacode/cli/commands/base.py,sha256=Ge_lNQA-GDfcb1Ap1oznCH3UrifBiHH3bA9DNL-tCDw,2519
13
- tunacode/cli/commands/registry.py,sha256=qey9iqLL-gJF2zWATtY-AjNgE9zfA7197MuJpHVf_C0,15433
13
+ tunacode/cli/commands/registry.py,sha256=J2zS2QZmVaUyOA6GEgYwKqh1bZ9QBbsuuxMYniF7YSA,15139
14
14
  tunacode/cli/commands/template_shortcut.py,sha256=ApYTPkDVBRaLxa7rWaPrsGcJdkR7eg09k18KyTjYg_E,3447
15
15
  tunacode/cli/commands/implementations/__init__.py,sha256=dFczjIqCJTPrsSycD6PZYnp5_cIEQEGgKr0Y14MRGjs,1088
16
16
  tunacode/cli/commands/implementations/command_reload.py,sha256=GyjeKvJbgE4VYkaasGajspdk9wffumZMNLzfCUeNazM,1555
@@ -124,7 +124,7 @@ tunacode/tutorial/content.py,sha256=qaQewFwXtKKEmzLH-4oMECGAa4Z4nd1qh2HfRWLpwyk,
124
124
  tunacode/tutorial/manager.py,sha256=ZgkzSC6ZtYSDq5Ce_TfYk9O9cvgFSL-pXrLZb7_HStM,6309
125
125
  tunacode/tutorial/steps.py,sha256=l2bbRVJuYlC186A-U1TIoMPBtLl4j053h4Wlzo1VO8c,4393
126
126
  tunacode/ui/__init__.py,sha256=aRNE2pS50nFAX6y--rSGMNYwhz905g14gRd6g4BolYU,13
127
- tunacode/ui/completers.py,sha256=O5wAlo9cufV2VWbFKMWzZeXRqptdS4CwK6wok1DdDak,14786
127
+ tunacode/ui/completers.py,sha256=2VLVu5Nsl9LT34KcZ6zsKiEfZkgzVNcbt4vx6jw8MpQ,19548
128
128
  tunacode/ui/config_dashboard.py,sha256=FhfWwEzPTNjvornTb0njxv_o2SavoEW4EXqyOCrbqk0,21657
129
129
  tunacode/ui/console.py,sha256=HfE30vUy8ebXCobP7psFNJc17-dvH6APChg2tbi7aTw,2632
130
130
  tunacode/ui/constants.py,sha256=A76B_KpM8jCuBYRg4cPmhi8_j6LLyWttO7_jjv47r3w,421
@@ -136,6 +136,7 @@ tunacode/ui/logging_compat.py,sha256=5v6lcjVaG1CxdY1Zm9FAGr9H7Sy-tP6ihGfhP-5YvAY
136
136
  tunacode/ui/model_selector.py,sha256=07kV9v0VWFgDapiXTz1_BjzHF1AliRq24I9lDq-hmHc,13426
137
137
  tunacode/ui/output.py,sha256=ybVVutiilOQcULtA1zjjs_tTu5okwxHFp2MHtNz3s2E,6767
138
138
  tunacode/ui/panels.py,sha256=6XGOeax4m-yQvwP1iML67GK10AKlDLD46dB522gcNPU,17236
139
+ tunacode/ui/path_heuristics.py,sha256=SkhGaM8WCRuK86vLwypbfhtI81PrXtOsWoz-P0CTsmQ,2221
139
140
  tunacode/ui/prompt_manager.py,sha256=HUL6443pFPb41uDAnAKD-sZsrWd_VhWYRGwvrFH_9SI,5618
140
141
  tunacode/ui/tool_descriptions.py,sha256=vk61JPIXy7gHNfJ--77maXgK6WwNwxqY47QYsw_a2uw,4126
141
142
  tunacode/ui/tool_ui.py,sha256=MVmBLXx6OTJVFLl58SpoW0KoStOrbAY9sc6XXMKgWtQ,7216
@@ -147,7 +148,6 @@ tunacode/utils/bm25.py,sha256=fd59YQXovC8rXwZrdoqIAfFrLn_WCVjzCh0pkU22APE,1966
147
148
  tunacode/utils/config_comparator.py,sha256=iMShhYCKlo0dXycbfpRu5rj3ckT460FoDvkbr5_-yTY,12879
148
149
  tunacode/utils/diff_utils.py,sha256=V9QqQ0q4MfabVTnWptF3IXDp3estnfOKcJtDe_Sj14I,2372
149
150
  tunacode/utils/file_utils.py,sha256=84g-MQRzmBI2aG_CuXsDl2OhvvWoSL7YdL5Kz_UKSwk,979
150
- tunacode/utils/fuzzy_utils.py,sha256=Dl2C4ksNVfqdhjn-bVur4_JFiPkQXYerCjR-EOmtmRI,1140
151
151
  tunacode/utils/import_cache.py,sha256=q_xjJbtju05YbFopLDSkIo1hOtCx3DOTl3GQE5FFDgs,295
152
152
  tunacode/utils/json_utils.py,sha256=cMVctSwwV9Z1c-rZdj6UuOlZwsUPSTF5oUruP6uPix0,6470
153
153
  tunacode/utils/message_utils.py,sha256=V4MrZZPmwO22_MVGupMqtE5ltQEBwaSIqGD5LEb_bLw,1050
@@ -159,8 +159,8 @@ tunacode/utils/system.py,sha256=J8KqJ4ZqQrNSnM5rrJxPeMk9z2xQQp6dWtI1SKBY1-0,1112
159
159
  tunacode/utils/text_utils.py,sha256=HAwlT4QMy41hr53cDbbNeNo05MI461TpI9b_xdIv8EY,7288
160
160
  tunacode/utils/token_counter.py,sha256=dmFuqVz4ywGFdLfAi5Mg9bAGf8v87Ek-mHU-R3fsYjI,2711
161
161
  tunacode/utils/user_configuration.py,sha256=OA-L0BgWNbf9sWpc8lyivgLscwJdpdI8TAYbe0wRs1s,4836
162
- tunacode_cli-0.0.76.3.dist-info/METADATA,sha256=jomzgPHW7B0qAiE2rjTVw4mZZmnOjerJ8QjgfQ0Bqxg,8913
163
- tunacode_cli-0.0.76.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
164
- tunacode_cli-0.0.76.3.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
165
- tunacode_cli-0.0.76.3.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
166
- tunacode_cli-0.0.76.3.dist-info/RECORD,,
162
+ tunacode_cli-0.0.76.6.dist-info/METADATA,sha256=qXHrlK7J8ftroVTJyUoF-6dRHc2YZ0LsdGPZ-Zm6qN0,8913
163
+ tunacode_cli-0.0.76.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
164
+ tunacode_cli-0.0.76.6.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
165
+ tunacode_cli-0.0.76.6.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
166
+ tunacode_cli-0.0.76.6.dist-info/RECORD,,
@@ -1,33 +0,0 @@
1
- """Shared fuzzy matching utilities.
2
-
3
- Minimal helper to provide consistent fuzzy matching behavior across
4
- components (commands, file completers, etc.) without introducing new
5
- dependencies. Reuses difflib.get_close_matches with a default cutoff of 0.75
6
- matching the command registry behavior.
7
- """
8
-
9
- from difflib import get_close_matches
10
- from typing import List, Sequence
11
-
12
-
13
- def find_fuzzy_matches(
14
- query: str,
15
- choices: Sequence[str],
16
- *,
17
- n: int = 5,
18
- cutoff: float = 0.75,
19
- ) -> List[str]:
20
- """Return up to ``n`` fuzzy matches from ``choices`` for ``query``.
21
-
22
- The return order is the order produced by ``difflib.get_close_matches``.
23
- Matching is case-insensitive; original casing is preserved in the result.
24
- """
25
- if not query or not choices:
26
- return []
27
-
28
- # Map lower-case to original to do case-insensitive matching while
29
- # returning original items.
30
- lower_to_original = {c.lower(): c for c in choices}
31
- candidates = list(lower_to_original.keys())
32
- matches_lower = get_close_matches(query.lower(), candidates, n=n, cutoff=cutoff)
33
- return [lower_to_original[m] for m in matches_lower]