forgexa-cli 1.10.1__tar.gz → 1.10.3__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.
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/PKG-INFO +1 -1
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli/daemon.py +89 -35
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/pyproject.toml +1 -1
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/README.md +0 -0
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.10.1 → forgexa_cli-1.10.3}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.10.
|
|
2
|
+
__version__ = "1.10.3"
|
|
@@ -396,7 +396,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
396
396
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
397
397
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
398
398
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
399
|
-
DAEMON_VERSION = "1.10.
|
|
399
|
+
DAEMON_VERSION = "1.10.3"
|
|
400
400
|
|
|
401
401
|
|
|
402
402
|
def _detect_client_type() -> str:
|
|
@@ -648,6 +648,31 @@ class TaskResult:
|
|
|
648
648
|
git: dict = field(default_factory=dict)
|
|
649
649
|
|
|
650
650
|
|
|
651
|
+
def _resolve_git_author(project: dict) -> tuple[str, str]:
|
|
652
|
+
"""Resolve git commit author (name, email) using a fallback chain.
|
|
653
|
+
|
|
654
|
+
Priority:
|
|
655
|
+
1. Task triggering user — populated by the API server from ``graph.triggered_by``
|
|
656
|
+
(the human who submitted/re-triggered the workflow).
|
|
657
|
+
2. Runtime owner user — populated by the API server from ``runtime.owner``
|
|
658
|
+
(the user who registered this daemon).
|
|
659
|
+
3. Hardcoded fallback — ``"Forgexa Agent" / "agent@forgexa.net"``
|
|
660
|
+
(backwards-compatible default when neither is available).
|
|
661
|
+
|
|
662
|
+
Using real user identities makes commits auditable: customers can trace
|
|
663
|
+
each automated commit back to the person who initiated the workflow.
|
|
664
|
+
"""
|
|
665
|
+
name = (project.get("triggered_by_git_name") or "").strip()
|
|
666
|
+
email = (project.get("triggered_by_git_email") or "").strip()
|
|
667
|
+
if name and email:
|
|
668
|
+
return name, email
|
|
669
|
+
name = (project.get("runtime_owner_git_name") or "").strip()
|
|
670
|
+
email = (project.get("runtime_owner_git_email") or "").strip()
|
|
671
|
+
if name and email:
|
|
672
|
+
return name, email
|
|
673
|
+
return "Forgexa Agent", "agent@forgexa.net"
|
|
674
|
+
|
|
675
|
+
|
|
651
676
|
# ── Type-aware analysis outputs (inline fallback for standalone daemons) ──
|
|
652
677
|
# Mirrors type_workflow_profiles.py — used when import is unavailable (CLI/Desktop).
|
|
653
678
|
_ANALYSIS_OUTPUTS_BY_TYPE: dict[str, list[str]] = {
|
|
@@ -1206,9 +1231,10 @@ class WorkspaceManager:
|
|
|
1206
1231
|
readme = ws_path / "README.md"
|
|
1207
1232
|
readme.write_text(f"# {project_key}\n\nInitialized by Forgexa.\n")
|
|
1208
1233
|
await self._git("add", ".", cwd=ws_path)
|
|
1234
|
+
_git_name, _git_email = _resolve_git_author(project)
|
|
1209
1235
|
await self._git(
|
|
1210
|
-
"-c", "user.name=
|
|
1211
|
-
"-c", "user.email=
|
|
1236
|
+
"-c", f"user.name={_git_name}",
|
|
1237
|
+
"-c", f"user.email={_git_email}",
|
|
1212
1238
|
"commit", "-m", "Initial commit",
|
|
1213
1239
|
cwd=ws_path,
|
|
1214
1240
|
)
|
|
@@ -4569,9 +4595,10 @@ class RuntimeDaemon:
|
|
|
4569
4595
|
# Remove physical files
|
|
4570
4596
|
shutil.rmtree(str(dir_to_wipe), ignore_errors=True)
|
|
4571
4597
|
# Commit the wipe so the branch diff is clean
|
|
4598
|
+
_git_name, _git_email = _resolve_git_author(task.project)
|
|
4572
4599
|
await self._git(
|
|
4573
|
-
"-c", "user.name=
|
|
4574
|
-
"-c", "user.email=
|
|
4600
|
+
"-c", f"user.name={_git_name}",
|
|
4601
|
+
"-c", f"user.email={_git_email}",
|
|
4575
4602
|
"commit", "-m",
|
|
4576
4603
|
f"cleanup: wipe analysis docs in {output_dir_norm} before fresh re-analysis",
|
|
4577
4604
|
cwd=workspace_path,
|
|
@@ -5106,13 +5133,23 @@ class RuntimeDaemon:
|
|
|
5106
5133
|
|
|
5107
5134
|
# Analysis deliverables live in analysis_output_dir (docs/requirements/...)
|
|
5108
5135
|
_input = task.input_data or {}
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
)
|
|
5136
|
+
_top_dir = (_input.get("analysis_output_dir") or "").replace("\\", "/").rstrip("/")
|
|
5137
|
+
_ctx_dir = ((_input.get("context") or {}).get("analysis_output_dir") or "").replace("\\", "/").rstrip("/")
|
|
5138
|
+
# Prefer the deeper (v2) path — context always has the authoritative
|
|
5139
|
+
# req_analysis_dir() value while the top-level field may contain a
|
|
5140
|
+
# stale v1 path (e.g. "docs/requirements/KEY") from nodes created
|
|
5141
|
+
# before the Phase 2 doc-paths migration (2026-06-10).
|
|
5142
|
+
if _ctx_dir and len(_ctx_dir.split("/")) > len(_top_dir.split("/")):
|
|
5143
|
+
doc_dir = _ctx_dir
|
|
5144
|
+
elif _top_dir:
|
|
5145
|
+
doc_dir = _top_dir
|
|
5146
|
+
else:
|
|
5147
|
+
doc_dir = (
|
|
5148
|
+
_ctx_dir
|
|
5149
|
+
or _input.get("output_dir")
|
|
5150
|
+
or (_input.get("context") or {}).get("output_dir")
|
|
5151
|
+
or ""
|
|
5152
|
+
)
|
|
5116
5153
|
if doc_dir:
|
|
5117
5154
|
base = workspace_path / doc_dir
|
|
5118
5155
|
else:
|
|
@@ -5561,11 +5598,22 @@ class RuntimeDaemon:
|
|
|
5561
5598
|
|
|
5562
5599
|
# Build a targeted fix prompt: original task + validation issues.
|
|
5563
5600
|
_input = task.input_data or {}
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5601
|
+
# For analysis nodes prefer analysis_output_dir (may be v2 while output_dir
|
|
5602
|
+
# is still a stale v1 path in pre-migration nodes).
|
|
5603
|
+
if task.node_type == "analysis":
|
|
5604
|
+
_fix_doc_dir = (
|
|
5605
|
+
_input.get("analysis_output_dir")
|
|
5606
|
+
or (_input.get("context") or {}).get("analysis_output_dir")
|
|
5607
|
+
or _input.get("output_dir")
|
|
5608
|
+
or (_input.get("context") or {}).get("output_dir")
|
|
5609
|
+
or ""
|
|
5610
|
+
)
|
|
5611
|
+
else:
|
|
5612
|
+
_fix_doc_dir = (
|
|
5613
|
+
_input.get("output_dir")
|
|
5614
|
+
or (_input.get("context") or {}).get("output_dir")
|
|
5615
|
+
or ""
|
|
5616
|
+
)
|
|
5569
5617
|
fix_prompt = (
|
|
5570
5618
|
f"{original_prompt}\n\n"
|
|
5571
5619
|
"---\n\n"
|
|
@@ -5787,13 +5835,15 @@ class RuntimeDaemon:
|
|
|
5787
5835
|
except Exception as msg_err:
|
|
5788
5836
|
logger.warning("Failed to build rich commit message: %s — using fallback", msg_err)
|
|
5789
5837
|
commit_msg = f"{task.node_type}({task.requirement_key or task.task_id}): {display_title}"
|
|
5838
|
+
_git_name, _git_email = _resolve_git_author(task.project)
|
|
5790
5839
|
proc = await asyncio.create_subprocess_exec(
|
|
5791
5840
|
"git", "commit", "-m", commit_msg,
|
|
5792
5841
|
cwd=str(workspace_path),
|
|
5793
5842
|
stdout=asyncio.subprocess.PIPE,
|
|
5794
5843
|
stderr=asyncio.subprocess.PIPE,
|
|
5795
|
-
env={**os.environ,
|
|
5796
|
-
"
|
|
5844
|
+
env={**os.environ,
|
|
5845
|
+
"GIT_AUTHOR_NAME": _git_name, "GIT_AUTHOR_EMAIL": _git_email,
|
|
5846
|
+
"GIT_COMMITTER_NAME": _git_name, "GIT_COMMITTER_EMAIL": _git_email},
|
|
5797
5847
|
)
|
|
5798
5848
|
await proc.communicate()
|
|
5799
5849
|
has_new_commit = True
|
|
@@ -5833,7 +5883,7 @@ class RuntimeDaemon:
|
|
|
5833
5883
|
return {"push_error": f"Would push to default branch {current_branch}"}
|
|
5834
5884
|
|
|
5835
5885
|
# ── Push ──
|
|
5836
|
-
push_error = await self._push_branch(workspace_path, project_key)
|
|
5886
|
+
push_error = await self._push_branch(workspace_path, project_key, task=task)
|
|
5837
5887
|
if push_error:
|
|
5838
5888
|
return {"push_error": push_error}
|
|
5839
5889
|
return {}
|
|
@@ -6127,6 +6177,7 @@ class RuntimeDaemon:
|
|
|
6127
6177
|
"""
|
|
6128
6178
|
git = self.workspace_manager._git
|
|
6129
6179
|
target = f"origin/{default_branch}"
|
|
6180
|
+
_git_name, _git_email = _resolve_git_author(task.project)
|
|
6130
6181
|
|
|
6131
6182
|
# Fetch latest remote state
|
|
6132
6183
|
try:
|
|
@@ -6174,8 +6225,8 @@ class RuntimeDaemon:
|
|
|
6174
6225
|
# ── Tier 1: rebase (safe — branch not yet on remote) ──
|
|
6175
6226
|
try:
|
|
6176
6227
|
await git(
|
|
6177
|
-
"-c", "user.name=
|
|
6178
|
-
"-c", "user.email=
|
|
6228
|
+
"-c", f"user.name={_git_name}",
|
|
6229
|
+
"-c", f"user.email={_git_email}",
|
|
6179
6230
|
"rebase", target,
|
|
6180
6231
|
cwd=workspace_path, timeout=120,
|
|
6181
6232
|
)
|
|
@@ -6197,8 +6248,8 @@ class RuntimeDaemon:
|
|
|
6197
6248
|
# ── Tier 2: merge ──
|
|
6198
6249
|
try:
|
|
6199
6250
|
await git(
|
|
6200
|
-
"-c", "user.name=
|
|
6201
|
-
"-c", "user.email=
|
|
6251
|
+
"-c", f"user.name={_git_name}",
|
|
6252
|
+
"-c", f"user.email={_git_email}",
|
|
6202
6253
|
"merge", target, "--no-edit",
|
|
6203
6254
|
cwd=workspace_path, timeout=120,
|
|
6204
6255
|
)
|
|
@@ -6221,8 +6272,7 @@ class RuntimeDaemon:
|
|
|
6221
6272
|
in a clean state and push whatever we had before.
|
|
6222
6273
|
"""
|
|
6223
6274
|
git = self.workspace_manager._git
|
|
6224
|
-
|
|
6225
|
-
# 1. List conflicted files
|
|
6275
|
+
_git_name, _git_email = _resolve_git_author(task.project)
|
|
6226
6276
|
try:
|
|
6227
6277
|
diff_out = await git(
|
|
6228
6278
|
"diff", "--name-only", "--diff-filter=U", cwd=workspace_path,
|
|
@@ -6235,8 +6285,8 @@ class RuntimeDaemon:
|
|
|
6235
6285
|
# No actual conflicts (merge completed or something unusual)
|
|
6236
6286
|
try:
|
|
6237
6287
|
await git(
|
|
6238
|
-
"-c", "user.name=
|
|
6239
|
-
"-c", "user.email=
|
|
6288
|
+
"-c", f"user.name={_git_name}",
|
|
6289
|
+
"-c", f"user.email={_git_email}",
|
|
6240
6290
|
"merge", "--continue", cwd=workspace_path,
|
|
6241
6291
|
)
|
|
6242
6292
|
except RuntimeError:
|
|
@@ -6361,8 +6411,8 @@ class RuntimeDaemon:
|
|
|
6361
6411
|
await git("add", "-A", cwd=workspace_path)
|
|
6362
6412
|
try:
|
|
6363
6413
|
await git(
|
|
6364
|
-
"-c", "user.name=
|
|
6365
|
-
"-c", "user.email=
|
|
6414
|
+
"-c", f"user.name={_git_name}",
|
|
6415
|
+
"-c", f"user.email={_git_email}",
|
|
6366
6416
|
"commit", "--no-edit",
|
|
6367
6417
|
cwd=workspace_path,
|
|
6368
6418
|
)
|
|
@@ -6378,9 +6428,13 @@ class RuntimeDaemon:
|
|
|
6378
6428
|
except RuntimeError:
|
|
6379
6429
|
await git("reset", "--hard", "HEAD", cwd=workspace_path)
|
|
6380
6430
|
|
|
6381
|
-
async def _push_branch(
|
|
6431
|
+
async def _push_branch(
|
|
6432
|
+
self, workspace_path: Path, project_key: str = "default",
|
|
6433
|
+
task: "TaskInfo | None" = None,
|
|
6434
|
+
) -> str | None:
|
|
6382
6435
|
"""Push the current branch to origin. Returns error message on failure, None on success."""
|
|
6383
6436
|
git = self.workspace_manager._git
|
|
6437
|
+
_git_name, _git_email = _resolve_git_author(task.project if task else {})
|
|
6384
6438
|
try:
|
|
6385
6439
|
# Get current branch name
|
|
6386
6440
|
branch = (await git("rev-parse", "--abbrev-ref", "HEAD", cwd=workspace_path)).strip()
|
|
@@ -6517,8 +6571,8 @@ class RuntimeDaemon:
|
|
|
6517
6571
|
cwd=workspace_path,
|
|
6518
6572
|
)
|
|
6519
6573
|
await git(
|
|
6520
|
-
"-c", "user.name=
|
|
6521
|
-
"-c", "user.email=
|
|
6574
|
+
"-c", f"user.name={_git_name}",
|
|
6575
|
+
"-c", f"user.email={_git_email}",
|
|
6522
6576
|
"cherry-pick", *new_shas,
|
|
6523
6577
|
cwd=workspace_path,
|
|
6524
6578
|
)
|
|
@@ -6566,8 +6620,8 @@ class RuntimeDaemon:
|
|
|
6566
6620
|
# version. This is correct: on an agent-managed feature
|
|
6567
6621
|
# branch the latest agent output is always authoritative.
|
|
6568
6622
|
await git(
|
|
6569
|
-
"-c", "user.name=
|
|
6570
|
-
"-c", "user.email=
|
|
6623
|
+
"-c", f"user.name={_git_name}",
|
|
6624
|
+
"-c", f"user.email={_git_email}",
|
|
6571
6625
|
"rebase", "-X", "theirs", f"origin/{branch}",
|
|
6572
6626
|
cwd=workspace_path,
|
|
6573
6627
|
)
|
|
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
|