Skip to content

Commit 4f2e701

Browse files
authored
feat: Set review_code include/exclude paths in config file (#130)
* feat: ability to set review_code include/exclude paths in config file * feat(docs): review_code_paths * fix(docs): trim trailing whitespaces
1 parent 71c25da commit 4f2e701

File tree

4 files changed

+126
-10
lines changed

4 files changed

+126
-10
lines changed

docs/config/review_code_paths.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Selective Code Review: Include/Exclude Paths
2+
3+
Metis now supports fine-grained control over which files are targeted during a code review. By utilizing `review_code_include_paths` and `review_code_exclude_paths` in your configuration, you can focus the engine on critical business logic while skipping boilerplate, definitions, and tests.
4+
5+
## Overview
6+
7+
These settings allow you to filter files **specifically for the review process** without removing them from the project's broader context. This ensures that Metis stays fast and the results remain relevant.
8+
9+
- **`review_code_include_paths`**: Limits the review to specific directories or files.
10+
- **`review_code_exclude_paths`**: Explicitly skips specific files or patterns during the review phase.
11+
12+
The **`review_code_include_paths`** and **`review_code_exclude_paths`** configurations utilize standard **gitignore-style** pattern matching.
13+
14+
---
15+
16+
## Why use this instead of `.metisignore`?
17+
18+
It is important to distinguish between these configuration options and the global `.metisignore` file:
19+
20+
| Feature | Scope | Impact on Context |
21+
| :--- | :--- | :--- |
22+
| **`.metisignore`** | **Global** | Files are completely invisible. They are not indexed and cannot be used by the model at all (e.g., `.venv`, `dist`, `node_modules`). |
23+
| **Review Paths** | **Command-Specific** | Files are skipped by the `review_code` logic, but **remain available** as "Relevant Context" (via embeddings or tools) to help the AI understand the code it *is* reviewing. |
24+
25+
**The Logic:** You generally don't want Metis to waste time looking for bugs in a `.d.ts` or `interface.ts` file. However, if Metis is reviewing a service that imports those types, it still needs to be able to "see" those files to understand the data structures.
26+
27+
---
28+
29+
## Configuration
30+
31+
Add these options to your `metis.yaml` file under the `metis_engine` block.
32+
33+
### Example Configuration
34+
35+
```yaml
36+
metis_engine:
37+
# Only run reviews within the backend folder
38+
# Where the Core Logic resides
39+
review_code_include_paths:
40+
- 'backend/'
41+
42+
# Skip files that don't require logic analysis
43+
review_code_exclude_paths:
44+
- 'dto/'
45+
- '*.d.ts'
46+
- '*.spec.ts'
47+
- '*.fixture.ts'
48+
- '*.dto.ts'
49+
- '*.interface.ts'
50+
- '*.type.ts'
51+
- '*.schema.ts'
52+
- 'backend/test/'
53+
- 'e2e/'
54+
```
55+
56+
View other examples for focused review at the `examples/focused_review_code` folder.
57+
58+
## Key Benefits
59+
60+
- **Focused Reviews**: Configure Metis to only review Core Logic, suchs as specific apps inside your big monorepo.
61+
- **Significant Speed Increase**: Metis avoids reviewing definition files and tests.
62+
- **Noise Reduction**: Prevents "false positive" issues or irrelevant suggestions on generated code, type interfaces, or DTOs where logic-based review is unnecessary.
63+
- **Preserved Context**: Unlike a global ignore, the AI still has access to these files to provide better context for the logic it is actually reviewing.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
metis_engine:
2+
3+
# Only review code inside the "backend" folder
4+
review_code_include_paths:
5+
- 'backend/'
6+
7+
# Don't review dtos, schemas, tests...
8+
review_code_exclude_paths:
9+
- 'dto/'
10+
- '*.d.ts'
11+
- '*.spec.ts'
12+
- '*.fixture.ts'
13+
- '*.dto.ts'
14+
- '*.const.ts'
15+
- '*.interface.ts'
16+
- '*.model.ts'
17+
- '*.type.ts'
18+
- '*.request.ts'
19+
- '*.response.ts'
20+
- '*.response.api.ts'
21+
- '*.params.ts'
22+
- '*.schema.ts'
23+
- '*.mapper.ts'
24+
- '*.store.ts'
25+
- 'e2e_ssr/'
26+
- 'e2e/'
27+
- '*.e2e-spec.ts'
28+
- 'backend/test'
29+
- 'schema.ts'

src/metis/configuration.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ def load_runtime_config(config_path=None, enable_psql=False):
127127
},
128128
)
129129
runtime["metisignore_file"] = engine_cfg.get("metisignore_file", None)
130+
runtime["review_code_include_paths"] = engine_cfg.get(
131+
"review_code_include_paths", []
132+
)
133+
runtime["review_code_exclude_paths"] = engine_cfg.get(
134+
"review_code_exclude_paths", []
135+
)
130136

