llm-ide-rules 0.7.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 +20 -1
- llm_ide_rules/agents/__init__.py +4 -0
- llm_ide_rules/agents/agents.py +124 -0
- llm_ide_rules/agents/base.py +11 -0
- llm_ide_rules/agents/claude.py +15 -4
- llm_ide_rules/agents/cursor.py +36 -7
- llm_ide_rules/agents/gemini.py +28 -4
- llm_ide_rules/agents/github.py +34 -3
- llm_ide_rules/agents/opencode.py +6 -0
- llm_ide_rules/agents/vscode.py +88 -0
- llm_ide_rules/commands/config.py +46 -0
- llm_ide_rules/commands/delete.py +111 -5
- llm_ide_rules/commands/download.py +33 -14
- llm_ide_rules/commands/explode.py +67 -16
- llm_ide_rules/commands/implode.py +18 -18
- llm_ide_rules/commands/mcp.py +1 -1
- llm_ide_rules/constants.py +1 -1
- llm_ide_rules/utils.py +118 -0
- {llm_ide_rules-0.7.0.dist-info → llm_ide_rules-0.8.0.dist-info}/METADATA +3 -3
- llm_ide_rules-0.8.0.dist-info/RECORD +27 -0
- llm_ide_rules-0.7.0.dist-info/RECORD +0 -23
- {llm_ide_rules-0.7.0.dist-info → llm_ide_rules-0.8.0.dist-info}/WHEEL +0 -0
- {llm_ide_rules-0.7.0.dist-info → llm_ide_rules-0.8.0.dist-info}/entry_points.txt +0 -0
llm_ide_rules/commands/delete.py
CHANGED
|
@@ -7,7 +7,58 @@ import typer
|
|
|
7
7
|
from typing_extensions import Annotated
|
|
8
8
|
|
|
9
9
|
from llm_ide_rules.commands.download import INSTRUCTION_TYPES, DEFAULT_TYPES
|
|
10
|
+
from llm_ide_rules.constants import header_to_filename
|
|
10
11
|
from llm_ide_rules.log import log
|
|
12
|
+
from llm_ide_rules.markdown_parser import parse_sections
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_generated_files(target_dir: Path) -> set[Path]:
|
|
16
|
+
"""Identify files that would be generated from local instruction files."""
|
|
17
|
+
generated = set()
|
|
18
|
+
|
|
19
|
+
# Check instructions.md
|
|
20
|
+
instructions_path = target_dir / "instructions.md"
|
|
21
|
+
if instructions_path.exists():
|
|
22
|
+
try:
|
|
23
|
+
general, sections = parse_sections(instructions_path.read_text())
|
|
24
|
+
|
|
25
|
+
# If general instructions exist, these files are generated
|
|
26
|
+
if any(line.strip() for line in general):
|
|
27
|
+
generated.add(target_dir / ".cursor/rules/general.mdc")
|
|
28
|
+
generated.add(target_dir / ".github/copilot-instructions.md")
|
|
29
|
+
generated.add(target_dir / "CLAUDE.md")
|
|
30
|
+
|
|
31
|
+
# If any sections exist, root docs are definitely generated
|
|
32
|
+
if sections:
|
|
33
|
+
generated.add(target_dir / "CLAUDE.md")
|
|
34
|
+
|
|
35
|
+
# Section specific files
|
|
36
|
+
for header in sections:
|
|
37
|
+
filename = header_to_filename(header)
|
|
38
|
+
generated.add(target_dir / f".cursor/rules/{filename}.mdc")
|
|
39
|
+
generated.add(
|
|
40
|
+
target_dir / f".github/instructions/{filename}.instructions.md"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
except Exception as e:
|
|
44
|
+
log.warning("failed to parse instructions.md", error=str(e))
|
|
45
|
+
|
|
46
|
+
# Check commands.md
|
|
47
|
+
commands_path = target_dir / "commands.md"
|
|
48
|
+
if commands_path.exists():
|
|
49
|
+
try:
|
|
50
|
+
_, sections = parse_sections(commands_path.read_text())
|
|
51
|
+
for header in sections:
|
|
52
|
+
filename = header_to_filename(header)
|
|
53
|
+
generated.add(target_dir / f".cursor/commands/{filename}.md")
|
|
54
|
+
generated.add(target_dir / f".github/prompts/{filename}.prompt.md")
|
|
55
|
+
generated.add(target_dir / f".gemini/commands/{filename}.toml")
|
|
56
|
+
generated.add(target_dir / f".claude/commands/{filename}.md")
|
|
57
|
+
generated.add(target_dir / f".opencode/commands/{filename}.md")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
log.warning("failed to parse commands.md", error=str(e))
|
|
60
|
+
|
|
61
|
+
return {p.resolve() for p in generated}
|
|
11
62
|
|
|
12
63
|
|
|
13
64
|
def find_files_to_delete(
|
|
@@ -38,6 +89,11 @@ def find_files_to_delete(
|
|
|
38
89
|
if file_path.exists() and file_path.is_file():
|
|
39
90
|
files_to_delete.append(file_path)
|
|
40
91
|
|
|
92
|
+
for file_name in config.get("generated_files", []):
|
|
93
|
+
file_path = target_dir / file_name
|
|
94
|
+
if file_path.exists() and file_path.is_file():
|
|
95
|
+
files_to_delete.append(file_path)
|
|
96
|
+
|
|
41
97
|
for file_pattern in config.get("recursive_files", []):
|
|
42
98
|
matching_files = list(target_dir.rglob(file_pattern))
|
|
43
99
|
files_to_delete.extend([f for f in matching_files if f.is_file()])
|
|
@@ -49,12 +105,19 @@ def delete_main(
|
|
|
49
105
|
instruction_types: Annotated[
|
|
50
106
|
list[str] | None,
|
|
51
107
|
typer.Argument(
|
|
52
|
-
help="Types of instructions to delete (cursor, github, gemini, claude,
|
|
108
|
+
help="Types of instructions to delete (cursor, github, gemini, claude, opencode, agents). Deletes everything by default."
|
|
53
109
|
),
|
|
54
110
|
] = None,
|
|
55
111
|
target_dir: Annotated[
|
|
56
112
|
str, typer.Option("--target", "-t", help="Target directory to delete from")
|
|
57
113
|
] = ".",
|
|
114
|
+
everything: Annotated[
|
|
115
|
+
bool,
|
|
116
|
+
typer.Option(
|
|
117
|
+
"--everything",
|
|
118
|
+
help="Delete all instruction files, not just those generated from local sources.",
|
|
119
|
+
),
|
|
120
|
+
] = False,
|
|
58
121
|
yes: Annotated[
|
|
59
122
|
bool,
|
|
60
123
|
typer.Option(
|
|
@@ -64,17 +127,25 @@ def delete_main(
|
|
|
64
127
|
):
|
|
65
128
|
"""Remove downloaded LLM instruction files.
|
|
66
129
|
|
|
67
|
-
This command removes files and directories that were downloaded by the 'download' command
|
|
68
|
-
|
|
130
|
+
This command removes files and directories that were downloaded by the 'download' command
|
|
131
|
+
or generated by the 'explode' command.
|
|
132
|
+
|
|
133
|
+
By default, it ONLY deletes files that correspond to your local 'instructions.md' and
|
|
134
|
+
'commands.md' files. This prevents accidental deletion of manually created files.
|
|
135
|
+
Use --everything to delete all standard instruction files and directories.
|
|
69
136
|
|
|
70
137
|
Examples:
|
|
71
138
|
|
|
72
139
|
\b
|
|
73
|
-
# Delete
|
|
140
|
+
# Delete only generated files (safest, default)
|
|
74
141
|
llm_ide_rules delete
|
|
75
142
|
|
|
76
143
|
\b
|
|
77
|
-
# Delete
|
|
144
|
+
# Delete ALL instruction files (including manual ones)
|
|
145
|
+
llm_ide_rules delete --everything
|
|
146
|
+
|
|
147
|
+
\b
|
|
148
|
+
# Delete only Cursor and Gemini files (but only if generated)
|
|
78
149
|
llm_ide_rules delete cursor gemini
|
|
79
150
|
|
|
80
151
|
\b
|
|
@@ -115,9 +186,39 @@ def delete_main(
|
|
|
115
186
|
instruction_types, target_path
|
|
116
187
|
)
|
|
117
188
|
|
|
189
|
+
skipped_files = []
|
|
190
|
+
|
|
191
|
+
if not everything:
|
|
192
|
+
log.info("filtering files to delete based on local sources")
|
|
193
|
+
generated_files = get_generated_files(target_path)
|
|
194
|
+
|
|
195
|
+
# Expand directories to files for granular filtering
|
|
196
|
+
expanded_files = []
|
|
197
|
+
for d in dirs_to_delete:
|
|
198
|
+
expanded_files.extend([f for f in d.rglob("*") if f.is_file()])
|
|
199
|
+
|
|
200
|
+
all_candidates = files_to_delete + expanded_files
|
|
201
|
+
|
|
202
|
+
# Filter: keep only files that are in the generated set
|
|
203
|
+
# We compare resolved paths to be safe
|
|
204
|
+
files_to_delete = [f for f in all_candidates if f.resolve() in generated_files]
|
|
205
|
+
|
|
206
|
+
# Identify skipped files (candidates that were NOT in generated set)
|
|
207
|
+
skipped_files = [
|
|
208
|
+
f for f in all_candidates if f.resolve() not in generated_files
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
# We are no longer deleting whole directories in safe mode
|
|
212
|
+
dirs_to_delete = []
|
|
213
|
+
|
|
118
214
|
if not dirs_to_delete and not files_to_delete:
|
|
119
215
|
log.info("no files found to delete")
|
|
120
216
|
typer.echo("No matching instruction files found to delete.")
|
|
217
|
+
if skipped_files:
|
|
218
|
+
typer.echo(
|
|
219
|
+
f"\n{len(skipped_files)} files were skipped because they don't match local instructions/commands."
|
|
220
|
+
)
|
|
221
|
+
typer.echo("Use --everything to delete them.")
|
|
121
222
|
return
|
|
122
223
|
|
|
123
224
|
typer.echo("\nThe following files and directories will be deleted:\n")
|
|
@@ -137,6 +238,11 @@ def delete_main(
|
|
|
137
238
|
total_items = len(dirs_to_delete) + len(files_to_delete)
|
|
138
239
|
typer.echo(f"\nTotal: {total_items} items")
|
|
139
240
|
|
|
241
|
+
if skipped_files:
|
|
242
|
+
typer.echo(
|
|
243
|
+
f"\n(Note: {len(skipped_files)} other files will be preserved. Use --everything to delete them)"
|
|
244
|
+
)
|
|
245
|
+
|
|
140
246
|
if not yes:
|
|
141
247
|
typer.echo()
|
|
142
248
|
confirm = typer.confirm("Are you sure you want to delete these files?")
|
|
@@ -46,7 +46,7 @@ def normalize_repo(repo: str) -> str:
|
|
|
46
46
|
# The directories listed here are what gets created by explode and what delete removes.
|
|
47
47
|
INSTRUCTION_TYPES = {
|
|
48
48
|
"cursor": {
|
|
49
|
-
"directories": [".cursor"],
|
|
49
|
+
"directories": [".cursor/rules", ".cursor/commands"],
|
|
50
50
|
"files": [],
|
|
51
51
|
"include_patterns": [],
|
|
52
52
|
},
|
|
@@ -56,22 +56,23 @@ INSTRUCTION_TYPES = {
|
|
|
56
56
|
"include_patterns": [],
|
|
57
57
|
},
|
|
58
58
|
"gemini": {
|
|
59
|
-
"directories": [".gemini"],
|
|
60
|
-
"files": [
|
|
59
|
+
"directories": [".gemini/commands"],
|
|
60
|
+
"files": [],
|
|
61
|
+
"generated_files": [],
|
|
61
62
|
"include_patterns": [],
|
|
62
63
|
},
|
|
63
64
|
"claude": {
|
|
64
|
-
"directories": [".claude"],
|
|
65
|
-
"files": [
|
|
65
|
+
"directories": [".claude/commands"],
|
|
66
|
+
"files": [],
|
|
67
|
+
"generated_files": ["CLAUDE.md"],
|
|
66
68
|
"include_patterns": [],
|
|
67
69
|
},
|
|
68
70
|
"opencode": {
|
|
69
|
-
"directories": [".opencode"],
|
|
71
|
+
"directories": [".opencode/commands"],
|
|
70
72
|
"files": [],
|
|
71
73
|
"include_patterns": [],
|
|
72
74
|
},
|
|
73
|
-
"
|
|
74
|
-
"agents": {"directories": [], "files": [], "recursive_files": ["AGENTS.md"]},
|
|
75
|
+
"agents": {"directories": [], "files": [], "generated_files": ["AGENTS.md"]},
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
# Default types to download when no specific types are specified
|
|
@@ -288,7 +289,7 @@ def download_main(
|
|
|
288
289
|
instruction_types: Annotated[
|
|
289
290
|
list[str] | None,
|
|
290
291
|
typer.Argument(
|
|
291
|
-
help="Types of instructions to download (cursor, github, gemini, claude,
|
|
292
|
+
help="Types of instructions to download (cursor, github, gemini, claude, opencode, agents). Downloads everything by default."
|
|
292
293
|
),
|
|
293
294
|
] = None,
|
|
294
295
|
repo: Annotated[
|
|
@@ -355,7 +356,10 @@ def download_main(
|
|
|
355
356
|
|
|
356
357
|
try:
|
|
357
358
|
# Copy instruction files
|
|
358
|
-
copied_items =
|
|
359
|
+
copied_items = [
|
|
360
|
+
f"Downloaded: {item}"
|
|
361
|
+
for item in copy_instruction_files(repo_dir, instruction_types, target_path)
|
|
362
|
+
]
|
|
359
363
|
|
|
360
364
|
# Check for source files (instructions.md, commands.md) and copy them if available
|
|
361
365
|
# These are needed for 'explode' logic
|
|
@@ -373,7 +377,7 @@ def download_main(
|
|
|
373
377
|
log.info("copying source file", source=str(src), target=str(dst))
|
|
374
378
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
375
379
|
dst.write_bytes(src.read_bytes())
|
|
376
|
-
copied_items.append(source_file)
|
|
380
|
+
copied_items.append(f"Downloaded: {source_file}")
|
|
377
381
|
sources_copied = True
|
|
378
382
|
|
|
379
383
|
# Generate rule files locally for supported agents
|
|
@@ -395,7 +399,7 @@ def download_main(
|
|
|
395
399
|
agent=agent,
|
|
396
400
|
working_dir=target_path,
|
|
397
401
|
)
|
|
398
|
-
copied_items.append(f"
|
|
402
|
+
copied_items.append(f"Generated: {agent} rules")
|
|
399
403
|
except Exception as e:
|
|
400
404
|
log.error("failed to generate rules", agent=agent, error=str(e))
|
|
401
405
|
typer.echo(
|
|
@@ -408,8 +412,23 @@ def download_main(
|
|
|
408
412
|
for item in copied_items:
|
|
409
413
|
typer.echo(f" - {item}")
|
|
410
414
|
else:
|
|
411
|
-
log.
|
|
412
|
-
|
|
415
|
+
log.info("no files were copied or generated")
|
|
416
|
+
|
|
417
|
+
# Build list of expected files
|
|
418
|
+
expected_files = []
|
|
419
|
+
for inst_type in instruction_types:
|
|
420
|
+
config = INSTRUCTION_TYPES[inst_type]
|
|
421
|
+
expected_files.extend(config.get("directories", []))
|
|
422
|
+
expected_files.extend(config.get("files", []))
|
|
423
|
+
expected_files.extend(config.get("recursive_files", []))
|
|
424
|
+
|
|
425
|
+
error_msg = "No matching instruction files found in the repository."
|
|
426
|
+
typer.echo(typer.style(error_msg, fg=typer.colors.RED), err=True)
|
|
427
|
+
|
|
428
|
+
if expected_files:
|
|
429
|
+
typer.echo("\nExpected files/directories:", err=True)
|
|
430
|
+
for expected in expected_files:
|
|
431
|
+
typer.echo(f" - {expected}", err=True)
|
|
413
432
|
|
|
414
433
|
finally:
|
|
415
434
|
# Clean up temporary directory
|
|
@@ -54,9 +54,19 @@ def process_unmapped_as_always_apply(
|
|
|
54
54
|
section_content = replace_header_with_proper_casing(section_content, section_name)
|
|
55
55
|
|
|
56
56
|
cursor_agent.write_rule(
|
|
57
|
-
section_content,
|
|
57
|
+
section_content,
|
|
58
|
+
filename,
|
|
59
|
+
cursor_rules_dir,
|
|
60
|
+
glob_pattern=None,
|
|
61
|
+
description=section_name,
|
|
62
|
+
)
|
|
63
|
+
github_agent.write_rule(
|
|
64
|
+
section_content,
|
|
65
|
+
filename,
|
|
66
|
+
copilot_dir,
|
|
67
|
+
glob_pattern=None,
|
|
68
|
+
description=section_name,
|
|
58
69
|
)
|
|
59
|
-
github_agent.write_rule(section_content, filename, copilot_dir, glob_pattern=None)
|
|
60
70
|
|
|
61
71
|
return True
|
|
62
72
|
|
|
@@ -88,7 +98,14 @@ def explode_implementation(
|
|
|
88
98
|
# Initialize only the agents we need
|
|
89
99
|
agents_to_process = []
|
|
90
100
|
if agent == "all":
|
|
91
|
-
agents_to_process = [
|
|
101
|
+
agents_to_process = [
|
|
102
|
+
"cursor",
|
|
103
|
+
"github",
|
|
104
|
+
"claude",
|
|
105
|
+
"gemini",
|
|
106
|
+
"opencode",
|
|
107
|
+
"agents",
|
|
108
|
+
]
|
|
92
109
|
else:
|
|
93
110
|
agents_to_process = [agent]
|
|
94
111
|
|
|
@@ -103,14 +120,14 @@ def explode_implementation(
|
|
|
103
120
|
# These agents have both rules and commands
|
|
104
121
|
rules_dir = working_dir / agent_instances[agent_name].rules_dir
|
|
105
122
|
commands_dir = working_dir / agent_instances[agent_name].commands_dir
|
|
106
|
-
rules_dir.mkdir(parents=True, exist_ok=True)
|
|
107
|
-
commands_dir.mkdir(parents=True, exist_ok=True)
|
|
108
123
|
agent_dirs[agent_name] = {"rules": rules_dir, "commands": commands_dir}
|
|
109
|
-
|
|
124
|
+
elif agent_instances[agent_name].commands_dir:
|
|
110
125
|
# claude, gemini, and opencode only have commands
|
|
111
126
|
commands_dir = working_dir / agent_instances[agent_name].commands_dir
|
|
112
|
-
commands_dir.mkdir(parents=True, exist_ok=True)
|
|
113
127
|
agent_dirs[agent_name] = {"commands": commands_dir}
|
|
128
|
+
else:
|
|
129
|
+
# agents has neither rules nor commands dirs (only generates root doc)
|
|
130
|
+
agent_dirs[agent_name] = {}
|
|
114
131
|
|
|
115
132
|
input_path = working_dir / input_file
|
|
116
133
|
|
|
@@ -135,7 +152,8 @@ def explode_implementation(
|
|
|
135
152
|
if any(line.strip() for line in general):
|
|
136
153
|
general_header = """
|
|
137
154
|
---
|
|
138
|
-
description:
|
|
155
|
+
description: General Instructions
|
|
156
|
+
globs:
|
|
139
157
|
alwaysApply: true
|
|
140
158
|
---
|
|
141
159
|
"""
|
|
@@ -180,6 +198,7 @@ alwaysApply: true
|
|
|
180
198
|
filename,
|
|
181
199
|
agent_dirs["cursor"]["rules"],
|
|
182
200
|
glob_pattern=None,
|
|
201
|
+
description=section_name,
|
|
183
202
|
)
|
|
184
203
|
elif "github" in agent_instances:
|
|
185
204
|
agent_instances["github"].write_rule(
|
|
@@ -187,6 +206,7 @@ alwaysApply: true
|
|
|
187
206
|
filename,
|
|
188
207
|
agent_dirs["github"]["rules"],
|
|
189
208
|
glob_pattern=None,
|
|
209
|
+
description=section_name,
|
|
190
210
|
)
|
|
191
211
|
elif glob_pattern != "manual":
|
|
192
212
|
# Has glob pattern = file-specific rule
|
|
@@ -196,6 +216,7 @@ alwaysApply: true
|
|
|
196
216
|
filename,
|
|
197
217
|
agent_dirs["cursor"]["rules"],
|
|
198
218
|
glob_pattern,
|
|
219
|
+
description=section_name,
|
|
199
220
|
)
|
|
200
221
|
if "github" in agent_instances:
|
|
201
222
|
agent_instances["github"].write_rule(
|
|
@@ -203,6 +224,7 @@ alwaysApply: true
|
|
|
203
224
|
filename,
|
|
204
225
|
agent_dirs["github"]["rules"],
|
|
205
226
|
glob_pattern,
|
|
227
|
+
description=section_name,
|
|
206
228
|
)
|
|
207
229
|
|
|
208
230
|
# Process commands for all agents
|
|
@@ -210,15 +232,21 @@ alwaysApply: true
|
|
|
210
232
|
command_sections = {}
|
|
211
233
|
if commands_text:
|
|
212
234
|
_, command_sections_data = parse_sections(commands_text)
|
|
213
|
-
|
|
235
|
+
agents_with_commands = [
|
|
236
|
+
agent_instances[name]
|
|
237
|
+
for name in agents_to_process
|
|
238
|
+
if agent_instances[name].commands_dir
|
|
239
|
+
]
|
|
214
240
|
command_dirs = {
|
|
215
|
-
name: agent_dirs[name]["commands"]
|
|
241
|
+
name: agent_dirs[name]["commands"]
|
|
242
|
+
for name in agents_to_process
|
|
243
|
+
if "commands" in agent_dirs[name]
|
|
216
244
|
}
|
|
217
245
|
|
|
218
246
|
for section_name, section_data in command_sections_data.items():
|
|
219
247
|
command_sections[section_name] = section_data.content
|
|
220
248
|
process_command_section(
|
|
221
|
-
section_name, section_data.content,
|
|
249
|
+
section_name, section_data.content, agents_with_commands, command_dirs
|
|
222
250
|
)
|
|
223
251
|
|
|
224
252
|
# Generate root documentation (CLAUDE.md, GEMINI.md, etc.)
|
|
@@ -228,6 +256,7 @@ alwaysApply: true
|
|
|
228
256
|
rules_sections,
|
|
229
257
|
command_sections,
|
|
230
258
|
working_dir,
|
|
259
|
+
section_globs=section_globs,
|
|
231
260
|
)
|
|
232
261
|
|
|
233
262
|
# Build log message and user output based on processed agents
|
|
@@ -239,15 +268,37 @@ alwaysApply: true
|
|
|
239
268
|
log_data[f"{agent_name}_rules"] = str(agent_dirs[agent_name]["rules"])
|
|
240
269
|
log_data[f"{agent_name}_commands"] = str(agent_dirs[agent_name]["commands"])
|
|
241
270
|
created_dirs.append(f".{agent_name}/")
|
|
242
|
-
|
|
271
|
+
elif agent_dirs[agent_name]:
|
|
272
|
+
# Has commands directory
|
|
243
273
|
log_data[f"{agent_name}_commands"] = str(agent_dirs[agent_name]["commands"])
|
|
244
274
|
created_dirs.append(f".{agent_name}/")
|
|
275
|
+
# else: agent has no directories (e.g., agents which only generates root doc)
|
|
245
276
|
|
|
246
|
-
if
|
|
247
|
-
|
|
248
|
-
|
|
277
|
+
if "gemini" in agent_instances:
|
|
278
|
+
if not agent_instances["gemini"].check_agents_md_config(working_dir):
|
|
279
|
+
typer.secho(
|
|
280
|
+
"Warning: Gemini CLI configuration missing for AGENTS.md.",
|
|
281
|
+
fg=typer.colors.YELLOW,
|
|
282
|
+
)
|
|
283
|
+
typer.secho(
|
|
284
|
+
"Run this command to configure it:",
|
|
285
|
+
fg=typer.colors.YELLOW,
|
|
286
|
+
)
|
|
287
|
+
typer.secho(
|
|
288
|
+
" gemini config set agent.instructionFile AGENTS.md",
|
|
289
|
+
fg=typer.colors.YELLOW,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if created_dirs:
|
|
293
|
+
if len(created_dirs) == 1:
|
|
294
|
+
success_msg = f"Created files in {created_dirs[0]} directory"
|
|
295
|
+
typer.echo(typer.style(success_msg, fg=typer.colors.GREEN))
|
|
296
|
+
else:
|
|
297
|
+
success_msg = f"Created files in {', '.join(created_dirs)} directories"
|
|
298
|
+
typer.echo(typer.style(success_msg, fg=typer.colors.GREEN))
|
|
249
299
|
else:
|
|
250
|
-
|
|
300
|
+
# No directories created (e.g., agents that only generate root docs)
|
|
301
|
+
success_msg = "Created root documentation files"
|
|
251
302
|
typer.echo(typer.style(success_msg, fg=typer.colors.GREEN))
|
|
252
303
|
|
|
253
304
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Implode command: Bundle rule files into a single instruction file."""
|
|
2
2
|
|
|
3
|
-
from pathlib import Path
|
|
4
3
|
from typing_extensions import Annotated
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
|
|
8
7
|
from llm_ide_rules.agents import get_agent
|
|
9
8
|
from llm_ide_rules.log import log
|
|
9
|
+
from llm_ide_rules.utils import find_project_root
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def cursor(
|
|
@@ -17,7 +17,7 @@ def cursor(
|
|
|
17
17
|
"""Bundle Cursor rules into instructions.md and commands into commands.md."""
|
|
18
18
|
|
|
19
19
|
agent = get_agent("cursor")
|
|
20
|
-
|
|
20
|
+
base_dir = find_project_root()
|
|
21
21
|
|
|
22
22
|
rules_dir = agent.rules_dir
|
|
23
23
|
if not rules_dir:
|
|
@@ -30,14 +30,14 @@ def cursor(
|
|
|
30
30
|
commands_dir=agent.commands_dir,
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
rules_path =
|
|
33
|
+
rules_path = base_dir / rules_dir
|
|
34
34
|
if not rules_path.exists():
|
|
35
35
|
log.error("cursor rules directory not found", rules_dir=str(rules_path))
|
|
36
36
|
error_msg = f"Cursor rules directory not found: {rules_path}"
|
|
37
37
|
typer.echo(typer.style(error_msg, fg=typer.colors.RED), err=True)
|
|
38
38
|
raise typer.Exit(1)
|
|
39
39
|
|
|
40
|
-
output_path =
|
|
40
|
+
output_path = base_dir / output
|
|
41
41
|
rules_written = agent.bundle_rules(output_path)
|
|
42
42
|
if rules_written:
|
|
43
43
|
success_msg = f"Bundled cursor rules into {output}"
|
|
@@ -46,7 +46,7 @@ def cursor(
|
|
|
46
46
|
output_path.unlink(missing_ok=True)
|
|
47
47
|
log.info("no cursor rules to bundle")
|
|
48
48
|
|
|
49
|
-
commands_output_path =
|
|
49
|
+
commands_output_path = base_dir / "commands.md"
|
|
50
50
|
commands_written = agent.bundle_commands(commands_output_path)
|
|
51
51
|
if commands_written:
|
|
52
52
|
success_msg = "Bundled cursor commands into commands.md"
|
|
@@ -63,7 +63,7 @@ def github(
|
|
|
63
63
|
"""Bundle GitHub instructions into instructions.md and prompts into commands.md."""
|
|
64
64
|
|
|
65
65
|
agent = get_agent("github")
|
|
66
|
-
|
|
66
|
+
base_dir = find_project_root()
|
|
67
67
|
|
|
68
68
|
rules_dir = agent.rules_dir
|
|
69
69
|
if not rules_dir:
|
|
@@ -76,7 +76,7 @@ def github(
|
|
|
76
76
|
prompts_dir=agent.commands_dir,
|
|
77
77
|
)
|
|
78
78
|
|
|
79
|
-
rules_path =
|
|
79
|
+
rules_path = base_dir / rules_dir
|
|
80
80
|
if not rules_path.exists():
|
|
81
81
|
log.error(
|
|
82
82
|
"github instructions directory not found", instructions_dir=str(rules_path)
|
|
@@ -85,7 +85,7 @@ def github(
|
|
|
85
85
|
typer.echo(typer.style(error_msg, fg=typer.colors.RED), err=True)
|
|
86
86
|
raise typer.Exit(1)
|
|
87
87
|
|
|
88
|
-
output_path =
|
|
88
|
+
output_path = base_dir / output
|
|
89
89
|
instructions_written = agent.bundle_rules(output_path)
|
|
90
90
|
if instructions_written:
|
|
91
91
|
success_msg = f"Bundled github instructions into {output}"
|
|
@@ -94,7 +94,7 @@ def github(
|
|
|
94
94
|
output_path.unlink(missing_ok=True)
|
|
95
95
|
log.info("no github instructions to bundle")
|
|
96
96
|
|
|
97
|
-
commands_output_path =
|
|
97
|
+
commands_output_path = base_dir / "commands.md"
|
|
98
98
|
prompts_written = agent.bundle_commands(commands_output_path)
|
|
99
99
|
if prompts_written:
|
|
100
100
|
success_msg = "Bundled github prompts into commands.md"
|
|
@@ -109,7 +109,7 @@ def claude(
|
|
|
109
109
|
"""Bundle Claude Code commands into commands.md."""
|
|
110
110
|
|
|
111
111
|
agent = get_agent("claude")
|
|
112
|
-
|
|
112
|
+
base_dir = find_project_root()
|
|
113
113
|
|
|
114
114
|
commands_dir = agent.commands_dir
|
|
115
115
|
if not commands_dir:
|
|
@@ -121,7 +121,7 @@ def claude(
|
|
|
121
121
|
commands_dir=commands_dir,
|
|
122
122
|
)
|
|
123
123
|
|
|
124
|
-
commands_path =
|
|
124
|
+
commands_path = base_dir / commands_dir
|
|
125
125
|
if not commands_path.exists():
|
|
126
126
|
log.error(
|
|
127
127
|
"claude code commands directory not found", commands_dir=str(commands_path)
|
|
@@ -130,7 +130,7 @@ def claude(
|
|
|
130
130
|
typer.echo(typer.style(error_msg, fg=typer.colors.RED), err=True)
|
|
131
131
|
raise typer.Exit(1)
|
|
132
132
|
|
|
133
|
-
output_path =
|
|
133
|
+
output_path = base_dir / output
|
|
134
134
|
commands_written = agent.bundle_commands(output_path)
|
|
135
135
|
if commands_written:
|
|
136
136
|
success_msg = f"Bundled claude commands into {output}"
|
|
@@ -146,7 +146,7 @@ def gemini(
|
|
|
146
146
|
"""Bundle Gemini CLI commands into commands.md."""
|
|
147
147
|
|
|
148
148
|
agent = get_agent("gemini")
|
|
149
|
-
|
|
149
|
+
base_dir = find_project_root()
|
|
150
150
|
|
|
151
151
|
commands_dir = agent.commands_dir
|
|
152
152
|
if not commands_dir:
|
|
@@ -158,7 +158,7 @@ def gemini(
|
|
|
158
158
|
commands_dir=commands_dir,
|
|
159
159
|
)
|
|
160
160
|
|
|
161
|
-
commands_path =
|
|
161
|
+
commands_path = base_dir / commands_dir
|
|
162
162
|
if not commands_path.exists():
|
|
163
163
|
log.error(
|
|
164
164
|
"gemini cli commands directory not found", commands_dir=str(commands_path)
|
|
@@ -167,7 +167,7 @@ def gemini(
|
|
|
167
167
|
typer.echo(typer.style(error_msg, fg=typer.colors.RED), err=True)
|
|
168
168
|
raise typer.Exit(1)
|
|
169
169
|
|
|
170
|
-
output_path =
|
|
170
|
+
output_path = base_dir / output
|
|
171
171
|
commands_written = agent.bundle_commands(output_path)
|
|
172
172
|
if commands_written:
|
|
173
173
|
success_msg = f"Bundled gemini commands into {output}"
|
|
@@ -183,7 +183,7 @@ def opencode(
|
|
|
183
183
|
"""Bundle OpenCode commands into commands.md."""
|
|
184
184
|
|
|
185
185
|
agent = get_agent("opencode")
|
|
186
|
-
|
|
186
|
+
base_dir = find_project_root()
|
|
187
187
|
|
|
188
188
|
commands_dir = agent.commands_dir
|
|
189
189
|
if not commands_dir:
|
|
@@ -195,7 +195,7 @@ def opencode(
|
|
|
195
195
|
commands_dir=commands_dir,
|
|
196
196
|
)
|
|
197
197
|
|
|
198
|
-
commands_path =
|
|
198
|
+
commands_path = base_dir / commands_dir
|
|
199
199
|
if not commands_path.exists():
|
|
200
200
|
log.error(
|
|
201
201
|
"opencode commands directory not found", commands_dir=str(commands_path)
|
|
@@ -204,7 +204,7 @@ def opencode(
|
|
|
204
204
|
typer.echo(typer.style(error_msg, fg=typer.colors.RED), err=True)
|
|
205
205
|
raise typer.Exit(1)
|
|
206
206
|
|
|
207
|
-
output_path =
|
|
207
|
+
output_path = base_dir / output
|
|
208
208
|
commands_written = agent.bundle_commands(output_path)
|
|
209
209
|
if commands_written:
|
|
210
210
|
success_msg = f"Bundled opencode commands into {output}"
|
llm_ide_rules/commands/mcp.py
CHANGED
|
@@ -23,7 +23,7 @@ def explode(
|
|
|
23
23
|
"all",
|
|
24
24
|
"--agent",
|
|
25
25
|
"-a",
|
|
26
|
-
help="Agent: claude, cursor, gemini, opencode, copilot, or all",
|
|
26
|
+
help="Agent: claude, cursor, gemini, opencode, copilot, vscode, or all",
|
|
27
27
|
),
|
|
28
28
|
) -> None:
|
|
29
29
|
"""Convert unified mcp.json to platform-specific configs."""
|
llm_ide_rules/constants.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Shared constants for explode and implode functionality."""
|
|
2
2
|
|
|
3
|
-
VALID_AGENTS = ["cursor", "github", "claude", "gemini", "opencode", "all"]
|
|
3
|
+
VALID_AGENTS = ["cursor", "github", "claude", "gemini", "opencode", "agents", "all"]
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def header_to_filename(header: str) -> str:
|