patchpal 0.23.0__tar.gz → 0.23.1__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.
- {patchpal-0.23.0/patchpal.egg-info → patchpal-0.23.1}/PKG-INFO +6 -2
- {patchpal-0.23.0 → patchpal-0.23.1}/README.md +5 -1
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/__init__.py +1 -1
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/agent/function_calling.py +60 -20
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/agent/react.py +17 -20
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/prompts/system_prompt.md +1 -1
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/common.py +64 -1
- {patchpal-0.23.0 → patchpal-0.23.1/patchpal.egg-info}/PKG-INFO +6 -2
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal.egg-info/SOURCES.txt +1 -0
- patchpal-0.23.1/tests/test_memory_locations.py +117 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/LICENSE +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/MANIFEST.in +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/agent/__init__.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/agent/bedrock_profile_utils.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/cli/__init__.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/cli/autopilot.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/cli/interactive.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/cli/mcp.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/cli/sandbox.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/cli/streaming.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/config.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/context.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/permissions.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/prompts/react_prompt.md +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/skills.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/__init__.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/audit.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/code_analysis.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/definitions.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/file_reading.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/file_writing.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/find_tool.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/grep_tool.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/image_handler.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/mcp.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/repo_map.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/shell_tools.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/todo_tools.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/tool_schema.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/user_interaction.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal/tools/web_tools.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal.egg-info/dependency_links.txt +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal.egg-info/entry_points.txt +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal.egg-info/requires.txt +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/patchpal.egg-info/top_level.txt +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/pyproject.toml +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/setup.cfg +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_agent.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_cli.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_config_dynamic.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_context.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_custom_tools.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_enabled_tools.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_find_tool.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_guardrails.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_image_blocking.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_maximum_security.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_mcp_config.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_memory.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_operational_safety.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_optional_tools.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_permissions.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_react.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_reasoning_content.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_repo_map.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_simplified_prompt.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_skills.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_streaming.py +0 -0
- {patchpal-0.23.0 → patchpal-0.23.1}/tests/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchpal
|
|
3
|
-
Version: 0.23.
|
|
3
|
+
Version: 0.23.1
|
|
4
4
|
Summary: An agentic coding and automation assistant, supporting both local and cloud LLMs
|
|
5
5
|
Author: PatchPal Contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -64,7 +64,7 @@ Most agent frameworks are [built in TypeScript](https://news.ycombinator.com/ite
|
|
|
64
64
|
- [Built-In](https://amaiya.github.io/patchpal/features/tools/) and [Custom Tools](https://amaiya.github.io/patchpal/features/custom-tools/)
|
|
65
65
|
- [Skills System](https://amaiya.github.io/patchpal/features/skills/) and [MCP Integration](https://amaiya.github.io/patchpal/features/mcp/)
|
|
66
66
|
- [Autopilot Mode](https://amaiya.github.io/patchpal/usage/autopilot/) using [Ralph Wiggum loops](https://github.com/amaiya/patchpal/tree/main/examples/ralph/)
|
|
67
|
-
- [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `~/.patchpal/repos/<repo-name>/MEMORY.md` at startup.
|
|
67
|
+
- [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `MEMORY.md` (repository root or `~/.patchpal/repos/<repo-name>/MEMORY.md`) at startup.
|
|
68
68
|
|
|
69
69
|
PatchPal prioritizes customizability: custom tools, custom skills, a flexible Python API, and support for any tool-calling LLM.
|
|
70
70
|
|
|
@@ -177,6 +177,10 @@ While originally designed for software development, PatchPal is also a general-p
|
|
|
177
177
|
2. PatchPal includes a [unique guardrails system](https://amaiya.github.io/patchpal/safety/) that is better suited to privacy-conscious use cases involving sensitive data.
|
|
178
178
|
3. We needed an agent harness that seamlessly works with [both local and cloud models](https://amaiya.github.io/patchpal/models/overview/#supported-models), including AWS GovCloud Bedrock models.
|
|
179
179
|
|
|
180
|
+
> I noticed there's another package called PatchPal on GitLab. Are they related?
|
|
181
|
+
|
|
182
|
+
No, they're separate projects. The [project with the same name on GitLab](https://gitlab.com/patchpal-ai) is unrelated to this one.
|
|
183
|
+
|
|
180
184
|
> On Windows Subsystem for Linux (WSL), why is it stalling intermittently at "Thinking..."?
|
|
181
185
|
|
|
182
186
|
This is a [known issue](https://github.com/microsoft/WSL/issues/6264#issuecomment-762154193) with WSL2.
|
|
@@ -16,7 +16,7 @@ Most agent frameworks are [built in TypeScript](https://news.ycombinator.com/ite
|
|
|
16
16
|
- [Built-In](https://amaiya.github.io/patchpal/features/tools/) and [Custom Tools](https://amaiya.github.io/patchpal/features/custom-tools/)
|
|
17
17
|
- [Skills System](https://amaiya.github.io/patchpal/features/skills/) and [MCP Integration](https://amaiya.github.io/patchpal/features/mcp/)
|
|
18
18
|
- [Autopilot Mode](https://amaiya.github.io/patchpal/usage/autopilot/) using [Ralph Wiggum loops](https://github.com/amaiya/patchpal/tree/main/examples/ralph/)
|
|
19
|
-
- [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `~/.patchpal/repos/<repo-name>/MEMORY.md` at startup.
|
|
19
|
+
- [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `MEMORY.md` (repository root or `~/.patchpal/repos/<repo-name>/MEMORY.md`) at startup.
|
|
20
20
|
|
|
21
21
|
PatchPal prioritizes customizability: custom tools, custom skills, a flexible Python API, and support for any tool-calling LLM.
|
|
22
22
|
|
|
@@ -129,6 +129,10 @@ While originally designed for software development, PatchPal is also a general-p
|
|
|
129
129
|
2. PatchPal includes a [unique guardrails system](https://amaiya.github.io/patchpal/safety/) that is better suited to privacy-conscious use cases involving sensitive data.
|
|
130
130
|
3. We needed an agent harness that seamlessly works with [both local and cloud models](https://amaiya.github.io/patchpal/models/overview/#supported-models), including AWS GovCloud Bedrock models.
|
|
131
131
|
|
|
132
|
+
> I noticed there's another package called PatchPal on GitLab. Are they related?
|
|
133
|
+
|
|
134
|
+
No, they're separate projects. The [project with the same name on GitLab](https://gitlab.com/patchpal-ai) is unrelated to this one.
|
|
135
|
+
|
|
132
136
|
> On Windows Subsystem for Linux (WSL), why is it stalling intermittently at "Thinking..."?
|
|
133
137
|
|
|
134
138
|
This is a [known issue](https://github.com/microsoft/WSL/issues/6264#issuecomment-762154193) with WSL2.
|
|
@@ -360,8 +360,30 @@ def _apply_prompt_caching(messages: List[Dict[str, Any]], model_id: str) -> List
|
|
|
360
360
|
max_cache_markers = 4
|
|
361
361
|
available_slots = max_cache_markers - existing_cache_count
|
|
362
362
|
|
|
363
|
+
# Debug logging
|
|
364
|
+
import os
|
|
365
|
+
|
|
366
|
+
if os.environ.get("PATCHPAL_DEBUG_CACHE") == "true":
|
|
367
|
+
print("\n[CACHE DEBUG] _apply_prompt_caching called")
|
|
368
|
+
print(f" Total messages: {len(messages)}")
|
|
369
|
+
print(f" Existing cache markers: {existing_cache_count}")
|
|
370
|
+
print(f" Available slots: {available_slots}")
|
|
371
|
+
# Show which messages have markers
|
|
372
|
+
marked_indices = []
|
|
373
|
+
for i, msg in enumerate(messages):
|
|
374
|
+
if isinstance(msg.get("content"), list):
|
|
375
|
+
for block in msg["content"]:
|
|
376
|
+
if isinstance(block, dict) and (
|
|
377
|
+
"cache_control" in block or "cachePoint" in block
|
|
378
|
+
):
|
|
379
|
+
marked_indices.append(i)
|
|
380
|
+
break
|
|
381
|
+
print(f" Messages with existing markers: {marked_indices}")
|
|
382
|
+
|
|
363
383
|
if available_slots <= 0:
|
|
364
384
|
# Already at or over limit, don't add any more cache markers
|
|
385
|
+
if os.environ.get("PATCHPAL_DEBUG_CACHE") == "true":
|
|
386
|
+
print(" Result: No slots available, returning without changes")
|
|
365
387
|
return messages
|
|
366
388
|
|
|
367
389
|
# Find system messages (usually at the start)
|
|
@@ -373,6 +395,9 @@ def _apply_prompt_caching(messages: List[Dict[str, Any]], model_id: str) -> List
|
|
|
373
395
|
non_system_messages[-2:] if len(non_system_messages) >= 2 else non_system_messages
|
|
374
396
|
)
|
|
375
397
|
|
|
398
|
+
if os.environ.get("PATCHPAL_DEBUG_CACHE") == "true":
|
|
399
|
+
print(f" Last 2 non-system indices: {last_two_indices}")
|
|
400
|
+
|
|
376
401
|
# Build list of candidate indices to cache (prioritize system messages)
|
|
377
402
|
candidates = []
|
|
378
403
|
|
|
@@ -405,6 +430,12 @@ def _apply_prompt_caching(messages: List[Dict[str, Any]], model_id: str) -> List
|
|
|
405
430
|
if not has_cache:
|
|
406
431
|
candidates.append(idx)
|
|
407
432
|
|
|
433
|
+
import os
|
|
434
|
+
|
|
435
|
+
if os.environ.get("PATCHPAL_DEBUG_CACHE") == "true":
|
|
436
|
+
print(f" Candidates for marking: {candidates}")
|
|
437
|
+
print(f" Will mark first {available_slots} candidates")
|
|
438
|
+
|
|
408
439
|
# Apply cache markers to candidates, respecting the available slots
|
|
409
440
|
# This ensures we never exceed the 4 marker limit
|
|
410
441
|
for idx in candidates[:available_slots]:
|
|
@@ -425,6 +456,20 @@ def _apply_prompt_caching(messages: List[Dict[str, Any]], model_id: str) -> List
|
|
|
425
456
|
"content": [{"type": "text", "text": content_text, **cache_marker}],
|
|
426
457
|
}
|
|
427
458
|
|
|
459
|
+
if os.environ.get("PATCHPAL_DEBUG_CACHE") == "true":
|
|
460
|
+
# Show final state
|
|
461
|
+
final_marked = []
|
|
462
|
+
for i, msg in enumerate(messages):
|
|
463
|
+
if isinstance(msg.get("content"), list):
|
|
464
|
+
for block in msg["content"]:
|
|
465
|
+
if isinstance(block, dict) and (
|
|
466
|
+
"cache_control" in block or "cachePoint" in block
|
|
467
|
+
):
|
|
468
|
+
final_marked.append(i)
|
|
469
|
+
break
|
|
470
|
+
print(f" Final messages with markers: {final_marked}")
|
|
471
|
+
print(f" Markers applied to indices: {[c for c in candidates[:available_slots]]}")
|
|
472
|
+
|
|
428
473
|
return messages
|
|
429
474
|
|
|
430
475
|
|
|
@@ -598,38 +643,33 @@ class PatchPalAgent:
|
|
|
598
643
|
def _load_project_memory(self):
|
|
599
644
|
"""Load MEMORY.md file at session start if it has non-template content."""
|
|
600
645
|
try:
|
|
601
|
-
from patchpal.tools.common import
|
|
646
|
+
from patchpal.tools.common import get_memory_info
|
|
602
647
|
|
|
603
|
-
|
|
604
|
-
if not MEMORY_FILE.exists():
|
|
605
|
-
return
|
|
606
|
-
|
|
607
|
-
memory_content = MEMORY_FILE.read_text(encoding="utf-8")
|
|
648
|
+
info = get_memory_info()
|
|
608
649
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if "---" in memory_content:
|
|
612
|
-
parts = memory_content.split("---", 1)
|
|
613
|
-
if len(parts) > 1:
|
|
614
|
-
user_content = parts[1].strip()
|
|
615
|
-
if user_content and len(user_content) > 10:
|
|
616
|
-
has_user_content = True
|
|
650
|
+
if not info["exists"]:
|
|
651
|
+
return
|
|
617
652
|
|
|
618
653
|
# Build the message - include full content if user added info, otherwise just location
|
|
619
|
-
if
|
|
654
|
+
if info["has_content"]:
|
|
620
655
|
memory_msg = f"""# Project Memory (from MEMORY.md)
|
|
621
656
|
|
|
622
|
-
{
|
|
657
|
+
{info["content"]}
|
|
623
658
|
|
|
624
|
-
The information above is from {
|
|
625
|
-
To update it, use edit_file("{
|
|
659
|
+
The information above is from MEMORY.md ({info["location_note"]}) and persists across sessions.
|
|
660
|
+
To update it, use edit_file("{info["path"]}", ...) or write_file("{info["path"]}", ...)."""
|
|
626
661
|
else:
|
|
627
662
|
# Empty template - just inform agent
|
|
628
663
|
memory_msg = f"""# Project Memory (MEMORY.md)
|
|
629
664
|
|
|
630
|
-
Your project memory file is located at: {
|
|
665
|
+
Your project memory file is located at: {info["path"]}
|
|
666
|
+
|
|
667
|
+
It's currently empty (just the template). The file is automatically loaded at session start.
|
|
631
668
|
|
|
632
|
-
|
|
669
|
+
Note: You can use MEMORY.md in either location:
|
|
670
|
+
- Repository root: MEMORY.md (can be version controlled)
|
|
671
|
+
- Home directory: ~/.patchpal/repos/<repo-name>/MEMORY.md (default)
|
|
672
|
+
Repository root takes priority if both exist."""
|
|
633
673
|
|
|
634
674
|
# Add as a system message at the start
|
|
635
675
|
self.messages.insert(
|
|
@@ -174,28 +174,25 @@ class ReActAgent:
|
|
|
174
174
|
|
|
175
175
|
def _load_project_memory(self):
|
|
176
176
|
"""Load project memory file if it exists."""
|
|
177
|
-
|
|
177
|
+
try:
|
|
178
|
+
from patchpal.tools.common import get_memory_info
|
|
178
179
|
|
|
179
|
-
|
|
180
|
-
home = Path.home()
|
|
181
|
-
memory_path = home / ".patchpal" / "repos" / repo_root.name / "MEMORY.md"
|
|
180
|
+
info = get_memory_info()
|
|
182
181
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"# Project Memory
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
except Exception:
|
|
198
|
-
pass # Silently skip if can't read
|
|
182
|
+
# Only add if there's actual content beyond the template
|
|
183
|
+
if info["has_content"]:
|
|
184
|
+
self.messages.append(
|
|
185
|
+
{
|
|
186
|
+
"role": "system",
|
|
187
|
+
"content": f"""# Project Memory (from MEMORY.md)
|
|
188
|
+
|
|
189
|
+
{info["content"]}
|
|
190
|
+
|
|
191
|
+
The information above is from MEMORY.md ({info["location_note"]}) and persists across sessions.""",
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
except Exception:
|
|
195
|
+
pass # Silently skip if can't read
|
|
199
196
|
|
|
200
197
|
def _build_system_prompt(self, custom_instructions: str) -> str:
|
|
201
198
|
"""Build the ReAct system prompt with tool descriptions.
|
|
@@ -31,4 +31,4 @@ Assistant: "I found the issue at line 45. The timeout is set to 0 instead of 360
|
|
|
31
31
|
|
|
32
32
|
## Project Memory
|
|
33
33
|
|
|
34
|
-
If project memory is included in your context, use that information throughout the session. When you learn important new information (architecture decisions, deployment details, conventions), suggest updating
|
|
34
|
+
If project memory is included in your context, use that information throughout the session. When you learn important new information (architecture decisions, deployment details, conventions), suggest updating the project's `MEMORY.md` file to maintain continuity across sessions.
|
|
@@ -247,9 +247,22 @@ def _get_patchpal_dir() -> Path:
|
|
|
247
247
|
def _ensure_memory_file() -> Path:
|
|
248
248
|
"""Ensure MEMORY.md file exists, creating it from template if needed.
|
|
249
249
|
|
|
250
|
+
Priority order:
|
|
251
|
+
1. Repo root: MEMORY.md (if it exists, use it)
|
|
252
|
+
2. PatchPal dir: ~/.patchpal/repos/<repo-name>/MEMORY.md (default location)
|
|
253
|
+
|
|
254
|
+
This allows users to keep MEMORY.md in the repository (e.g., for version control)
|
|
255
|
+
while maintaining backward compatibility with the home directory location.
|
|
256
|
+
|
|
250
257
|
Returns:
|
|
251
|
-
Path to the MEMORY.md file
|
|
258
|
+
Path to the MEMORY.md file (either repo root or patchpal dir)
|
|
252
259
|
"""
|
|
260
|
+
# Check repo root first (new alternative location)
|
|
261
|
+
repo_memory_path = REPO_ROOT / "MEMORY.md"
|
|
262
|
+
if repo_memory_path.exists():
|
|
263
|
+
return repo_memory_path
|
|
264
|
+
|
|
265
|
+
# Fall back to patchpal directory (default location)
|
|
253
266
|
memory_path = PATCHPAL_DIR / "MEMORY.md"
|
|
254
267
|
|
|
255
268
|
if not memory_path.exists():
|
|
@@ -267,6 +280,56 @@ BACKUP_DIR = PATCHPAL_DIR / "backups"
|
|
|
267
280
|
AUDIT_LOG_FILE = PATCHPAL_DIR / "audit.log"
|
|
268
281
|
MEMORY_FILE = _ensure_memory_file()
|
|
269
282
|
|
|
283
|
+
|
|
284
|
+
def get_memory_info() -> dict:
|
|
285
|
+
"""Get memory file information for display to agent.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
dict with keys:
|
|
289
|
+
- path: Path to memory file
|
|
290
|
+
- exists: bool
|
|
291
|
+
- is_in_repo: bool (True if in repository root)
|
|
292
|
+
- has_content: bool (True if has user content beyond template)
|
|
293
|
+
- content: str (file content if exists)
|
|
294
|
+
- location_note: str (human-readable location description)
|
|
295
|
+
"""
|
|
296
|
+
info = {
|
|
297
|
+
"path": MEMORY_FILE,
|
|
298
|
+
"exists": MEMORY_FILE.exists(),
|
|
299
|
+
"is_in_repo": False,
|
|
300
|
+
"has_content": False,
|
|
301
|
+
"content": "",
|
|
302
|
+
"location_note": "",
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if not info["exists"]:
|
|
306
|
+
return info
|
|
307
|
+
|
|
308
|
+
# Check if in repo
|
|
309
|
+
try:
|
|
310
|
+
MEMORY_FILE.relative_to(REPO_ROOT)
|
|
311
|
+
info["is_in_repo"] = True
|
|
312
|
+
info["location_note"] = "in repository root"
|
|
313
|
+
except ValueError:
|
|
314
|
+
info["location_note"] = f"at {MEMORY_FILE}"
|
|
315
|
+
|
|
316
|
+
# Read content
|
|
317
|
+
try:
|
|
318
|
+
info["content"] = MEMORY_FILE.read_text(encoding="utf-8")
|
|
319
|
+
|
|
320
|
+
# Check for user content after "---" separator
|
|
321
|
+
if "---" in info["content"]:
|
|
322
|
+
parts = info["content"].split("---", 1)
|
|
323
|
+
if len(parts) > 1:
|
|
324
|
+
user_content = parts[1].strip()
|
|
325
|
+
if user_content and len(user_content) > 10:
|
|
326
|
+
info["has_content"] = True
|
|
327
|
+
except Exception:
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
return info
|
|
331
|
+
|
|
332
|
+
|
|
270
333
|
# Permission manager
|
|
271
334
|
_permission_manager = None
|
|
272
335
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchpal
|
|
3
|
-
Version: 0.23.
|
|
3
|
+
Version: 0.23.1
|
|
4
4
|
Summary: An agentic coding and automation assistant, supporting both local and cloud LLMs
|
|
5
5
|
Author: PatchPal Contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -64,7 +64,7 @@ Most agent frameworks are [built in TypeScript](https://news.ycombinator.com/ite
|
|
|
64
64
|
- [Built-In](https://amaiya.github.io/patchpal/features/tools/) and [Custom Tools](https://amaiya.github.io/patchpal/features/custom-tools/)
|
|
65
65
|
- [Skills System](https://amaiya.github.io/patchpal/features/skills/) and [MCP Integration](https://amaiya.github.io/patchpal/features/mcp/)
|
|
66
66
|
- [Autopilot Mode](https://amaiya.github.io/patchpal/usage/autopilot/) using [Ralph Wiggum loops](https://github.com/amaiya/patchpal/tree/main/examples/ralph/)
|
|
67
|
-
- [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `~/.patchpal/repos/<repo-name>/MEMORY.md` at startup.
|
|
67
|
+
- [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `MEMORY.md` (repository root or `~/.patchpal/repos/<repo-name>/MEMORY.md`) at startup.
|
|
68
68
|
|
|
69
69
|
PatchPal prioritizes customizability: custom tools, custom skills, a flexible Python API, and support for any tool-calling LLM.
|
|
70
70
|
|
|
@@ -177,6 +177,10 @@ While originally designed for software development, PatchPal is also a general-p
|
|
|
177
177
|
2. PatchPal includes a [unique guardrails system](https://amaiya.github.io/patchpal/safety/) that is better suited to privacy-conscious use cases involving sensitive data.
|
|
178
178
|
3. We needed an agent harness that seamlessly works with [both local and cloud models](https://amaiya.github.io/patchpal/models/overview/#supported-models), including AWS GovCloud Bedrock models.
|
|
179
179
|
|
|
180
|
+
> I noticed there's another package called PatchPal on GitLab. Are they related?
|
|
181
|
+
|
|
182
|
+
No, they're separate projects. The [project with the same name on GitLab](https://gitlab.com/patchpal-ai) is unrelated to this one.
|
|
183
|
+
|
|
180
184
|
> On Windows Subsystem for Linux (WSL), why is it stalling intermittently at "Thinking..."?
|
|
181
185
|
|
|
182
186
|
This is a [known issue](https://github.com/microsoft/WSL/issues/6264#issuecomment-762154193) with WSL2.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Tests for MEMORY.md location priority (repo root vs home dir)."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_memory_priority_repo_root_first(tmp_path):
|
|
5
|
+
"""Test that repo root MEMORY.md takes priority over home dir."""
|
|
6
|
+
# Setup: Create both locations
|
|
7
|
+
repo_root = tmp_path / "repo"
|
|
8
|
+
repo_root.mkdir()
|
|
9
|
+
|
|
10
|
+
patchpal_dir = tmp_path / "home" / ".patchpal" / "repos" / "repo"
|
|
11
|
+
patchpal_dir.mkdir(parents=True)
|
|
12
|
+
|
|
13
|
+
# Create MEMORY.md in both locations with different content
|
|
14
|
+
repo_memory = repo_root / "MEMORY.md"
|
|
15
|
+
repo_memory.write_text("# Repo Memory\n\n---\n\nContent from repo root")
|
|
16
|
+
|
|
17
|
+
home_memory = patchpal_dir / "MEMORY.md"
|
|
18
|
+
home_memory.write_text("# Home Memory\n\n---\n\nContent from home dir")
|
|
19
|
+
|
|
20
|
+
# Test the priority logic (repo root exists, so it should be used)
|
|
21
|
+
assert repo_memory.exists()
|
|
22
|
+
assert home_memory.exists()
|
|
23
|
+
|
|
24
|
+
# Repo root should be chosen first
|
|
25
|
+
content = repo_memory.read_text()
|
|
26
|
+
assert "Content from repo root" in content
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_memory_fallback_to_home(tmp_path):
|
|
30
|
+
"""Test that home dir is used when repo root doesn't have MEMORY.md."""
|
|
31
|
+
# Setup: Create only home location
|
|
32
|
+
repo_root = tmp_path / "repo"
|
|
33
|
+
repo_root.mkdir()
|
|
34
|
+
|
|
35
|
+
patchpal_dir = tmp_path / "home" / ".patchpal" / "repos" / "repo"
|
|
36
|
+
patchpal_dir.mkdir(parents=True)
|
|
37
|
+
|
|
38
|
+
# Create MEMORY.md only in home location
|
|
39
|
+
home_memory = patchpal_dir / "MEMORY.md"
|
|
40
|
+
home_memory.write_text("# Template\n\n---\n\nContent from home dir")
|
|
41
|
+
|
|
42
|
+
# Repo root doesn't have it
|
|
43
|
+
repo_memory = repo_root / "MEMORY.md"
|
|
44
|
+
assert not repo_memory.exists()
|
|
45
|
+
|
|
46
|
+
# Should fall back to home dir
|
|
47
|
+
assert home_memory.exists()
|
|
48
|
+
content = home_memory.read_text()
|
|
49
|
+
assert "Content from home dir" in content
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_memory_has_content_detection():
|
|
53
|
+
"""Test that user content detection works correctly."""
|
|
54
|
+
# Test the content detection logic directly
|
|
55
|
+
memory_content = "# Project Memory\n\n---\n\nActual user content here"
|
|
56
|
+
|
|
57
|
+
# Check for user content after "---" separator
|
|
58
|
+
has_content = False
|
|
59
|
+
if "---" in memory_content:
|
|
60
|
+
parts = memory_content.split("---", 1)
|
|
61
|
+
if len(parts) > 1:
|
|
62
|
+
user_content = parts[1].strip()
|
|
63
|
+
if user_content and len(user_content) > 10:
|
|
64
|
+
has_content = True
|
|
65
|
+
|
|
66
|
+
assert has_content is True
|
|
67
|
+
assert "Actual user content here" in memory_content
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_memory_empty_template_detection():
|
|
71
|
+
"""Test that empty template is detected as no content."""
|
|
72
|
+
# Test with only template (no content after ---)
|
|
73
|
+
memory_content = "# Project Memory\n\n---\n\n"
|
|
74
|
+
|
|
75
|
+
# Check for user content after "---" separator
|
|
76
|
+
has_content = False
|
|
77
|
+
if "---" in memory_content:
|
|
78
|
+
parts = memory_content.split("---", 1)
|
|
79
|
+
if len(parts) > 1:
|
|
80
|
+
user_content = parts[1].strip()
|
|
81
|
+
if user_content and len(user_content) > 10:
|
|
82
|
+
has_content = True
|
|
83
|
+
|
|
84
|
+
# Should detect as empty (no real user content)
|
|
85
|
+
assert has_content is False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_memory_get_info_function(tmp_path, monkeypatch):
|
|
89
|
+
"""Test get_memory_info helper function with real file."""
|
|
90
|
+
# Create a temporary repo with MEMORY.md
|
|
91
|
+
repo_root = tmp_path / "test_repo"
|
|
92
|
+
repo_root.mkdir()
|
|
93
|
+
|
|
94
|
+
# Create MEMORY.md in repo
|
|
95
|
+
repo_memory = repo_root / "MEMORY.md"
|
|
96
|
+
repo_memory.write_text("# Memory\n\n---\n\nProject uses Python 3.11")
|
|
97
|
+
|
|
98
|
+
# Change to that directory
|
|
99
|
+
monkeypatch.chdir(repo_root)
|
|
100
|
+
|
|
101
|
+
# Now test that the helper function would work (without reloading)
|
|
102
|
+
# We can't actually test get_memory_info without reloading,
|
|
103
|
+
# but we can verify the file is there
|
|
104
|
+
assert repo_memory.exists()
|
|
105
|
+
content = repo_memory.read_text()
|
|
106
|
+
assert "Python 3.11" in content
|
|
107
|
+
|
|
108
|
+
# Test content detection
|
|
109
|
+
has_content = False
|
|
110
|
+
if "---" in content:
|
|
111
|
+
parts = content.split("---", 1)
|
|
112
|
+
if len(parts) > 1:
|
|
113
|
+
user_content = parts[1].strip()
|
|
114
|
+
if user_content and len(user_content) > 10:
|
|
115
|
+
has_content = True
|
|
116
|
+
|
|
117
|
+
assert has_content is True
|
|
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
|
|
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
|