alpiecode 10.5.7__tar.gz → 10.5.9__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.
Files changed (56) hide show
  1. {alpiecode-10.5.7 → alpiecode-10.5.9}/PKG-INFO +7 -5
  2. {alpiecode-10.5.7 → alpiecode-10.5.9}/pyproject.toml +9 -3
  3. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/alpiecode.egg-info/PKG-INFO +7 -5
  4. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/alpiecode.egg-info/SOURCES.txt +1 -0
  5. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/alpiecode.egg-info/requires.txt +4 -0
  6. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/__init__.py +1 -1
  7. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/cache.py +13 -10
  8. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/config.py +30 -6
  9. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/dream_rsi.py +27 -8
  10. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/local_model.py +14 -7
  11. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/memory.py +10 -3
  12. alpiecode-10.5.9/src/codeagent/paths.py +40 -0
  13. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/updater.py +13 -5
  14. {alpiecode-10.5.7 → alpiecode-10.5.9}/test/test_dream_rsi.py +32 -0
  15. {alpiecode-10.5.7 → alpiecode-10.5.9}/README.md +0 -0
  16. {alpiecode-10.5.7 → alpiecode-10.5.9}/setup.cfg +0 -0
  17. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/alpiecode/__init__.py +0 -0
  18. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/alpiecode.egg-info/dependency_links.txt +0 -0
  19. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/alpiecode.egg-info/entry_points.txt +0 -0
  20. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/alpiecode.egg-info/top_level.txt +0 -0
  21. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/agent.py +0 -0
  22. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/assessment.py +0 -0
  23. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/backends/__init__.py +0 -0
  24. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/backends/base.py +0 -0
  25. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/backends/local_backend.py +0 -0
  26. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/backends/openai_backend.py +0 -0
  27. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/cli.py +0 -0
  28. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/client.py +0 -0
  29. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/compaction.py +0 -0
  30. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/context.py +0 -0
  31. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/discovery.py +0 -0
  32. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/doctor.py +0 -0
  33. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/executor.py +0 -0
  34. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/extension/alpiecode.vsix +0 -0
  35. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/git_ops.py +0 -0
  36. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/github.py +0 -0
  37. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/guardian.py +0 -0
  38. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/guardrails.py +0 -0
  39. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/ipython_ext.py +0 -0
  40. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/media.py +0 -0
  41. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/metrics.py +0 -0
  42. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/orchestrator.py +0 -0
  43. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/progress.py +0 -0
  44. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/prompt.py +0 -0
  45. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/rephraser.py +0 -0
  46. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/server.py +0 -0
  47. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/session.py +0 -0
  48. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/system1.py +0 -0
  49. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/tools.py +0 -0
  50. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/verifier.py +0 -0
  51. {alpiecode-10.5.7 → alpiecode-10.5.9}/src/codeagent/vscode_installer.py +0 -0
  52. {alpiecode-10.5.7 → alpiecode-10.5.9}/test/test_adaptive_resilience.py +0 -0
  53. {alpiecode-10.5.7 → alpiecode-10.5.9}/test/test_cli_ui.py +0 -0
  54. {alpiecode-10.5.7 → alpiecode-10.5.9}/test/test_context_and_trajectory.py +0 -0
  55. {alpiecode-10.5.7 → alpiecode-10.5.9}/test/test_task_assessment.py +0 -0
  56. {alpiecode-10.5.7 → alpiecode-10.5.9}/test/test_verifier.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 10.5.7
3
+ Version: 10.5.9
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -9,7 +9,9 @@ Requires-Dist: click>=8.0
9
9
  Requires-Dist: httpx[http2]>=0.24.0
10
10
  Requires-Dist: beautifulsoup4>=4.12.0
11
11
  Requires-Dist: yt-dlp>=2024.0.0
