llm-ide-rules 0.6.0__py3-none-any.whl → 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
llm_ide_rules/__init__.py CHANGED
@@ -12,9 +12,18 @@ from llm_ide_rules.commands.explode import explode_main
12
12
  from llm_ide_rules.commands.implode import cursor, github, claude, gemini, opencode
13
13
  from llm_ide_rules.commands.download import download_main
14
14
  from llm_ide_rules.commands.delete import delete_main
15
+ from llm_ide_rules.commands.config import config_main
15
16
  from llm_ide_rules.commands.mcp import mcp_app
16
17
 
17
- __version__ = "0.6.0"
18
+ __version__ = "0.8.0"
19
+
20
+
21
+ def version_callback(value: bool):
22
+ """Callback to display the version and exit."""
23
+ if value:
24
+ print(f"llm-ide-rules version {__version__}")
25
+ raise typer.Exit()
26
+
18
27
 
19
28
  app = typer.Typer(
20
29
  name="llm_ide_rules",
@@ -31,6 +40,15 @@ def main_callback(
31
40
  "--verbose", "-v", help="Enable verbose logging (sets LOG_LEVEL=DEBUG)"
32
41
  ),
33
42
  ] = False,
43
+ version: Annotated[
44
+ bool | None,
45
+ typer.Option(
46
+ "--version",
47
+ help="Show the version and exit",
48
+ callback=version_callback,
49
+ is_eager=True,
50
+ ),
51
+ ] = None,
34
52
  ):
35
53
  """Global CLI options."""
36
54
  if verbose:
@@ -48,6 +66,7 @@ app.command("download", help="Download LLM instruction files from GitHub reposit
48
66
  download_main
49
67
  )
50
68
  app.command("delete", help="Remove downloaded LLM instruction files")(delete_main)
69
+ app.command("config", help="Configure agents to use AGENTS.md")(config_main)
51
70
 
52
71
  # Create implode sub-typer
53
72
  implode_app = typer.Typer(help="Bundle rule files into a single instruction file")
@@ -6,6 +6,8 @@ from llm_ide_rules.agents.github import GitHubAgent
6
6
  from llm_ide_rules.agents.claude import ClaudeAgent
7
7
  from llm_ide_rules.agents.gemini import GeminiAgent
8
8
  from llm_ide_rules.agents.opencode import OpenCodeAgent
9
+ from llm_ide_rules.agents.agents import AgentsAgent
10
+ from llm_ide_rules.agents.vscode import VSCodeAgent
9
11
 
