iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0

昨天我們在 CodebaseAnalyzer 中建立了路徑邊界防禦與專案骨架。今天我們要進入下一步:讓分析引擎真正遍歷專案檔案

最直覺的寫法通常長這樣:

for file in root.rglob("*.py"):
    read(file)

但把這段程式碼直接跑在真實專案上會立刻崩潰:.venv 動輒包含數千個第三方套件檔案,快取目錄(__pycache__)充斥暫存檔,更危險的是可能直接把 .env 或私鑰讀進來。

做 Codebase Agent 的第一步不是狂讀猛讀,而是先學會「閉眼」。

排除規則的兩道防線

我們在掃描器中定義兩道明確的過濾門檻:

  1. 目錄黑名單(Prune Dirs):遇到這些目錄直接剪枝,完全不遞迴深入,避免遍歷浪費時間。
  2. 敏感檔案過濾(Skip Sensitive Files):命中特定副檔名或包含敏感關鍵字的檔案一律阻絕。

擴充 app/codebase.py 實作掃描器

我們在昨天的 CodebaseAnalyzer 中加入 discover_python_files() 方法:

# app/codebase.py (續接昨日進度)
import os
from pathlib import Path
from typing import List, Set

class CodebaseAnalyzer:
    # 略過目錄黑名單
    IGNORED_DIRS: Set[str] = {
        ".git", ".venv", "venv", "__pycache__", 
        ".mypy_cache", ".pytest_cache", "data"
    }

    # 敏感副檔名與檔名特徵
    SENSITIVE_SUFFIXES: Set[str] = {
        ".pem", ".key", ".p12", ".pfx"
    }
    SENSITIVE_NAMES: Set[str] = {
        "id_rsa", "id_ed25519"
    }

    def __init__(self, repo_root: str):
        self.repo_root = Path(repo_root).resolve()
        if not self.repo_root.exists():
            raise FileNotFoundError(f"目標目錄不存在: {self.repo_root}")

    def is_safe_file(self, file_path: Path) -> bool:
        """檢查檔案是否安全且非敏感資訊"""
        name = file_path.name.lower()
        
        # 阻絕 .env 開頭的檔案
        if name.startswith(".env"):
            return False
        # 阻絕敏感副檔名
        if file_path.suffix.lower() in self.SENSITIVE_SUFFIXES:
            return False
        # 阻絕金鑰名稱
        if any(token in name for token in self.SENSITIVE_NAMES):
            return False
            
        return True

    def discover_python_files(self) -> List[Path]:
        """安全遍歷 repository,回傳所有合規的 Python 檔案相對路徑"""
        discovered = []

        # 使用 os.walk 手動控制目錄遍歷,便於原地剪枝 (in-place pruning)
        for root, dirs, files in os.walk(self.repo_root):
            # 剪除黑名單目錄,防止進入 .venv 或 .git
            dirs[:] = [d for d in dirs if d not in self.IGNORED_DIRS]

            for file in files:
                if not file.endswith(".py"):
                    continue

                full_path = Path(root) / file
                if self.is_safe_file(full_path):
                    rel_path = full_path.relative_to(self.repo_root)
                    discovered.append(rel_path)

        return sorted(discovered)

實作單元測試:tests/unit/test_scanner.py

tests/unit/ 下加入測試,驗證掃描器是否確實做到「閉眼」:

# tests/unit/test_scanner.py
from app.codebase import CodebaseAnalyzer

def test_discover_files_with_ignored_and_sensitive(tmp_path):
    repo = tmp_path / "test_repo"
    repo.mkdir()

    # 建立合法檔案
    (repo / "src").mkdir()
    (repo / "src" / "app.py").write_text("print(1)")
    
    # 建立應該被忽略的檔案與目錄
    (repo / ".venv").mkdir()
    (repo / ".venv" / "lib.py").write_text("print(2)")
    (repo / ".env").write_text("SECRET=123")
    (repo / "id_rsa").write_text("private_key")

    analyzer = CodebaseAnalyzer(str(repo))
    files = analyzer.discover_python_files()

    # 斷言:只抓出合法的 src/app.py
    assert [str(f).replace("\\", "/") for f in files] == ["src/app.py"]

執行測試確認綠燈:

uv run pytest tests/unit/test_scanner.py -v

實際掃描 mobileai-local-rag

現在將過濾器套用到我們的受測目標 mobileai-local-rag

uv run python -m app.cli index

輸出結果:

{
  "scanned_files": 7,
  "skipped_dirs": [".git", ".venv", "__pycache__"],
  "blocked_sensitive": 0,
  "status": "ready_for_ast"
}

原本目錄下包含虛擬環境超過 3,000 個檔案,經由剪枝與過濾,收斂為 7 支純淨的目標業務程式碼

乾淨的檔案清單已就緒。有了這道安全防線,明天在 Day 7 中,我們將正式把 Python 原生 ast 掛上來,逐行解析這 7 支檔案中的 Class、Function 與 Import 依據!



上一篇
Day 5:先把工作桌整理好:CLI 專案骨架與零依賴原則
下一篇
Day 7:先別急著向量搜尋:基礎文字與路徑搜尋實作
系列文
30 天打造 Codebase Intelligence Agent:從程式碼檢索、結構化索引到變更影響分析實戰7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言