forgexa-cli 1.84.3__tar.gz → 1.84.4__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 (34) hide show
  1. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/PKG-INFO +1 -1
  2. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/__init__.py +1 -1
  3. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/agent_core.py +15 -5
  4. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/daemon.py +122 -112
  5. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli.egg-info/PKG-INFO +1 -1
  6. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/pyproject.toml +1 -1
  7. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/README.md +0 -0
  8. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/_build_config.py +0 -0
  9. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/_local_bind.py +0 -0
  10. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/autoupgrade.py +0 -0
  11. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/aws_evidence.py +0 -0
  12. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/aws_logs_evidence.py +0 -0
  13. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/main.py +0 -0
  14. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/observability_contract.py +0 -0
  15. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/observability_evidence.py +0 -0
  16. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/py.typed +0 -0
  17. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/runtime_evidence.py +0 -0
  18. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli/zcode_protocol.py +0 -0
  19. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli.egg-info/SOURCES.txt +0 -0
  20. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  21. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli.egg-info/entry_points.txt +0 -0
  22. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli.egg-info/requires.txt +0 -0
  23. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/forgexa_cli.egg-info/top_level.txt +0 -0
  24. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/setup.cfg +0 -0
  25. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_auth_and_runtime_commands.py +0 -0
  26. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_autoupgrade.py +0 -0
  27. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_check_command.py +0 -0
  28. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_expiry_warnings_and_revoke.py +0 -0
  29. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_local_bind_commands.py +0 -0
  30. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_publish_script.py +0 -0
  31. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_runtime_credentials.py +0 -0
  32. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_session_credentials.py +0 -0
  33. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_silent_install.py +0 -0
  34. {forgexa_cli-1.84.3 → forgexa_cli-1.84.4}/tests/test_upgrade_observability.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.84.3
3
+ Version: 1.84.4
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -1,2 +1,2 @@
1
1
  """forgexa-cli — Forgexa command-line client."""
2
- __version__ = "1.84.3"
2
+ __version__ = "1.84.4"
@@ -85,19 +85,29 @@ def assess_approved_plan_conformance(baseline: dict, report: object) -> dict:
85
85
  ).encode()).hexdigest()
86
86
  if baseline.get("version") != 1 or result["baseline_fingerprint"] != fingerprint:
87
87
  result["reason"] = "approved_plan_baseline_invalid"
88
+ result["diagnostic_code"] = "approved_plan_baseline_invalid"
88
89
  return result
89
- if not isinstance(report, dict) or (
90
- report.get("baseline_fingerprint") != fingerprint
91
- or report.get("task_id") != result["task_id"]
92
- or report.get("status") not in ("conformant", "deviation", "unverified")
93
- ):
90
+ if not isinstance(report, dict):
91
+ result["reason"] = "approved_plan_review_missing_or_mismatched"
92
+ result["diagnostic_code"] = "approved_plan_review_missing" if report is None else "approved_plan_review_invalid_payload"
93
+ return result
94
+ diagnostic = None
95
+ if report.get("baseline_fingerprint") != fingerprint:
96
+ diagnostic = "approved_plan_review_baseline_mismatch"
97
+ elif report.get("task_id") != result["task_id"]:
98
+ diagnostic = "approved_plan_review_task_mismatch"
99
+ elif report.get("status") not in ("conformant", "deviation", "unverified"):
100
+ diagnostic = "approved_plan_review_status_invalid"
101
+ if diagnostic is not None:
94
102
  result["reason"] = "approved_plan_review_missing_or_mismatched"
103
+ result["diagnostic_code"] = diagnostic
95
104
  return result
96
105
  evidence = report.get("evidence")
97
106
  if not isinstance(evidence, list) or not evidence or any(
98
107
  not isinstance(item, str) or not item.strip() for item in evidence
99
108
  ):
100
109
  result["reason"] = "approved_plan_review_evidence_missing"
110
+ result["diagnostic_code"] = "approved_plan_review_evidence_missing"
101
111
  return result
102
112
  result["status"] = report["status"]
103
113
  result["evidence_refs"] = [hashlib.sha256(item.encode()).hexdigest() for item in evidence]
@@ -1234,7 +1234,7 @@ except (ImportError, ModuleNotFoundError):
1234
1234
  # DAEMON_VERSION is the protocol/logic version of the daemon code.
1235
1235
  # Kept in sync with pyproject.toml version via bump-version.sh.
1236
1236
  # CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
1237
- DAEMON_VERSION = "1.84.3"
1237
+ DAEMON_VERSION = "1.84.4"
1238
1238
 
1239
1239
 
1240
1240
  def _detect_client_type() -> str:
@@ -1668,7 +1668,8 @@ Review only the supplied task context, code diff and committed source snapshots.
1668
1668
  isolated review capsule, not the project workspace. Do not write or modify files,
1669
1669
  run mutating commands, create commits, or make network changes.
1670
1670
 
1671
- Return exactly one JSON object with this schema:
1671
+ Return exactly one JSON object. This base schema must be extended with the task-bound
1672
+ plan_conformance field when a frozen approved baseline is supplied:
1672
1673
  {
1673
1674
  "verdict": "approve" | "request_changes" | "reject",
1674
1675
  "score": 0.0 to 1.0,
@@ -1741,9 +1742,19 @@ def _build_runtime_critic_prompt(
1741
1742
  "Implementation details may refine it but cannot silently replace its responsibilities, "
1742
1743
  "interfaces, required files or acceptance criteria. Report insufficient evidence honestly.",
1743
1744
  json.dumps(baseline, ensure_ascii=True, sort_keys=True),
1744
- 'Also return "plan_conformance":{"baseline_fingerprint":"<frozen fingerprint>",'
1745
- '"task_id":"<frozen task id>","status":"conformant|deviation|unverified",'
1746
- '"evidence":["concrete supplied source/test reference"]}. '
1745
+ "Bound review response example:\n" + json.dumps({
1746
+ "verdict": "request_changes", "score": 0.5,
1747
+ "summary": "<independent assessment of the supplied source and required checks>",
1748
+ "dimension_scores": {}, "issues": [], "plan_conformance": {
1749
+ "baseline_fingerprint": baseline.get("fingerprint"),
1750
+ "task_id": (baseline.get("task") or {}).get("id"), "status": "unverified",
1751
+ "evidence": ["<concrete independently inspected source or check evidence>"],
1752
+ },
1753
+ }, ensure_ascii=True, separators=(",", ":")),
1754
+ "The task-bound field is mandatory, including when issues is empty. Preserve its exact task_id "
1755
+ "and baseline_fingerprint; do not substitute a requirement key, node UUID or your own digest. "
1756
+ "Replace placeholder evidence with observed facts. Choose conformant, deviation or unverified "
1757
+ "honestly; the example defaults to unverified and is not completion evidence. "
1747
1758
  "Assess every assigned obligation, including behavior omitted from the diff. "
1748
1759
  "Use deviation for an actual conflict or omission; use unverified when supplied evidence "
1749
1760
  "cannot establish conformance. A high quality score never replaces this conclusion. "
@@ -3189,6 +3200,10 @@ class _WorkspaceFileLock:
3189
3200
  handle.close()
3190
3201
 
3191
3202
 
3203
+ class _FreshAnalysisBranchError(RuntimeError):
3204
+ code = "fresh_analysis_branch_reset_blocked"
3205
+
3206
+
3192
3207
  class WorkspaceManager:
3193
3208
  """Manages isolated workspaces per task using git worktrees."""
3194
3209
 
@@ -4818,6 +4833,55 @@ class WorkspaceManager:
4818
4833
  pass
4819
4834
  return False
4820
4835
 
4836
+ async def _prepare_cloud_fresh_branch(
4837
+ self, repository: Path, branch_name: str, default_branch: str, *, project_key: str,
4838
+ ) -> None:
4839
+ branch_ref = f"refs/heads/{branch_name}"
4840
+ backup_ref = None
4841
+ backup_ready = False
4842
+ try:
4843
+ if branch_name == default_branch:
4844
+ raise RuntimeError("Fresh analysis cannot reset the project default branch")
4845
+ await self._git("check-ref-format", branch_ref, cwd=repository)
4846
+ for name in (default_branch, branch_name):
4847
+ await self._git(
4848
+ "fetch", "origin", f"+{name}:refs/remotes/origin/{name}", cwd=repository,
4849
+ timeout=settings.GIT_FETCH_TIMEOUT, project_key=project_key,
4850
+ )
4851
+ old_sha = (await self._git("rev-parse", "--verify", f"refs/remotes/origin/{branch_name}", cwd=repository)).strip()
4852
+ default_sha = (await self._git("rev-parse", "--verify", f"refs/remotes/origin/{default_branch}", cwd=repository)).strip()
4853
+ if not all(re.fullmatch(r"[0-9a-fA-F]{40}", value) for value in (old_sha, default_sha)):
4854
+ raise RuntimeError("Fresh analysis requires verified remote branch commits")
4855
+ if old_sha == default_sha:
4856
+ return
4857
+ backup_ref = f"refs/tags/forgexa/fresh-analysis-backup/{branch_name}/{old_sha}"
4858
+ await self._git("check-ref-format", backup_ref, cwd=repository)
4859
+ existing = (await self._git(
4860
+ "ls-remote", "--tags", "origin", backup_ref, cwd=repository,
4861
+ timeout=settings.GIT_FETCH_TIMEOUT, project_key=project_key,
4862
+ )).strip()
4863
+ if existing:
4864
+ if existing.split()[0] != old_sha:
4865
+ raise RuntimeError("Fresh analysis recovery reference has a different commit")
4866
+ else:
4867
+ await self._git(
4868
+ "push", "origin", f"{old_sha}:{backup_ref}", cwd=repository,
4869
+ timeout=settings.GIT_PUSH_TIMEOUT, project_key=project_key,
4870
+ )
4871
+ backup_ready = True
4872
+ await self._git(
4873
+ "push", f"--force-with-lease={branch_ref}:{old_sha}", "origin", f"{default_sha}:{branch_ref}",
4874
+ cwd=repository, timeout=settings.GIT_PUSH_TIMEOUT, project_key=project_key,
4875
+ )
4876
+ await self._git("update-ref", f"refs/remotes/origin/{branch_name}", default_sha, cwd=repository)
4877
+ logger.info("Prepared fresh analysis branch %s after preserving %s", branch_name, backup_ref)
4878
+ except RuntimeError as error:
4879
+ recovery = f" Recovery reference: {backup_ref}." if backup_ready else ""
4880
+ raise _FreshAnalysisBranchError(
4881
+ "Fresh analysis branch preparation failed; do not run the Agent or delete the remote branch."
4882
+ + recovery + " " + self._git_push_diagnostic(error)
4883
+ ) from error
4884
+
4821
4885
  async def _branch_has_impl_commits(
4822
4886
  self, ws_path: Path, branch_name: str, default_branch: str,
4823
4887
  ) -> bool:
@@ -5239,19 +5303,8 @@ class WorkspaceManager:
5239
5303
  fresh_start = False
5240
5304
 
5241
5305
  if fresh_start:
5242
- # Safety check: if the branch already has IMPLEMENTATION commits
5243
- # beyond the default branch, do NOT reset to origin/default_branch
5244
- # — that would destroy prior design/coding/testing work.
5245
- #
5246
- # Previous behaviour blocked the reset whenever *any* remote commit
5247
- # existed, including analysis-only commits (PRD.md, cleanup wipes).
5248
- # That was overly conservative: a branch whose only commits are
5249
- # analysis documents is safe to reset for a fresh re-analysis
5250
- # because no code has been written yet.
5251
- #
5252
- # The new check distinguishes:
5253
- # • analysis-only commits → safe to reset → allow fresh start
5254
- # • implementation commits (design/coding/testing) → block reset
5306
+ # Preserve existing implementation history before preparing a
5307
+ # confirmed fresh baseline; a failed preparation must stop here.
5255
5308
  branch_has_impl = False
5256
5309
  ahead_count = 0
5257
5310
  try:
@@ -5270,55 +5323,9 @@ class WorkspaceManager:
5270
5323
  )
5271
5324
 
5272
5325
  if branch_has_impl:
5273
- logger.warning(
5274
- "Fresh start requested for %s: remote branch has implementation "
5275
- "commit(s) ahead of %s — deleting remote branch to ensure "
5276
- "a clean analysis environment (user explicitly chose fresh analysis)",
5277
- branch_name, default_branch,
5326
+ await self._prepare_cloud_fresh_branch(
5327
+ ws_path, branch_name, default_branch, project_key=project_key,
5278
5328
  )
5279
- # When the user explicitly chooses "fresh analysis", they want a clean
5280
- # slate. Deleting the remote branch achieves this: the agent runs on
5281
- # a new branch forked from default, completely free of prior
5282
- # implementation commits that may have been wrong or irrelevant.
5283
- # This is intentional and safe because:
5284
- # 1. The user selected fresh analysis (not refine) — they accept
5285
- # that prior work on this branch will be discarded.
5286
- # 2. Keeping the old implementation commits would contaminate the
5287
- # fresh analysis with incorrect context (the original bug report).
5288
- # 3. "Refine" mode preserves the branch — fresh mode does not.
5289
- try:
5290
- await self._git(
5291
- "push", "origin", "--delete", branch_name,
5292
- cwd=ws_path,
5293
- timeout=settings.GIT_PUSH_TIMEOUT,
5294
- project_key=project_key,
5295
- )
5296
- logger.info(
5297
- "Deleted remote branch %s for fresh analysis restart",
5298
- branch_name,
5299
- )
5300
- # Also remove local branch reference so checkout -B below starts clean
5301
- try:
5302
- await self._git("branch", "-D", branch_name, cwd=ws_path)
5303
- except RuntimeError:
5304
- pass # Local branch may not exist; not an error
5305
- # Remove the remote-tracking ref so git doesn't think the
5306
- # branch still exists on the remote
5307
- try:
5308
- await self._git(
5309
- "update-ref", "-d", f"refs/remotes/origin/{branch_name}",
5310
- cwd=ws_path,
5311
- )
5312
- except RuntimeError:
5313
- pass
5314
- except RuntimeError as _del_err:
5315
- logger.warning(
5316
- "Failed to delete remote branch %s: %s — "
5317
- "proceeding with reset to origin/%s instead",
5318
- branch_name, _del_err, default_branch,
5319
- )
5320
- # fresh_start remains True: checkout -B below will recreate
5321
- # the branch from origin/default_branch
5322
5329
  elif ahead_count > 0:
5323
5330
  # Branch has commits but they are all analysis-only (safe to reset).
5324
5331
  logger.info(
@@ -5698,11 +5705,8 @@ class WorkspaceManager:
5698
5705
  except RuntimeError as exc:
5699
5706
  last_branch_fetch_error = self._summarize_git_error(exc)
5700
5707
 
5701
- # Fresh analysis with an existing remote branch that has implementation commits:
5702
- # delete the remote branch so the agent starts from a clean slate.
5703
- # We must do this check here (new worktree path) as well as in the existing
5704
- # worktree path above, because the daemon that runs a fresh re-analysis may
5705
- # never have processed this requirement before (no ws_path yet).
5708
+ # Prepare the same recoverable fresh baseline on runtimes without a
5709
+ # previous worktree for this requirement.
5706
5710
  if fresh_start and branch_exists_remote:
5707
5711
  # Check for implementation commits — same logic as in the existing worktree path.
5708
5712
  _new_branch_has_impl = False
@@ -5722,44 +5726,9 @@ class WorkspaceManager:
5722
5726
  )
5723
5727
 
5724
5728
  if _new_branch_has_impl:
5725
- logger.warning(
5726
- "Fresh analysis (new worktree) for %s: remote branch has implementation "
5727
- "commit(s) — deleting remote branch for a clean analysis environment",
5728
- branch_name,
5729
+ await self._prepare_cloud_fresh_branch(
5730
+ main_repo, branch_name, default_branch, project_key=project_key,
5729
5731
  )
5730
- try:
5731
- await self._git(
5732
- "push", "origin", "--delete", branch_name,
5733
- cwd=main_repo,
5734
- timeout=settings.GIT_PUSH_TIMEOUT,
5735
- project_key=project_key,
5736
- )
5737
- logger.info(
5738
- "Deleted remote branch %s for fresh analysis restart (new worktree path)",
5739
- branch_name,
5740
- )
5741
- # Remove local branch ref and remote-tracking ref
5742
- try:
5743
- await self._git("branch", "-D", branch_name, cwd=main_repo)
5744
- except RuntimeError:
5745
- pass
5746
- try:
5747
- await self._git(
5748
- "update-ref", "-d", f"refs/remotes/origin/{branch_name}",
5749
- cwd=main_repo,
5750
- )
5751
- except RuntimeError:
5752
- pass
5753
- # Branch no longer exists on remote
5754
- branch_exists_remote = False
5755
- except RuntimeError as _del_err2:
5756
- logger.warning(
5757
- "Failed to delete remote branch %s (new worktree path): %s — "
5758
- "proceeding with worktree add from default branch",
5759
- branch_name, _del_err2,
5760
- )
5761
- # Treat as if branch doesn't exist to force fresh worktree
5762
- branch_exists_remote = False
5763
5732
 
5764
5733
  if expect_branch and not fresh_start and not branch_exists_remote:
5765
5734
  self._safe_rmtree(ws_path)
@@ -14019,6 +13988,7 @@ class RuntimeDaemon:
14019
13988
  paths = agent_core.missing_planned_task_files({
14020
13989
  "files": planned.get("files") or [], "items": planned.get("items") or [],
14021
13990
  }, [])
13991
+ assigned_paths = list(paths)
14022
13992
  primary_paths = list(paths)
14023
13993
  if planned.get("type") in ("testing", "review") and getattr(task, "requirement_key", ""):
14024
13994
  output_dir = str((task.input_data or {}).get("output_dir") or "").replace("\\", "/").rstrip("/")
@@ -14106,8 +14076,7 @@ class RuntimeDaemon:
14106
14076
  paths = [*primary_paths, *confirmed, *paths]
14107
14077
  except Exception:
14108
14078
  coverage["reference_inventory_unavailable"] = True
14109
- paths.extend(changed_files)
14110
- paths = list(dict.fromkeys(paths))
14079
+ paths = list(dict.fromkeys([*assigned_paths, *changed_files, *paths]))
14111
14080
  coverage["candidate_files"] = len(paths)
14112
14081
  remaining = _RUNTIME_CRITIC_DIFF_MAX_CHARS - len(diff or "")
14113
14082
  source_context = []
@@ -14252,8 +14221,23 @@ class RuntimeDaemon:
14252
14221
  "agent_attempts": [attempt],
14253
14222
  **metadata,
14254
14223
  }
14255
- if not task.local_evidence_upload and isinstance(baseline, dict):
14256
- conformance = agent_core.assess_approved_plan_conformance(baseline, structured_result.get("plan_conformance"))
14224
+ conformance = (
14225
+ agent_core.assess_approved_plan_conformance(baseline, structured_result.get("plan_conformance"))
14226
+ if isinstance(baseline, dict) else None
14227
+ )
14228
+ if conformance is not None and conformance["status"] != "conformant":
14229
+ diagnostic_lines = [f"[daemon] Local independent review: {conformance['status']}."]
14230
+ if conformance.get("reason"):
14231
+ diagnostic_lines.append(
14232
+ "[daemon] Review contract diagnostic: " + conformance.get("diagnostic_code", conformance["reason"])
14233
+ )
14234
+ else:
14235
+ diagnostic_lines.extend(
14236
+ "[daemon] Review evidence: " + re.sub(r"https?://\S+", "[redacted-url]", item.replace("\x00", ""))[:600]
14237
+ for item in structured_result["plan_conformance"]["evidence"][:10]
14238
+ )
14239
+ _append_local_bind_output_log(task.task_id, diagnostic_lines)
14240
+ if not task.local_evidence_upload and conformance is not None:
14257
14241
  score = structured_result.get("score")
14258
14242
  verdict = structured_result.get("verdict")
14259
14243
  structured_result = {
@@ -14414,6 +14398,7 @@ class RuntimeDaemon:
14414
14398
 
14415
14399
  audit_contract = input_data.get("implementation_audit_contract")
14416
14400
  result.metrics.pop("implementation_audit_node_complete", None)
14401
+ result.metrics.pop("implementation_audit_subject_blocked", None)
14417
14402
  if isinstance(audit_contract, dict):
14418
14403
  agent_id = str(result.metrics.get("actual_agent") or getattr(task, "agent_type", "") or "")
14419
14404
  audit_report = agent_core.extract_implementation_audit(_daemon_extract_agent_output(agent_id, result.stdout))
@@ -14427,6 +14412,11 @@ class RuntimeDaemon:
14427
14412
  result.metrics["implementation_audit_node_complete"] = bool(
14428
14413
  audit_result["report_valid"] and audit_result["node_complete"]
14429
14414
  )
14415
+ result.metrics["implementation_audit_subject_blocked"] = bool(
14416
+ audit_result["report_valid"] and not audit_result["node_complete"]
14417
+ and any(finding.get("task_id") == audit_contract.get("subject_task_id") and finding.get("status") == "blocked"
14418
+ for finding in audit_result.get("findings") or [])
14419
+ )
14430
14420
  if audit_result["report_valid"] and audit_result["status"] == "plan_revision_required":
14431
14421
  result.status = "failed"
14432
14422
  result.failure_code = "implementation_plan_revision_required"
@@ -16450,6 +16440,21 @@ class RuntimeDaemon:
16450
16440
  *[f"[daemon] {issue}" for issue in issues],
16451
16441
  ])
16452
16442
 
16443
+ if (
16444
+ agent_core.is_implementation_testing_task(task.node_type, task.input_data or {})
16445
+ and result.metrics.get("implementation_audit_subject_blocked") is True
16446
+ ):
16447
+ result.status = "failed"
16448
+ result.failure_code = "implementation_audit_incomplete"
16449
+ result.error = (
16450
+ str(next((issue for issue in issues if issue.code == "implementation_audit_incomplete"), issues[0]))
16451
+ + ". Resolve the reported verification prerequisites before retrying; "
16452
+ "rewriting artifacts does not execute blocked acceptance checks."
16453
+ )
16454
+ _record_output_validation_issues(result, issues)
16455
+ self._record_testing_telemetry(workspace_path, task, result, retries_used=attempt)
16456
+ return result
16457
+
16453
16458
  if (
16454
16459
  agent_core.is_implementation_testing_task(task.node_type, task.input_data or {})
16455
16460
  and all(
@@ -16638,6 +16643,11 @@ class RuntimeDaemon:
16638
16643
  elif audit_incomplete:
16639
16644
  result.failure_code = "implementation_audit_incomplete"
16640
16645
  result.error = "; ".join(remaining[:3])
16646
+ if (
16647
+ agent_core.is_implementation_testing_task(task.node_type, task.input_data or {})
16648
+ and result.metrics.get("implementation_audit_subject_blocked") is True
16649
+ ):
16650
+ result.error += ". Resolve the reported verification prerequisites before retrying."
16641
16651
  else:
16642
16652
  result.failure_code = "validation_retry_exhausted"
16643
16653
  result.error = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.84.3
3
+ Version: 1.84.4
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.84.3"
3
+ version = "1.84.4"
4
4
  description = "Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform"
5
5
  requires-python = ">=3.9"
6
6
  license = "MIT"
File without changes
File without changes