Create a simple ignore predicate based on `.agentignore`, `.gitignore`, and defaults. Supported patterns (subset): - Trailing-slash directory rules, e.g. `node_modules/`, `__pycache__/` - Simple filenames, e.g. `.DS_Store` - Basic `*`/`?` globs on basenames, e.g. `*.pyc`, `*.log`
(project: dict[str, str])
| 136 | |
| 137 | |
| 138 | def make_ignore_predicate(project: dict[str, str]) -> "callable[[str], bool]": |
| 139 | """Create a simple ignore predicate based on `.agentignore`, `.gitignore`, and defaults. |
| 140 | |
| 141 | Supported patterns (subset): |
| 142 | - Trailing-slash directory rules, e.g. `node_modules/`, `__pycache__/` |
| 143 | - Simple filenames, e.g. `.DS_Store` |
| 144 | - Basic `*`/`?` globs on basenames, e.g. `*.pyc`, `*.log` |
| 145 | The semantics are intentionally simple and operate on basenames for globs. |
| 146 | """ |
| 147 | |
| 148 | agentignore = project.get(".agentignore", "") or "" |
| 149 | gitignore = project.get(".gitignore", "") or "" |
| 150 | patterns: list[str] = [ |
| 151 | *DEFAULT_AGENT_IGNORE_PATTERNS, |
| 152 | *_parse_ignore_lines(gitignore), |
| 153 | *_parse_ignore_lines(agentignore), |
| 154 | ] |
| 155 | |
| 156 | # Also include nested ignore files by prefixing their folder path onto rules |
| 157 | try: |
| 158 | for path, text in project.items(): |
| 159 | if not path or "/.gitignore" not in path and "/.agentignore" not in path: |
| 160 | continue |
| 161 | # folder prefix (strip the ".gitignore" or ".agentignore" filename) |
| 162 | base = path.rsplit("/", 1)[0].lstrip("/") |
| 163 | if not base: |
| 164 | continue |
| 165 | for rule in _parse_ignore_lines(text or ""): |
| 166 | r = rule.strip() |
| 167 | if not r: |
| 168 | continue |
| 169 | if r.endswith("/"): |
| 170 | # directory rule stays a directory rule under base |
| 171 | patterns.append(f"{base}/{r}") |
| 172 | else: |
| 173 | # file/glob rule under base; keep relative to base |
| 174 | patterns.append(f"{base}/{r}") |
| 175 | except Exception: |
| 176 | # best-effort; ignore errors loading nested ignore files |
| 177 | pass |
| 178 | |
| 179 | def to_predicate(pat: str): |
| 180 | pattern = pat.lstrip("/").strip() |
| 181 | if not pattern: |
| 182 | return lambda _p: False |
| 183 | |
| 184 | # Directory match (e.g. foo/). If pattern includes a slash, treat as anchored path prefix. |
| 185 | if pattern.endswith("/"): |
| 186 | directory = pattern[:-1].lstrip("/") |
| 187 | |
| 188 | def _dir(path: str) -> bool: |
| 189 | n = (path or "").lstrip("/") |
| 190 | if not directory: |
| 191 | return False |
| 192 | if "/" in directory: |
| 193 | # anchored subpath: match exact or prefix (e.g., "frontend/node_modules/") |
| 194 | return n == directory or n.startswith(directory + "/") |
| 195 | # segment match anywhere (e.g., any "node_modules" segment in the path) |
no test coverage detected