aider-ce 0.88.20__py3-none-any.whl → 0.88.38__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.
Files changed (113) hide show
  1. aider/__init__.py +1 -1
  2. aider/_version.py +2 -2
  3. aider/args.py +63 -43
  4. aider/coders/agent_coder.py +331 -79
  5. aider/coders/agent_prompts.py +3 -15
  6. aider/coders/architect_coder.py +21 -5
  7. aider/coders/base_coder.py +661 -413
  8. aider/coders/base_prompts.py +6 -3
  9. aider/coders/chat_chunks.py +39 -17
  10. aider/commands.py +79 -15
  11. aider/diffs.py +10 -9
  12. aider/exceptions.py +1 -1
  13. aider/helpers/coroutines.py +8 -0
  14. aider/helpers/requests.py +45 -0
  15. aider/history.py +5 -0
  16. aider/io.py +179 -25
  17. aider/main.py +86 -35
  18. aider/models.py +16 -8
  19. aider/queries/tree-sitter-language-pack/c-tags.scm +3 -0
  20. aider/queries/tree-sitter-language-pack/clojure-tags.scm +5 -0
  21. aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +5 -0
  22. aider/queries/tree-sitter-language-pack/cpp-tags.scm +3 -0
  23. aider/queries/tree-sitter-language-pack/csharp-tags.scm +6 -0
  24. aider/queries/tree-sitter-language-pack/dart-tags.scm +5 -0
  25. aider/queries/tree-sitter-language-pack/elixir-tags.scm +5 -0
  26. aider/queries/tree-sitter-language-pack/elm-tags.scm +3 -0
  27. aider/queries/tree-sitter-language-pack/go-tags.scm +7 -0
  28. aider/queries/tree-sitter-language-pack/java-tags.scm +6 -0
  29. aider/queries/tree-sitter-language-pack/javascript-tags.scm +8 -0
  30. aider/queries/tree-sitter-language-pack/lua-tags.scm +5 -0
  31. aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +3 -0
  32. aider/queries/tree-sitter-language-pack/python-tags.scm +10 -0
  33. aider/queries/tree-sitter-language-pack/r-tags.scm +6 -0
  34. aider/queries/tree-sitter-language-pack/ruby-tags.scm +5 -0
  35. aider/queries/tree-sitter-language-pack/rust-tags.scm +3 -0
  36. aider/queries/tree-sitter-language-pack/solidity-tags.scm +1 -1
  37. aider/queries/tree-sitter-language-pack/swift-tags.scm +4 -1
  38. aider/queries/tree-sitter-languages/c-tags.scm +3 -0
  39. aider/queries/tree-sitter-languages/c_sharp-tags.scm +6 -0
  40. aider/queries/tree-sitter-languages/cpp-tags.scm +3 -0
  41. aider/queries/tree-sitter-languages/dart-tags.scm +2 -1
  42. aider/queries/tree-sitter-languages/elixir-tags.scm +5 -0
  43. aider/queries/tree-sitter-languages/elm-tags.scm +3 -0
  44. aider/queries/tree-sitter-languages/fortran-tags.scm +3 -0
  45. aider/queries/tree-sitter-languages/go-tags.scm +6 -0
  46. aider/queries/tree-sitter-languages/haskell-tags.scm +2 -0
  47. aider/queries/tree-sitter-languages/java-tags.scm +6 -0
  48. aider/queries/tree-sitter-languages/javascript-tags.scm +8 -0
  49. aider/queries/tree-sitter-languages/julia-tags.scm +2 -2
  50. aider/queries/tree-sitter-languages/kotlin-tags.scm +3 -0
  51. aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +6 -0
  52. aider/queries/tree-sitter-languages/php-tags.scm +6 -0
  53. aider/queries/tree-sitter-languages/python-tags.scm +10 -0
  54. aider/queries/tree-sitter-languages/ruby-tags.scm +5 -0
  55. aider/queries/tree-sitter-languages/rust-tags.scm +3 -0
  56. aider/queries/tree-sitter-languages/scala-tags.scm +2 -3
  57. aider/queries/tree-sitter-languages/typescript-tags.scm +3 -0
  58. aider/queries/tree-sitter-languages/zig-tags.scm +20 -3
  59. aider/repomap.py +71 -11
  60. aider/resources/model-metadata.json +27335 -635
  61. aider/resources/model-settings.yml +190 -0
  62. aider/scrape.py +2 -0
  63. aider/tools/__init__.py +2 -0
  64. aider/tools/command.py +84 -94
  65. aider/tools/command_interactive.py +95 -110
  66. aider/tools/delete_block.py +131 -159
  67. aider/tools/delete_line.py +97 -132
  68. aider/tools/delete_lines.py +120 -160
  69. aider/tools/extract_lines.py +288 -312
  70. aider/tools/finished.py +30 -43
  71. aider/tools/git_branch.py +107 -109
  72. aider/tools/git_diff.py +44 -56
  73. aider/tools/git_log.py +39 -53
  74. aider/tools/git_remote.py +37 -51
  75. aider/tools/git_show.py +33 -47
  76. aider/tools/git_status.py +30 -44
  77. aider/tools/grep.py +214 -242
  78. aider/tools/indent_lines.py +175 -201
  79. aider/tools/insert_block.py +220 -253
  80. aider/tools/list_changes.py +65 -80
  81. aider/tools/ls.py +64 -80
  82. aider/tools/make_editable.py +57 -73
  83. aider/tools/make_readonly.py +50 -66
  84. aider/tools/remove.py +64 -80
  85. aider/tools/replace_all.py +96 -109
  86. aider/tools/replace_line.py +118 -156
  87. aider/tools/replace_lines.py +160 -197
  88. aider/tools/replace_text.py +159 -160
  89. aider/tools/show_numbered_context.py +115 -141
  90. aider/tools/thinking.py +52 -0
  91. aider/tools/undo_change.py +78 -91
  92. aider/tools/update_todo_list.py +130 -138
  93. aider/tools/utils/base_tool.py +64 -0
  94. aider/tools/utils/output.py +118 -0
  95. aider/tools/view.py +38 -54
  96. aider/tools/view_files_matching.py +131 -134
  97. aider/tools/view_files_with_symbol.py +108 -120
  98. aider/urls.py +1 -1
  99. aider/versioncheck.py +4 -3
  100. aider/website/docs/config/adv-model-settings.md +237 -0
  101. aider/website/docs/config/agent-mode.md +36 -3
  102. aider/website/docs/config/model-aliases.md +2 -1
  103. aider/website/docs/faq.md +6 -11
  104. aider/website/docs/languages.md +2 -2
  105. aider/website/docs/more/infinite-output.md +27 -0
  106. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/METADATA +112 -70
  107. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/RECORD +112 -107
  108. aider_ce-0.88.38.dist-info/entry_points.txt +6 -0
  109. aider_ce-0.88.20.dist-info/entry_points.txt +0 -2
  110. /aider/tools/{tool_utils.py → utils/helpers.py} +0 -0
  111. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/WHEEL +0 -0
  112. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/licenses/LICENSE.txt +0 -0
  113. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/top_level.txt +0 -0
