forgexa-cli 1.79.2__tar.gz → 1.80.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.
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/PKG-INFO +1 -1
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/agent_core.py +35 -1
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/daemon.py +109 -9
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/pyproject.toml +1 -1
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/README.md +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/aws_evidence.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/aws_logs_evidence.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/observability_contract.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/observability_evidence.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli/zcode_protocol.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/setup.cfg +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.79.2 → forgexa_cli-1.80.0}/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.80.0"
|
|
@@ -106,6 +106,25 @@ def is_implementation_testing_task(node_type: str, input_data: dict) -> bool:
|
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
def validate_task_delivery_baseline(
|
|
110
|
+
baseline: object, node_id: str, graph_id: str, branch: str | None = None,
|
|
111
|
+
) -> str:
|
|
112
|
+
if not isinstance(baseline, dict) or (
|
|
113
|
+
type(baseline.get("version")) is not int or baseline["version"] != 1
|
|
114
|
+
or set(baseline) - {"version", "node_id", "graph_id", "branch", "before_sha", "anchor_node_id"}
|
|
115
|
+
or not baseline.get("node_id") or not baseline.get("graph_id")
|
|
116
|
+
or baseline.get("node_id") != str(node_id)
|
|
117
|
+
or baseline.get("graph_id") != str(graph_id)
|
|
118
|
+
or not isinstance(baseline.get("branch"), str)
|
|
119
|
+
or not baseline["branch"].strip()
|
|
120
|
+
or len(baseline["branch"]) > 255
|
|
121
|
+
or branch is not None and baseline["branch"] != branch
|
|
122
|
+
or not re.fullmatch(r"[0-9a-fA-F]{40}", str(baseline.get("before_sha") or ""))
|
|
123
|
+
):
|
|
124
|
+
raise ValueError("Planned task delivery baseline does not match its task, graph, or branch")
|
|
125
|
+
return baseline["before_sha"].lower()
|
|
126
|
+
|
|
127
|
+
|
|
109
128
|
def missing_planned_task_files(task_input: dict, changed_files: list[str]) -> list[str]:
|
|
110
129
|
"""Find planned files without delivery evidence, including grouped sub-tasks."""
|
|
111
130
|
def normalize_path(value: object) -> str | None:
|
|
@@ -142,6 +161,15 @@ def missing_planned_task_files(task_input: dict, changed_files: list[str]) -> li
|
|
|
142
161
|
)
|
|
143
162
|
|
|
144
163
|
|
|
164
|
+
def requires_task_delivery_baseline(node_type: str, task_input: dict) -> bool:
|
|
165
|
+
return (
|
|
166
|
+
node_type == "coding" or node_type == "fix" and task_input.get("task") == "phase_remediation_fix"
|
|
167
|
+
) and (
|
|
168
|
+
task_input.get("planned_task_git_baseline") is not None
|
|
169
|
+
or bool(missing_planned_task_files(task_input, []))
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
145
173
|
def extract_implementation_audit(text: str) -> dict | None:
|
|
146
174
|
"""Read the last complete audit envelope from mixed assistant output."""
|
|
147
175
|
decoder = json.JSONDecoder()
|
|
@@ -227,7 +255,7 @@ def _assess_implementation_contributions(contract: dict, report: object) -> dict
|
|
|
227
255
|
key: implementation[key] for key in ("status", "evidence", "description", "files", "plan_revision")
|
|
228
256
|
if key in implementation
|
|
229
257
|
}}
|
|
230
|
-
if row.get("plan_revision") is not None:
|
|
258
|
+
if row.get("plan_revision") is not None and implementation.get("status") != "covered":
|
|
231
259
|
check["plan_revision"] = row["plan_revision"]
|
|
232
260
|
checks.append(check)
|
|
233
261
|
if implementation.get("status") == "covered" and row.get("status") == "gap":
|
|
@@ -305,6 +333,12 @@ def assess_implementation_audit(contract: dict, report: object) -> dict:
|
|
|
305
333
|
):
|
|
306
334
|
issues.append(f"Implementation {kind} check requires an explicit status and concrete evidence")
|
|
307
335
|
continue
|
|
336
|
+
if status == "covered" and row.get("plan_revision") is not None:
|
|
337
|
+
issues.append(
|
|
338
|
+
"A covered implementation check cannot request a plan revision; "
|
|
339
|
+
"report the affected check as gap, blocked, or unverified"
|
|
340
|
+
)
|
|
341
|
+
continue
|
|
308
342
|
if kind == "task":
|
|
309
343
|
task_id = row.get("task_id")
|
|
310
344
|
if not isinstance(task_id, str) or task_id not in expected or task_id in checked:
|
|
@@ -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.80.0"
|
|
1238
1238
|
|
|
1239
1239
|
|
|
1240
1240
|
def _detect_client_type() -> str:
|
|
@@ -8950,6 +8950,7 @@ class ProgressReporter:
|
|
|
8950
8950
|
lines_added: int = 0,
|
|
8951
8951
|
lines_removed: int = 0,
|
|
8952
8952
|
execution_attempt_id: str | None = None,
|
|
8953
|
+
task_delivery_baseline: dict | None = None,
|
|
8953
8954
|
) -> bool:
|
|
8954
8955
|
output_lines = [
|
|
8955
8956
|
*self._pending_output_lines.get(task_id, []),
|
|
@@ -8957,7 +8958,7 @@ class ProgressReporter:
|
|
|
8957
8958
|
]
|
|
8958
8959
|
activity_lines = activity_lines or []
|
|
8959
8960
|
files_changed = files_changed or []
|
|
8960
|
-
if self._should_skip_progress(
|
|
8961
|
+
if task_delivery_baseline is None and self._should_skip_progress(
|
|
8961
8962
|
task_id,
|
|
8962
8963
|
progress_percent,
|
|
8963
8964
|
current_step,
|
|
@@ -8981,12 +8982,15 @@ class ProgressReporter:
|
|
|
8981
8982
|
"lines_added": lines_added,
|
|
8982
8983
|
"lines_removed": lines_removed,
|
|
8983
8984
|
"execution_attempt_id": execution_attempt_id,
|
|
8985
|
+
**({"task_delivery_baseline": task_delivery_baseline} if task_delivery_baseline is not None else {}),
|
|
8984
8986
|
},
|
|
8985
8987
|
timeout=10,
|
|
8986
8988
|
)
|
|
8987
8989
|
if 200 <= response.status_code < 300:
|
|
8988
8990
|
if response.json().get("status") == "ignored":
|
|
8989
8991
|
return False
|
|
8992
|
+
if task_delivery_baseline is not None and response.json().get("task_delivery_baseline") != task_delivery_baseline:
|
|
8993
|
+
return False
|
|
8990
8994
|
self._pending_output_lines.pop(task_id, None)
|
|
8991
8995
|
self._last_progress_sent[task_id] = {
|
|
8992
8996
|
"progress_percent": progress_percent,
|
|
@@ -9946,6 +9950,7 @@ class ServerConnection:
|
|
|
9946
9950
|
"protocol_version": runtime_evidence.DEFERRED_ACCEPTANCE_VERIFICATION_PROTOCOL_VERSION,
|
|
9947
9951
|
},
|
|
9948
9952
|
"implementation_audit": {"protocol_version": 3},
|
|
9953
|
+
"task_delivery": {"protocol_version": 1},
|
|
9949
9954
|
"task_attachment_staging": {"protocol_version": 1, "runtime_proxy": True, "image_agents": ["claude"]},
|
|
9950
9955
|
},
|
|
9951
9956
|
"server_url": self.server_url,
|
|
@@ -10370,6 +10375,7 @@ class ServerConnection:
|
|
|
10370
10375
|
"protocol_version": runtime_evidence.DEFERRED_ACCEPTANCE_VERIFICATION_PROTOCOL_VERSION,
|
|
10371
10376
|
},
|
|
10372
10377
|
"implementation_audit": {"protocol_version": 3},
|
|
10378
|
+
"task_delivery": {"protocol_version": 1},
|
|
10373
10379
|
"local_evidence": {"protocol_version": 1, "structured_summary": True},
|
|
10374
10380
|
"task_attachment_staging": {"protocol_version": 1, "runtime_proxy": True, "image_agents": ["claude"]},
|
|
10375
10381
|
},
|
|
@@ -12608,6 +12614,53 @@ class RuntimeDaemon:
|
|
|
12608
12614
|
f"wipe_analysis_dir failed for {output_dir_norm}: {_wipe_err}"
|
|
12609
12615
|
) from _wipe_err
|
|
12610
12616
|
|
|
12617
|
+
async def _resolve_task_delivery_before_sha(
|
|
12618
|
+
self, task: TaskInfo, workspace_path: Path, attempt_before_sha: str,
|
|
12619
|
+
) -> str:
|
|
12620
|
+
baseline = (task.input_data or {}).get("planned_task_git_baseline")
|
|
12621
|
+
if baseline is None and not agent_core.requires_task_delivery_baseline(task.node_type, task.input_data or {}):
|
|
12622
|
+
return attempt_before_sha
|
|
12623
|
+
if not re.fullmatch(r"[0-9a-fA-F]{40}", attempt_before_sha):
|
|
12624
|
+
raise ValueError("Planned task attempt has no verified starting commit")
|
|
12625
|
+
try:
|
|
12626
|
+
branch = (await self.workspace_manager._git(
|
|
12627
|
+
"rev-parse", "--abbrev-ref", "HEAD", cwd=workspace_path, timeout=10,
|
|
12628
|
+
)).strip()
|
|
12629
|
+
except Exception as error:
|
|
12630
|
+
raise ValueError("Planned task delivery branch could not be verified") from error
|
|
12631
|
+
if baseline is None:
|
|
12632
|
+
baseline = {
|
|
12633
|
+
"version": 1, "node_id": task.task_id, "graph_id": task.graph_id,
|
|
12634
|
+
"branch": branch, "before_sha": attempt_before_sha,
|
|
12635
|
+
}
|
|
12636
|
+
task.input_data = {**(task.input_data or {}), "planned_task_git_baseline": baseline}
|
|
12637
|
+
before_sha = agent_core.validate_task_delivery_baseline(
|
|
12638
|
+
baseline, task.task_id, task.graph_id, branch,
|
|
12639
|
+
)
|
|
12640
|
+
try:
|
|
12641
|
+
await self.workspace_manager._git(
|
|
12642
|
+
"merge-base", "--is-ancestor", before_sha, "HEAD", cwd=workspace_path, timeout=10,
|
|
12643
|
+
)
|
|
12644
|
+
except Exception as error:
|
|
12645
|
+
raise ValueError("Planned task delivery baseline is not a verified ancestor of HEAD") from error
|
|
12646
|
+
return before_sha
|
|
12647
|
+
|
|
12648
|
+
async def _collect_task_delivery_git(
|
|
12649
|
+
self, task: TaskInfo, workspace_path: Path, result: TaskResult, *, attempt_before_sha: str,
|
|
12650
|
+
) -> None:
|
|
12651
|
+
if task.node_type != "coding" and not (
|
|
12652
|
+
task.node_type == "fix" and (task.input_data or {}).get("task") == "phase_remediation_fix"
|
|
12653
|
+
):
|
|
12654
|
+
return
|
|
12655
|
+
before_sha = await self._resolve_task_delivery_before_sha(task, workspace_path, attempt_before_sha)
|
|
12656
|
+
git_info = await self.process_manager._collect_git_info_vs_parent(workspace_path, before_sha=before_sha)
|
|
12657
|
+
if git_info.get("files_changed_verified") is not True:
|
|
12658
|
+
raise ValueError("Planned task cumulative Git delivery could not be verified")
|
|
12659
|
+
result.git = {**(result.git or {}), **git_info, "attempt_before_sha": attempt_before_sha}
|
|
12660
|
+
result.files_changed = list(git_info.get("files_changed") or [])
|
|
12661
|
+
result.lines_added = git_info.get("lines_added", 0)
|
|
12662
|
+
result.lines_removed = git_info.get("lines_removed", 0)
|
|
12663
|
+
|
|
12611
12664
|
async def _execute_task(self, task: TaskInfo, conn: ServerConnection):
|
|
12612
12665
|
"""Execute a single task, reporting to the originating server connection."""
|
|
12613
12666
|
reporter = conn.reporter
|
|
@@ -12617,7 +12670,22 @@ class RuntimeDaemon:
|
|
|
12617
12670
|
# skipped for a node bound to the user's own real directory. See
|
|
12618
12671
|
# docs/designs/forgexa-ide-design.md §6.2.
|
|
12619
12672
|
is_local_bind = task.workspace_resolution == "local_bind"
|
|
12673
|
+
workspace_path: Path | None = None
|
|
12674
|
+
node_before_sha = ""
|
|
12675
|
+
delivery_before_sha = ""
|
|
12620
12676
|
async def report_task_complete(result: TaskResult) -> None:
|
|
12677
|
+
if workspace_path is not None and node_before_sha:
|
|
12678
|
+
try:
|
|
12679
|
+
await self._collect_task_delivery_git(
|
|
12680
|
+
task, workspace_path, result, attempt_before_sha=node_before_sha,
|
|
12681
|
+
)
|
|
12682
|
+
except ValueError as error:
|
|
12683
|
+
if result.status == "success":
|
|
12684
|
+
result.status = "failed"
|
|
12685
|
+
result.failure_code = "planned_task_baseline_invalid"
|
|
12686
|
+
result.error = str(error)
|
|
12687
|
+
else:
|
|
12688
|
+
result.metrics["task_delivery_issue"] = str(error)
|
|
12621
12689
|
if is_local_bind:
|
|
12622
12690
|
if result.error:
|
|
12623
12691
|
_append_local_bind_output_log(task.task_id, [f"[daemon] {result.error}"])
|
|
@@ -12640,7 +12708,6 @@ class RuntimeDaemon:
|
|
|
12640
12708
|
return
|
|
12641
12709
|
|
|
12642
12710
|
default_branch = str((task.project or {}).get("default_branch") or "").strip()
|
|
12643
|
-
node_before_sha = ""
|
|
12644
12711
|
# Use workspace-level granularity (requirement_workflow_id or graph_id) so that
|
|
12645
12712
|
# tasks for DIFFERENT requirements can run in parallel on the same daemon, while
|
|
12646
12713
|
# tasks for the SAME requirement (e.g. analysis + retry) are still serialized to
|
|
@@ -12839,6 +12906,29 @@ class RuntimeDaemon:
|
|
|
12839
12906
|
_before_sha_err,
|
|
12840
12907
|
)
|
|
12841
12908
|
|
|
12909
|
+
try:
|
|
12910
|
+
delivery_before_sha = await self._resolve_task_delivery_before_sha(task, workspace_path, node_before_sha)
|
|
12911
|
+
except ValueError as error:
|
|
12912
|
+
await report_task_complete(TaskResult(
|
|
12913
|
+
status="failed", exit_code=-1, stdout="", stderr="", error=str(error),
|
|
12914
|
+
failure_code="planned_task_baseline_invalid", execution_attempt_id=task.execution_attempt_id,
|
|
12915
|
+
))
|
|
12916
|
+
return
|
|
12917
|
+
|
|
12918
|
+
if agent_core.requires_task_delivery_baseline(task.node_type, task.input_data or {}):
|
|
12919
|
+
confirmed = await reporter.report_progress(
|
|
12920
|
+
task.task_id, 9, "freezing_task_delivery_baseline",
|
|
12921
|
+
execution_attempt_id=task.execution_attempt_id,
|
|
12922
|
+
task_delivery_baseline=task.input_data["planned_task_git_baseline"],
|
|
12923
|
+
)
|
|
12924
|
+
if not confirmed:
|
|
12925
|
+
await report_task_complete(TaskResult(
|
|
12926
|
+
status="failed", exit_code=-1, stdout="", stderr="",
|
|
12927
|
+
error="Server did not confirm the durable task delivery baseline; update the backend and verify connectivity before retrying",
|
|
12928
|
+
failure_code="planned_task_baseline_invalid", execution_attempt_id=task.execution_attempt_id,
|
|
12929
|
+
))
|
|
12930
|
+
return
|
|
12931
|
+
|
|
12842
12932
|
# 3. Run agent with real-time output streaming + periodic progress heartbeat
|
|
12843
12933
|
await reporter.report_progress(task.task_id, 10, "running_agent", execution_attempt_id=task.execution_attempt_id)
|
|
12844
12934
|
|
|
@@ -13198,7 +13288,7 @@ class RuntimeDaemon:
|
|
|
13198
13288
|
result, agent = await self._validate_with_agent_fallback(
|
|
13199
13289
|
agent, task, workspace_path, result,
|
|
13200
13290
|
reporter, on_output_chunk, tried_agents=tried_agents,
|
|
13201
|
-
default_branch=default_branch, before_sha=
|
|
13291
|
+
default_branch=default_branch, before_sha=delivery_before_sha,
|
|
13202
13292
|
)
|
|
13203
13293
|
# Re-collect git info if validation triggered retries
|
|
13204
13294
|
if result.metrics.get("validation_issues") is not None or True:
|
|
@@ -13280,7 +13370,7 @@ class RuntimeDaemon:
|
|
|
13280
13370
|
result, agent = await self._validate_with_agent_fallback(
|
|
13281
13371
|
agent, task, workspace_path, result,
|
|
13282
13372
|
reporter, on_output_chunk, tried_agents=tried_agents,
|
|
13283
|
-
default_branch=default_branch, before_sha=
|
|
13373
|
+
default_branch=default_branch, before_sha=delivery_before_sha,
|
|
13284
13374
|
)
|
|
13285
13375
|
pre_commit_git = await self.process_manager._collect_git_info(workspace_path)
|
|
13286
13376
|
except Exception:
|
|
@@ -13416,7 +13506,7 @@ class RuntimeDaemon:
|
|
|
13416
13506
|
try:
|
|
13417
13507
|
result = await self._validate_and_retry(
|
|
13418
13508
|
agent, task, workspace_path, result, reporter, on_output_chunk,
|
|
13419
|
-
max_retries=0, default_branch=default_branch, before_sha=
|
|
13509
|
+
max_retries=0, default_branch=default_branch, before_sha=delivery_before_sha,
|
|
13420
13510
|
)
|
|
13421
13511
|
except Exception:
|
|
13422
13512
|
_mark_qa_source_validation_unavailable(result)
|
|
@@ -13526,7 +13616,7 @@ class RuntimeDaemon:
|
|
|
13526
13616
|
post_commit_git = await self.process_manager._collect_git_info_vs_parent(
|
|
13527
13617
|
workspace_path,
|
|
13528
13618
|
default_branch=default_branch,
|
|
13529
|
-
before_sha=
|
|
13619
|
+
before_sha=delivery_before_sha,
|
|
13530
13620
|
)
|
|
13531
13621
|
result.git = post_commit_git
|
|
13532
13622
|
result.files_changed = result.git.get("files_changed", [])
|
|
@@ -13574,13 +13664,16 @@ class RuntimeDaemon:
|
|
|
13574
13664
|
)
|
|
13575
13665
|
|
|
13576
13666
|
if result.status == "success" and task.critic_review_required:
|
|
13667
|
+
await self._collect_task_delivery_git(
|
|
13668
|
+
task, workspace_path, result, attempt_before_sha=node_before_sha,
|
|
13669
|
+
)
|
|
13577
13670
|
await reporter.report_progress(task.task_id, 95, "running_critic_review", execution_attempt_id=task.execution_attempt_id)
|
|
13578
13671
|
result.critic_review = await self._run_runtime_critic_review(
|
|
13579
13672
|
task,
|
|
13580
13673
|
agent,
|
|
13581
13674
|
workspace_path,
|
|
13582
13675
|
result,
|
|
13583
|
-
before_sha=
|
|
13676
|
+
before_sha=delivery_before_sha,
|
|
13584
13677
|
)
|
|
13585
13678
|
|
|
13586
13679
|
# 6. Report completion (include actual agent used if different from requested)
|
|
@@ -16146,8 +16239,12 @@ class RuntimeDaemon:
|
|
|
16146
16239
|
qa_policy_violations = [
|
|
16147
16240
|
issue for issue in remaining if issue.startswith("qa_policy.")
|
|
16148
16241
|
]
|
|
16242
|
+
planned_delivery_issues = [
|
|
16243
|
+
issue for issue in remaining
|
|
16244
|
+
if issue.startswith("Planned task file has no implementation evidence:")
|
|
16245
|
+
]
|
|
16149
16246
|
blocking_issues = list(dict.fromkeys(
|
|
16150
|
-
critical_issues + scope_violations + qa_policy_violations
|
|
16247
|
+
critical_issues + scope_violations + qa_policy_violations + planned_delivery_issues
|
|
16151
16248
|
))
|
|
16152
16249
|
if blocking_issues:
|
|
16153
16250
|
# Agent didn't produce required output — mark as failed
|
|
@@ -16161,6 +16258,9 @@ class RuntimeDaemon:
|
|
|
16161
16258
|
if scope_violations:
|
|
16162
16259
|
result.failure_code = "verification_work_item_scope_violation"
|
|
16163
16260
|
result.error = "; ".join(scope_violations[:3])
|
|
16261
|
+
elif planned_delivery_issues:
|
|
16262
|
+
result.failure_code = "planned_task_incomplete"
|
|
16263
|
+
result.error = "; ".join(planned_delivery_issues[:3])
|
|
16164
16264
|
else:
|
|
16165
16265
|
result.failure_code = "validation_retry_exhausted"
|
|
16166
16266
|
result.error = (
|
|
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
|