Make dotall optional in affected tools

This commit is contained in:
Michael Panchenko authored and Dominik Jain committed 2026-05-14 22:43:00 +02:00
1 parent bc9d4e050c
commit be5b60bddb
5 files changed
+60 -57

No files matched your search

+8
View File
@@ -10,6 +10,14 @@ Status of the `main` branch. Changes prior to the next official version change w
- Modal markup cleanup: extract shared CSS classes (`.modal-info`, `.modal-hint`, `.modal-prompt`, `.modal-field`, `.modal-input`, `.modal-select`, `.modal-textarea`, `.modal-actions`, `.btn-secondary`) and remove duplicated inline styles from all seven modals. Inputs and textareas get an accent-colored focus ring; the modal backdrop has a subtle blur.
- Add Language: replace the native `<select>` with a filterable combobox — type to filter, keyboard navigation (Up/Down/Enter/Esc), substring highlight, click-outside to close. The typed value is validated against the available languages list before submission.
* Tools:
- `edit_memory`: New `dotall` parameter (default `False`) controlling whether ``.`` matches
newlines in regex mode. **Behavior change**: previously DOTALL was forced on; the new default
restricts ``.`` to a single line, which is safer for typical line-bounded memory edits and
avoids accidentally consuming large spans of content. Pass `dotall=True` for multi-line edits.
- `search_for_pattern`: New `dotall` parameter (default `True`, preserving existing behavior)
exposing the DOTALL flag, and the docstring was tightened.
# v1.3.0 (2026-05-11)
* General:
+14 -2
View File
@@ -715,7 +715,14 @@ class MemoriesManager:
return f"Memory renamed from {old_name} to {new_name}."
def edit_memory(
self, name: str, needle: str, repl: str, mode: Literal["literal", "regex"], allow_multiple_occurrences: bool, is_tool_context: bool
self,
name: str,
needle: str,
repl: str,
mode: Literal["literal", "regex"],
allow_multiple_occurrences: bool,
is_tool_context: bool,
dotall: bool = True,
) -> str:
"""
Edit a memory by replacing content matching a pattern.
@@ -725,6 +732,8 @@ class MemoriesManager:
:param repl: the replacement string
:param mode: "literal" or "regex"
:param allow_multiple_occurrences:
:param is_tool_context: whether the call originates from a tool invocation (affects write-access checks)
:param dotall: whether to compile the regex with the DOTALL flag (``.`` matches newlines). Only relevant in regex mode.
"""
name = self._prepare_name(name)
self._check_not_ignored(name)
@@ -734,7 +743,7 @@ class MemoriesManager:
raise FileNotFoundError(f"Memory {name} not found.")
with open(memory_file_path, encoding=self._encoding) as f:
original_content = f.read()
replacer = ContentReplacer(mode=mode, allow_multiple_occurrences=allow_multiple_occurrences)
replacer = ContentReplacer(mode=mode, allow_multiple_occurrences=allow_multiple_occurrences, dotall=dotall)
updated_content = replacer.replace(original_content, needle, repl)
with open(memory_file_path, "w", encoding=self._encoding) as f:
f.write(updated_content)
@@ -1291,6 +1300,7 @@ class Project(ToStringMixin):
context_lines_after: int = 0,
paths_include_glob: str | None = None,
paths_exclude_glob: str | None = None,
dotall: bool = True,
) -> list[MatchedConsecutiveLines]:
"""
Search for a pattern across all (non-ignored) source files
@@ -1301,6 +1311,7 @@ class Project(ToStringMixin):
:param context_lines_after: Number of lines of context to include after each match
:param paths_include_glob: Glob pattern to filter which files to include in the search
:param paths_exclude_glob: Glob pattern to filter which files to exclude from the search. Takes precedence over paths_include_glob.
:param dotall: Whether to compile the regex with the DOTALL flag (``.`` matches newlines).
:return: List of matched consecutive lines with context
"""
relative_file_paths = self.gather_source_files(relative_path=relative_path)
@@ -1313,6 +1324,7 @@ class Project(ToStringMixin):
context_lines_after=context_lines_after,
paths_include_glob=paths_include_glob,
paths_exclude_glob=paths_exclude_glob,
dotall=dotall,
)
def retrieve_content_around_line(
+16 -49
View File
@@ -332,59 +332,24 @@ class SearchForPatternTool(Tool):
paths_exclude_glob: str = "",
relative_path: str = "",
restrict_search_to_code_files: bool = False,
dotall: bool = True,
max_answer_chars: int = -1,
) -> str:
"""
Offers a flexible search for arbitrary patterns in the codebase, including the
possibility to search in non-code files.
Generally, symbolic operations like find_symbol or find_referencing_symbols
should be preferred if you know which symbols you are looking for.
Searches for a regex pattern across project files, returning whole matched lines (plus optional context).
Prefer `find_symbol`/`find_referencing_symbols` when looking for known symbols.
Pattern Matching Logic:
For each match, the returned result will contain the full lines where the
substring pattern is found, as well as optionally some lines before and after it. The pattern will be compiled with
DOTALL, meaning that the dot will match all characters including newlines.
This also means that it never makes sense to have .* at the beginning or end of the pattern,
but it may make sense to have it in the middle for complex patterns.
If a pattern matches multiple lines, all those lines will be part of the match.
Be careful to not use greedy quantifiers unnecessarily, it is usually better to use non-greedy quantifiers like .*? to avoid
matching too much content.
File Selection Logic:
The files in which the search is performed can be restricted very flexibly.
Using `restrict_search_to_code_files` is useful if you are only interested in code symbols (i.e., those
symbols that can be manipulated with symbolic tools like find_symbol).
You can also restrict the search to a specific file or directory,
and provide glob patterns to include or exclude certain files on top of that.
The globs are matched against relative file paths from the project root (not to the `relative_path` parameter that
is used to further restrict the search).
Smartly combining the various restrictions allows you to perform very targeted searches.
:param substring_pattern: Regular expression for a substring pattern to search for
:param context_lines_before: Number of lines of context to include before each match
:param context_lines_after: Number of lines of context to include after each match
:param paths_include_glob: optional glob pattern specifying files to include in the search.
Matches against relative file paths from the project root (e.g., "*.py", "src/**/*.ts").
Supports standard glob patterns (*, ?, [seq], **, etc.) and brace expansion {a,b,c}.
Only matches files, not directories. If left empty, all non-ignored files will be included.
:param paths_exclude_glob: optional glob pattern specifying files to exclude from the search.
Matches against relative file paths from the project root (e.g., "*test*", "**/*_generated.py").
Supports standard glob patterns (*, ?, [seq], **, etc.) and brace expansion {a,b,c}.
Takes precedence over paths_include_glob. Only matches files, not directories. If left empty, no files are excluded.
:param relative_path: only subpaths of this path (relative to the repo root) will be analyzed. If a path to a single
file is passed, only that will be searched. The path must exist, otherwise a `FileNotFoundError` is raised.
:param max_answer_chars: if the output is longer than this number of characters,
no content will be returned.
-1 means the default value from the config will be used.
Don't adjust unless there is really no other way to get the content
required for the task. Instead, if the output is too long, you should
make a stricter query.
:param restrict_search_to_code_files: whether to restrict the search to only those files where
analyzed code symbols can be found. Otherwise, will search all non-ignored files.
Set this to True if your search is only meant to discover code that can be manipulated with symbolic tools.
For example, for finding classes or methods from a name pattern.
Setting to False is a better choice if you also want to search in non-code files, like in html or yaml files,
which is why it is the default.
:param substring_pattern: regular expression to search for.
:param context_lines_before: number of context lines to include before each match.
:param context_lines_after: number of context lines to include after each match.
:param paths_include_glob: optional glob (relative to project root, e.g. ``"src/**/*.ts"``) restricting which files are searched.
:param paths_exclude_glob: optional glob to exclude files; takes precedence over `paths_include_glob`.
:param relative_path: restricts the search to this file or subdirectory of the project root. Must exist.
:param restrict_search_to_code_files: whether to search only files containing analyzable code symbols
(useful when looking for class/method definitions); otherwise also search non-code files.
:param dotall: whether to compile the regex with the DOTALL flag (``.`` matches newlines, enabling multi-line matches).
:param max_answer_chars: if the output exceeds this many characters, a progressively shortened summary is returned instead.
``-1`` uses the configured default.
:return: A mapping from file paths to matched consecutive lines (0-based line numbers).
"""
relative_path = relative_path.strip()
@@ -403,6 +368,7 @@ class SearchForPatternTool(Tool):
context_lines_after=context_lines_after,
paths_include_glob=paths_include_glob.strip(),
paths_exclude_glob=paths_exclude_glob.strip(),
dotall=dotall,
)
else:
if os.path.isfile(abs_path):
@@ -426,6 +392,7 @@ class SearchForPatternTool(Tool):
root_path=self.get_project_root(),
paths_include_glob=paths_include_glob,
paths_exclude_glob=paths_exclude_glob,
dotall=dotall,
)
# group matches by file
+9 -2
View File
@@ -104,6 +104,7 @@ class EditMemoryTool(Tool, ToolMarkerCanEdit):
repl: str,
mode: Literal["literal", "regex"],
allow_multiple_occurrences: bool = False,
dotall: bool = False,
) -> str:
r"""
Replace content matching a regular expression in a memory.
@@ -112,10 +113,16 @@ class EditMemoryTool(Tool, ToolMarkerCanEdit):
:param needle: the string or regex pattern to search for. In regex mode, be careful to not replace too much!
If `mode` is "literal", this string will be matched exactly.
If `mode` is "regex", this string will be treated as a regular expression (syntax of Python's `re` module,
with flags DOTALL and MULTILINE enabled).
with the MULTILINE flag enabled and DOTALL controlled by the `dotall` parameter).
:param repl: the replacement string (verbatim).
:param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
:param allow_multiple_occurrences: whether to allow matching and replacing multiple occurrences.
If false and multiple occurrences are found, an error will be returned.
:param dotall: whether to compile the regex with the DOTALL flag, so that ``.`` matches newlines.
Only relevant in regex mode. Defaults to False, restricting ``.`` to a single line, which is the safer
choice for typical line-bounded memory edits and avoids accidentally consuming entire blocks of content.
Set to True deliberately when you intend a multi-line replacement (alternatively, use ``[\s\S]*?``).
"""
return self.memories_manager.edit_memory(memory_name, needle, repl, mode, allow_multiple_occurrences, is_tool_context=True)
return self.memories_manager.edit_memory(
memory_name, needle, repl, mode, allow_multiple_occurrences, is_tool_context=True, dotall=dotall
)
+13 -4
View File
@@ -145,6 +145,7 @@ def search_text(
context_lines_before: int = 0,
context_lines_after: int = 0,
is_glob: bool = False,
dotall: bool = True,
) -> list[MatchedConsecutiveLines]:
"""
Search for a pattern in text content. Supports both regex and glob-like patterns.
@@ -159,6 +160,8 @@ def search_text(
:param context_lines_after: Number of context lines to include after matches
:param is_glob: If True, pattern is treated as a glob-like pattern (e.g., "*.py", "test_??.py")
and will be converted to regex internally
:param dotall: Whether to compile the regex with the DOTALL flag (``.`` matches newlines).
Only takes effect when `allow_multiline_match` is True (the line-by-line branch never spans newlines).
:return: List of `TextSearchMatch` objects
@@ -180,8 +183,8 @@ def search_text(
if is_glob:
pattern = glob_to_regex(pattern)
if allow_multiline_match:
# For multiline matches, we need to use the DOTALL flag to make '.' match newlines
compiled_pattern = re.compile(pattern, re.DOTALL)
# For multiline matches, optionally use DOTALL so '.' matches newlines
compiled_pattern = re.compile(pattern, re.DOTALL if dotall else 0)
# Search across the entire content as a single string
for match in compiled_pattern.finditer(content):
start_pos = match.start()
@@ -327,6 +330,7 @@ def search_files(
context_lines_after: int = 0,
paths_include_glob: str | None = None,
paths_exclude_glob: str | None = None,
dotall: bool = True,
) -> list[MatchedConsecutiveLines]:
"""
Search for a pattern in a list of files.
@@ -340,6 +344,7 @@ def search_files(
:param context_lines_after: Number of context lines to include after matches
:param paths_include_glob: Optional glob pattern to include files from the list
:param paths_exclude_glob: Optional glob pattern to exclude files from the list
:param dotall: Whether to compile the regex with the DOTALL flag (``.`` matches newlines).
:return: List of MatchedConsecutiveLines objects
"""
# Pre-filter paths (done sequentially to avoid overhead)
@@ -375,6 +380,7 @@ def search_files(
allow_multiline_match=True,
context_lines_before=context_lines_before,
context_lines_after=context_lines_after,
dotall=dotall,
)
if len(search_results) > 0:
log.debug(f"Found {len(search_results)} matches in {path}")
@@ -430,16 +436,19 @@ class ContentReplacer:
provides dual modes for maximum flexibility.
"""
def __init__(self, mode: Literal["literal", "regex"], allow_multiple_occurrences: bool):
def __init__(self, mode: Literal["literal", "regex"], allow_multiple_occurrences: bool, dotall: bool = True):
"""
:param mode: the mode indicating whether to the needle in replacements corresponds to a regular expression
(mode "regex") or to a literal string (mode "literal")
:param allow_multiple_occurrences: whether it is allowed that the search expression matches multiple occurrences.
If False, an error will be raised if more than one match is found.
:param dotall: whether to compile the regex with the DOTALL flag, which makes ``.`` match newlines.
Only relevant in regex mode. The MULTILINE flag is always enabled (so ``^`` and ``$`` match line boundaries).
"""
self.mode = mode
self.allow_multiple_occurrences = allow_multiple_occurrences
self.dotall = dotall
@staticmethod
def _create_replacement_function(regex_pattern: str, repl_template: str, regex_flags: int) -> Callable[[re.Match], str]:
@@ -505,7 +514,7 @@ class ContentReplacer:
else:
raise ValueError(f"Invalid mode: '{self.mode}', expected 'literal' or 'regex'.")
regex_flags = re.DOTALL | re.MULTILINE
regex_flags = re.MULTILINE | (re.DOTALL if self.dotall else 0)
# create replacement function with validation and backreference handling
repl_fn = self._create_replacement_function(regex, repl, regex_flags=regex_flags)