aider/io.py CHANGED
@@ -2,6 +2,7 @@ import asyncio
2
2
  import base64
3
3
  import functools
4
4
  import os
5
+ import re
5
6
  import shutil
6
7
  import signal
7
8
  import subprocess
@@ -32,10 +33,13 @@ from rich.color import ColorParseError
32
33
  from rich.columns import Columns
33
34
  from rich.console import Console
34
35
  from rich.markdown import Markdown
36
+ from rich.markup import escape
35
37
  from rich.spinner import SPINNERS
36
38
  from rich.style import Style as RichStyle
37
39
  from rich.text import Text
38
40
 
41
+ from aider.helpers import coroutines
42
+
39
43
  from .dump import dump # noqa: F401
40
44
  from .editor import pipe_editor
41
45
  from .utils import is_image_file, run_fzf
@@ -292,6 +296,13 @@ class InputOutput:
292
296
  bell_on_next_input = False
293
297
  notifications_command = None
294
298
  encoding = "utf-8"
299
+ VALID_STYLES = {"bold", "red", "green", "blue", "orange"}
300
+ VALID_OPEN_TAG_PATTERN = re.compile(
301
+ r"\\\[(" + "|".join(re.escape(s) for s in VALID_STYLES) + r")\]"
302
+ )
303
+ VALID_CLOSE_TAG_PATTERN = re.compile(
304
+ r"\\\[/(?:" + "|".join(re.escape(s) for s in VALID_STYLES) + r"|)]"
305
+ )
295
306
 
