tunacode-cli 0.0.76.4__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.
- tunacode/cli/commands/registry.py +2 -7
- tunacode/cli/main.py +1 -1
- tunacode/constants.py +1 -1
- tunacode/ui/completers.py +172 -39
- tunacode/ui/path_heuristics.py +89 -0
- {tunacode_cli-0.0.76.4.dist-info → tunacode_cli-0.0.76.6.dist-info}/METADATA +1 -1
- {tunacode_cli-0.0.76.4.dist-info → tunacode_cli-0.0.76.6.dist-info}/RECORD +10 -10
- tunacode/utils/fuzzy_utils.py +0 -33
- {tunacode_cli-0.0.76.4.dist-info → tunacode_cli-0.0.76.6.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.76.4.dist-info → tunacode_cli-0.0.76.6.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.76.4.dist-info → tunacode_cli-0.0.76.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
-
#
|
|
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
|
-
|
|
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
|
|
22
|
+
app = typer.Typer(help="TunaCode - OS AI-powered development assistant")
|
|
23
23
|
state_manager = StateManager()
|
|
24
24
|
|
|
25
25
|
|
tunacode/constants.py
CHANGED
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
|
|
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
|
-
|
|
56
|
-
command_names = ["/help", "/clear", "/dump", "/yolo", "/branch", "/compact", "/model"]
|
|
69
|
+
command_names = list(self._DEFAULT_COMMANDS)
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
81
|
-
|
|
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
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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,16 +1,16 @@
|
|
|
1
1
|
tunacode/__init__.py,sha256=yUul8igNYMfUrHnYfioIGAqvrH8b5BKiO_pt1wVnmd0,119
|
|
2
|
-
tunacode/constants.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
163
|
-
tunacode_cli-0.0.76.
|
|
164
|
-
tunacode_cli-0.0.76.
|
|
165
|
-
tunacode_cli-0.0.76.
|
|
166
|
-
tunacode_cli-0.0.76.
|
|
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,,
|
tunacode/utils/fuzzy_utils.py
DELETED
|
@@ -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]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|