@@ -84,12 +84,9 @@ async def execute(cls, io, coder, args, **kwargs):
8484 for matched_file in sorted (all_matched_files ):
8585 abs_file_path = coder .abs_root_path (matched_file )
8686
87- if (
88- coder .repo
89- and coder .repo .git_ignored_file (matched_file )
90- and not coder .add_gitignore_files
91- ):
92- io .tool_error (f"Can't add { matched_file } which is in gitignore" )
87+ blocked = cls ._add_blocked_message (coder , matched_file )
88+ if blocked :
89+ io .tool_error (blocked )
9390 continue
9491
9592 if abs_file_path in coder .abs_fnames :
@@ -240,3 +237,79 @@ def get_help(cls) -> str:
240237 help_text += "Files can be moved from read-only to editable status.\n "
241238 help_text += "Image files can be added if the model supports vision.\n "
242239 return help_text
240+
241+ @staticmethod
242+ def _display_add_path (matched_file : str ) -> str :
243+ try :
244+ label = os .path .relpath (matched_file )
245+ except ValueError :
246+ label = matched_file
247+ if len (label ) > 64 :
248+ label = f".../{ os .path .basename (label )} "
249+ return label
250+
251+ @staticmethod
252+ def _git_repo_for_add_check (coder , matched_file : str ):
253+ """Resolve the GitRepo used for ignore checks (RepoSet-aware)."""
254+ repo = coder .repo
255+ if not repo :
256+ return None , matched_file
257+ if hasattr (repo , "repo_for_rel_path" ):
258+ git_repo = repo .repo_for_rel_path (matched_file )
259+ try :
260+ rel = repo .path_relative_to_repo (matched_file , git_repo )
261+ except (ValueError , OSError ):
262+ rel = matched_file
263+ return git_repo , rel
264+ return repo , matched_file
265+
266+ @classmethod
267+ def _add_blocked_message (cls , coder , matched_file : str ) -> str | None :
268+ """
269+ Explain why /add refused a path. Distinguishes .gitignore vs .cecli.ignore when possible.
270+ """
271+ if not coder .repo or coder .add_gitignore_files :
272+ return None
273+ if not coder .repo .git_ignored_file (matched_file ):
274+ return None
275+
276+ display = cls ._display_add_path (matched_file )
277+ git_repo , rel = cls ._git_repo_for_add_check (coder , matched_file )
278+ if not git_repo :
279+ return (
280+ f"Can't add { display } : excluded by ignore rules for this session. "
281+ "Confirm Settings \u2192 project folder is the git root for this file."
282+ )
283+
284+ gitignore_hit = False
285+ cecli_hit = False
286+ if hasattr (git_repo , "_is_gitignored_by_pathspec" ):
287+ try :
288+ gitignore_hit = bool (git_repo ._is_gitignored_by_pathspec (rel ))
289+ except (ValueError , OSError ):
290+ gitignore_hit = False
291+
292+ ignore_file = getattr (git_repo , "cecli_ignore_file" , None ) or getattr (
293+ git_repo , "aider_ignore_file" , None
294+ )
295+ if ignore_file and Path (ignore_file ).is_file () and hasattr (git_repo , "ignored_file_raw" ):
296+ try :
297+ cecli_hit = bool (git_repo .ignored_file_raw (rel ))
298+ except (ValueError , OSError ):
299+ cecli_hit = False
300+
301+ if cecli_hit and not gitignore_hit :
302+ return (
303+ f"Can't add { display } : blocked by { Path (ignore_file ).name } "
304+ "(cecli ignore rules), not .gitignore."
305+ )
306+ if gitignore_hit :
307+ return (
308+ f"Can't add { display } : matched .gitignore under the session workspace. "
309+ "If this is normal tracked source, check the project folder in Settings."
310+ )
311+ return (
312+ f"Can't add { display } : excluded by ignore rules for this session "
313+ "(.gitignore and/or cecli ignore). "
314+ "Confirm the project folder is the git root that contains this file."
315+ )
0 commit comments