@@ -225,19 +225,49 @@ def scan_content_for_secrets(content: str, filename: str = "") -> list[str]:
225225
226226
227227# ---------------------------------------------------------------------------
228- # 4. .gitignore coverage checker
228+ # 4. .gitignore coverage checker & generator
229229# ---------------------------------------------------------------------------
230230
231231
232+ def generate_smart_gitignore (project_path : str , include_env : bool = False ) -> str :
233+ """
234+ Generate a tailored .gitignore for the project using Gemini and local context.
235+ """
236+ from devguardian .utils .file_reader import build_project_context
237+ from devguardian .utils .gemini_client import ask_gemini
238+
239+ ctx = build_project_context (project_path )
240+ env_msg = (
241+ "ALWAYS include .env and credentials patterns."
242+ if include_env
243+ else "DO NOT explicitly include .env unless the user confirms (ask for permission). "
244+ "Actually, for safety, suggest it but mark it with a comment."
245+ )
246+
247+ prompt = (
248+ f"{ ctx } \n \n "
249+ "Generate a professional, tailored .gitignore for this project. "
250+ "1. Identify the programming language and frameworks.\n "
251+ "2. Include common ignore patterns for that stack (e.g., __pycache__, node_modules, .venv, etc.).\n "
252+ "3. Look at the file structure above and identify any specific large folders or temp files to ignore.\n "
253+ f"4. Regarding .env: { env_msg } \n "
254+ "\n Return ONLY the content of the .gitignore file. No markdown fences, no explanations."
255+ )
256+
257+ system_instr = (
258+ "You are a DevOps architect. Generate a clean, well-organized .gitignore file. "
259+ "Group patterns by category (e.g., # IDEs, # Environment, # Build artifacts)."
260+ )
261+
262+ result = ask_gemini (prompt , system_instruction = system_instr )
263+ # Strip fences if present
264+ lines = [l for l in result .splitlines () if not l .strip ().startswith ("```" )]
265+ return "\n " .join (lines ).strip ()
266+
267+
232268def check_gitignore (repo_path : str ) -> dict :
233269 """
234270 Check that .gitignore properly covers sensitive file patterns.
235-
236- Returns:
237- ok : bool — True if all critical patterns are covered
238- covered : list — patterns that ARE in .gitignore
239- missing : list — patterns that should be in .gitignore but aren't
240- warnings : list — human-readable warnings
241271 """
242272 root = Path (repo_path )
243273 gitignore_path = root / ".gitignore"
@@ -257,6 +287,7 @@ def check_gitignore(repo_path: str) -> dict:
257287 line .strip () for line in gitignore_content .splitlines () if line .strip () and not line .strip ().startswith ("#" )
258288 }
259289
290+ # First check the high-priority "MUST IGNORE" list
260291 for pattern in _MUST_IGNORE :
261292 # Check exact match or wildcard coverage
262293 covered = (
@@ -272,7 +303,7 @@ def check_gitignore(repo_path: str) -> dict:
272303 if result ["missing" ]:
273304 result ["ok" ] = False
274305 result ["warnings" ].append (
275- "These patterns are missing from .gitignore: " + ", " .join (f"`{ p } `" for p in result ["missing" ])
306+ "These critical patterns are missing from .gitignore: " + ", " .join (f"`{ p } `" for p in result ["missing" ])
276307 )
277308
278309 return result
0 commit comments