forgexa-cli 1.84.2__tar.gz → 1.84.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.
Files changed (34) hide show
  1. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/PKG-INFO +1 -1
  2. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/__init__.py +1 -1
  3. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/agent_core.py +12 -4
  4. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/daemon.py +52 -7
  5. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/PKG-INFO +1 -1
  6. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/pyproject.toml +1 -1
  7. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/README.md +0 -0
  8. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/_build_config.py +0 -0
  9. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/_local_bind.py +0 -0
  10. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/autoupgrade.py +0 -0
  11. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/aws_evidence.py +0 -0
  12. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/aws_logs_evidence.py +0 -0
  13. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/main.py +0 -0
  14. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/observability_contract.py +0 -0
  15. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/observability_evidence.py +0 -0
  16. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/py.typed +0 -0
  17. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/runtime_evidence.py +0 -0
  18. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/zcode_protocol.py +0 -0
  19. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/SOURCES.txt +0 -0
  20. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  21. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/entry_points.txt +0 -0
  22. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/requires.txt +0 -0
  23. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/top_level.txt +0 -0
  24. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/setup.cfg +0 -0
  25. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_auth_and_runtime_commands.py +0 -0
  26. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_autoupgrade.py +0 -0
  27. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_check_command.py +0 -0
  28. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_expiry_warnings_and_revoke.py +0 -0
  29. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_local_bind_commands.py +0 -0
  30. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_publish_script.py +0 -0
  31. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_runtime_credentials.py +0 -0
  32. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_session_credentials.py +0 -0
  33. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_silent_install.py +0 -0
  34. {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/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.2
3
+ Version: 1.84.3
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.2"
2
+ __version__ = "1.84.3"
@@ -419,10 +419,18 @@ def assess_implementation_audit(contract: dict, report: object) -> dict:
419
419
  index = row.get("index")
420
420
  related = row.get("task_ids")
421
421
  if type(index) is int and not 1 <= index <= criterion_count:
422
- issues.append(
423
- f"Acceptance index {index} is outside the frozen baseline 1-{criterion_count}; "
424
- "report the baseline conflict instead of dropping or renumbering requirements"
425
- )
422
+ if criterion_count == 0:
423
+ issues.append(
424
+ f"Acceptance index {index} is invalid because the frozen baseline has no acceptance criteria; "
425
+ "return acceptance_checks=[] and retain all approved task obligations. If approved documents "
426
+ "prove criteria are missing, request an evidenced plan_revision instead of inventing or "
427
+ "renumbering requirements"
428
+ )
429
+ else:
430
+ issues.append(
431
+ f"Acceptance index {index} is outside the frozen baseline 1-{criterion_count}; "
432
+ "report the baseline conflict instead of dropping or renumbering requirements"
433
+ )
426
434
  continue
427
435
  if (
428
436
  type(index) is not int or not 1 <= index <= criterion_count or index in checked_criteria
@@ -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.2"
1237
+ DAEMON_VERSION = "1.84.3"
1238
1238
 
1239
1239
 
1240
1240
  def _detect_client_type() -> str:
@@ -3018,10 +3018,17 @@ def _is_sensitive_local_path(path: str) -> bool:
3018
3018
  return any(pattern.search(normalized) for pattern in _LOCAL_BIND_SENSITIVE_PATH_PATTERNS)
3019
3019
 
3020
3020
 
3021
- def _runtime_critic_reference_candidates(baseline: dict, source_paths: list[str]) -> list[str]:
3021
+ def _runtime_critic_reference_candidates(
3022
+ baseline: dict, source_paths: list[str], *, inventory: list[str] | None = None,
3023
+ ) -> list[str]:
3022
3024
  planned = baseline.get("task") or {}
3023
3025
  texts = [str(planned.get("description") or "")]
3024
3026
  texts.extend(str(item.get("description") or "") for item in planned.get("items") or [] if isinstance(item, dict))
3027
+ if planned.get("type") in ("testing", "review"):
3028
+ for related in baseline.get("related_tasks") or []:
3029
+ if isinstance(related, dict):
3030
+ texts.append(str(related.get("description") or ""))
3031
+ texts.extend(str(item.get("description") or "") for item in related.get("items") or [] if isinstance(item, dict))
3025
3032
  if planned.get("type") == "design":
3026
3033
  texts.append(json.dumps({"analysis": baseline.get("approved_analysis"),
3027
3034
  "documents": baseline.get("analysis_documents")}, ensure_ascii=True)[:200_000])
@@ -3042,6 +3049,7 @@ def _runtime_critic_reference_candidates(baseline: dict, source_paths: list[str]
3042
3049
  return None
3043
3050
  return normalized
3044
3051
 
3052
+ php_context = any(Path(path).suffix.lower() == ".php" for path in source_paths)
3045
3053
  directories = {Path(path).parent.as_posix() for path in source_paths if safe_path(path)} - {"."}
3046
3054
  explicit = set()
3047
3055
  for token in re.findall(r"(?<![\w./\\:-])[A-Za-z0-9_.-]+(?:/[A-Za-z0-9_.-]+)+", text):
@@ -3051,16 +3059,39 @@ def _runtime_critic_reference_candidates(baseline: dict, source_paths: list[str]
3051
3059
  if Path(candidate).suffix.lower() in suffixes:
3052
3060
  explicit.add(candidate)
3053
3061
  directories.add(Path(candidate).parent.as_posix())
3054
- elif not Path(candidate).suffix:
3062
+ elif not Path(candidate).suffix and not (
3063
+ php_context and all(re.fullmatch(r"[A-Z][A-Za-z0-9]*[A-Z][A-Za-z0-9]*", part) for part in Path(candidate).parts)
3064
+ ):
3055
3065
  directories.add(candidate)
3056
3066
  names = set(re.findall(
3057
3067
  r"(?<![\w./\\:-])([A-Za-z0-9_][A-Za-z0-9_.-]*\.(?:php|py|tsx?|jsx?|xml|json|md|ya?ml|toml|html|phtml))\b", text,
3058
3068
  ))
3069
+ if php_context:
3070
+ names.update(name + ".php" for name in re.findall(
3071
+ r"(?<![\w./\\:-])([A-Z][A-Za-z0-9_]*)::[A-Za-z_][A-Za-z0-9_]*\b", text,
3072
+ ))
3073
+ bare_text = re.sub(r"(?<!\w)(?:[A-Za-z]:[\\/]|/|\.\.?/)[^\s,;()]+", " ", text)
3074
+ names.update(name + ".php" for name in re.findall(
3075
+ r"(?<![\w.\\:-])(?=[A-Za-z0-9]*[a-z])([A-Z][A-Za-z0-9]*[A-Z][A-Za-z0-9]*)(?![\w.\\:-])", bare_text,
3076
+ ))
3077
+ module_roots = sorted({
3078
+ "/".join(Path(path).parts[:4]) for path in source_paths
3079
+ if safe_path(path) and len(Path(path).parts) > 4 and Path(path).parts[:2] == ("app", "code")
3080
+ and Path(path).suffix.lower() == ".php"
3081
+ })[:5]
3059
3082
  known_names = {Path(path).name for path in [*source_paths, *explicit]}
3060
3083
  candidates = set(explicit)
3061
3084
  for name in sorted(names - known_names)[:20]:
3085
+ if inventory is not None and module_roots and name.endswith(".php"):
3086
+ matches = [path for path in inventory if Path(path).name == name
3087
+ and any(path.startswith(root + "/") for root in module_roots)]
3088
+ if len(matches) == 1 and (path := safe_path(matches[0])) is not None:
3089
+ candidates.add(path)
3090
+ continue
3062
3091
  candidates.update(path for directory in sorted(directories)[:5]
3063
3092
  if (path := safe_path(f"{directory}/{name}")) is not None)
3093
+ if name.endswith(".php"):
3094
+ candidates.update(f"{root}/{name}" for root in module_roots)
3064
3095
  return sorted(candidates - set(source_paths))[:128]
3065
3096
 
3066
3097
 
@@ -14011,6 +14042,7 @@ class RuntimeDaemon:
14011
14042
  path for path in support_paths.splitlines()[:20]
14012
14043
  if path.startswith(directory + "/") and path.endswith((".ts", ".js", ".py"))
14013
14044
  )
14045
+ reference_tasks = []
14014
14046
  audit_contract = (task.input_data or {}).get("implementation_audit_contract")
14015
14047
  if isinstance(audit_contract, dict) and baseline.get("plan_id") is not None and audit_contract.get("plan_id") == baseline["plan_id"]:
14016
14048
  approved_tasks = {entry["id"]: entry for entry in audit_contract.get("tasks") or [] if isinstance(entry, dict) and isinstance(entry.get("id"), str)}
@@ -14038,6 +14070,8 @@ class RuntimeDaemon:
14038
14070
  visited.add(dependency_id)
14039
14071
  dependency = approved_tasks.get(dependency_id)
14040
14072
  if dependency is not None:
14073
+ if audit_contract.get("subject_task_id") == planned.get("id"):
14074
+ reference_tasks.append(dependency)
14041
14075
  dependency_paths = agent_core.missing_planned_task_files({
14042
14076
  "files": (dependency.get("input") or {}).get("files") or [],
14043
14077
  "items": dependency.get("items") or [],
@@ -14050,14 +14084,25 @@ class RuntimeDaemon:
14050
14084
  "files": (related.get("input") or {}).get("files") or [],
14051
14085
  "items": related.get("items") or [],
14052
14086
  }, []))
14053
- reference_candidates = _runtime_critic_reference_candidates(baseline, primary_paths)
14087
+ reference_baseline = {**baseline, "related_tasks": reference_tasks}
14088
+ reference_candidates = _runtime_critic_reference_candidates(reference_baseline, primary_paths)
14054
14089
  if reference_candidates:
14055
14090
  try:
14091
+ reference_modules = sorted({
14092
+ Path(path).parent.as_posix() for path in reference_candidates
14093
+ if len(Path(path).parts) == 5 and Path(path).parts[:2] == ("app", "code")
14094
+ and Path(path).suffix.lower() == ".php"
14095
+ })[:5]
14056
14096
  inventory = await self.workspace_manager._git(
14057
- "ls-tree", "-r", "--name-only", after_sha, "--", *reference_candidates,
14097
+ "ls-tree", "-r", "--name-only", after_sha, "--", *reference_candidates, *reference_modules,
14058
14098
  cwd=workspace_path, timeout=10,
14059
14099
  )
14060
- confirmed = [path for path in inventory.splitlines() if path in reference_candidates]
14100
+ if len(inventory.encode("utf-8")) > _RUNTIME_CRITIC_SOURCE_MAX_BYTES:
14101
+ raise ValueError("Committed reference inventory exceeds its bounded evidence budget")
14102
+ inventory_paths = inventory.splitlines()
14103
+ inventory_set = set(inventory_paths)
14104
+ reference_candidates = _runtime_critic_reference_candidates(reference_baseline, primary_paths, inventory=inventory_paths)
14105
+ confirmed = [path for path in reference_candidates if path in inventory_set]
14061
14106
  paths = [*primary_paths, *confirmed, *paths]
14062
14107
  except Exception:
14063
14108
  coverage["reference_inventory_unavailable"] = True
@@ -14094,7 +14139,7 @@ class RuntimeDaemon:
14094
14139
  diff = (diff or "No new changes; review committed delivery instead.") + "".join(source_context)
14095
14140
  coverage["full_source_files"] = len(committed_sources)
14096
14141
  coverage["omitted_files"] = coverage["candidate_files"] - coverage["full_source_files"]
14097
- coverage["capsule_incomplete"] = coverage["omitted_files"] > 0
14142
+ coverage["capsule_incomplete"] = coverage["omitted_files"] > 0 or coverage.get("reference_inventory_unavailable", False)
14098
14143
  result.metrics["runtime_critic_coverage"] = coverage
14099
14144
  if not diff:
14100
14145
  return {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.84.2
3
+ Version: 1.84.3
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.2"
3
+ version = "1.84.3"
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