131137
# Query config
132138
query_cfg = cfg.get("query", {})

src/metis/engine/core.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,23 +93,25 @@ def __init__(
9393
self._review_graph = None
9494
self._ask_graph = None
9595
self.metisignore_file = kwargs.get("metisignore_file") or ".metisignore"
96+
self.review_code_include_paths = kwargs.get("review_code_include_paths", [])
97+
self.review_code_exclude_paths = kwargs.get("review_code_exclude_paths", [])
9698

97-
def load_metisignore(self):
99+
def load_metisignore(self) -> pathspec.GitIgnoreSpec | None:
98100
"""
99-
Load metisignore file and return a PathSpec matcher.
101+
Load metisignore file and return a GitIgnoreSpec matcher.
100102
101103
Args:
102104
metisignore: Path to a file that have the ignore regex ( use the .gitignore syntax )
103105
104106
Returns:
105-
pathspec.PathSpec object or None if file doesn't exist
107+
pathspec.GitIgnoreSpec object or None if file doesn't exist
106108
"""
107109
try:
108110
if not self.metisignore_file:
109111
logger.info("No MetisIgnore file provided")
110112
return None
111113
with open(self.metisignore_file, "r") as f:
112-
spec = pathspec.PathSpec.from_lines("gitwildmatch", f)
114+
spec = pathspec.GitIgnoreSpec.from_lines(f)
113115
logger.info(f"MetisIgnore file loaded: {self.metisignore_file}")
114116
return spec
115117
except FileNotFoundError:
@@ -340,19 +342,35 @@ def review_file(self, file_path):
340342
def get_code_files(self):
341343
"""
342344
Return a list of file names in the self.codebase_path folder.
343-
Evaulate the path with metisignore file if requested
345+
Evaluate the path with metisignore file, include/exclude paths if requested
344346
"""
345347
base_path = os.path.abspath(self.codebase_path)
346348
metisignore_spec = self.load_metisignore()
349+
include_spec = None
350+
if self.review_code_include_paths:
351+
include_spec = pathspec.GitIgnoreSpec.from_lines(
352+
self.review_code_include_paths
353+
)
354+
exclude_spec = None
355+
if self.review_code_exclude_paths:
356+
exclude_spec = pathspec.GitIgnoreSpec.from_lines(
357+
self.review_code_exclude_paths
358+
)
347359
file_list = []
348360
for root, _, files in os.walk(base_path):
349361
for file in files:
362+
full_path = os.path.join(root, file)
350363
ext = os.path.splitext(file)[1].lower()
351-
if ext in self.code_exts and (
352-
not metisignore_spec
353-
or not metisignore_spec.match_file(os.path.join(root, file))
354-
):
355-
file_list.append(os.path.join(root, file))
364+
if ext not in self.code_exts:
365+
continue
366+
rel_path = os.path.relpath(full_path, base_path)
367+
if metisignore_spec and metisignore_spec.match_file(rel_path):
368+
continue
369+
if include_spec and not include_spec.match_file(rel_path):
370+
continue
371+
if exclude_spec and exclude_spec.match_file(rel_path):
372+
continue
373+
file_list.append(full_path)
356374
return file_list
357375

358376
def review_code(self):

0 commit comments

Comments
 (0)