10
12
  AGENTS: dict[str, type[BaseAgent]] = {
11
13
  "cursor": CursorAgent,
@@ -13,6 +15,8 @@ AGENTS: dict[str, type[BaseAgent]] = {
13
15
  "claude": ClaudeAgent,
14
16
  "gemini": GeminiAgent,
15
17
  "opencode": OpenCodeAgent,
18
+ "agents": AgentsAgent,
19
+ "vscode": VSCodeAgent,
16
20
  }
17
21
 
18
22
 
@@ -0,0 +1,124 @@
1
+ """Agents documentation agent implementation."""
2
+
3
+ from pathlib import Path
4
+ import typer
5
+
6
+ from llm_ide_rules.agents.base import BaseAgent
7
+
8
+
9
+ class AgentsAgent(BaseAgent):
10
+ """Agent for generating AGENTS.md documentation."""
11
+
12
+ name = "agents"
13
+ rules_dir = None
14
+ commands_dir = None
15
+ rule_extension = None
16
+ command_extension = None
17
+
18
+ mcp_global_path = None
19
+ mcp_project_path = None
20
+
21
+ def bundle_rules(
22
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
23
+ ) -> bool:
24
+ """Agents doesn't support bundling rules."""
25
+ return False
26
+
27
+ def bundle_commands(
28
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
29
+ ) -> bool:
30
+ """Agents doesn't support bundling commands."""
31
+ return False
32
+
33
+ def write_rule(
34
+ self,
35
+ content_lines: list[str],
36
+ filename: str,
37
+ rules_dir: Path,
38
+ glob_pattern: str | None = None,
39
+ description: str | None = None,
40
+ ) -> None:
41
+ """Agents doesn't support writing rules."""
42
+ pass
43
+
44
+ def write_command(
45
+ self,
46
+ content_lines: list[str],
47
+ filename: str,
48
+ commands_dir: Path,
49
+ section_name: str | None = None,
50
+ ) -> None:
51
+ """Agents doesn't support writing commands."""
52
+ pass
53
+
54
+ def generate_root_doc(
55
+ self,
56
+ general_lines: list[str],
57
+ rules_sections: dict[str, list[str]],
58
+ command_sections: dict[str, list[str]],
59
+ output_dir: Path,
60
+ section_globs: dict[str, str | None] | None = None,
61
+ ) -> None:
62
+ """Generate AGENTS.md files, potentially distributed based on globs."""
63
+ if not section_globs:
64
+ # Fallback to single root AGENTS.md
65
+ content = self.build_root_doc_content(general_lines, rules_sections)
66
+ if content.strip():
67
+ (output_dir / "AGENTS.md").write_text(content)
68
+ return
69
+
70
+ # Group rules by target directory
71
+ rules_by_dir: dict[Path, dict[str, list[str]]] = {}
72
+
73
+ # Always include root directory for rules without specific directory targets
74
+ rules_by_dir[output_dir] = {}
75
+
76
+ for section_name, lines in rules_sections.items():
77
+ target_dir = output_dir
78
+ glob_pattern = section_globs.get(section_name)
79
+
80
+ if glob_pattern and "**" in glob_pattern:
81
+ # Extract path before **
82
+ prefix = glob_pattern.split("**")[0].strip("/")
83
+ potential_dir = output_dir / prefix
84
+
85
+ # Check if directory exists, if not traverse up
86
+ check_dir = potential_dir
87
+ while not check_dir.exists() and check_dir != output_dir:
88
+ check_dir = check_dir.parent
89
+
90
+ if check_dir != potential_dir:
91
+ # We fell back
92
+ if check_dir == output_dir:
93
+ # Only warn if falling back to root from a deep path
94
+ pass
95
+
96
+ # We can log this if we want, but simple traversal is fine.
97
+ # The requirement says "warn to the user".
98
+ rel_potential = potential_dir.relative_to(output_dir)
99
+ rel_actual = check_dir.relative_to(output_dir)
100
+ typer.secho(
101
+ f"Warning: Directory '{rel_potential}' for section '{section_name}' does not exist. "
102
+ f"Placing in '{rel_actual}' instead.",
103
+ fg=typer.colors.YELLOW,
104
+ err=True,
105
+ )
106
+
107
+ target_dir = check_dir
108
+
109
+ if target_dir not in rules_by_dir:
110
+ rules_by_dir[target_dir] = {}
111
+
112
+ rules_by_dir[target_dir][section_name] = lines
113
+
114
+ # Generate AGENTS.md for each directory
115
+ for target_dir, sections in rules_by_dir.items():
116
+ if not sections:
117
+ continue
118
+
119
+ # Only include general instructions in the root AGENTS.md
120
+ current_general_lines = general_lines if target_dir == output_dir else []
121
+
122
+ content = self.build_root_doc_content(current_general_lines, sections)
123
+ if content.strip():
124
+ (target_dir / "AGENTS.md").write_text(content)
@@ -23,14 +23,14 @@ class BaseAgent(ABC):
23
23
 
24
24
  @abstractmethod
25
25
  def bundle_rules(
26
- self, output_file: Path, section_globs: dict[str, str | None]
26
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
27
27
  ) -> bool:
28
28
  """Bundle rule files into a single output file."""
29
29
  ...
30
30
 
31
31
  @abstractmethod
32
32
  def bundle_commands(
33
- self, output_file: Path, section_globs: dict[str, str | None]
33
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
34
34
  ) -> bool:
35
35
  """Bundle command files into a single output file."""
36
36
  ...
@@ -42,6 +42,7 @@ class BaseAgent(ABC):
42
42
  filename: str,
43
43
  rules_dir: Path,
44
44
  glob_pattern: str | None = None,
45
+ description: str | None = None,
45
46
  ) -> None:
46
47
  """Write a single rule file."""
47
48
  ...
@@ -57,6 +58,49 @@ class BaseAgent(ABC):
57
58
  """Write a single command file."""
58
59
  ...
59
60
 