296
307
  def __init__(
297
308
  self,
@@ -341,11 +352,14 @@ class InputOutput:
341
352
  self.bell_on_next_input = False
342
353
  self.notifications = notifications
343
354
  self.verbose = verbose
355
+ self.profile_start_time = None
356
+ self.profile_last_time = None
344
357
 
345
358
  # Variables used to interface with base_coder
346
359
  self.coder = None
347
360
  self.input_task = None
348
361
  self.output_task = None
362
+ self.linear = False
349
363
 
350
364
  # State tracking for confirmation input
351
365
  self.confirmation_in_progress = False
@@ -414,14 +428,17 @@ class InputOutput:
414
428
  self.chat_history_file = None
415
429
 
416
430
  self.encoding = encoding
417
- valid_line_endings = {"platform", "lf", "crlf"}
431
+ valid_line_endings = {"platform", "lf", "crlf", "preserve"}
418
432
  if line_endings not in valid_line_endings:
419
433
  raise ValueError(
420
434
  f"Invalid line_endings value: {line_endings}. "
421
435
  f"Must be one of: {', '.join(valid_line_endings)}"
422
436
  )
437
+ self.line_endings = line_endings
423
438
  self.newline = (
424
- None if line_endings == "platform" else "\n" if line_endings == "lf" else "\r\n"
439
+ None
440
+ if line_endings in ("platform", "preserve")
441
+ else "\n" if line_endings == "lf" else "\r\n"
425
442
  )
426
443
  self.dry_run = dry_run
427
444
 
@@ -438,6 +455,7 @@ class InputOutput:
438
455
  self.spinner_running = False
439
456
  self.spinner_text = ""
440
457
  self.last_spinner_text = ""
458
+ self.spinner_suffix = ""
441
459
  self.spinner_frame_index = 0
442
460
  self.spinner_last_frame_index = 0
443
461
  self.unicode_palette = "░█"
@@ -505,6 +523,7 @@ class InputOutput:
505
523
  self.spinner_running = True
506
524
  self.spinner_text = text
507
525
  self.spinner_frame_index = self.spinner_last_frame_index
526
+ self.spinner_suffix = ""
508
527
 
509
528
  if update_last_text:
510
529
  self.last_spinner_text = text
@@ -515,10 +534,18 @@ class InputOutput:
515
534
  def update_spinner(self, text):
516
535
  self.spinner_text = text
517
536
 
537
+ def update_spinner_suffix(self, text=None):
538
+ if text:
539
+ self.spinner_suffix = f" • {text[:16].strip()}"
540
+ else:
541
+ self.spinner_suffix = ""
542
+
518
543
  def stop_spinner(self):
519
544
  """Stop the spinner."""
520
545
  self.spinner_running = False
521
546
  self.spinner_text = ""
547
+ self.spinner_suffix = ""
548
+
522
549
  # Keep last frame index to avoid spinner "jumping" on restart
523
550
  self.spinner_last_frame_index = self.spinner_frame_index
524
551
  if self.fallback_spinner:
@@ -527,13 +554,13 @@ class InputOutput:
527
554
 
528
555
  def get_bottom_toolbar(self):
529
556
  """Get the current spinner frame and text for the bottom toolbar."""
530
- if not self.spinner_running or not self.spinner_frames:
557
+ if not self.spinner_running or not self.spinner_frames or self.linear:
531
558
  return None
532
559
 
533
560
  frame = self.spinner_frames[self.spinner_frame_index]
534
561
  self.spinner_frame_index = (self.spinner_frame_index + 1) % len(self.spinner_frames)
535
562
 
536
- return f"{frame} {self.spinner_text}"
563
+ return f"{frame} {self.spinner_text}{self.spinner_suffix}"
537
564
 
538
565
  def _validate_color_settings(self):
539
566
  """Validate configured color strings and reset invalid ones."""
@@ -640,6 +667,18 @@ class InputOutput:
640
667
  self.tool_error("Use --encoding to set the unicode encoding.")
641
668
  return
642
669
 
670
+ def _detect_newline(self, filename):
671
+ try:
672
+ with open(filename, "rb") as f:
673
+ chunk = f.read(1024)
674
+ if b"\r\n" in chunk:
675
+ return "\r\n"
676
+ elif b"\n" in chunk:
677
+ return "\n"
678
+ except (FileNotFoundError, IsADirectoryError):
679
+ pass # File doesn't exist or is a directory, will use default
680
+ return None
681
+
643
682
  def write_text(self, filename, content, max_retries=5, initial_delay=0.1):
644
683
  """
645
684
  Writes content to a file, retrying with progressive backoff if the file is locked.
@@ -652,10 +691,14 @@ class InputOutput:
652
691
  if self.dry_run:
653
692
  return
654
693
 
694
+ newline = self.newline
695
+ if self.line_endings == "preserve":
696
+ newline = self._detect_newline(filename) or self.newline
697
+
655
698
  delay = initial_delay
656
699
  for attempt in range(max_retries):
657
700
  try:
658
- with open(str(filename), "w", encoding=self.encoding, newline=self.newline) as f:
701
+ with open(str(filename), "w", encoding=self.encoding, newline=newline) as f:
659
702
  f.write(content)
660
703
  return # Successfully wrote the file
661
704
  except PermissionError as err:
@@ -694,9 +737,13 @@ class InputOutput:
694
737
  # This method is now a no-op since we removed the confirmation_future logic
695
738
  pass
696
739
 
740
+ def get_coder(self):
741
+ coder = self.coder() if self.coder else None
742
+ return coder
743
+
697
744
  async def recreate_input(self, future=None):
698
- if not self.input_task or self.input_task.done() or self.input_task.cancelled():
699
- coder = self.coder() if self.coder else None
745
+ if not coroutines.is_active(self.input_task):
746
+ coder = self.get_coder()
700
747
 
701
748
  if coder:
702
749
  self.input_task = asyncio.create_task(coder.get_input())
@@ -720,10 +767,10 @@ class InputOutput:
720
767
  show = ""
721
768
  if rel_fnames:
722
769
  rel_read_only_fnames = [
723
- get_rel_fname(fname, root) for fname in (abs_read_only_fnames or [])
770
+ get_rel_fname(fname, root) for fname in abs_read_only_fnames or []
724
771
  ]
725
772
  rel_read_only_stubs_fnames = [
726
- get_rel_fname(fname, root) for fname in (abs_read_only_stubs_fnames or [])
773
+ get_rel_fname(fname, root) for fname in abs_read_only_stubs_fnames or []
727
774
  ]
728
775
  show = self.format_files_for_input(
729
776
  rel_fnames, rel_read_only_fnames, rel_read_only_stubs_fnames
@@ -886,9 +933,14 @@ class InputOutput:
886
933
  return cmd
887
934
 
888
935
  except EOFError:
889
- raise
936
+ coder = self.get_coder()
937
+
938
+ if coder:
939
+ await coder.commands.cmd_exit(None)
940
+ else:
941
+ raise SystemExit
942
+
890
943
  except KeyboardInterrupt:
891
- await self.cancel_output_task()
892
944
  self.console.print()
893
945
  return ""
894
946
  except UnicodeEncodeError as err:
@@ -954,26 +1006,46 @@ class InputOutput:
954
1006
  self.user_input(inp)
955
1007
  return inp
956
1008
 
957
- async def cancel_input_task(self):
1009
+ async def stop_input_task(self):
958
1010
  if self.input_task:
959
1011
  input_task = self.input_task
960
1012
  self.input_task = None
961
1013
  try:
962
1014
  input_task.cancel()
963
1015
  await input_task
964
- except (asyncio.CancelledError, EOFError, IndexError):
1016
+ except (
1017
+ asyncio.CancelledError,
1018
+ Exception,
1019
+ EOFError,
1020
+ IndexError,
1021
+ RuntimeError,
1022
+ SystemExit,
1023
+ ):
965
1024
  pass
966
1025
 
967
- async def cancel_output_task(self):
1026
+ async def stop_output_task(self):
968
1027
  if self.output_task:
969
1028
  output_task = self.output_task
970
1029
  self.output_task = None
971
1030
  try:
972
1031
  output_task.cancel()
973
1032
  await output_task
974
- except (asyncio.CancelledError, EOFError, IndexError):
1033
+ except (
1034
+ asyncio.CancelledError,
1035
+ Exception,
1036
+ EOFError,
1037
+ IndexError,
1038
+ RuntimeError,
1039
+ SystemExit,
1040
+ ):
975
1041
  pass
976
1042
 
1043
+ async def stop_task_streams(self):
1044
+ input_task = asyncio.create_task(self.stop_input_task())
1045
+ output_task = asyncio.create_task(self.stop_output_task())
1046
+
1047
+ await asyncio.wait([input_task, output_task], return_when=asyncio.ALL_COMPLETED)
1048
+
977
1049
  def add_to_input_history(self, inp):
978
1050
  if not self.input_history_file:
979
1051
  return
@@ -1042,6 +1114,24 @@ class InputOutput:
1042
1114
  hist = "\n" + content.strip() + "\n\n"
1043
1115
  self.append_chat_history(hist)
1044
1116
 
1117
+ def edit_in_editor(self, content):
1118
+ import subprocess
1119
+ import tempfile
1120
+
1121
+ with tempfile.NamedTemporaryFile(
1122
+ suffix=".md", mode="w", delete=False, encoding=self.encoding
1123
+ ) as tmpfile:
1124
+ tmpfile.write(content)
1125
+ tmpfile.flush()
1126
+ editor = os.environ.get("EDITOR", "vi")
1127
+ subprocess.call([editor, tmpfile.name])
1128
+
1129
+ with open(tmpfile.name, "r", encoding=self.encoding) as f:
1130
+ edited = f.read()
1131
+
1132
+ os.unlink(tmpfile.name)
1133
+ return edited
1134
+
1045
1135
  async def offer_url(
1046
1136
  self, url, prompt="Open URL for more info?", allow_never=True, acknowledge=False
1047
1137
  ):
@@ -1092,6 +1182,7 @@ class InputOutput:
1092
1182
  group=None,
1093
1183
  group_response=None,
1094
1184
  allow_never=False,
1185
+ allow_tweak=False,
1095
1186
  acknowledge=False,
1096
1187
  ):
1097
1188
  self.num_user_asks += 1
@@ -1110,6 +1201,9 @@ class InputOutput:
1110
1201
  valid_responses = ["yes", "no", "skip", "all"]
1111
1202
  options = " (Y)es/(N)o"
1112
1203
 
1204
+ if allow_tweak:
1205
+ valid_responses.append("tweak")
1206
+ options += "/(T)weak"
1113
1207
  if group or group_response:
1114
1208
  if not explicit_yes_required or group_response:
1115
1209
  options += "/(A)ll"
@@ -1153,11 +1247,7 @@ class InputOutput:
1153
1247
  if self.prompt_session:
1154
1248
  await self.recreate_input()
1155
1249
 
1156
- if (
1157
- self.input_task
1158
- and not self.input_task.done()
1159
- and not self.input_task.cancelled()
1160
- ):
1250
+ if coroutines.is_active(self.input_task):
1161
1251
  self.prompt_session.message = question
1162
1252
  self.prompt_session.app.invalidate()
1163
1253
  else:
@@ -1200,6 +1290,9 @@ class InputOutput:
1200
1290
  self.append_chat_history(hist, linebreak=True, blockquote=True)
1201
1291
  return False
1202
1292
 
1293
+ if res == "t":
1294
+ return "tweak"
1295
+
1203
1296
  if explicit_yes_required and not group_response:
1204
1297
  is_yes = res == "y"
1205
1298
  else:
@@ -1314,7 +1407,7 @@ class InputOutput:
1314
1407
  if log_only:
1315
1408
  return
1316
1409
 
1317
- messages = list(map(Text, messages))
1410
+ messages = list(map(lambda message: self.escape(message), messages))
1318
1411
  style = dict()
1319
1412
  if self.pretty:
1320
1413
  if self.tool_output_color:
@@ -1326,6 +1419,49 @@ class InputOutput:
1326
1419
 
1327
1420
  self.stream_print(*messages, style=style)
1328
1421
 
1422
+ def escape(self, text):
1423
+ """Formats valid Rich tags and prints invalid ones as literal text using a single regex pass."""
1424
+
1425
+ # 1. Escape everything initially using Rich's built-in function
1426
+ escaped_text = escape(text)
1427
+
1428
+ if text == escaped_text:
1429
+ return Text(text)
1430
+
1431
+ # 2. Un-escape ONLY the valid opening tags
1432
+ # Replaces '\[style]' with '[style]'
1433
+ unescaped_text = self.__class__.VALID_OPEN_TAG_PATTERN.sub(
1434
+ lambda m: f"[{m.group(1)}]", escaped_text
1435
+ )
1436
+
1437
+ # 3. Un-escape ONLY the valid closing tags (handles both [/style] and [/] formats)
1438
+ # Replaces '\[/style]' or '\[/]' with '[/]'
1439
+ final_text = self.__class__.VALID_CLOSE_TAG_PATTERN.sub(r"[/]", unescaped_text)
1440
+
1441
+ # 4. Print the result
1442
+ return Text(final_text)
1443
+
1444
+ def profile(self, *messages, start=False):
1445
+ if not self.verbose:
1446
+ return
1447
+
1448
+ now = time.time()
1449
+ message_str = " ".join(map(str, messages))
1450
+
1451
+ # Treat uninitialized as an implicit start.
1452
+ if start or self.profile_start_time is None:
1453
+ self.profile_start_time = now
1454
+ self.stream_print(f"PROFILE: {message_str}")
1455
+ else:
1456
+ total_elapsed = now - self.profile_start_time
1457
+ last_elapsed = now - self.profile_last_time
1458
+ output_message = (
1459
+ f"PROFILE: [+{last_elapsed:6.2f}s] {message_str} (total {total_elapsed:.2f}s)"
1460
+ )
1461
+ self.stream_print(output_message)
1462
+
1463
+ self.profile_last_time = now
1464
+
1329
1465
  def assistant_output(self, message, pretty=None):
1330
1466
  if not message:
1331
1467
  self.tool_warning("Empty response received from LLM. Check your provider account?")
@@ -1385,14 +1521,32 @@ class InputOutput:
1385
1521
 
1386
1522
  self._stream_buffer = incomplete_line
1387
1523
 
1524
+ should_print = False
1525
+ should_reset = False
1526
+
1388
1527
  if not final:
1389
1528
  if len(lines) > 1:
1529
+ should_print = True
1530
+ else:
1531
+ # Ensure any remaining buffered content is printed using the full response
1532
+ should_print = True
1533
+ should_reset = True
1534
+
1535
+ if should_print:
1536
+ try:
1390
1537
  self.console.print(
1391
1538
  Text.from_ansi(output) if self.has_ansi_codes(output) else output
1392
1539
  )
1393
- else:
1394
- # Ensure any remaining buffered content is printed using the full response
1395
- self.console.print(Text.from_ansi(output) if self.has_ansi_codes(output) else output)
1540
+ except Exception as e:
1541
+ if self.verbose:
1542
+ print(e)
1543
+
1544
+ self.console.print(
1545
+ (Text.from_ansi(output)) if self.has_ansi_codes(output) else output,
1546
+ markup=False,
1547
+ )
1548
+
1549
+ if should_reset:
1396
1550
  self.reset_streaming_response()
1397
1551
 
1398
1552
  def remove_consecutive_empty_strings(self, string_list):
@@ -1558,7 +1712,7 @@ class InputOutput:
1558
1712
  return "\n".join(lines) + "\n"
1559
1713
 
1560
1714
  output = StringIO()
1561
- console = Console(file=output, force_terminal=False)
1715
+ console = Console(file=output, force_terminal=False, markup=False)
1562
1716
 
1563
1717
  # Handle read-only files
1564
1718
  if rel_read_only_fnames or rel_read_only_stubs_fnames: