aicortexpy 0.2.5__tar.gz → 0.2.6__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 (100) hide show
  1. {aicortexpy-0.2.5/aicortexpy.egg-info → aicortexpy-0.2.6}/PKG-INFO +1 -1
  2. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/__init__.py +1 -1
  3. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/daemon.py +13 -4
  4. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/execenv.py +12 -2
  5. aicortexpy-0.2.6/aicortexpy/pinned_workdir.py +127 -0
  6. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/runner.py +4 -1
  7. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/runtime_config.py +2 -11
  8. {aicortexpy-0.2.5 → aicortexpy-0.2.6/aicortexpy.egg-info}/PKG-INFO +1 -1
  9. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/pyproject.toml +1 -1
  10. aicortexpy-0.2.5/aicortexpy/pinned_workdir.py +0 -60
  11. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/MANIFEST.in +0 -0
  12. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/README.md +0 -0
  13. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortex/__init__.py +0 -0
  14. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortex/__main__.py +0 -0
  15. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/__main__.py +0 -0
  16. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/acp_client.py +0 -0
  17. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/acp_conn.py +0 -0
  18. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/acp_errors.py +0 -0
  19. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/agent_env.py +0 -0
  20. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/app_workdir.py +0 -0
  21. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/artifact_ws.py +0 -0
  22. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/artifacts.py +0 -0
  23. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/attachments.py +0 -0
  24. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/auto_commit.py +0 -0
  25. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/capabilities.py +0 -0
  26. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/chat_acp_pool.py +0 -0
  27. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/cli.py +0 -0
  28. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/cli_context.py +0 -0
  29. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/client.py +0 -0
  30. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/codex_config.py +0 -0
  31. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/__init__.py +0 -0
  32. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/agent_cmd.py +0 -0
  33. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/attachment_cmd.py +0 -0
  34. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/auth_cmd.py +0 -0
  35. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/autopilot_cmd.py +0 -0
  36. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/config_cmd.py +0 -0
  37. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/daemon_cmd.py +0 -0
  38. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/graph_cmd.py +0 -0
  39. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/issue_cmd.py +0 -0
  40. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/label_cmd.py +0 -0
  41. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/meeting_cmd.py +0 -0
  42. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/memory_cmd.py +0 -0
  43. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/project_cmd.py +0 -0
  44. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/repo_cmd.py +0 -0
  45. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/runtime_cmd.py +0 -0
  46. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/setup_cmd.py +0 -0
  47. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/skill_cmd.py +0 -0
  48. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/squad_cmd.py +0 -0
  49. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/update_cmd.py +0 -0
  50. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/version_cmd.py +0 -0
  51. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/commands/workspace_cmd.py +0 -0
  52. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/config.py +0 -0
  53. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/agent-memory/SKILL.md +0 -0
  54. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md +0 -0
  55. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/aicorkit-graph/SKILL.md +0 -0
  56. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md +0 -0
  57. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md +0 -0
  58. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/aicortex-basics/SKILL.md +0 -0
  59. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md +0 -0
  60. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md +0 -0
  61. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/aicortex-graph/SKILL.md +0 -0
  62. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/graphify/SKILL.md +0 -0
  63. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/question-form/SKILL.md +0 -0
  64. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/test-report-generator/SKILL.md +0 -0
  65. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py +0 -0
  66. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py +0 -0
  67. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/design_seed.py +0 -0
  68. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/diskusage.py +0 -0
  69. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/gc.py +0 -0
  70. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/handoff_bundle.py +0 -0
  71. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/health.py +0 -0
  72. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/id_resolve.py +0 -0
  73. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/local_skills.py +0 -0
  74. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/models.py +0 -0
  75. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/oauth_login.py +0 -0
  76. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/openclaw_config.py +0 -0
  77. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/output.py +0 -0
  78. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/permission.py +0 -0
  79. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/platform_client.py +0 -0
  80. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/platform_info.py +0 -0
  81. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/poisoned.py +0 -0
  82. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/process.py +0 -0
  83. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/procutil.py +0 -0
  84. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/prompt.py +0 -0
  85. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/repo.py +0 -0
  86. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/sandbox.py +0 -0
  87. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/security.py +0 -0
  88. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/stream.py +0 -0
  89. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/terminal.py +0 -0
  90. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/tunnel.py +0 -0
  91. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy/wakeup.py +0 -0
  92. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy.egg-info/SOURCES.txt +0 -0
  93. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy.egg-info/dependency_links.txt +0 -0
  94. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy.egg-info/entry_points.txt +0 -0
  95. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy.egg-info/requires.txt +0 -0
  96. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/aicortexpy.egg-info/top_level.txt +0 -0
  97. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/scripts/aicortex +0 -0
  98. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/scripts/aicortex.cmd +0 -0
  99. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/setup.cfg +0 -0
  100. {aicortexpy-0.2.5 → aicortexpy-0.2.6}/tests/test_app_workdir.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: aicortexpy
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: AICortex Python CLI and local agent runtime daemon (Go CLI compatible)
5
5
  Author: AICortex
6
6
  License: Apache-2.0
@@ -1,3 +1,3 @@
1
1
  """AICortex Python CLI + local agent runtime daemon."""
2
2
 
3
- __version__ = "0.2.5"
3
+ __version__ = "0.2.6"
@@ -307,11 +307,20 @@ class Daemon:
307
307
  p = Path(prior)
308
308
  roots.append(p.parent if p.name == "workdir" else p)
309
309
  if task.project_id.strip():
310
- from .pinned_workdir import extract_local_path, pinned_workdir_path
310
+ from .pinned_workdir import pinned_workdir_path, select_local_path
311
311
 
312
- local = extract_local_path(task.project_resources)
313
- if local:
314
- roots.append(Path(local))
312
+ local, _explicit = select_local_path(
313
+ task.project_resources, self.cfg.daemon_id
314
+ )
315
+ local_path = Path(local) if local else None
316
+ exists = False
317
+ if local_path is not None:
318
+ try:
319
+ exists = local_path.is_dir()
320
+ except OSError:
321
+ exists = False
322
+ if exists and local_path is not None:
323
+ roots.append(local_path)
315
324
  else:
316
325
  roots.append(
317
326
  pinned_workdir_path(self.cfg.workspaces_root, ws, task.project_id)
@@ -13,7 +13,11 @@ from .client import Task
13
13
  from .codex_config import ensure_codex_multi_agent_config, sanitize_copied_codex_config
14
14
  from .openclaw_config import prepare_openclaw_config
15
15
  from .app_workdir import app_uses_pinned_workdir, prepare_app_workdir
16
- from .pinned_workdir import prepare_pinned_workdir, project_uses_pinned_workdir
16
+ from .pinned_workdir import (
17
+ narrow_local_path_resources,
18
+ prepare_pinned_workdir,
19
+ project_uses_pinned_workdir,
20
+ )
17
21
  from .prompt import build_prompt
18
22
  from .runtime_config import (
19
23
  inject_runtime_config,
@@ -51,9 +55,13 @@ def prepare_environment(
51
55
  workspaces_root: Path,
52
56
  task: Task,
53
57
  provider: str,
58
+ *,
59
+ daemon_id: str = "",
54
60
  ) -> Environment:
55
61
  ws = task.workspace_id or "default"
56
62
  env_updates: dict[str, str] = {}
63
+ # None leaves project resources unchanged. "" means local_path was not used.
64
+ used_local: str | None = None
57
65
 
58
66
  # Priority 0: App edit/run pinned workdir (same layout as Go daemon).
59
67
  if app_uses_pinned_workdir(task):
@@ -67,7 +75,7 @@ def prepare_environment(
67
75
  task.app_mode,
68
76
  )
69
77
  elif project_uses_pinned_workdir(task):
70
- work_dir = prepare_pinned_workdir(workspaces_root, task)
78
+ work_dir, used_local = prepare_pinned_workdir(workspaces_root, task, daemon_id)
71
79
  root_dir = work_dir
72
80
  pinned = True
73
81
  else:
@@ -94,6 +102,8 @@ def prepare_environment(
94
102
 
95
103
  agent = task.agent or {}
96
104
  ctx = task_context_from_task(task)
105
+ if used_local is not None:
106
+ ctx.project_resources = narrow_local_path_resources(ctx.project_resources, used_local)
97
107
  runtime_brief = inject_runtime_config(work_dir, provider, ctx)
98
108
  write_project_sidecar(work_dir, ctx)
99
109
 
@@ -0,0 +1,127 @@
1
+ """Shared project workdir layout."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from .client import Task
11
+
12
+ log = logging.getLogger("aicortexpy.pinned")
13
+
14
+
15
+ def project_uses_pinned_workdir(task: Task) -> bool:
16
+ return bool(task.project_id.strip())
17
+
18
+
19
+ def _local_path_payload(resources: list[dict[str, Any]]) -> tuple[str, dict[str, str]] | None:
20
+ for r in resources:
21
+ rtype = str(r.get("resource_type") or r.get("type") or "")
22
+ if rtype != "local_path":
23
+ continue
24
+ ref = r.get("resource_ref")
25
+ if ref is None:
26
+ ref = r.get("ref") or {}
27
+ if isinstance(ref, str):
28
+ try:
29
+ ref = json.loads(ref)
30
+ except json.JSONDecodeError:
31
+ return "", {}
32
+ if not isinstance(ref, dict):
33
+ return "", {}
34
+ path = str(ref.get("path") or "").strip()
35
+ by_daemon: dict[str, str] = {}
36
+ raw = ref.get("by_daemon") or {}
37
+ if isinstance(raw, dict):
38
+ for key, value in raw.items():
39
+ daemon_id = str(key).strip()
40
+ directory = str(value).strip() if value is not None else ""
41
+ if daemon_id and directory:
42
+ by_daemon[daemon_id] = directory
43
+ return path, by_daemon
44
+ return None
45
+
46
+
47
+ def select_local_path(resources: list[dict[str, Any]], daemon_id: str) -> tuple[str, bool]:
48
+ """Return (path, explicit). explicit is true when by_daemon names this daemon.
49
+
50
+ Other daemons use the default path, so each machine does not need a row.
51
+ """
52
+ parsed = _local_path_payload(resources)
53
+ if parsed is None:
54
+ return "", False
55
+ path, by_daemon = parsed
56
+ daemon_id = (daemon_id or "").strip()
57
+ if daemon_id and by_daemon.get(daemon_id):
58
+ return by_daemon[daemon_id], True
59
+ return path, False
60
+
61
+
62
+ def extract_local_path(resources: list[dict[str, Any]]) -> str:
63
+ path, _explicit = select_local_path(resources, "")
64
+ return path
65
+
66
+
67
+ def narrow_local_path_resources(resources: list[dict[str, Any]], used_path: str) -> list[dict[str, Any]]:
68
+ """Keep a single local_path ref of {path: used_path}. Drop it when unused."""
69
+ used_path = (used_path or "").strip()
70
+ out: list[dict[str, Any]] = []
71
+ kept = False
72
+ for r in resources:
73
+ rtype = str(r.get("resource_type") or r.get("type") or "")
74
+ if rtype != "local_path":
75
+ out.append(r)
76
+ continue
77
+ if not used_path or kept:
78
+ continue
79
+ copied = dict(r)
80
+ copied["resource_ref"] = {"path": used_path}
81
+ out.append(copied)
82
+ kept = True
83
+ return out
84
+
85
+
86
+ def pinned_workdir_path(workspaces_root: Path, workspace_id: str, project_id: str) -> Path:
87
+ return workspaces_root / workspace_id / "projects" / project_id
88
+
89
+
90
+ def ensure_project_layout(root: Path) -> None:
91
+ """Create the project workdir itself. Do not add stage subdirectories."""
92
+ root.mkdir(parents=True, exist_ok=True)
93
+
94
+
95
+ def _dir_exists(path: Path) -> bool:
96
+ try:
97
+ return path.is_dir()
98
+ except OSError:
99
+ return False
100
+
101
+
102
+ def prepare_pinned_workdir(
103
+ workspaces_root: Path,
104
+ task: Task,
105
+ daemon_id: str = "",
106
+ ) -> tuple[Path, str]:
107
+ """Return (workdir, used_local_path). used_local_path is empty on fallback."""
108
+ local, explicit = select_local_path(task.project_resources, daemon_id)
109
+ if local:
110
+ root = Path(local)
111
+ if _dir_exists(root):
112
+ ensure_project_layout(root)
113
+ log.info("using local_path project workdir %s explicit=%s", root, explicit)
114
+ return root, local
115
+ if explicit or not project_uses_pinned_workdir(task):
116
+ raise RuntimeError(
117
+ f"project local_path: working directory {local!r} is not accessible"
118
+ )
119
+ log.warning(
120
+ "default local_path %s is not on this machine; using the daemon project directory",
121
+ local,
122
+ )
123
+ root = pinned_workdir_path(
124
+ workspaces_root, task.workspace_id or "default", task.project_id
125
+ )
126
+ ensure_project_layout(root)
127
+ return root, ""
@@ -300,7 +300,10 @@ def run_agent(
300
300
  ) -> RunResult:
301
301
  from .security import default_security_policy
302
302
 
303
- env_info: Environment = prepare_environment(workspaces_root, task, provider)
303
+ daemon_id = cfg.daemon_id if cfg is not None else ""
304
+ env_info: Environment = prepare_environment(
305
+ workspaces_root, task, provider, daemon_id=daemon_id
306
+ )
304
307
  work_dir = env_info.work_dir
305
308
  policy = security_policy or default_security_policy()
306
309
  roots = [str(work_dir)]
@@ -272,6 +272,7 @@ def _format_project_resource(r: dict[str, Any]) -> str:
272
272
  if hint:
273
273
  out += f" (default branch: `{hint}`)"
274
274
  elif rtype == "local_path":
275
+ # by_daemon is resolved before this runs; only this machine's path is shown.
275
276
  out = f"**Local path**: `{ref.get('path') or ''}`"
276
277
  else:
277
278
  out = f"**{rtype}**: `{json.dumps(ref, ensure_ascii=False)}`"
@@ -322,15 +323,6 @@ def _project_context_section(ctx: TaskContextForEnv) -> str:
322
323
  parts.append(f"This {scope} belongs to **{ctx.project_title}**.\n\n")
323
324
  if ctx.project_id:
324
325
  parts.append(f"Project working directory (logical): `/workspace/{ctx.project_id}/`\n\n")
325
- parts.append(
326
- "Use this shared layout:\n\n"
327
- "- `docs/` — project materials\n"
328
- "- `design/` — design HTML and static mockups\n"
329
- "- `app/` — application / development code\n"
330
- "- `test/` — tests, fixtures, and reports\n"
331
- "- `release/` — build artifacts and release notes\n\n"
332
- "Write only in the directory that matches the current task.\n\n"
333
- )
334
326
  if ctx.project_resources:
335
327
  parts.append("Project resources (also at `.aicortex/project/resources.json`):\n\n")
336
328
  for r in ctx.project_resources:
@@ -574,8 +566,7 @@ def _design_studio_runtime(provider: str, ctx: TaskContextForEnv) -> str:
574
566
  parts.append(f"This design session belongs to **{ctx.project_title}**.\n\n")
575
567
  if ctx.project_id:
576
568
  parts.append(
577
- f"Project working directory (logical): `/workspace/{ctx.project_id}/`\n"
578
- "Write design HTML under `design/`.\n\n"
569
+ f"Project working directory (logical): `/workspace/{ctx.project_id}/`\n\n"
579
570
  )
580
571
  if ctx.project_resources:
581
572
  parts.append("Project resources (tokens/branding only):\n\n")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: aicortexpy
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: AICortex Python CLI and local agent runtime daemon (Go CLI compatible)
5
5
  Author: AICortex
6
6
  License: Apache-2.0
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  # PyPI + import + console script entry for full Go CLI replacement.
7
7
  name = "aicortexpy"
8
- version = "0.2.5"
8
+ version = "0.2.6"
9
9
  description = "AICortex Python CLI and local agent runtime daemon (Go CLI compatible)"
10
10
  readme = "README.md"
11
11
  license = { text = "Apache-2.0" }
@@ -1,60 +0,0 @@
1
- """Shared project workdir layout."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import logging
7
- from pathlib import Path
8
- from typing import Any
9
-
10
- from .client import Task
11
-
12
- log = logging.getLogger("aicortexpy.pinned")
13
-
14
- STAGE_DIRS = ("docs", "design", "app", "test", "release")
15
-
16
-
17
- def project_uses_pinned_workdir(task: Task) -> bool:
18
- return bool(task.project_id.strip())
19
-
20
-
21
- def extract_local_path(resources: list[dict[str, Any]]) -> str:
22
- for r in resources:
23
- if r.get("resource_type") != "local_path":
24
- continue
25
- ref = r.get("resource_ref")
26
- if isinstance(ref, str):
27
- try:
28
- ref = json.loads(ref)
29
- except json.JSONDecodeError:
30
- continue
31
- if isinstance(ref, dict) and ref.get("path"):
32
- return str(ref["path"])
33
- return ""
34
-
35
-
36
- def pinned_workdir_path(workspaces_root: Path, workspace_id: str, project_id: str) -> Path:
37
- return workspaces_root / workspace_id / "projects" / project_id
38
-
39
-
40
- def ensure_project_layout(root: Path) -> None:
41
- root.mkdir(parents=True, exist_ok=True)
42
- for name in STAGE_DIRS:
43
- (root / name).mkdir(parents=True, exist_ok=True)
44
-
45
-
46
- def prepare_pinned_workdir(
47
- workspaces_root: Path,
48
- task: Task,
49
- ) -> Path:
50
- local = extract_local_path(task.project_resources)
51
- if local:
52
- root = Path(local)
53
- ensure_project_layout(root)
54
- log.info("using local_path project workdir %s", root)
55
- return root
56
- root = pinned_workdir_path(
57
- workspaces_root, task.workspace_id or "default", task.project_id
58
- )
59
- ensure_project_layout(root)
60
- return root
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes