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.
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/PKG-INFO +1 -1
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/agent_core.py +12 -4
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/daemon.py +52 -7
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/pyproject.toml +1 -1
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/README.md +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/aws_evidence.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/aws_logs_evidence.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/observability_contract.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/observability_evidence.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/setup.cfg +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_publish_script.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.84.2 → forgexa_cli-1.84.3}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.84.
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|