forgexa-cli 1.75.2__tar.gz → 1.76.1__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.75.2 → forgexa_cli-1.76.1}/PKG-INFO +1 -1
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/agent_core.py +43 -3
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/daemon.py +105 -22
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/pyproject.toml +1 -1
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/README.md +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/aws_evidence.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/aws_logs_evidence.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/setup.cfg +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.75.2 → forgexa_cli-1.76.1}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.76.1"
|
|
@@ -55,7 +55,7 @@ import tempfile
|
|
|
55
55
|
import time
|
|
56
56
|
from contextlib import contextmanager
|
|
57
57
|
from dataclasses import dataclass, field
|
|
58
|
-
from pathlib import Path
|
|
58
|
+
from pathlib import Path, PurePosixPath
|
|
59
59
|
from typing import Any
|
|
60
60
|
|
|
61
61
|
# fcntl is Unix-only; on Windows we use msvcrt instead (see acquire_file_lock)
|
|
@@ -67,6 +67,42 @@ except ImportError:
|
|
|
67
67
|
logger = logging.getLogger("agent_core")
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
def missing_planned_task_files(task_input: dict, changed_files: list[str]) -> list[str]:
|
|
71
|
+
"""Find planned files without delivery evidence, including grouped sub-tasks."""
|
|
72
|
+
def normalize_path(value: object) -> str | None:
|
|
73
|
+
if not isinstance(value, str) or not value.strip():
|
|
74
|
+
return None
|
|
75
|
+
path = PurePosixPath(value.strip().replace("\\", "/"))
|
|
76
|
+
if path.is_absolute() or ".." in path.parts or not path.parts:
|
|
77
|
+
return None
|
|
78
|
+
return path.as_posix()
|
|
79
|
+
|
|
80
|
+
file_lists = [task_input.get("files")]
|
|
81
|
+
for item in task_input.get("items") or []:
|
|
82
|
+
if isinstance(item, dict):
|
|
83
|
+
file_lists.append(item.get("files"))
|
|
84
|
+
item_input = item.get("input")
|
|
85
|
+
if isinstance(item_input, dict):
|
|
86
|
+
file_lists.append(item_input.get("files"))
|
|
87
|
+
planned_paths = {
|
|
88
|
+
((normalize_path(path) or path).rstrip("/") + "/")
|
|
89
|
+
if path.endswith("/") else (normalize_path(path) or path)
|
|
90
|
+
for paths in file_lists if isinstance(paths, list)
|
|
91
|
+
for path in paths if isinstance(path, str) and path.strip()
|
|
92
|
+
}
|
|
93
|
+
changed_paths = {
|
|
94
|
+
normalized for path in changed_files
|
|
95
|
+
if (normalized := normalize_path(path)) is not None
|
|
96
|
+
}
|
|
97
|
+
return sorted(
|
|
98
|
+
path for path in planned_paths
|
|
99
|
+
if not any(
|
|
100
|
+
changed == path or (path.endswith("/") and changed.startswith(path))
|
|
101
|
+
for changed in changed_paths
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
70
106
|
_WORKSPACE_INSTRUCTION_PATHS = (
|
|
71
107
|
"AGENTS.md",
|
|
72
108
|
"CLAUDE.md",
|
|
@@ -1324,17 +1360,21 @@ def _kill_proc(proc: asyncio.subprocess.Process) -> None:
|
|
|
1324
1360
|
Owned agents start a new session, so their PID remains the process-group
|
|
1325
1361
|
ID even after the leader exits.
|
|
1326
1362
|
"""
|
|
1363
|
+
process_id = getattr(proc, "pid", None)
|
|
1364
|
+
if type(process_id) is not int or process_id <= 1:
|
|
1365
|
+
return
|
|
1366
|
+
|
|
1327
1367
|
try:
|
|
1328
1368
|
if sys.platform != "win32":
|
|
1329
1369
|
import signal as _signal
|
|
1330
1370
|
try:
|
|
1331
|
-
os.killpg(
|
|
1371
|
+
os.killpg(process_id, _signal.SIGKILL)
|
|
1332
1372
|
except (ProcessLookupError, PermissionError, OSError):
|
|
1333
1373
|
pass
|
|
1334
1374
|
else:
|
|
1335
1375
|
import subprocess as _subprocess
|
|
1336
1376
|
_subprocess.run(
|
|
1337
|
-
["taskkill", "/F", "/T", "/PID", str(
|
|
1377
|
+
["taskkill", "/F", "/T", "/PID", str(process_id)],
|
|
1338
1378
|
capture_output=True,
|
|
1339
1379
|
timeout=5,
|
|
1340
1380
|
**_agent_process_group_kwargs(),
|
|
@@ -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.
|
|
1237
|
+
DAEMON_VERSION = "1.76.1"
|
|
1238
1238
|
|
|
1239
1239
|
|
|
1240
1240
|
def _detect_client_type() -> str:
|
|
@@ -6329,6 +6329,18 @@ class ProcessManager:
|
|
|
6329
6329
|
return "unavailable"
|
|
6330
6330
|
return "unable to run"
|
|
6331
6331
|
|
|
6332
|
+
@staticmethod
|
|
6333
|
+
def format_all_agents_failure(
|
|
6334
|
+
failure_kinds: list[str], attempts: list[tuple[str, str]],
|
|
6335
|
+
) -> str:
|
|
6336
|
+
label = ProcessManager.all_agents_failure_label(failure_kinds)
|
|
6337
|
+
agent_order = ", ".join(agent_id for agent_id, _error in attempts)
|
|
6338
|
+
details = "\n".join(
|
|
6339
|
+
f"[{agent_id}] {error or 'Agent failed without an error message'}"
|
|
6340
|
+
for agent_id, error in attempts
|
|
6341
|
+
)
|
|
6342
|
+
return f"All agents {label} (tried: {agent_order}). Agent failures:\n{details}"
|
|
6343
|
+
|
|
6332
6344
|
@staticmethod
|
|
6333
6345
|
def is_idle_timeout_without_work(result: "TaskResult") -> bool:
|
|
6334
6346
|
"""Return whether an idle-killed agent left no work worth preserving.
|
|
@@ -7953,6 +7965,7 @@ class ProcessManager:
|
|
|
7953
7965
|
disable_builtin_mcps: bool = False,
|
|
7954
7966
|
_retry_without_effort: bool = False,
|
|
7955
7967
|
_retry_without_token_env: bool = False,
|
|
7968
|
+
_retry_model_discovery_timeout: bool = False,
|
|
7956
7969
|
_isolated_copilot_home: str | None = None,
|
|
7957
7970
|
_auth_state: dict[str, bool] | None = None,
|
|
7958
7971
|
) -> TaskResult:
|
|
@@ -8219,12 +8232,40 @@ class ProcessManager:
|
|
|
8219
8232
|
disable_builtin_mcps=True,
|
|
8220
8233
|
_retry_without_effort=_retry_without_effort,
|
|
8221
8234
|
_retry_without_token_env=_retry_without_token_env,
|
|
8235
|
+
_retry_model_discovery_timeout=_retry_model_discovery_timeout,
|
|
8222
8236
|
_isolated_copilot_home=_isolated_copilot_home,
|
|
8223
8237
|
_auth_state=_auth_state,
|
|
8224
8238
|
)
|
|
8225
8239
|
retry_result.metrics["copilot_builtin_mcp_fallback"] = True
|
|
8226
8240
|
return retry_result
|
|
8227
8241
|
|
|
8242
|
+
model_discovery_detail = "\n".join([*structured_errors, stderr]).lower()
|
|
8243
|
+
if (
|
|
8244
|
+
"could not retrieve the list of available models" in model_discovery_detail
|
|
8245
|
+
and "timed out" in model_discovery_detail
|
|
8246
|
+
and not _retry_model_discovery_timeout
|
|
8247
|
+
and not (copilot_exit == 0 and returncode == 0)
|
|
8248
|
+
and not signals["has_meaningful_content"]
|
|
8249
|
+
and not self._copilot_called_any_tools(stdout)
|
|
8250
|
+
):
|
|
8251
|
+
logger.warning(
|
|
8252
|
+
"Copilot model discovery timed out before work for task %s; "
|
|
8253
|
+
"retrying once with the same credentials and configuration",
|
|
8254
|
+
task_id,
|
|
8255
|
+
)
|
|
8256
|
+
retry_result = await self._run_copilot(
|
|
8257
|
+
agent, prompt, cwd, timeout, task_id, on_chunk,
|
|
8258
|
+
stream_stderr=stream_stderr,
|
|
8259
|
+
disable_builtin_mcps=disable_builtin_mcps,
|
|
8260
|
+
_retry_without_effort=_retry_without_effort,
|
|
8261
|
+
_retry_without_token_env=_retry_without_token_env,
|
|
8262
|
+
_retry_model_discovery_timeout=True,
|
|
8263
|
+
_isolated_copilot_home=_isolated_copilot_home,
|
|
8264
|
+
_auth_state=_auth_state,
|
|
8265
|
+
)
|
|
8266
|
+
retry_result.metrics["copilot_model_discovery_timeout_recovery"] = True
|
|
8267
|
+
return retry_result
|
|
8268
|
+
|
|
8228
8269
|
model_discovery_error = agent_core.is_copilot_model_discovery_auth_error(
|
|
8229
8270
|
*structured_errors,
|
|
8230
8271
|
stderr,
|
|
@@ -8264,6 +8305,7 @@ class ProcessManager:
|
|
|
8264
8305
|
disable_builtin_mcps=disable_builtin_mcps,
|
|
8265
8306
|
_retry_without_effort=_retry_without_effort,
|
|
8266
8307
|
_retry_without_token_env=True,
|
|
8308
|
+
_retry_model_discovery_timeout=_retry_model_discovery_timeout,
|
|
8267
8309
|
_isolated_copilot_home=_isolated_copilot_home,
|
|
8268
8310
|
_auth_state=_auth_state,
|
|
8269
8311
|
)
|
|
@@ -8316,6 +8358,7 @@ class ProcessManager:
|
|
|
8316
8358
|
disable_builtin_mcps=disable_builtin_mcps,
|
|
8317
8359
|
_retry_without_effort=_retry_without_effort,
|
|
8318
8360
|
_retry_without_token_env=True,
|
|
8361
|
+
_retry_model_discovery_timeout=_retry_model_discovery_timeout,
|
|
8319
8362
|
_isolated_copilot_home=_isolated_copilot_home,
|
|
8320
8363
|
_auth_state=_auth_state,
|
|
8321
8364
|
)
|
|
@@ -8381,6 +8424,7 @@ class ProcessManager:
|
|
|
8381
8424
|
disable_builtin_mcps=disable_builtin_mcps,
|
|
8382
8425
|
_retry_without_effort=True,
|
|
8383
8426
|
_retry_without_token_env=_retry_without_token_env,
|
|
8427
|
+
_retry_model_discovery_timeout=_retry_model_discovery_timeout,
|
|
8384
8428
|
_isolated_copilot_home=_isolated_copilot_home,
|
|
8385
8429
|
_auth_state=_auth_state,
|
|
8386
8430
|
)
|
|
@@ -12877,6 +12921,7 @@ class RuntimeDaemon:
|
|
|
12877
12921
|
else "timed out"
|
|
12878
12922
|
)
|
|
12879
12923
|
fallback_failure_kinds = [agent_failure_kind or fallback_reason] if fallback_reason else []
|
|
12924
|
+
fallback_attempt_errors = [(agent.agent_id, result.error)] if fallback_reason else []
|
|
12880
12925
|
_skip_fallback = False
|
|
12881
12926
|
if fallback_reason:
|
|
12882
12927
|
_pre_fallback_git = await self.process_manager._collect_git_info(workspace_path)
|
|
@@ -12949,6 +12994,7 @@ class RuntimeDaemon:
|
|
|
12949
12994
|
result = await self.process_manager.run_agent(
|
|
12950
12995
|
agent, task, workspace_path, on_chunk=on_output_chunk,
|
|
12951
12996
|
)
|
|
12997
|
+
fallback_attempt_errors.append((agent.agent_id, result.error))
|
|
12952
12998
|
finally:
|
|
12953
12999
|
progress_stop2.set()
|
|
12954
13000
|
ticker2.cancel()
|
|
@@ -12989,12 +13035,8 @@ class RuntimeDaemon:
|
|
|
12989
13035
|
)
|
|
12990
13036
|
|
|
12991
13037
|
if ProcessManager.is_agent_fallback_eligible(result):
|
|
12992
|
-
|
|
12993
|
-
fallback_failure_kinds,
|
|
12994
|
-
)
|
|
12995
|
-
result.error = (
|
|
12996
|
-
f"All agents {failure_label} (tried: {', '.join(tried_agents)}). "
|
|
12997
|
-
f"Original error: {result.error}"
|
|
13038
|
+
result.error = ProcessManager.format_all_agents_failure(
|
|
13039
|
+
fallback_failure_kinds, fallback_attempt_errors,
|
|
12998
13040
|
)
|
|
12999
13041
|
result.status = "failed"
|
|
13000
13042
|
result.failure_code = ProcessManager.all_agents_failure_code(
|
|
@@ -13139,6 +13181,9 @@ class RuntimeDaemon:
|
|
|
13139
13181
|
post_validation_failure_kinds = (
|
|
13140
13182
|
[post_validation_failure_kind] if post_validation_failure_kind else []
|
|
13141
13183
|
)
|
|
13184
|
+
post_validation_attempt_errors = (
|
|
13185
|
+
[(agent.agent_id, result.error)] if post_validation_failure_kind else []
|
|
13186
|
+
)
|
|
13142
13187
|
post_validation_failure_label = (
|
|
13143
13188
|
"rate-limited"
|
|
13144
13189
|
if post_validation_failure_kind == "rate_limit"
|
|
@@ -13172,6 +13217,7 @@ class RuntimeDaemon:
|
|
|
13172
13217
|
result = await self.process_manager.run_agent(
|
|
13173
13218
|
agent, task, workspace_path, on_chunk=on_output_chunk,
|
|
13174
13219
|
)
|
|
13220
|
+
post_validation_attempt_errors.append((agent.agent_id, result.error))
|
|
13175
13221
|
fallback_failure_kind = ProcessManager.agent_failure_kind(result)
|
|
13176
13222
|
if fallback_failure_kind:
|
|
13177
13223
|
post_validation_failure_kinds.append(fallback_failure_kind)
|
|
@@ -13207,12 +13253,8 @@ class RuntimeDaemon:
|
|
|
13207
13253
|
# If every agent we tried is still rate-limited, signal the server
|
|
13208
13254
|
# NOT to re-enqueue — it would hit the same quota wall immediately.
|
|
13209
13255
|
if ProcessManager.is_agent_fallback_eligible(result):
|
|
13210
|
-
|
|
13211
|
-
post_validation_failure_kinds,
|
|
13212
|
-
)
|
|
13213
|
-
result.error = (
|
|
13214
|
-
f"All agents {failure_label} (tried: {', '.join(tried_agents)}). "
|
|
13215
|
-
f"Original error: {result.error}"
|
|
13256
|
+
result.error = ProcessManager.format_all_agents_failure(
|
|
13257
|
+
post_validation_failure_kinds, post_validation_attempt_errors,
|
|
13216
13258
|
)
|
|
13217
13259
|
result.status = "failed"
|
|
13218
13260
|
result.failure_code = ProcessManager.all_agents_failure_code(
|
|
@@ -13810,6 +13852,16 @@ class RuntimeDaemon:
|
|
|
13810
13852
|
req_type = input_data.get("requirement_type", "feature")
|
|
13811
13853
|
verification_work_item = bool(input_data.get("verification_work_item"))
|
|
13812
13854
|
|
|
13855
|
+
if node_type == "coding":
|
|
13856
|
+
delivered_files = list(result.files_changed)
|
|
13857
|
+
delivered_files.extend(input_data.get("planned_task_predecessor_files") or [])
|
|
13858
|
+
for missing_path in agent_core.missing_planned_task_files(input_data, delivered_files):
|
|
13859
|
+
issues.append(
|
|
13860
|
+
f"Planned task file has no implementation evidence: {missing_path}. "
|
|
13861
|
+
"Complete the planned change or report the task as blocked; "
|
|
13862
|
+
"documentation-only changes do not complete a source-code task."
|
|
13863
|
+
)
|
|
13864
|
+
|
|
13813
13865
|
qa_policy, qa_policy_issues = _parse_frozen_qa_test_level_policy(
|
|
13814
13866
|
input_data.get("qa_test_level_policy_snapshot")
|
|
13815
13867
|
)
|
|
@@ -14284,11 +14336,36 @@ class RuntimeDaemon:
|
|
|
14284
14336
|
else:
|
|
14285
14337
|
result.metrics["acceptance_criteria_manual_review_required"] = True
|
|
14286
14338
|
else:
|
|
14287
|
-
ac_list =
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
|
|
14339
|
+
ac_list = (
|
|
14340
|
+
cm_data.get("acceptance_criteria")
|
|
14341
|
+
if isinstance(cm_data, dict) else None
|
|
14342
|
+
)
|
|
14343
|
+
if (
|
|
14344
|
+
not isinstance(ac_list, list)
|
|
14345
|
+
or not ac_list
|
|
14346
|
+
or any(
|
|
14347
|
+
not isinstance(ac, dict)
|
|
14348
|
+
or not isinstance(ac.get("id"), str)
|
|
14349
|
+
or not ac["id"].strip()
|
|
14350
|
+
or ac.get("status") not in (
|
|
14351
|
+
"covered", "partial", "uncovered", "unverifiable",
|
|
14352
|
+
"not_applicable", "blocked",
|
|
14353
|
+
)
|
|
14354
|
+
for ac in ac_list
|
|
14355
|
+
)
|
|
14356
|
+
):
|
|
14357
|
+
issues.append(
|
|
14358
|
+
"coverage-matrix.json has invalid acceptance criteria: "
|
|
14359
|
+
"coverage_matrix_invalid"
|
|
14360
|
+
)
|
|
14361
|
+
else:
|
|
14362
|
+
uncovered = [ac for ac in ac_list if ac["status"] != "covered"]
|
|
14363
|
+
if uncovered:
|
|
14364
|
+
ids = ", ".join(ac["id"] for ac in uncovered[:5])
|
|
14365
|
+
issues.append(
|
|
14366
|
+
"Uncovered acceptance criteria in "
|
|
14367
|
+
f"coverage-matrix.json: {ids}"
|
|
14368
|
+
)
|
|
14292
14369
|
except (_json.JSONDecodeError, UnicodeDecodeError) as e:
|
|
14293
14370
|
issues.append(f"coverage-matrix.json is not valid JSON: {e}")
|
|
14294
14371
|
elif _requires_structured_artifacts or _requires_e2e_artifacts:
|
|
@@ -15732,7 +15809,6 @@ class RuntimeDaemon:
|
|
|
15732
15809
|
|
|
15733
15810
|
if (
|
|
15734
15811
|
task.node_type == "testing"
|
|
15735
|
-
and (task.input_data or {}).get("requirement_type") == "task"
|
|
15736
15812
|
and all(
|
|
15737
15813
|
issue.startswith("Uncovered acceptance criteria in coverage-matrix.json:")
|
|
15738
15814
|
for issue in issues
|
|
@@ -15740,7 +15816,11 @@ class RuntimeDaemon:
|
|
|
15740
15816
|
):
|
|
15741
15817
|
result.status = "failed"
|
|
15742
15818
|
result.failure_code = "acceptance_criteria_uncovered"
|
|
15743
|
-
result.error =
|
|
15819
|
+
result.error = (
|
|
15820
|
+
f"Acceptance verification is incomplete: {issues[0]}. "
|
|
15821
|
+
"Resolve the unmet criteria or missing test prerequisites, "
|
|
15822
|
+
"then rerun verification."
|
|
15823
|
+
)
|
|
15744
15824
|
result.metrics["validation_issues"] = issues
|
|
15745
15825
|
self._record_testing_telemetry(
|
|
15746
15826
|
workspace_path, task, result, retries_used=attempt
|
|
@@ -15850,7 +15930,6 @@ class RuntimeDaemon:
|
|
|
15850
15930
|
])
|
|
15851
15931
|
if (
|
|
15852
15932
|
task.node_type == "testing"
|
|
15853
|
-
and (task.input_data or {}).get("requirement_type") == "task"
|
|
15854
15933
|
and all(
|
|
15855
15934
|
issue.startswith("Uncovered acceptance criteria in coverage-matrix.json:")
|
|
15856
15935
|
for issue in remaining
|
|
@@ -15858,7 +15937,11 @@ class RuntimeDaemon:
|
|
|
15858
15937
|
):
|
|
15859
15938
|
result.status = "failed"
|
|
15860
15939
|
result.failure_code = "acceptance_criteria_uncovered"
|
|
15861
|
-
result.error =
|
|
15940
|
+
result.error = (
|
|
15941
|
+
f"Acceptance verification is incomplete: {remaining[0]}. "
|
|
15942
|
+
"Resolve the unmet criteria or missing test prerequisites, "
|
|
15943
|
+
"then rerun verification."
|
|
15944
|
+
)
|
|
15862
15945
|
result.metrics["validation_issues"] = remaining
|
|
15863
15946
|
self._record_testing_telemetry(
|
|
15864
15947
|
workspace_path, task, result, retries_used=max_retries
|
|
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
|