aru-code 0.13.3__tar.gz → 0.14.0__tar.gz
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.
- {aru_code-0.13.3/aru_code.egg-info → aru_code-0.14.0}/PKG-INFO +1 -1
- aru_code-0.14.0/aru/__init__.py +1 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/agents/base.py +18 -1
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/context.py +101 -14
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/tools/codebase.py +45 -2
- {aru_code-0.13.3 → aru_code-0.14.0/aru_code.egg-info}/PKG-INFO +1 -1
- {aru_code-0.13.3 → aru_code-0.14.0}/pyproject.toml +1 -1
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_codebase.py +5 -5
- aru_code-0.13.3/aru/__init__.py +0 -1
- {aru_code-0.13.3 → aru_code-0.14.0}/LICENSE +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/README.md +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/agent_factory.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/agents/__init__.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/agents/executor.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/agents/planner.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/cli.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/commands.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/completers.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/config.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/display.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/permissions.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/providers.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/runner.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/runtime.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/session.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/tools/__init__.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/tools/ast_tools.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/tools/gitignore.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/tools/mcp_client.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/tools/ranker.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru/tools/tasklist.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru_code.egg-info/SOURCES.txt +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru_code.egg-info/dependency_links.txt +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru_code.egg-info/entry_points.txt +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru_code.egg-info/requires.txt +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/aru_code.egg-info/top_level.txt +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/setup.cfg +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_agents_base.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_ast_tools.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_cli.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_cli_advanced.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_cli_base.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_cli_completers.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_cli_new.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_cli_run_cli.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_cli_session.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_cli_shell.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_config.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_context.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_executor.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_gitignore.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_main.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_mcp_client.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_permissions.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_planner.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_providers.py +0 -0
- {aru_code-0.13.3 → aru_code-0.14.0}/tests/test_ranker.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.14.0"
|
|
@@ -3,9 +3,26 @@
|
|
|
3
3
|
# Common rules shared across all agents (planner, executor, general).
|
|
4
4
|
# Each agent appends its role-specific instructions to this base.
|
|
5
5
|
BASE_INSTRUCTIONS = """\
|
|
6
|
-
|
|
6
|
+
## Output rules — CRITICAL for token efficiency
|
|
7
|
+
|
|
8
|
+
Minimize output tokens. Your responses should be fewer than 4 lines unless the user \
|
|
9
|
+
asks for detail or you are writing code. One word answers are best when they suffice.
|
|
10
|
+
|
|
11
|
+
Do NOT add unnecessary preamble or postamble. Avoid introductions, conclusions, \
|
|
12
|
+
and explanations of what you will do or just did. Do not add code explanation \
|
|
13
|
+
summaries unless the user requests them. Only address the specific query or task at hand.
|
|
14
|
+
|
|
7
15
|
NEVER write narration before calling tools. Do NOT say "I will analyze...", "Let me check...", \
|
|
8
16
|
"Now I will...", or any similar preamble. Call the tool immediately and silently.
|
|
17
|
+
|
|
18
|
+
Examples of ideal responses:
|
|
19
|
+
- user: "2 + 2" → assistant: "4"
|
|
20
|
+
- user: "is 11 prime?" → assistant: "Yes"
|
|
21
|
+
- user: "what command lists files?" → assistant: "ls"
|
|
22
|
+
- user: "fix the typo in line 5" → [call edit_file immediately, no narration]
|
|
23
|
+
|
|
24
|
+
## Scope rules
|
|
25
|
+
|
|
9
26
|
NEVER create documentation files (*.md) unless the user explicitly asks for them.
|
|
10
27
|
Focus on writing working code, not documentation.
|
|
11
28
|
Deliver EXACTLY what was asked — no more, no less. \
|
|
@@ -18,12 +18,17 @@ PRUNED_PLACEHOLDER = "[previous output cleared to save context]"
|
|
|
18
18
|
PRUNE_USER_MSG_THRESHOLD = 2_000 # ~570 tokens — catches @file mentions
|
|
19
19
|
# How many chars to keep from the start of a pruned user message
|
|
20
20
|
PRUNE_USER_MSG_KEEP = 500 # ~140 tokens — enough to understand the request
|
|
21
|
+
# Minimum number of recent user turns always protected (regardless of char budget)
|
|
22
|
+
PRUNE_PROTECT_TURNS = 2
|
|
23
|
+
# Tool result markers that should never be pruned (critical context)
|
|
24
|
+
PRUNE_PROTECTED_MARKERS = {"[SubAgent-", "delegate_task"}
|
|
21
25
|
|
|
22
26
|
# Truncation: universal limits for any tool output
|
|
23
27
|
TRUNCATE_MAX_LINES = 500
|
|
24
28
|
TRUNCATE_MAX_BYTES = 20 * 1024 # 20 KB
|
|
25
29
|
TRUNCATE_KEEP_START = 350 # lines to keep from the start
|
|
26
30
|
TRUNCATE_KEEP_END = 100 # lines to keep from the end
|
|
31
|
+
TRUNCATE_MAX_LINE_LENGTH = 2000 # chars per individual line (prevents minified files)
|
|
27
32
|
|
|
28
33
|
# Compaction: trigger when per-run input tokens exceed this fraction of model limit
|
|
29
34
|
COMPACTION_THRESHOLD_RATIO = 0.85
|
|
@@ -70,15 +75,26 @@ MODEL_CONTEXT_LIMITS: dict[str, int] = {
|
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
COMPACTION_TEMPLATE = """\
|
|
73
|
-
Summarize this conversation
|
|
74
|
-
|
|
75
|
-
2. **Key decisions**: Important choices made during the conversation
|
|
76
|
-
3. **Discoveries**: What was learned about the codebase or problem
|
|
77
|
-
4. **Accomplished**: What has been done so far (be specific about files changed)
|
|
78
|
-
5. **Relevant files**: File paths that are important for continuing the work
|
|
79
|
-
6. **Next steps**: What remains to be done
|
|
78
|
+
Summarize this conversation into the EXACT sections below. Be concise but complete — \
|
|
79
|
+
this summary replaces the full conversation history. Output ONLY these sections:
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
## Goal
|
|
82
|
+
What the user is trying to accomplish (1-2 sentences).
|
|
83
|
+
|
|
84
|
+
## Instructions
|
|
85
|
+
Important instructions or preferences the user stated (bullet list). \
|
|
86
|
+
If none, write "None stated."
|
|
87
|
+
|
|
88
|
+
## Discoveries
|
|
89
|
+
Notable things learned about the codebase, bugs, or architecture (bullet list). \
|
|
90
|
+
If none, write "None."
|
|
91
|
+
|
|
92
|
+
## Accomplished
|
|
93
|
+
What was done so far — be specific about files created/changed and functions added/modified. \
|
|
94
|
+
List what is in progress and what remains (bullet list).
|
|
95
|
+
|
|
96
|
+
## Relevant files / directories
|
|
97
|
+
Structured list of file paths relevant to continuing the work (one per line)."""
|
|
82
98
|
|
|
83
99
|
|
|
84
100
|
# ── Layer 1: Pruning ──────────────────────────────────────────────
|
|
@@ -104,9 +120,14 @@ def prune_history(
|
|
|
104
120
|
Walks backward through history, protecting the most recent content
|
|
105
121
|
(scaled to the model's context size). Older messages beyond that
|
|
106
122
|
budget are pruned:
|
|
107
|
-
- Assistant messages: replaced entirely with placeholder
|
|
123
|
+
- Assistant messages: replaced entirely with placeholder (unless protected)
|
|
108
124
|
- User messages over PRUNE_USER_MSG_THRESHOLD: truncated to first N chars
|
|
109
125
|
|
|
126
|
+
Protection layers:
|
|
127
|
+
1. Turn-based: last PRUNE_PROTECT_TURNS user turns always kept
|
|
128
|
+
2. Char-based: recent content within the protection window
|
|
129
|
+
3. Content-based: messages containing PRUNE_PROTECTED_MARKERS never pruned
|
|
130
|
+
|
|
110
131
|
Returns a new list (does not mutate the input).
|
|
111
132
|
"""
|
|
112
133
|
if len(history) <= 2:
|
|
@@ -121,6 +142,18 @@ def prune_history(
|
|
|
121
142
|
if total_chars < protect_chars + PRUNE_MINIMUM_CHARS:
|
|
122
143
|
return list(history)
|
|
123
144
|
|
|
145
|
+
# Identify indices of last N user turns (always protected)
|
|
146
|
+
turn_protected: set[int] = set()
|
|
147
|
+
user_turns_seen = 0
|
|
148
|
+
for i in range(len(history) - 1, -1, -1):
|
|
149
|
+
if history[i]["role"] == "user":
|
|
150
|
+
user_turns_seen += 1
|
|
151
|
+
if user_turns_seen <= PRUNE_PROTECT_TURNS:
|
|
152
|
+
turn_protected.add(i)
|
|
153
|
+
# Also protect the assistant response right after this user turn
|
|
154
|
+
if i + 1 < len(history):
|
|
155
|
+
turn_protected.add(i + 1)
|
|
156
|
+
|
|
124
157
|
# Walk backward, protecting recent content
|
|
125
158
|
result = list(history)
|
|
126
159
|
protected = 0
|
|
@@ -129,10 +162,20 @@ def prune_history(
|
|
|
129
162
|
msg = result[i]
|
|
130
163
|
msg_len = len(msg["content"])
|
|
131
164
|
|
|
165
|
+
# Turn-based protection: never prune last N user turns
|
|
166
|
+
if i in turn_protected:
|
|
167
|
+
protected += msg_len
|
|
168
|
+
continue
|
|
169
|
+
|
|
132
170
|
if protected + msg_len <= protect_chars:
|
|
133
171
|
# Still within protection window
|
|
134
172
|
protected += msg_len
|
|
135
173
|
else:
|
|
174
|
+
# Check protected markers before pruning
|
|
175
|
+
if any(marker in msg["content"] for marker in PRUNE_PROTECTED_MARKERS):
|
|
176
|
+
protected += msg_len
|
|
177
|
+
continue
|
|
178
|
+
|
|
136
179
|
# Beyond protection window — prune
|
|
137
180
|
if msg["role"] == "assistant":
|
|
138
181
|
if msg["content"] != PRUNED_PLACEHOLDER:
|
|
@@ -147,11 +190,36 @@ def prune_history(
|
|
|
147
190
|
|
|
148
191
|
# ── Layer 2: Truncation ───────────────────────────────────────────
|
|
149
192
|
|
|
193
|
+
def _truncate_long_lines(lines: list[str]) -> list[str]:
|
|
194
|
+
"""Truncate individual lines that exceed MAX_LINE_LENGTH.
|
|
195
|
+
|
|
196
|
+
Prevents minified JS/CSS or log lines from consuming massive tokens.
|
|
197
|
+
"""
|
|
198
|
+
result = []
|
|
199
|
+
for line in lines:
|
|
200
|
+
if len(line) > TRUNCATE_MAX_LINE_LENGTH:
|
|
201
|
+
result.append(
|
|
202
|
+
line[:TRUNCATE_MAX_LINE_LENGTH]
|
|
203
|
+
+ f"... (line truncated to {TRUNCATE_MAX_LINE_LENGTH} chars)\n"
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
result.append(line)
|
|
207
|
+
return result
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
_TRUNCATION_HINT = (
|
|
211
|
+
"\n[Hint: Use grep_search to find specific content, or read_file with "
|
|
212
|
+
"start_line/end_line for incremental reading. "
|
|
213
|
+
"For large exploration tasks, use delegate_task to keep your context clean.]"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
150
217
|
def truncate_output(text: str) -> str:
|
|
151
218
|
"""Universal truncation for tool outputs.
|
|
152
219
|
|
|
153
220
|
Caps output at TRUNCATE_MAX_BYTES / TRUNCATE_MAX_LINES, keeping the
|
|
154
221
|
start and end with a middle marker showing what was cut.
|
|
222
|
+
Also truncates individual lines exceeding TRUNCATE_MAX_LINE_LENGTH.
|
|
155
223
|
"""
|
|
156
224
|
if not text:
|
|
157
225
|
return text
|
|
@@ -161,8 +229,11 @@ def truncate_output(text: str) -> str:
|
|
|
161
229
|
lines = text.splitlines(keepends=True)
|
|
162
230
|
line_count = len(lines)
|
|
163
231
|
|
|
232
|
+
# Truncate individual long lines first
|
|
233
|
+
lines = _truncate_long_lines(lines)
|
|
234
|
+
|
|
164
235
|
if byte_len <= TRUNCATE_MAX_BYTES and line_count <= TRUNCATE_MAX_LINES:
|
|
165
|
-
return
|
|
236
|
+
return "".join(lines)
|
|
166
237
|
|
|
167
238
|
# Truncate by lines
|
|
168
239
|
if line_count > TRUNCATE_MAX_LINES:
|
|
@@ -171,8 +242,8 @@ def truncate_output(text: str) -> str:
|
|
|
171
242
|
omitted = line_count - TRUNCATE_KEEP_START - TRUNCATE_KEEP_END
|
|
172
243
|
return (
|
|
173
244
|
"".join(head)
|
|
174
|
-
+ f"\n\n[... {omitted:,} lines omitted ({line_count:,} total)
|
|
175
|
-
|
|
245
|
+
+ f"\n\n[... {omitted:,} lines omitted ({line_count:,} total)]"
|
|
246
|
+
+ _TRUNCATION_HINT + "\n\n"
|
|
176
247
|
+ "".join(tail)
|
|
177
248
|
)
|
|
178
249
|
|
|
@@ -190,7 +261,8 @@ def truncate_output(text: str) -> str:
|
|
|
190
261
|
return (
|
|
191
262
|
"".join(kept_lines)
|
|
192
263
|
+ f"\n\n[... truncated at ~{TRUNCATE_MAX_BYTES // 1024}KB — "
|
|
193
|
-
f"{remaining:,} more lines
|
|
264
|
+
f"{remaining:,} more lines]"
|
|
265
|
+
+ _TRUNCATION_HINT + "\n"
|
|
194
266
|
)
|
|
195
267
|
|
|
196
268
|
|
|
@@ -287,7 +359,7 @@ def apply_compaction(
|
|
|
287
359
|
|
|
288
360
|
Uses the same protection window as pruning: recent messages within
|
|
289
361
|
the window are preserved as-is, older messages are replaced by a
|
|
290
|
-
compaction summary.
|
|
362
|
+
compaction summary. Replays the last user message to maintain continuity.
|
|
291
363
|
"""
|
|
292
364
|
_, recent = _split_history(history, model_id)
|
|
293
365
|
|
|
@@ -296,6 +368,21 @@ def apply_compaction(
|
|
|
296
368
|
]
|
|
297
369
|
compacted.extend(recent)
|
|
298
370
|
|
|
371
|
+
# Replay: ensure the last message is from the user so the LLM continues naturally
|
|
372
|
+
if not compacted or compacted[-1]["role"] != "user":
|
|
373
|
+
# Find last user message in original history for replay
|
|
374
|
+
last_user = None
|
|
375
|
+
for msg in reversed(history):
|
|
376
|
+
if msg["role"] == "user":
|
|
377
|
+
last_user = msg["content"]
|
|
378
|
+
break
|
|
379
|
+
if last_user:
|
|
380
|
+
# Truncate replayed message to avoid re-bloating context
|
|
381
|
+
replay = last_user[:1000] if len(last_user) > 1000 else last_user
|
|
382
|
+
compacted.append({"role": "user", "content": replay})
|
|
383
|
+
else:
|
|
384
|
+
compacted.append({"role": "user", "content": "Continue if you have next steps, or stop and ask for clarification."})
|
|
385
|
+
|
|
299
386
|
return compacted
|
|
300
387
|
|
|
301
388
|
|
|
@@ -321,6 +321,35 @@ def write_files(file_list: list[dict]) -> str:
|
|
|
321
321
|
return "\n".join(parts) or "No files to write."
|
|
322
322
|
|
|
323
323
|
|
|
324
|
+
def _compact_diff(old_string: str, new_string: str, file_path: str = "") -> str:
|
|
325
|
+
"""Generate a compact unified diff string for the LLM context.
|
|
326
|
+
|
|
327
|
+
Returns only the changed lines (not the full file), saving tokens while
|
|
328
|
+
giving the LLM enough context to continue working.
|
|
329
|
+
"""
|
|
330
|
+
old_lines = old_string.splitlines(keepends=True)
|
|
331
|
+
new_lines = new_string.splitlines(keepends=True)
|
|
332
|
+
# Ensure trailing newlines for clean diff
|
|
333
|
+
if old_lines and not old_lines[-1].endswith("\n"):
|
|
334
|
+
old_lines[-1] += "\n"
|
|
335
|
+
if new_lines and not new_lines[-1].endswith("\n"):
|
|
336
|
+
new_lines[-1] += "\n"
|
|
337
|
+
|
|
338
|
+
import difflib
|
|
339
|
+
diff_lines = list(difflib.unified_diff(
|
|
340
|
+
old_lines, new_lines,
|
|
341
|
+
fromfile=file_path, tofile=file_path,
|
|
342
|
+
lineterm="",
|
|
343
|
+
))
|
|
344
|
+
if not diff_lines:
|
|
345
|
+
return ""
|
|
346
|
+
# Cap diff output to avoid huge diffs bloating context
|
|
347
|
+
MAX_DIFF_LINES = 40
|
|
348
|
+
if len(diff_lines) > MAX_DIFF_LINES:
|
|
349
|
+
return "\n".join(diff_lines[:MAX_DIFF_LINES]) + f"\n... ({len(diff_lines) - MAX_DIFF_LINES} more diff lines)"
|
|
350
|
+
return "\n".join(diff_lines)
|
|
351
|
+
|
|
352
|
+
|
|
324
353
|
def edit_file(file_path: str, old_string: str, new_string: str) -> str:
|
|
325
354
|
"""Replace an exact string in a file. The old_string must appear exactly once.
|
|
326
355
|
|
|
@@ -347,7 +376,12 @@ def edit_file(file_path: str, old_string: str, new_string: str) -> str:
|
|
|
347
376
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
348
377
|
f.write(new_content)
|
|
349
378
|
_notify_file_mutation()
|
|
350
|
-
|
|
379
|
+
|
|
380
|
+
# Return compact diff instead of just success message
|
|
381
|
+
diff_text = _compact_diff(old_string, new_string, file_path)
|
|
382
|
+
if diff_text:
|
|
383
|
+
return f"Edited {file_path}\n{diff_text}"
|
|
384
|
+
return f"Edited {file_path}"
|
|
351
385
|
except FileNotFoundError:
|
|
352
386
|
return f"Error: File not found: {file_path}"
|
|
353
387
|
except Exception as e:
|
|
@@ -423,7 +457,16 @@ def edit_files(edits: list[dict]) -> str:
|
|
|
423
457
|
if results:
|
|
424
458
|
_notify_file_mutation()
|
|
425
459
|
unique = list(dict.fromkeys(results)) # preserve order, dedupe
|
|
426
|
-
parts.append(f"
|
|
460
|
+
parts.append(f"Applied {len(results)} edits across {len(unique)} files: {', '.join(unique)}")
|
|
461
|
+
# Append compact diffs for each edit
|
|
462
|
+
for entry in edits:
|
|
463
|
+
old = entry.get("old_string", "")
|
|
464
|
+
new = entry.get("new_string", "")
|
|
465
|
+
path = entry.get("path", "")
|
|
466
|
+
if old and path in written:
|
|
467
|
+
diff_text = _compact_diff(old, new, path)
|
|
468
|
+
if diff_text:
|
|
469
|
+
parts.append(diff_text)
|
|
427
470
|
if errors:
|
|
428
471
|
parts.append("\n".join(errors))
|
|
429
472
|
return "\n".join(parts) or "No edits to apply."
|
|
@@ -209,7 +209,7 @@ def test_edit_file_basic(tmp_path):
|
|
|
209
209
|
finally:
|
|
210
210
|
set_skip_permissions(False)
|
|
211
211
|
|
|
212
|
-
assert "
|
|
212
|
+
assert "Edited" in result
|
|
213
213
|
assert f.read_text() == "def hello():\n return 'earth'\n"
|
|
214
214
|
|
|
215
215
|
|
|
@@ -234,7 +234,7 @@ def test_edit_file_search_replace(tmp_path):
|
|
|
234
234
|
finally:
|
|
235
235
|
set_skip_permissions(False)
|
|
236
236
|
|
|
237
|
-
assert "
|
|
237
|
+
assert "Edited" in result
|
|
238
238
|
updated = f.read_text()
|
|
239
239
|
assert "DB_HOST = 'production.example.com'" in updated
|
|
240
240
|
assert "DB_PORT = 5433" in updated
|
|
@@ -427,7 +427,7 @@ class TestEditFiles:
|
|
|
427
427
|
finally:
|
|
428
428
|
set_skip_permissions(False)
|
|
429
429
|
|
|
430
|
-
assert "
|
|
430
|
+
assert "Applied" in result or "Edited" in result
|
|
431
431
|
assert f1.read_text() == "alpha = 10"
|
|
432
432
|
assert f2.read_text() == "beta = 20"
|
|
433
433
|
|
|
@@ -446,7 +446,7 @@ class TestEditFiles:
|
|
|
446
446
|
finally:
|
|
447
447
|
set_skip_permissions(False)
|
|
448
448
|
|
|
449
|
-
assert "
|
|
449
|
+
assert "Applied" in result or "Edited" in result
|
|
450
450
|
content = f.read_text()
|
|
451
451
|
assert "HOST = 'prod.example.com'" in content
|
|
452
452
|
assert "PORT = 8080" in content
|
|
@@ -501,7 +501,7 @@ class TestEditFiles:
|
|
|
501
501
|
finally:
|
|
502
502
|
set_skip_permissions(False)
|
|
503
503
|
|
|
504
|
-
assert "
|
|
504
|
+
assert "Applied" in result or "Edited" in result
|
|
505
505
|
|
|
506
506
|
content_a = f1.read_text()
|
|
507
507
|
assert "import logging" in content_a
|
aru_code-0.13.3/aru/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.13.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|