-
Notifications
You must be signed in to change notification settings - Fork 88
Expand file tree
/
Copy pathtest_cli_commands.py
More file actions
346 lines (279 loc) · 17.1 KB
/
Copy pathtest_cli_commands.py
File metadata and controls
346 lines (279 loc) · 17.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
import os
import shutil
import re # For checking generated rule file formats
import pytest
# --- Expected directory names in the TARGET project (tmp_target_repo_root) ---
TARGET_PROJECT_RULES_DIR = "project_rules"
TARGET_MEMORY_BANK_DIR = "memory"
TARGET_TOOLS_DIR = "tools"
def test_install_default_rule_set(script_runner, tmp_path):
"""Test `install` with the default rule set ('light-spec')."""
tmp_target_repo_root = tmp_path / "my_project_default_install"
tmp_target_repo_root.mkdir()
result = script_runner(["install"], tmp_target_repo_root)
assert result.returncode == 0, f"Script failed. STDERR:\n{result.stderr}\nSTDOUT:\n{result.stdout}"
# 1. Check presence of core directories in target
assert (tmp_target_repo_root / TARGET_PROJECT_RULES_DIR).is_dir()
assert (tmp_target_repo_root / TARGET_MEMORY_BANK_DIR).is_dir()
assert (tmp_target_repo_root / TARGET_TOOLS_DIR).is_dir()
# 2. Check project_rules content (should be 'light-spec' from the actual package)
project_rules_target = tmp_target_repo_root / TARGET_PROJECT_RULES_DIR
assert (project_rules_target / "01-rules" / "00-meta-rules.md").is_file()
assert (project_rules_target / "02-rules-architect" / "01-plan_v1.md").is_file()
# 3. Check memory_bank content (from actual package)
memory_bank_target = tmp_target_repo_root / TARGET_MEMORY_BANK_DIR
assert (memory_bank_target / "docs" / "architecture_template.md").is_file()
assert (memory_bank_target / "tasks" / "active_context_template.md").is_file()
# 4. Check tools content (from actual package)
tools_target = tmp_target_repo_root / TARGET_TOOLS_DIR
assert (tools_target / "llm_api.py").is_file()
assert (tools_target / "web_scraper.py").is_file()
# 5. Check for generated platform-specific rule directories
assert (tmp_target_repo_root / ".cursor" / "rules").is_dir()
assert (tmp_target_repo_root / ".clinerules").is_dir()
# Check mode-based assistants for multiple modes and a file within a mode
assert (tmp_target_repo_root / ".roo" / "rules").is_dir()
assert (tmp_target_repo_root / ".roo" / "rules-architect").is_dir()
assert (tmp_target_repo_root / ".roo" / "rules" / "00-meta-rules.md").is_file()
assert (tmp_target_repo_root / ".kilocode" / "rules").is_dir()
assert (tmp_target_repo_root / ".kilocode" / "rules-architect").is_dir()
assert (tmp_target_repo_root / ".kilocode" / "rules" / "00-meta-rules.md").is_file()
assert (tmp_target_repo_root / ".windsurf" / "rules").is_dir()
assert (tmp_target_repo_root / "WARP.md").is_file()
gh_copilot_instructions_file = tmp_target_repo_root / ".github" / "copilot-instructions.md"
assert gh_copilot_instructions_file.is_file()
assert (tmp_target_repo_root / "CLAUDE.md").is_file()
assert (tmp_target_repo_root / "AGENTS.md").is_file()
assert (tmp_target_repo_root / ".gemini" / "GEMINI.md").is_file()
# Check content of a generated file to ensure it's from the correct source rules
expected_content = "Meta-Rules for AI Assistant Interaction"
gh_copilot_content = gh_copilot_instructions_file.read_text()
assert expected_content in gh_copilot_content
claude_content = (tmp_target_repo_root / "CLAUDE.md").read_text()
assert expected_content in claude_content
warp_content = (tmp_target_repo_root / "WARP.md").read_text()
assert expected_content in warp_content
# 6. Check for .env.example and requirements.txt
assert (tmp_target_repo_root / ".env.example").is_file()
assert (tmp_target_repo_root / "requirements.txt").is_file()
def test_install_specific_rule_set(script_runner, tmp_path):
"""Test `install --rule-set heavy-spec`."""
tmp_target_repo_root = tmp_path / "my_project_heavy_install"
tmp_target_repo_root.mkdir()
result = script_runner(["install", "--rule-set", "heavy-spec"], tmp_target_repo_root)
assert result.returncode == 0, f"Script failed. STDERR:\n{result.stderr}"
project_rules_target = tmp_target_repo_root / TARGET_PROJECT_RULES_DIR
assert (project_rules_target / "01-rules" / "00-meta-rules.md").is_file()
assert (project_rules_target / "01-rules" / "01-memory.md").is_file()
gh_copilot_content = (tmp_target_repo_root / ".github" / "copilot-instructions.md").read_text()
assert "Meta-Rules for AI Assistant Interaction" in gh_copilot_content
assert (tmp_target_repo_root / "CLAUDE.md").is_file()
assert (tmp_target_repo_root / "AGENTS.md").is_file()
assert (tmp_target_repo_root / ".gemini" / "GEMINI.md").is_file()
assert (tmp_target_repo_root / "WARP.md").is_file()
assert (tmp_target_repo_root / ".kilocode" / "rules").is_dir()
def test_sync_after_manual_project_rules_modification(script_runner, tmp_path):
tmp_target_repo_root = tmp_path / "project_for_sync_test"
tmp_target_repo_root.mkdir()
install_result = script_runner(["install", "--rule-set", "light-spec"], tmp_target_repo_root)
assert install_result.returncode == 0, f"Setup install failed: {install_result.stderr}"
# Modify a rule in the first mode ('rules')
rule_to_modify_1 = tmp_target_repo_root / TARGET_PROJECT_RULES_DIR / "01-rules" / "00-meta-rules.md"
assert rule_to_modify_1.is_file()
modified_content_1 = " *** MODIFIED CONTENT 1 FOR SYNC TEST *** "
rule_to_modify_1.write_text(modified_content_1)
# Modify a rule in the second mode ('rules-architect')
rule_to_modify_2 = tmp_target_repo_root / TARGET_PROJECT_RULES_DIR / "02-rules-architect" / "01-plan_v1.md"
assert rule_to_modify_2.is_file()
modified_content_2 = " *** MODIFIED CONTENT 2 FOR SYNC TEST *** "
rule_to_modify_2.write_text(modified_content_2)
result = script_runner(["sync"], tmp_target_repo_root)
assert result.returncode == 0, f"Sync script failed. STDERR:\n{result.stderr}"
# --- Assertions for concatenated files ---
gh_copilot_file_path = tmp_target_repo_root / ".github" / "copilot-instructions.md"
claude_path = tmp_target_repo_root / "CLAUDE.md"
codex_path = tmp_target_repo_root / "AGENTS.md"
gemini_path = tmp_target_repo_root / ".gemini" / "GEMINI.md"
warp_path = tmp_target_repo_root / "WARP.md"
for path in [gh_copilot_file_path, claude_path, codex_path, gemini_path, warp_path]:
assert path.is_file()
content = path.read_text()
assert modified_content_1 in content
assert modified_content_2 in content
# --- Assertions for first modified rule in mode-based assistants ---
roo_path_1 = tmp_target_repo_root / ".roo" / "rules" / "00-meta-rules.md"
kilocode_path_1 = tmp_target_repo_root / ".kilocode" / "rules" / "00-meta-rules.md"
assert roo_path_1.is_file()
assert kilocode_path_1.is_file()
assert modified_content_1 in roo_path_1.read_text()
assert modified_content_1 in kilocode_path_1.read_text()
# --- Assertions for second modified rule in mode-based assistants ---
roo_path_2 = tmp_target_repo_root / ".roo" / "rules-architect" / "01-plan_v1.md"
kilocode_path_2 = tmp_target_repo_root / ".kilocode" / "rules-architect" / "01-plan_v1.md"
assert roo_path_2.is_file()
assert kilocode_path_2.is_file()
assert modified_content_2 in roo_path_2.read_text()
assert modified_content_2 in kilocode_path_2.read_text()
def test_clean_rules_removes_rules_and_generated_keeps_memory_tools(script_runner, tmp_path):
tmp_target_repo_root = tmp_path / "project_for_clean_rules"
tmp_target_repo_root.mkdir()
install_result = script_runner(["install"], tmp_target_repo_root)
assert install_result.returncode == 0, f"Setup install failed: {install_result.stderr}"
result = script_runner(["clean-rules"], tmp_target_repo_root)
assert result.returncode == 0, f"clean-rules script failed. STDERR:\n{result.stderr}"
assert not (tmp_target_repo_root / TARGET_PROJECT_RULES_DIR).exists()
assert not (tmp_target_repo_root / ".cursor").exists()
assert not (tmp_target_repo_root / ".windsurf").exists()
assert not (tmp_target_repo_root / ".clinerules").exists()
assert not (tmp_target_repo_root / ".roo").exists()
assert not (tmp_target_repo_root / ".kilocode").exists()
assert not (tmp_target_repo_root / "WARP.md").exists()
assert not (tmp_target_repo_root / ".github").exists()
assert not (tmp_target_repo_root / "CLAUDE.md").exists()
assert not (tmp_target_repo_root / "AGENTS.md").exists()
assert not (tmp_target_repo_root / ".gemini").exists()
assert (tmp_target_repo_root / TARGET_MEMORY_BANK_DIR).is_dir()
assert (tmp_target_repo_root / TARGET_TOOLS_DIR).is_dir()
assert (tmp_target_repo_root / TARGET_MEMORY_BANK_DIR / "docs" / "architecture_template.md").is_file()
def test_clean_all_with_confirmation_yes(script_runner, tmp_path):
tmp_target_repo_root = tmp_path / "project_for_clean_all_yes"
tmp_target_repo_root.mkdir()
install_result = script_runner(["install"], tmp_target_repo_root)
assert install_result.returncode == 0, f"Setup install failed: {install_result.stderr}"
result = script_runner(["clean-all"], tmp_target_repo_root, confirm_input="yes")
assert result.returncode == 0, f"clean-all script failed. STDERR:\n{result.stderr}"
assert not (tmp_target_repo_root / TARGET_PROJECT_RULES_DIR).exists()
assert not (tmp_target_repo_root / TARGET_MEMORY_BANK_DIR).exists()
assert not (tmp_target_repo_root / TARGET_TOOLS_DIR).exists()
assert not (tmp_target_repo_root / ".cursor").exists()
assert not (tmp_target_repo_root / ".clinerules").exists()
assert not (tmp_target_repo_root / ".roo").exists()
assert not (tmp_target_repo_root / ".kilocode").exists()
assert not (tmp_target_repo_root / "WARP.md").exists()
assert not (tmp_target_repo_root / ".windsurf").exists()
assert not (tmp_target_repo_root / ".github").exists()
assert not (tmp_target_repo_root / "CLAUDE.md").exists()
assert not (tmp_target_repo_root / "AGENTS.md").exists()
assert not (tmp_target_repo_root / ".gemini").exists()
assert not (tmp_target_repo_root / ".env.example").exists()
assert not (tmp_target_repo_root / "requirements.txt").exists()
def test_clean_all_with_confirmation_no(script_runner, tmp_path):
tmp_target_repo_root = tmp_path / "project_for_clean_all_no"
tmp_target_repo_root.mkdir()
install_result = script_runner(["install"], tmp_target_repo_root)
assert install_result.returncode == 0, f"Setup install failed: {install_result.stderr}"
result = script_runner(["clean-all"], tmp_target_repo_root, confirm_input="no")
assert result.returncode == 0, f"clean-all script failed. STDERR:\n{result.stderr}"
assert (tmp_target_repo_root / TARGET_PROJECT_RULES_DIR).is_dir()
assert (tmp_target_repo_root / TARGET_MEMORY_BANK_DIR).is_dir()
assert (tmp_target_repo_root / ".env.example").is_file()
assert (tmp_target_repo_root / "requirements.txt").is_file()
assert (tmp_target_repo_root / ".windsurf" / "rules").is_dir()
assert (tmp_target_repo_root / "CLAUDE.md").is_file()
assert (tmp_target_repo_root / "AGENTS.md").is_file()
assert (tmp_target_repo_root / ".gemini" / "GEMINI.md").is_file()
assert (tmp_target_repo_root / "WARP.md").is_file()
assert "Clean-all operation cancelled by user." in result.stdout
def test_list_rules(script_runner):
"""Test the `list-rules` command."""
result = script_runner(["list-rules"])
assert result.returncode == 0, f"Script failed. STDERR:\n{result.stderr}\nSTDOUT:\n{result.stdout}"
stdout = result.stdout
assert "Available rule sets:" in stdout
assert "- heavy-spec" in stdout
assert "- light-spec" in stdout
assert "https://github.com/botingw/rulebook-ai/wiki/Ratings-%26-Reviews-(Rulesets)" in stdout
def test_install_with_specific_assistant_flags(script_runner, tmp_path):
"""Test install with specific assistant flags."""
tmp_target_repo_root = tmp_path / "my_project_windsurf_only"
tmp_target_repo_root.mkdir()
# Test --windsurf flag
result = script_runner(["install", "--windsurf"], tmp_target_repo_root)
assert result.returncode == 0, f"Script failed. STDERR:\n{result.stderr}\nSTDOUT:\n{result.stdout}"
# Should create windsurf directory
assert (tmp_target_repo_root / ".windsurf" / "rules").is_dir()
# Should NOT create other assistant directories when specific flag is used
assert not (tmp_target_repo_root / ".cursor").exists()
assert not (tmp_target_repo_root / ".clinerules").exists()
assert not (tmp_target_repo_root / ".roo").exists()
assert not (tmp_target_repo_root / ".kilocode").exists()
assert not (tmp_target_repo_root / "WARP.md").exists()
assert not (tmp_target_repo_root / ".github").exists()
assert not (tmp_target_repo_root / "CLAUDE.md").exists()
assert not (tmp_target_repo_root / "AGENTS.md").exists()
assert not (tmp_target_repo_root / ".gemini").exists()
# Should still create generic directories
assert (tmp_target_repo_root / TARGET_PROJECT_RULES_DIR).is_dir()
assert (tmp_target_repo_root / TARGET_MEMORY_BANK_DIR).is_dir()
assert (tmp_target_repo_root / TARGET_TOOLS_DIR).is_dir()
def test_install_with_all_assistants_flag(script_runner, tmp_path):
"""Test install with --all-assistants flag."""
tmp_target_repo_root = tmp_path / "my_project_all_assistants"
tmp_target_repo_root.mkdir()
result = script_runner(["install", "--all"], tmp_target_repo_root)
assert result.returncode == 0, f"Script failed. STDERR:\n{result.stderr}\nSTDOUT:\n{result.stdout}"
# Should create ALL assistant directories
assert (tmp_target_repo_root / ".cursor" / "rules").is_dir()
assert (tmp_target_repo_root / ".clinerules").is_dir()
assert (tmp_target_repo_root / ".roo" / "rules").is_dir()
assert (tmp_target_repo_root / ".kilocode" / "rules").is_dir()
assert (tmp_target_repo_root / ".windsurf" / "rules").is_dir()
assert (tmp_target_repo_root / "WARP.md").is_file()
assert (tmp_target_repo_root / ".github" / "copilot-instructions.md").is_file()
assert (tmp_target_repo_root / "CLAUDE.md").is_file()
assert (tmp_target_repo_root / "AGENTS.md").is_file()
assert (tmp_target_repo_root / ".gemini" / "GEMINI.md").is_file()
def test_sync_with_specific_assistant_flags(script_runner, tmp_path):
"""Test sync with specific assistant flags."""
tmp_target_repo_root = tmp_path / "project_for_sync_assistant_test"
tmp_target_repo_root.mkdir()
# First install with all assistants
script_runner(["install"], tmp_target_repo_root)
# Modify project rules
rule_to_modify = tmp_target_repo_root / TARGET_PROJECT_RULES_DIR / "01-rules" / "00-meta-rules.md"
modified_content = " *** SYNC TEST WITH WINDSURF FLAG *** "
rule_to_modify.write_text(modified_content)
# Sync only windsurf
result = script_runner(["sync", "--windsurf"], tmp_target_repo_root)
assert result.returncode == 0, f"Sync script failed. STDERR:\n{result.stderr}"
# Check that windsurf was synced
synced_windsurf_rule_file = tmp_target_repo_root / ".windsurf" / "rules" / "01-meta-rules.md"
assert synced_windsurf_rule_file.is_file()
assert modified_content in synced_windsurf_rule_file.read_text()
def test_sync_detects_existing_assistants(script_runner, tmp_path):
"""Test that sync without flags detects existing assistant directories."""
tmp_target_repo_root = tmp_path / "project_for_sync_detection_test"
tmp_target_repo_root.mkdir()
# Install only windsurf initially
script_runner(["install", "--windsurf"], tmp_target_repo_root)
# Modify project rules
rule_to_modify = tmp_target_repo_root / TARGET_PROJECT_RULES_DIR / "01-rules" / "00-meta-rules.md"
modified_content = " *** AUTO-DETECTION SYNC TEST *** "
rule_to_modify.write_text(modified_content)
# Sync without flags should now sync ALL assistants, not just existing ones
result = script_runner(["sync"], tmp_target_repo_root)
assert result.returncode == 0, f"Sync script failed. STDERR:\n{result.stderr}"
# Check that windsurf was synced
synced_windsurf_rule_file = tmp_target_repo_root / ".windsurf" / "rules" / "01-meta-rules.md"
assert synced_windsurf_rule_file.is_file()
assert modified_content in synced_windsurf_rule_file.read_text()
# Check that another assistant (e.g., cursor) was ALSO synced
synced_cursor_rule_file = tmp_target_repo_root / ".cursor" / "rules" / "01-meta-rules.mdc"
assert synced_cursor_rule_file.is_file()
assert modified_content in synced_cursor_rule_file.read_text()
claude_file = tmp_target_repo_root / "CLAUDE.md"
assert claude_file.is_file()
assert modified_content in claude_file.read_text()
def test_bug_report_command(script_runner):
"""Verify the bug-report command opens the issue tracker URL."""
result = script_runner(["bug-report"])
assert result.returncode == 0, f"Command failed. STDERR:\n{result.stderr}"
assert "https://github.com/botingw/rulebook-ai/issues" in result.stdout
def test_rate_ruleset_command(script_runner):
"""Verify the rate-ruleset command opens the ratings page URL."""
result = script_runner(["rate-ruleset"])
assert result.returncode == 0, f"Command failed. STDERR:\n{result.stderr}"
assert (
"https://github.com/botingw/rulebook-ai/wiki/Ratings-%26-Reviews-(Rulesets)"
in result.stdout
)