61
+ def configure_agents_md(self, base_dir: Path) -> bool:
62
+ """Configure the agent to use AGENTS.md as context (default: no-op).
63
+
64
+ Returns:
65
+ bool: True if configuration was applied, False otherwise.
66
+ """
67
+ return False
68
+
69
+ def generate_root_doc(
70
+ self,
71
+ general_lines: list[str],
72
+ rules_sections: dict[str, list[str]],
73
+ command_sections: dict[str, list[str]],
74
+ output_dir: Path,
75
+ section_globs: dict[str, str | None] | None = None,
76
+ ) -> None:
77
+ """Generate a root documentation file (e.g. CLAUDE.md) if supported."""
78
+ pass
79
+
80
+ def build_root_doc_content(
81
+ self,
82
+ general_lines: list[str],
83
+ rules_sections: dict[str, list[str]],
84
+ ) -> str:
85
+ """Build the content string for a root documentation file by aggregating rules."""
86
+ content = []
87
+
88
+ # Add general instructions
89
+ if general_lines:
90
+ trimmed = trim_content(general_lines)
91
+ if trimmed:
92
+ content.extend(trimmed)
93
+ content.append("\n\n")
94
+
95
+ # Add sections in document order (dict maintains insertion order in Python 3.7+)
96
+ for section_name, lines in rules_sections.items():
97
+ trimmed = trim_content(lines)
98
+ if trimmed:
99
+ content.extend(trimmed)
100
+ content.append("\n\n")
101
+
102
+ return "".join(content).strip() + "\n" if content else ""
103
+
60
104
  def get_rules_path(self, base_dir: Path) -> Path:
61
105
  """Get the full path to the rules directory."""
62
106
  if not self.rules_dir:
@@ -155,9 +199,15 @@ def strip_toml_metadata(text: str) -> str:
155
199
 
156
200
 
157
201
  def get_ordered_files(
158
- file_list: list[Path], section_globs_keys: list[str]
202
+ file_list: list[Path], section_globs_keys: list[str] | None = None
159
203
  ) -> list[Path]:
160
- """Order files based on section_globs key order, with unmapped files at the end."""
204
+ """Order files based on section_globs key order, with unmapped files at the end.
205
+
206
+ If section_globs_keys is None, returns files sorted alphabetically.
207
+ """
208
+ if not section_globs_keys:
209
+ return sorted(file_list, key=lambda p: p.name)
210
+
161
211
  file_dict = {f.stem: f for f in file_list}
162
212
  ordered_files = []
163
213
 
@@ -174,9 +224,15 @@ def get_ordered_files(
174
224
 
175
225
 
176
226
  def get_ordered_files_github(
177
- file_list: list[Path], section_globs_keys: list[str]
227
+ file_list: list[Path], section_globs_keys: list[str] | None = None
178
228
  ) -> list[Path]:
179
- """Order GitHub instruction files, handling .instructions suffix."""
229
+ """Order GitHub instruction files, handling .instructions suffix.
230
+
231
+ If section_globs_keys is None, returns files sorted alphabetically.
232
+ """
233
+ if not section_globs_keys:
234
+ return sorted(file_list, key=lambda p: p.name)
235
+
180
236
  file_dict = {}
181
237
  for f in file_list:
182
238
  base_stem = f.stem.replace(".instructions", "")
@@ -232,6 +288,7 @@ def write_rule_file(path: Path, header_yaml: str, content_lines: list[str]) -> N
232
288
  """Write a rule file with front matter and content."""
233
289
  trimmed_content = trim_content(content_lines)
234
290
  output = header_yaml.strip() + "\n" + "".join(trimmed_content)
291
+ path.parent.mkdir(parents=True, exist_ok=True)
235
292
  path.write_text(output)
236
293
 
237
294
 
@@ -23,13 +23,13 @@ class ClaudeAgent(BaseAgent):
23
23
  mcp_project_path = ".mcp.json"
24
24
 
25
25
  def bundle_rules(
26
- self, output_file: Path, section_globs: dict[str, str | None]
26
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
27
27
  ) -> bool:
28
28
  """Claude Code doesn't support rules, only commands."""
29
29
  return False
30
30
 
31
31
  def bundle_commands(
32
- self, output_file: Path, section_globs: dict[str, str | None]
32
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
33
33
  ) -> bool:
34
34
  """Bundle Claude Code command files (.md) into a single output file."""
35
35
  commands_dir = self.commands_dir
@@ -48,7 +48,9 @@ class ClaudeAgent(BaseAgent):
48
48
  if not command_files:
49
49
  return False
50
50
 
51
- ordered_commands = get_ordered_files(command_files, list(section_globs.keys()))
51
+ ordered_commands = get_ordered_files(
52
+ command_files, list(section_globs.keys()) if section_globs else None
53
+ )
52
54
 
53
55
  content_parts: list[str] = []
54
56
  for command_file in ordered_commands:
@@ -56,7 +58,9 @@ class ClaudeAgent(BaseAgent):
56
58
  if not content:
57
59
  continue
58
60
 
59
- header = resolve_header_from_stem(command_file.stem, section_globs)
61
+ header = resolve_header_from_stem(
62
+ command_file.stem, section_globs if section_globs else {}
63
+ )
60
64
  content_parts.append(f"## {header}\n\n")
61
65
  content_parts.append(content)
62
66
  content_parts.append("\n\n")
@@ -73,6 +77,7 @@ class ClaudeAgent(BaseAgent):
73
77
  filename: str,
74
78
  rules_dir: Path,
75
79
  glob_pattern: str | None = None,
80
+ description: str | None = None,
76
81
  ) -> None:
77
82
  """Claude Code doesn't support rules."""
78
83
  pass
@@ -89,4 +94,26 @@ class ClaudeAgent(BaseAgent):
89
94
  filepath = commands_dir / f"{filename}{extension}"
90
95
 
91
96
  trimmed = trim_content(content_lines)
97
+ filepath.parent.mkdir(parents=True, exist_ok=True)
92
98
  filepath.write_text("".join(trimmed))
99
+
100
+ def generate_root_doc(
101
+ self,
102
+ general_lines: list[str],
103
+ rules_sections: dict[str, list[str]],
104
+ command_sections: dict[str, list[str]],
105
+ output_dir: Path,
106
+ section_globs: dict[str, str | None] | None = None,
107
+ ) -> None:
108
+ """Generate CLAUDE.md that references AGENTS.md."""
109
+ (output_dir / "CLAUDE.md").write_text("@./AGENTS.md\n")
110
+
111
+ def configure_agents_md(self, base_dir: Path) -> bool:
112
+ """Create CLAUDE.md pointing to AGENTS.md if AGENTS.md exists and CLAUDE.md doesn't."""
113
+ agents_md = base_dir / "AGENTS.md"
114
+ claude_md = base_dir / "CLAUDE.md"
115
+
116
+ if agents_md.exists() and not claude_md.exists():
117
+ claude_md.write_text("@./AGENTS.md\n")
118
+ return True
119
+ return False
@@ -27,7 +27,7 @@ class CursorAgent(BaseAgent):
27
27
  mcp_project_path = ".cursor/mcp.json"
28
28
 
29
29
  def bundle_rules(
30
- self, output_file: Path, section_globs: dict[str, str | None]
30
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
31
31
  ) -> bool:
32
32
  """Bundle Cursor rule files (.mdc) into a single output file."""
33
33
  rules_dir = self.rules_dir
@@ -45,21 +45,42 @@ class CursorAgent(BaseAgent):
45
45
  general = [f for f in rule_files if f.stem == "general"]
46
46
  others = [f for f in rule_files if f.stem != "general"]
47
47
 
48
- ordered_others = get_ordered_files(others, list(section_globs.keys()))
48
+ ordered_others = get_ordered_files(
49
+ others, list(section_globs.keys()) if section_globs else None
50
+ )
49
51
  ordered = general + ordered_others
50
52
 
51
53
  content_parts: list[str] = []
52
54
  for rule_file in ordered:
53
- content = rule_file.read_text().strip()
54
- if not content:
55
+ file_content = rule_file.read_text().strip()
56
+ if not file_content:
55
57
  continue
56
58
 
57
- content = strip_yaml_frontmatter(content)
59
+ lines = file_content.splitlines()
60
+ extracted_header = None
61
+ glob_pattern = None
62
+
63
+ for line in lines:
64
+ if line.startswith("## "):
65
+ extracted_header = line[3:].strip()
66
+ break
67
+
68
+ glob_pattern = self._extract_glob_from_frontmatter(file_content)
69
+
70
+ content = strip_yaml_frontmatter(file_content)
58
71
  content = strip_header(content)
