forgexa-cli 1.13.6__tar.gz → 1.14.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.13.6
3
+ Version: 1.14.0
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: MIT
@@ -1,2 +1,2 @@
1
1
  """forgexa-cli — Forgexa command-line client."""
2
- __version__ = "1.13.6"
2
+ __version__ = "1.14.0"
@@ -523,7 +523,7 @@ except (ImportError, ModuleNotFoundError):
523
523
  # DAEMON_VERSION is the protocol/logic version of the daemon code.
524
524
  # Kept in sync with pyproject.toml version via bump-version.sh.
525
525
  # CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
526
- DAEMON_VERSION = "1.13.6"
526
+ DAEMON_VERSION = "1.14.0"
527
527
 
528
528
 
529
529
  def _detect_client_type() -> str:
@@ -6611,6 +6611,39 @@ class RuntimeDaemon:
6611
6611
  if result.status == "success" and task.node_type == "fix":
6612
6612
  await self._collect_bugfix_artifacts(workspace_path, task, result)
6613
6613
 
6614
+ # 4.9 For review/coding/testing nodes: attach updated analysis.json inline
6615
+ # so the backend always receives the latest snapshot even if push fails.
6616
+ # This closes the mirror-sync gap: review agents update analysis.json
6617
+ # (open_risks, phase_handoffs) and this step ensures the content reaches
6618
+ # runtimes.py as an inline artifact regardless of push success.
6619
+ if result.status == "success" and task.node_type in ("review", "coding", "testing"):
6620
+ analysis_dir = (
6621
+ (task.input_data or {}).get("analysis_output_dir", "")
6622
+ or ""
6623
+ ).replace("\\", "/").lstrip("./").rstrip("/")
6624
+ if analysis_dir:
6625
+ _aj_path = workspace_path / analysis_dir / "analysis.json"
6626
+ if _aj_path.exists() and _aj_path.stat().st_size > 0:
6627
+ try:
6628
+ _aj_rel = str(_aj_path.relative_to(workspace_path)).replace("\\", "/")
6629
+ _existing_paths = {a.get("path", "").replace("\\", "/") for a in result.artifacts}
6630
+ if _aj_rel not in _existing_paths:
6631
+ _aj_content = _aj_path.read_text(encoding="utf-8", errors="replace")
6632
+ result.artifacts.append({
6633
+ "path": _aj_rel,
6634
+ "content": _aj_content,
6635
+ "type": "application/json",
6636
+ })
6637
+ logger.debug(
6638
+ "Task %s (%s): attached updated analysis.json inline (%d bytes)",
6639
+ task.task_id, task.node_type, len(_aj_content),
6640
+ )
6641
+ except Exception as _aj_err:
6642
+ logger.debug(
6643
+ "Task %s: could not attach analysis.json artifact: %s",
6644
+ task.task_id, _aj_err,
6645
+ )
6646
+
6614
6647
  # 5. Auto-commit and push if changes exist
6615
6648
  if result.status == "success":
6616
6649
  commit_result = await self._auto_commit(workspace_path, task)
@@ -6791,9 +6824,31 @@ class RuntimeDaemon:
6791
6824
  json_path = base / "analysis.json"
6792
6825
  if json_path.exists() and json_path.stat().st_size > 0:
6793
6826
  try:
6794
- _json.loads(json_path.read_text(encoding="utf-8"))
6827
+ _analysis_data = _json.loads(json_path.read_text(encoding="utf-8"))
6795
6828
  except _json.JSONDecodeError as e:
6796
6829
  issues.append(f"analysis.json is not valid JSON: {e}")
6830
+ _analysis_data = None
6831
+ # Task 6: validate phase_handoffs and open_risks fields
6832
+ if _analysis_data is not None:
6833
+ if "open_risks" not in _analysis_data:
6834
+ issues.append(
6835
+ "analysis.json missing 'open_risks' field "
6836
+ "(expected empty array [] at minimum)"
6837
+ )
6838
+ handoffs = _analysis_data.get("phase_handoffs")
6839
+ if not handoffs or not isinstance(handoffs, list):
6840
+ issues.append(
6841
+ "analysis.json missing 'phase_handoffs' array "
6842
+ "(must contain at least one entry with phase='analysis')"
6843
+ )
6844
+ elif not any(
6845
+ isinstance(h, dict) and h.get("phase") == "analysis"
6846
+ for h in handoffs
6847
+ ):
6848
+ issues.append(
6849
+ "analysis.json phase_handoffs has no entry "
6850
+ "with phase='analysis'"
6851
+ )
6797
6852
 
6798
6853
  # Validate test-intent.json if required by this type
6799
6854
  if "test-intent.json" in required_files:
@@ -6848,7 +6903,7 @@ class RuntimeDaemon:
6848
6903
  elif design_path.stat().st_size == 0:
6849
6904
  issues.append(f"Design document is empty: {doc_dir}/design.md")
6850
6905
 
6851
- elif node_type in ("coding", "testing", "fix"):
6906
+ elif node_type in ("coding", "testing", "fix", "review"):
6852
6907
  # Syntax-check modified Python and JS/TS files
6853
6908
  for f in result.files_changed:
6854
6909
  fpath = workspace_path / f
@@ -6871,6 +6926,29 @@ class RuntimeDaemon:
6871
6926
  f"bracket imbalance (open={opens}, close={closes})"
6872
6927
  )
6873
6928
 
6929
+ # Task 6 (P6 fix): warning-level phase_handoffs check for non-analysis
6930
+ # nodes. These nodes SHOULD append a phase_handoffs entry to analysis.json
6931
+ # after completion. Missing entry is NOT blocking (agent may have skipped
6932
+ # WRITEBACK) but is logged for observability and instructions iteration.
6933
+ _ao_dir = (task.input_data or {}).get("analysis_output_dir", "")
6934
+ if _ao_dir:
6935
+ _aj_p = workspace_path / _ao_dir.lstrip("./").rstrip("/").replace("\\", "/") / "analysis.json"
6936
+ if _aj_p.exists() and _aj_p.stat().st_size > 0:
6937
+ try:
6938
+ _aj_d = _json.loads(_aj_p.read_text(encoding="utf-8"))
6939
+ _handoffs = _aj_d.get("phase_handoffs") or []
6940
+ if not any(
6941
+ isinstance(h, dict) and h.get("phase") == node_type
6942
+ for h in _handoffs
6943
+ ):
6944
+ logger.info(
6945
+ "Node %s (type=%s) did not append phase_handoffs "
6946
+ "entry to analysis.json — agent may have skipped WRITEBACK",
6947
+ task.task_id, node_type,
6948
+ )
6949
+ except (_json.JSONDecodeError, UnicodeDecodeError):
6950
+ pass # analysis.json corrupt — already flagged elsewhere
6951
+
6874
6952
  # Testing-specific: validate structured test assets
6875
6953
  if node_type == "testing":
6876
6954
  # Determine which checks to run for this requirement type.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.13.6
3
+ Version: 1.14.0
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: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.13.6"
3
+ version = "1.14.0"
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 = { text = "MIT" }
File without changes
File without changes