12
- Requires-Dist: imageio-ffmpeg>=0.4.0
13
- Requires-Dist: fastapi>=0.100.0
14
- Requires-Dist: uvicorn>=0.20.0
15
- Requires-Dist: sse-starlette>=2.0.0
12
+ Provides-Extra: server
13
+ Requires-Dist: fastapi>=0.100.0; extra == "server"
14
+ Requires-Dist: uvicorn>=0.20.0; extra == "server"
15
+ Requires-Dist: sse-starlette>=2.0.0; extra == "server"
16
+ Provides-Extra: ffmpeg
17
+ Requires-Dist: imageio-ffmpeg>=0.4.0; extra == "ffmpeg"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "alpiecode"
7
- version = "10.5.7"
7
+ version = "10.5.9"
8
8
  description = "AlpieCode"
9
9
  requires-python = ">=3.9"
10
10
  dependencies = [
@@ -14,12 +14,19 @@ dependencies = [
14
14
  "httpx[http2]>=0.24.0",
15
15
  "beautifulsoup4>=4.12.0",
16
16
  "yt-dlp>=2024.0.0",
17
- "imageio-ffmpeg>=0.4.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ server = [
18
21
  "fastapi>=0.100.0",
19
22
  "uvicorn>=0.20.0",
20
23
  "sse-starlette>=2.0.0",
21
24
  ]
22
25
 
26
+ ffmpeg = [
27
+ "imageio-ffmpeg>=0.4.0",
28
+ ]
29
+
23
30
  [project.scripts]
24
31
  alpiecode = "codeagent.cli:main"
25
32
  codeagent = "codeagent.cli:main"
@@ -29,4 +36,3 @@ where = ["src"]
29
36
 
30
37
  [tool.setuptools.package-data]
31
38
  codeagent = ["extension/*.vsix"]
32
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 10.5.7
3
+ Version: 10.5.9
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -9,7 +9,9 @@ Requires-Dist: click>=8.0
9
9
  Requires-Dist: httpx[http2]>=0.24.0
10
10
  Requires-Dist: beautifulsoup4>=4.12.0
11
11
  Requires-Dist: yt-dlp>=2024.0.0
12
- Requires-Dist: imageio-ffmpeg>=0.4.0
13
- Requires-Dist: fastapi>=0.100.0
14
- Requires-Dist: uvicorn>=0.20.0
15
- Requires-Dist: sse-starlette>=2.0.0
12
+ Provides-Extra: server
13
+ Requires-Dist: fastapi>=0.100.0; extra == "server"
14
+ Requires-Dist: uvicorn>=0.20.0; extra == "server"
15
+ Requires-Dist: sse-starlette>=2.0.0; extra == "server"
16
+ Provides-Extra: ffmpeg
17
+ Requires-Dist: imageio-ffmpeg>=0.4.0; extra == "ffmpeg"
@@ -30,6 +30,7 @@ src/codeagent/media.py
30
30
  src/codeagent/memory.py
31
31
  src/codeagent/metrics.py
32
32
  src/codeagent/orchestrator.py
33
+ src/codeagent/paths.py
33
34
  src/codeagent/progress.py
34
35
  src/codeagent/prompt.py
35
36
  src/codeagent/rephraser.py
@@ -4,7 +4,11 @@ click>=8.0
4
4
  httpx[http2]>=0.24.0
5
5
  beautifulsoup4>=4.12.0
6
6
  yt-dlp>=2024.0.0
7
+
8
+ [ffmpeg]
7
9
  imageio-ffmpeg>=0.4.0
10
+
11
+ [server]
8
12
  fastapi>=0.100.0
9
13
  uvicorn>=0.20.0
10
14
  sse-starlette>=2.0.0
@@ -17,7 +17,7 @@ Supports:
17
17
  from pathlib import Path
18
18
  from typing import Optional, Any, Dict
19
19
 
20
- __version__ = "10.5.7"
20
+ __version__ = "10.5.9"
21
21
 
22
22
  def run(task: str, workdir: str = ".", reasoning_level: str = "thinking", **kwargs):
23
23
  """Run an autonomous coding task programmatically."""
@@ -17,7 +17,14 @@ import time
17
17
  from pathlib import Path
18
18
  from typing import Any, Dict, Optional
19
19
 
20
- CACHE_DIR = Path.home() / ".alpiecode" / "cache"
20
+ from .paths import ensure_dir, get_alpiecode_dir
21
+
22
+
23
+ def get_cache_dir() -> Path:
24
+ return get_alpiecode_dir() / "cache"
25
+
26
+
27
+ CACHE_DIR = get_alpiecode_dir() / "cache"
21
28
  MAX_ENTRIES = 500
22
29
  TTL_SECONDS = 7 * 24 * 3600 # 7 days
23
30
 
@@ -27,22 +34,17 @@ class ResponseCache:
27
34
 
28
35
  def __init__(
29
36
  self,
30
- cache_dir: Path = CACHE_DIR,
37
+ cache_dir: Optional[Path] = None,
31
38
  max_entries: int = MAX_ENTRIES,
32
39
  ttl: int = TTL_SECONDS,
33
40
  ):
34
- self.cache_dir = cache_dir
41
+ self.cache_dir = ensure_dir(cache_dir or get_cache_dir())
35
42
  self.max_entries = max_entries
36
43
  self.ttl = ttl
37
44
  self._mem: Dict[str, Dict[str, Any]] = {}
38
45
  self._hits = 0
39
46
  self._misses = 0
40
47
 
41
- try:
42
- self.cache_dir.mkdir(parents=True, exist_ok=True)
43
- except OSError:
44
- pass
45
-
46
48
  self._warm_up()
47
49
 
48
50
  # ── Public API ──────────────────────────────────────────────────────
@@ -253,7 +255,8 @@ class TestResultCache:
253
255
 
254
256
  def __init__(self, max_entries: int = 100):
255
257
  self.max_entries = max_entries
256
- self.disk_file = CACHE_DIR / "test_cache.json"
258
+ self.cache_dir = ensure_dir(get_cache_dir())
259
+ self.disk_file = self.cache_dir / "test_cache.json"
257
260
  self._cache: Dict[str, Dict[str, Any]] = {}
258
261
  self.hits = 0
259
262
  self.misses = 0
@@ -268,7 +271,7 @@ class TestResultCache:
268
271
 
269
272
  def _save_disk(self):
270
273
  try:
271
- CACHE_DIR.mkdir(parents=True, exist_ok=True)
274
+ ensure_dir(self.disk_file.parent)
272
275
  self.disk_file.write_text(json.dumps(self._cache, indent=2), encoding="utf-8")
273
276
  except Exception:
274
277
  pass
@@ -14,8 +14,30 @@ from dataclasses import dataclass, asdict, fields
14
14
  from pathlib import Path
15
15
  from typing import Optional
16
16
 
17
- CONFIG_DIR = Path.home() / ".alpiecode"
18
- CONFIG_PATH = CONFIG_DIR / "config.json"
17
+ from .paths import ensure_dir, get_alpiecode_dir
18
+
19
+
20
+ def get_config_dir() -> Path:
21
+ return get_alpiecode_dir()
22
+
23
+
24
+ def get_config_path() -> Path:
25
+ # 1. Check local/custom ALPIECODE_HOME directory first
26
+ local_cfg = get_alpiecode_dir() / "config.json"
27
+ if local_cfg.exists():
28
+ return local_cfg
29
+ # 2. Check legacy ~/.alpiecode/config.json if it exists
30
+ try:
31
+ home_cfg = Path.home() / ".alpiecode" / "config.json"
32
+ if home_cfg.exists():
33
+ return home_cfg
34
+ except Exception:
35
+ pass
36
+ return local_cfg
37
+
38
+
39
+ CONFIG_DIR = get_alpiecode_dir()
40
+ CONFIG_PATH = get_config_path()
19
41
 
20
42
  # Config version — bump this when defaults change to trigger auto-migration
21
43
  CONFIG_VERSION = 8 # v7: autonomous discovery engine (zero-flag flow)
@@ -116,9 +138,10 @@ class Config:
116
138
  def load_config() -> Config:
117
139
  data = dict(DEFAULTS)
118
140
  needs_save = False
119
- if CONFIG_PATH.exists():
141
+ cfg_file = get_config_path()
142
+ if cfg_file.exists():
120
143
  try:
121
- saved_data = json.loads(CONFIG_PATH.read_text())
144
+ saved_data = json.loads(cfg_file.read_text())
122
145
  # Skip null values so they don't override smart defaults (e.g. base_url)
123
146
  data.update({k: v for k, v in saved_data.items() if v is not None})
124
147
 
@@ -198,8 +221,9 @@ def load_config() -> Config:
198
221
 
199
222
 
200
223
  def save_config(cfg: Config) -> None:
201
- CONFIG_DIR.mkdir(parents=True, exist_ok=True)
202
- CONFIG_PATH.write_text(json.dumps(asdict(cfg), indent=2))
224
+ target_dir = ensure_dir(get_config_dir())
225
+ target_path = target_dir / "config.json"
226
+ target_path.write_text(json.dumps(asdict(cfg), indent=2))
203
227
 
204
228
 
205
229
  def interactive_init() -> Config:
@@ -18,10 +18,28 @@ from dataclasses import asdict, dataclass, field
18
18
  from pathlib import Path
19
19
  from typing import Any, Dict, List, Optional, Tuple
20
20
 
21
+ from .paths import ensure_dir, get_alpiecode_dir
22
+
21
23
  logger = logging.getLogger(__name__)
22
24
 
23
- TRAJECTORY_DIR = Path.home() / ".alpiecode" / "trajectories"
24
- POLICY_PATH = Path.home() / ".alpiecode" / "dream_policy.json"
25
+
26
+ _INITIAL_TRAJECTORY_DIR = get_alpiecode_dir() / "trajectories"
27
+ _INITIAL_POLICY_PATH = get_alpiecode_dir() / "dream_policy.json"
28
+
29
+ TRAJECTORY_DIR = _INITIAL_TRAJECTORY_DIR
30
+ POLICY_PATH = _INITIAL_POLICY_PATH
31
+
32
+
33
+ def get_trajectory_dir() -> Path:
34
+ if TRAJECTORY_DIR != _INITIAL_TRAJECTORY_DIR:
35
+ return TRAJECTORY_DIR
36
+ return get_alpiecode_dir() / "trajectories"
37
+
38
+
39
+ def get_policy_path() -> Path:
40
+ if POLICY_PATH != _INITIAL_POLICY_PATH:
41
+ return POLICY_PATH
42
+ return get_alpiecode_dir() / "dream_policy.json"
25
43
 
26
44
 
27
45
  # ?? 1. Trajectory Data Models ??????????????????????????????????????????
@@ -70,12 +88,13 @@ class TrajectoryStore:
70
88
  """Local persistent repository of discovery trajectories."""
71
89
 
72
90
  def __init__(self, storage_dir: Optional[Path] = None):
73
- self.storage_dir = storage_dir or TRAJECTORY_DIR
74
- self.storage_dir.mkdir(parents=True, exist_ok=True)
91
+ if storage_dir is None:
92
+ storage_dir = get_trajectory_dir()
93
+ self.storage_dir = ensure_dir(Path(storage_dir))
75
94
 
76
95
  def save_trajectory(self, record: TrajectoryRecord) -> Path:
77
96
  """Save a completed trajectory atomically to disk."""
78
- self.storage_dir.mkdir(parents=True, exist_ok=True)
97
+ ensure_dir(self.storage_dir)
79
98
  file_path = self.storage_dir / f"{record.session_id}.json"
80
99
  temp_path = self.storage_dir / f"{record.session_id}.json.tmp"
81
100
 
@@ -164,7 +183,7 @@ class DreamStrategyProfile:
164
183
 
165
184
  def load_active_dream_profile(path: Optional[Path] = None) -> DreamStrategyProfile:
166
185
  """Load the current active dream policy profile."""
167
- target = path or POLICY_PATH
186
+ target = path or get_policy_path()
168
187
  if not target.exists():
169
188
  return DreamStrategyProfile()
170
189
  try:
@@ -177,8 +196,8 @@ def load_active_dream_profile(path: Optional[Path] = None) -> DreamStrategyProfi
177
196
 
178
197
  def save_active_dream_profile(profile: DreamStrategyProfile, path: Optional[Path] = None) -> None:
179
198
  """Save a synthesized dream policy profile."""
180
- target = path or POLICY_PATH
181
- target.parent.mkdir(parents=True, exist_ok=True)
199
+ target = path or get_policy_path()
200
+ ensure_dir(target.parent)
182
201
  tmp = target.with_suffix(".tmp")
183
202
  try:
184
203
  with open(tmp, "w", encoding="utf-8") as f:
@@ -12,7 +12,14 @@ import sys
12
12
  from pathlib import Path
13
13
  from typing import Any, Dict, List, Optional
14
14
 
15
- CACHE_DIR = Path.home() / ".alpiecode" / "models"
15
+ from .paths import ensure_dir, get_alpiecode_dir
16
+
17
+
18
+ def get_model_cache_dir() -> Path:
19
+ return get_alpiecode_dir() / "models"
20
+
21
+
22
+ CACHE_DIR = get_alpiecode_dir() / "models"
16
23
  DEFAULT_REPO = "169Pi/Alpie_learn_prototype_GGUF_NEW"
17
24
  DEFAULT_CTX_SIZE = 32768 # 32k tokens default for fast loading (supports up to 256k)
18
25
 
@@ -117,11 +124,11 @@ def download_model(repo_id: str = DEFAULT_REPO, token: Optional[str] = None) ->
117
124
 
118
125
  OFFLINE-FIRST: Checks local cache before any network call.
119
126
  """
120
- CACHE_DIR.mkdir(parents=True, exist_ok=True)
127
+ cache_dir = ensure_dir(get_model_cache_dir())
121
128
  hf_token = token or os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
122
129
 
123
130
  # ── STEP 1: Check local cache FIRST (zero network) ───────────────
124
- local_gguf = list(CACHE_DIR.glob("*.gguf"))
131
+ local_gguf = list(cache_dir.glob("*.gguf"))
125
132
  # Filter out mmproj files for main model
126
133
  local_main = [f for f in local_gguf if "mmproj" not in f.name.lower()]
127
134
  if local_main:
@@ -185,7 +192,7 @@ def download_model(repo_id: str = DEFAULT_REPO, token: Optional[str] = None) ->
185
192
  main_gguf_files = gguf_files
186
193
 
187
194
  target_file = main_gguf_files[0]
188
- cached_file = CACHE_DIR / target_file
195
+ cached_file = cache_dir / target_file
189
196
 
190
197
  if not cached_file.exists():
191
198
  print(f"📥 Downloading local model from HuggingFace ({repo_id}/{target_file})...")
@@ -194,7 +201,7 @@ def download_model(repo_id: str = DEFAULT_REPO, token: Optional[str] = None) ->
194
201
  hf_hub_download(
195
202
  repo_id=repo_id,
196
203
  filename=target_file,
197
- local_dir=CACHE_DIR,
204
+ local_dir=cache_dir,
198
205
  token=hf_token,
199
206
  )
200
207
  except Exception as e:
@@ -210,14 +217,14 @@ def download_model(repo_id: str = DEFAULT_REPO, token: Optional[str] = None) ->
210
217
  # Download mmproj file if available (for vision features)
211
218
  if mmproj_files:
212
219
  mmproj_target = mmproj_files[0]
213
- mmproj_cached = CACHE_DIR / mmproj_target
220
+ mmproj_cached = cache_dir / mmproj_target
214
221
  if not mmproj_cached.exists():
215
222
  try:
216
223
  print(f"📥 Downloading vision projector ({repo_id}/{mmproj_target})...")
217
224
  hf_hub_download(
218
225
  repo_id=repo_id,
219
226
  filename=mmproj_target,
220
- local_dir=CACHE_DIR,
227
+ local_dir=cache_dir,
221
228
  token=hf_token,
222
229
  )
223
230
  except Exception as e:
@@ -17,7 +17,14 @@ import time
17
17
  from pathlib import Path
18
18
  from typing import List, Optional
19
19
 
20
- MEMORY_DIR = Path.home() / ".alpiecode" / "memories"
20
+ from .paths import ensure_dir, get_alpiecode_dir
21
+
22
+
23
+ def get_memory_dir() -> Path:
24
+ return get_alpiecode_dir() / "memories"
25
+
26
+
27
+ MEMORY_DIR = get_alpiecode_dir() / "memories"
21
28
 
22
29
 
23
30
  def _project_key(workdir: Path) -> str:
@@ -27,7 +34,7 @@ def _project_key(workdir: Path) -> str:
27
34
 
28
35
  def _memory_path(workdir: Path) -> Path:
29
36
  """Get the memory file path for a project."""
30
- return MEMORY_DIR / f"{_project_key(workdir)}.json"
37
+ return get_memory_dir() / f"{_project_key(workdir)}.json"
31
38
 
32
39
 
33
40
  def load_memories(workdir: Path) -> List[dict]:
@@ -58,7 +65,7 @@ def save_memories_batch(workdir: Path, entries: List[dict]) -> None:
58
65
  if not entries:
59
66
  return
60
67
 
61
- MEMORY_DIR.mkdir(parents=True, exist_ok=True)
68
+ ensure_dir(get_memory_dir())
62
69
  path = _memory_path(workdir)
63
70
 
64
71
  existing = load_memories(workdir)
@@ -0,0 +1,40 @@
1
+ """
2
+ Cross-platform path resolution for AlpieCode.
3
+
4
+ Supports iOS, Android, Linux, macOS, and Windows without hardcoded home directories.
5
+ Resolution order:
6
+ 1. ALPIECODE_HOME environment variable (override)
7
+ 2. Project/working directory: Path.cwd() / ".alpiecode"
8
+ 3. Resilient fallback to Path.home() / ".alpiecode" or tempfile
9
+ """
10
+
11
+ import os
12
+ import tempfile
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+
17
+ def get_alpiecode_dir() -> Path:
18
+ """
19
+ Return the base directory for AlpieCode state, caches, and trajectories.
20
+ User can override the location if needed via ALPIECODE_HOME.
21
+ Default: project/current working directory (.alpiecode).
22
+ """
23
+ custom_dir = os.environ.get("ALPIECODE_HOME")
24
+ if custom_dir:
25
+ return Path(custom_dir).expanduser()
26
+ return Path.cwd() / ".alpiecode"
27
+
28
+
29
+ def ensure_dir(path: Path) -> Path:
30
+ """Safely create directory with resilient fallback if path is not writable."""
31
+ try:
32
+ path.mkdir(parents=True, exist_ok=True)
33
+ return path
34
+ except (PermissionError, OSError):
35
+ fallback = Path(tempfile.gettempdir()) / ".alpiecode" / path.name
36
+ try:
37
+ fallback.mkdir(parents=True, exist_ok=True)
38
+ return fallback
39
+ except Exception:
40
+ return path
@@ -17,16 +17,23 @@ import urllib.request
17
17
  from pathlib import Path
18
18
 
19
19
  import threading
20
+ from .paths import ensure_dir, get_alpiecode_dir
20
21
 
21
- CACHE_FILE = Path.home() / ".alpiecode" / "update_cache.json"
22
+
23
+ def get_updater_cache_file() -> Path:
24
+ return get_alpiecode_dir() / "update_cache.json"
25
+
26
+
27
+ CACHE_FILE = get_alpiecode_dir() / "update_cache.json"
22
28
  CHECK_INTERVAL_SECONDS = 1800 # Check every 30 minutes
23
29
 
24
30
 
25
31
  def _get_cache() -> dict:
26
32
  """Read update cache data."""
27
- if CACHE_FILE.exists():
33
+ cache_file = get_updater_cache_file()
34
+ if cache_file.exists():
28
35
  try:
29
- return json.loads(CACHE_FILE.read_text())
36
+ return json.loads(cache_file.read_text())
30
37
  except Exception:
31
38
  pass
32
39
  return {}
@@ -35,8 +42,9 @@ def _get_cache() -> dict:
35
42
  def _save_cache(sha: str, updated: bool = False) -> None:
36
43
  """Save last checked commit SHA and timestamp."""
37
44
  try:
38
- CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
39
- CACHE_FILE.write_text(json.dumps({
45
+ cache_file = get_updater_cache_file()
46
+ ensure_dir(cache_file.parent)
47
+ cache_file.write_text(json.dumps({
40
48
  "sha": sha,
41
49
  "timestamp": time.time(),
42
50
  "updated": updated,
@@ -31,6 +31,9 @@ from codeagent.dream_rsi import (
31
31
  DreamOptimizer,
32
32
  load_active_dream_profile,
33
33
  save_active_dream_profile,
34
+ get_alpiecode_dir,
35
+ get_trajectory_dir,
36
+ get_policy_path,
34
37
  )
35
38
 
36
39
 
@@ -254,6 +257,35 @@ class TestTrajectoryStore:
254
257
  tmp_files = list(store.storage_dir.glob("*.tmp"))
255
258
  assert len(tmp_files) == 0
256
259
 
260
+ def test_default_storage_dir_with_alpiecode_home(self, tmp_path, monkeypatch):
261
+ custom_home = tmp_path / "custom_alpie_home"
262
+ monkeypatch.setenv("ALPIECODE_HOME", str(custom_home))
263
+ assert get_alpiecode_dir() == custom_home
264
+ assert get_trajectory_dir() == custom_home / "trajectories"
265
+
266
+ default_store = TrajectoryStore()
267
+ assert default_store.storage_dir == custom_home / "trajectories"
268
+ assert default_store.storage_dir.exists()
269
+
270
+ def test_default_storage_dir_without_alpiecode_home(self, monkeypatch):
271
+ monkeypatch.delenv("ALPIECODE_HOME", raising=False)
272
+ expected_dir = Path.cwd() / ".alpiecode"
273
+ assert get_alpiecode_dir() == expected_dir
274
+ assert get_trajectory_dir() == expected_dir / "trajectories"
275
+
276
+ def test_default_policy_path_with_alpiecode_home(self, tmp_path, monkeypatch):
277
+ custom_home = tmp_path / "custom_policy_home"
278
+ monkeypatch.setenv("ALPIECODE_HOME", str(custom_home))
279
+ expected_policy_file = custom_home / "dream_policy.json"
280
+ assert get_policy_path() == expected_policy_file
281
+
282
+ profile = DreamStrategyProfile(syntax_loop_threshold=3)
283
+ save_active_dream_profile(profile)
284
+ assert expected_policy_file.exists()
285
+
286
+ loaded = load_active_dream_profile()
287
+ assert loaded.syntax_loop_threshold == 3
288
+
257
289
 
258
290
  # ── 3. DreamStrategyProfile ─────────────────────────────────────────────
259
291
 
File without changes
File without changes