59
- header = resolve_header_from_stem(rule_file.stem, section_globs)
72
+
73
+ if extracted_header:
74
+ header = extracted_header
75
+ else:
76
+ header = resolve_header_from_stem(
77
+ rule_file.stem, section_globs if section_globs else {}
78
+ )
60
79
 
61
80
  if rule_file.stem != "general":
62
81
  content_parts.append(f"## {header}\n\n")
82
+ if glob_pattern:
83
+ content_parts.append(f"globs: {glob_pattern}\n\n")
63
84
 
64
85
  content_parts.append(content)
65
86
  content_parts.append("\n\n")
@@ -70,8 +91,23 @@ class CursorAgent(BaseAgent):
70
91
  output_file.write_text("".join(content_parts))
71
92
  return True
72
93
 
94
+ def _extract_glob_from_frontmatter(self, content: str) -> str | None:
95
+ """Extract glob pattern from YAML frontmatter."""
96
+ lines = content.splitlines()
97
+ if not lines or lines[0].strip() != "---":
98
+ return None
99
+
100
+ for i in range(1, len(lines)):
101
+ if lines[i].strip() == "---":
102
+ break
103
+ if lines[i].startswith("globs:"):
104
+ glob_value = lines[i][6:].strip()
105
+ return glob_value if glob_value else None
106
+
107
+ return None
108
+
73
109
  def bundle_commands(
74
- self, output_file: Path, section_globs: dict[str, str | None]
110
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
75
111
  ) -> bool:
76
112
  """Bundle Cursor command files (.md) into a single output file."""
77
113
  commands_dir = self.commands_dir
@@ -90,7 +126,9 @@ class CursorAgent(BaseAgent):
90
126
  if not command_files:
91
127
  return False
92
128
 
93
- ordered_commands = get_ordered_files(command_files, list(section_globs.keys()))
129
+ ordered_commands = get_ordered_files(
130
+ command_files, list(section_globs.keys()) if section_globs else None
131
+ )
94
132
 
95
133
  content_parts: list[str] = []
96
134
  for command_file in ordered_commands:
@@ -98,7 +136,9 @@ class CursorAgent(BaseAgent):
98
136
  if not content:
99
137
  continue
100
138
 
101
- header = resolve_header_from_stem(command_file.stem, section_globs)
139
+ header = resolve_header_from_stem(
140
+ command_file.stem, section_globs if section_globs else {}
141
+ )
102
142
  content_parts.append(f"## {header}\n\n")
103
143
  content_parts.append(content)
104
144
  content_parts.append("\n\n")
@@ -115,27 +155,32 @@ class CursorAgent(BaseAgent):
115
155
  filename: str,
116
156
  rules_dir: Path,
117
157
  glob_pattern: str | None = None,
158
+ description: str | None = None,
118
159
  ) -> None:
119
160
  """Write a Cursor rule file (.mdc) with YAML frontmatter."""
120
161
  extension = self.rule_extension or ".mdc"
121
162
  filepath = rules_dir / f"{filename}{extension}"
122
163
 
164
+ desc = description or filename.replace("-", " ").title()
165
+
123
166
  if glob_pattern and glob_pattern != "manual":
124
167
  header_yaml = f"""---
125
- description:
168
+ description: {desc}
126
169
  globs: {glob_pattern}
127
170
  alwaysApply: false
128
171
  ---
129
172
  """
130
173
  elif glob_pattern == "manual":
131
- header_yaml = """---
132
- description:
174
+ header_yaml = f"""---
175
+ description: {desc}
176
+ globs:
133
177
  alwaysApply: false
134
178
  ---
135
179
  """
136
180
  else:
137
- header_yaml = """---
138
- description:
181
+ header_yaml = f"""---
182
+ description: {desc}
183
+ globs:
139
184
  alwaysApply: true
140
185
  ---
141
186
  """
@@ -153,6 +198,7 @@ alwaysApply: true
153
198
  filepath = commands_dir / f"{filename}{extension}"
154
199
 
155
200
  trimmed = trim_content(content_lines)
201
+ filepath.parent.mkdir(parents=True, exist_ok=True)
156
202
  filepath.write_text("".join(trimmed))
157
203
 
158
204
  def write_prompt(
@@ -176,3 +222,7 @@ alwaysApply: true
176
222
 
177
223
  output_parts.extend(filtered_content)
178
224
  filepath.write_text("".join(output_parts))
225
+
226
+ def configure_agents_md(self, base_dir: Path) -> bool:
227
+ """Cursor doesn't require explicit configuration for AGENTS.md."""
228
+ return False
@@ -27,13 +27,13 @@ class GeminiAgent(BaseAgent):
27
27
  mcp_project_path = ".gemini/settings.json"
28
28
 
29
29
  def bundle_rules(
30
- self, output_file: Path, section_globs: dict[str, str | None]
30
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
31
31
  ) -> bool:
32
32
  """Gemini CLI doesn't support rules, only commands."""
33
33
  return False
34
34
 
35
35
  def bundle_commands(
36
- self, output_file: Path, section_globs: dict[str, str | None]
36
+ self, output_file: Path, section_globs: dict[str, str | None] | None = None
37
37
  ) -> bool:
38
38
  """Bundle Gemini CLI command files (.toml) into a single output file."""
39
39
  commands_dir = self.commands_dir
@@ -52,7 +52,9 @@ class GeminiAgent(BaseAgent):
52
52
  if not command_files:
53
53
  return False
54
54
 
55
- ordered_commands = get_ordered_files(command_files, list(section_globs.keys()))
55
+ ordered_commands = get_ordered_files(
56
+ command_files, list(section_globs.keys()) if section_globs else None
57
+ )
56
58
 
57
59
  content_parts: list[str] = []
58
60
  for command_file in ordered_commands:
@@ -61,7 +63,9 @@ class GeminiAgent(BaseAgent):
61
63
  continue
62
64
 
63
65
  content = strip_toml_metadata(content)
64
- header = resolve_header_from_stem(command_file.stem, section_globs)
66
+ header = resolve_header_from_stem(
67
+ command_file.stem, section_globs if section_globs else {}
68
+ )
65
69
  content_parts.append(f"## {header}\n\n")
66
70
  content_parts.append(content)
67
71
  content_parts.append("\n\n")
@@ -78,6 +82,7 @@ class GeminiAgent(BaseAgent):
78
82
  filename: str,
79
83
  rules_dir: Path,
80
84
  glob_pattern: str | None = None,
85
+ description: str | None = None,
81
86
  ) -> None:
82
87
  """Gemini CLI doesn't support rules."""
83
88
  pass
@@ -120,6 +125,7 @@ class GeminiAgent(BaseAgent):
120
125
 
121
126
  # tomli-w will handle escaping and multiline strings automatically
122
127
  output = tomli_w.dumps(data)
128
+ filepath.parent.mkdir(parents=True, exist_ok=True)
123
129
  filepath.write_text(output)
124
130
 
125
131
  def transform_mcp_server(self, server: McpServer) -> dict:
@@ -159,3 +165,37 @@ class GeminiAgent(BaseAgent):
159
165
 
160
166
  existing[self.mcp_root_key] = servers
161
167
  path.write_text(json.dumps(existing, indent=2))
168
+
169
+ def generate_root_doc(
170
+ self,
171
+ general_lines: list[str],
172
+ rules_sections: dict[str, list[str]],
173
+ command_sections: dict[str, list[str]],
174
+ output_dir: Path,
175
+ section_globs: dict[str, str | None] | None = None,
176
+ ) -> None:
177
+ """Gemini CLI uses AGENTS.md (generated by 'agents' agent). This is a no-op."""
178
+ pass
179
+
180
+ def configure_agents_md(self, base_dir: Path) -> bool:
181
+ """Configure Gemini CLI to use AGENTS.md."""
182
+ from llm_ide_rules.utils import modify_json_file
183
+
184
+ settings_path = base_dir / ".gemini" / "settings.json"
185
+
186
+ # Based on research, generic context setting might be:
187
+ updates = {"agent.instructionFile": "AGENTS.md"}
188
+
189
+ return modify_json_file(settings_path, updates)
190
+
191
+ def check_agents_md_config(self, base_dir: Path) -> bool:
192
+ """Check if Gemini CLI is configured to use AGENTS.md."""
193
+ settings_path = base_dir / ".gemini" / "settings.json"
194
+ if not settings_path.exists():
195
+ return False
196
+
197
+ try:
198
+ data = json.loads(settings_path.read_text())
199
+ return data.get("agent.instructionFile") == "AGENTS.md"
200
+ except Exception:
201
+ return False