Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c419d48f5 | |||
| daee737934 |
+44
-52
@@ -1,58 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Hardened the TDD bug-fix quality gate for issue #629: PR parsing now
|
||||
requires whole-word closing keywords (avoids false positives like
|
||||
"prefixes #12"), TDD bug tag discovery now uses exact token matching
|
||||
(avoids `@tdd_bug_42` matching `@tdd_bug_420`), and gate evaluation now
|
||||
requires expected-fail tag removal to be present in the PR diff. CI
|
||||
integration now fetches the PR base branch for diff analysis and includes
|
||||
`tdd_quality_gate` in `status-check` requirements for pull requests.
|
||||
Review-round fixes: `check_expected_fail_removed` now uses word-boundary
|
||||
matching via `_contains_tag_token` (avoids false positives on partial tag
|
||||
names); diff expected-fail removal detection now tracks flags at file level
|
||||
instead of per-hunk (fixes false negatives when tags span different hunks);
|
||||
`parse_bug_refs` filters out issue number zero; redundant double error
|
||||
reporting eliminated; regex compilation cached via `lru_cache`; nox session
|
||||
no longer installs the full project (script uses stdlib only); CI checkout
|
||||
uses `fetch-depth: 0` for reliable merge-base resolution.
|
||||
Review-round 2 fixes: diff expected-fail removal detection now requires the
|
||||
removed line to contain both the expected-fail tag and the specific bug tag
|
||||
(fixes false positives when two bugs' TDD tests reside in the same file);
|
||||
`check_expected_fail_removed` error messages now use the correct tag prefix
|
||||
per file type (`@tdd_bug_N` for `.feature`, `tdd_bug_N` for `.robot`);
|
||||
`bool` values are now rejected by bug-number validation guards; file-read
|
||||
error handling in `find_tdd_tests` and `check_expected_fail_removed` now
|
||||
catches `UnicodeDecodeError` (root-safe unreadable-file handling); temp
|
||||
directory cleanup added to `after_scenario` hook; 8 new Behave scenarios
|
||||
covering bool guards, co-located bug false-positive, `run_quality_gate`
|
||||
argument validation, and `main()` CLI entry point.
|
||||
Review-round 3 fixes: synthetic PR diff helper now auto-detects `.robot`
|
||||
vs `.feature` file type and generates the matching diff format (fixes
|
||||
under-tested robot-format diff code path); `check_expected_fail_removed`
|
||||
test step now filters files by bug tag via `find_tdd_tests` before
|
||||
checking (matches the production code path); `after_scenario` temp
|
||||
directory cleanup no longer sets `context.temp_dir = None` (fixes
|
||||
cleanup conflict with `cli_init_yes_flag_steps.py`); 2 new Behave
|
||||
scenarios covering multi-line PR description parsing and non-string
|
||||
`pr_diff` type guard.
|
||||
- Added Fix-then-Revalidate orchestration loop for required validations:
|
||||
bounded retry with configurable limits (0--100 per Safety Profile),
|
||||
strategy revision escalation via ``auto_strategy_revision`` float
|
||||
threshold, user escalation via ``needs_user_escalation`` result flag,
|
||||
and domain events (``VALIDATION_FIX_ATTEMPTED``, ``VALIDATION_FIX_SUCCEEDED``,
|
||||
``VALIDATION_FIX_EXHAUSTED``). Validation errors are treated as required
|
||||
failures regardless of mode. Includes ``auto_validation_fix`` threshold,
|
||||
per-resource retry tracking, early-exit signalling via ``None`` return from
|
||||
``FixCallback``, event bus circuit breaker with lock-protected failure
|
||||
counter, spec-required ``validation_summary`` and
|
||||
``final_validation_results`` fields on the result model, DI container
|
||||
All notable changes to this project will be documented in this file.
|
||||
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- **`task-implementor` posts work-started notification comments** (#11031): Both
|
||||
the `issue_impl` and `pr_fix` procedures now post an informational "work
|
||||
started" comment to the Forgejo issue/PR before beginning implementation.
|
||||
The comment includes the issue/PR title, procedure type, and expected
|
||||
duration. Posted asynchronously ("fire and move on") so it does not block
|
||||
the workflow. Step numbering in both procedures has been re-numbered to
|
||||
accommodate the new step.
|
||||
|
||||
- **`agents session tell` invokes real LLM orchestrator actor** (#5784): Replaced the
|
||||
M3 echo-stub with real actor invocation via `SessionWorkflow`, routing through
|
||||
`LangChainSessionCaller` → `ToolCallingRuntime.run_tool_loop()`. The user prompt
|
||||
@@ -74,9 +34,25 @@
|
||||
|
||||
### Added
|
||||
|
||||
- **`pr-review-worker` review-started notification** (#11028): The `first_review`
|
||||
and `re_review` modes now post a "review started" notification comment to the
|
||||
PR at the beginning of the review, giving PR authors immediate visibility
|
||||
that a review is in progress. Posted asynchronously so it does not block
|
||||
the workflow.
|
||||
|
||||
- **Plan Rollback Command** (#8557): Implemented `agents plan rollback <plan-id> [<checkpoint-id>]` for checkpoint-based plan state restoration in Epic #8493. The command restores a plan's sandbox to the state captured at a given checkpoint, discarding all decisions made after that checkpoint. The checkpoint can be specified as an optional positional second argument or via the `--to-checkpoint` named option. Supports `--yes/-y` flag to skip confirmation prompts and `--format/-f` for output format selection (rich/plain/json/yaml). Included with comprehensive BDD test coverage (>= 97%) and spec-aligned output formatting showing rollback summary, changes reverted, impact analysis, and post-rollback state panels.
|
||||
|
||||
### Fixed
|
||||
- Python 3.13 asyncio compatibility (issue #11134): replaced all deprecated ``asyncio.get_event_loop()`` calls with ``get_running_loop()`` + exception-safe fallback, updated ``datetime.utcnow()`` usage to ``datetime.now(timezone.utc)``, and added ``PYTHONWARNINGS=ignore::DeprecationWarning`` safety net in nox session. Fixes seven failing BDD scenarios caused by Python 3.13 deprecation warnings becoming errors during test execution across base.py, nodes.py, bridge.py, route_bridge.py, reactive_application_coverage_steps.py, langgraph_bridge_steps.py, and route_bridge_coverage_steps.py.
|
||||
- **Guard cleanup_stale against execute/processing and execute/complete plans** (#11121):
|
||||
``_create_sandbox_for_plan()`` in ``src/cleveragents/cli/commands/plan.py`` now
|
||||
skips ``GitWorktreeSandbox.cleanup_stale()`` when the plan is in
|
||||
``execute/processing`` (execution in progress) or ``execute/complete`` (execution
|
||||
finished, awaiting apply) state. Previously, re-invoking ``agents plan execute``
|
||||
on a completed plan would silently destroy the ``cleveragents/plan-<id>`` git
|
||||
worktree branch, causing ``plan apply`` to merge zero artifacts. The guard
|
||||
preserves the branch per spec (§sandbox.cleanup defaults to ``on_apply``).
|
||||
|
||||
- **Global CLI options ``--data-dir``, ``--config-path``, and ``-v`` now work correctly**
|
||||
(#6785): These spec-required flags were absent from ``main_callback()`` in
|
||||
``src/cleveragents/cli/main.py``, causing any invocation with these flags to crash
|
||||
@@ -86,6 +62,13 @@
|
||||
sets ``CLEVERAGENTS_CONFIG_PATH`` for ``ConfigService`` to pick up, and ``-v``
|
||||
(repeatable count) maps to the appropriate ``structlog`` log level.
|
||||
|
||||
- **Add regression test for ActorRegistry.add() nested provider/model extraction** (#4321):
|
||||
Added BDD regression test confirming that provider and model are correctly
|
||||
extracted from nested `actors.<name>.config` blocks when `type` is only at
|
||||
the nested level and `name` uses the `local/` namespace prefix. This
|
||||
scenario was previously fixed in #4300; the test ensures the fix remains
|
||||
in place and prevents future regressions.
|
||||
|
||||
- **Actor configuration validation incorrectly requires top-level provider field** (#4300):
|
||||
Actor configuration in V3 is now obtained from the nested configuration
|
||||
parameter, according to the specification.
|
||||
@@ -327,6 +310,16 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`invariant_enforced` decisions not propagated to child plans on subplan spawn** (#9131):
|
||||
Fixed `SubplanService.spawn()` to propagate all `invariant_enforced` decisions from the
|
||||
parent plan's decision tree to each child plan's decision tree. Previously, child plans
|
||||
started Strategize with a completely empty invariant set, violating the spec requirement:
|
||||
"recorded as `invariant_enforced` decisions that propagate to child plans." The fix adds
|
||||
a `_propagate_invariant_decisions()` helper that re-records each parent
|
||||
`invariant_enforced` decision on the child plan, including `non_overridable` global
|
||||
invariants. BDD regression coverage added in
|
||||
`features/tdd_invariant_propagation_subplan.feature`.
|
||||
|
||||
- **fix(repositories): derive PlanResult.success from result_success column instead of error_message** (#7501):
|
||||
Fixed a critical bug in `PlanRepository._to_domain` where `PlanResult.success` was incorrectly
|
||||
derived from `error_message is None`. Because `error_message` is shared between the build phase
|
||||
@@ -915,5 +908,4 @@
|
||||
- **TUI -- Permission Question Widget**: A new inline `PermissionQuestionWidget`
|
||||
renders permission requests directly in the conversation stream for single-file
|
||||
operations. Users can allow/reject with single-key shortcuts (`a`/`A`/`r`/`R`),
|
||||
navigate with arrow keys, confirm with `Enter`, or press `v` to open the full
|
||||
|
||||
navigate with arrow keys, confirm with `Enter`, or press `v` to open the full
|
||||
@@ -8,6 +8,12 @@
|
||||
* Luis Mendes <luis.p.mendes@gmail.com>
|
||||
* Rui Hu <rui.hu@cleverthis.com>
|
||||
|
||||
## Contributors
|
||||
|
||||
### Code Contributions
|
||||
|
||||
- **[asyncio and Deprecation Fixes](https://git.cleverthis.com/cleveragents/cleveragents-core/issues/11134)** - Replaced deprecated `asyncio.get_event_loop()` calls with `get_running_loop()` + safe fallback, updated `datetime.utcnow()` usage to `datetime.now(timezone.utc)`, and added PYTHONWARNINGS safety net in nox session. Fixed seven failing BDD scenarios under Python 3.13 (issue #11134).
|
||||
|
||||
# Details
|
||||
|
||||
Below are some of the specific details of various contributions.
|
||||
|
||||
@@ -116,7 +116,10 @@ def step_impl(context):
|
||||
graph.execute = exec_with_messages
|
||||
|
||||
executor = bridge._create_graph_executor({"graph": "test_graph"})
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
|
||||
def _run_operator(message):
|
||||
results = []
|
||||
@@ -146,6 +149,10 @@ def step_impl(context):
|
||||
except ValueError:
|
||||
context.results.append("graph_executor_error")
|
||||
|
||||
# Close the fresh event loop to prevent resource leak
|
||||
with __import__("contextlib").suppress(RuntimeError):
|
||||
loop.close() # Safe: get_running_loop catches if another loop already exists
|
||||
|
||||
|
||||
@when("I exercise state utilities and node operator")
|
||||
def step_impl(context):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import tempfile
|
||||
@@ -29,7 +30,10 @@ def _write_config_file(data: dict[str, Any]) -> Path:
|
||||
|
||||
def _run_async(coro):
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
if loop.is_closed():
|
||||
raise RuntimeError
|
||||
if loop.is_running():
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
@@ -153,7 +154,10 @@ def _ensure_topological_levels(graph):
|
||||
|
||||
def _run_async(coro):
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
if loop.is_closed():
|
||||
raise RuntimeError
|
||||
if loop.is_running():
|
||||
@@ -435,7 +439,7 @@ def step_graph_route_idle_time(context):
|
||||
@given("I have a graph state that has been idle too long")
|
||||
def step_graph_state_idle(context):
|
||||
context.graph_state = GraphState()
|
||||
current_time = asyncio.get_event_loop().time()
|
||||
current_time = time.time()
|
||||
context.graph_state.metadata = {"last_updated": current_time - 10.0}
|
||||
context.graph_state.messages = []
|
||||
|
||||
@@ -673,7 +677,11 @@ def step_attempt_downgrade(context):
|
||||
)
|
||||
return
|
||||
try:
|
||||
context.downgrade_result = asyncio.get_event_loop().run_until_complete(
|
||||
try:
|
||||
_loop_downgrade_result = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
_loop_downgrade_result = asyncio.new_event_loop()
|
||||
context.downgrade_result = _loop_downgrade_result.run_until_complete(
|
||||
context.route_bridge.downgrade_graph_to_stream(
|
||||
context.route_config, context.langgraph
|
||||
)
|
||||
@@ -709,7 +717,11 @@ def step_route_upgrade_downgrade(context):
|
||||
|
||||
@when("I perform an upgrade and then downgrade cycle")
|
||||
def step_cycle_upgrade_downgrade(context):
|
||||
context.langgraph = asyncio.get_event_loop().run_until_complete(
|
||||
try:
|
||||
_loop_langgraph = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
_loop_langgraph = asyncio.new_event_loop()
|
||||
context.langgraph = _loop_langgraph.run_until_complete(
|
||||
context.route_bridge.upgrade_stream_to_graph(
|
||||
context.route_config, context.stream_message
|
||||
)
|
||||
@@ -731,7 +743,15 @@ def step_cycle_upgrade_downgrade(context):
|
||||
name=name, type=getattr(node, "type", NodeType.FUNCTION)
|
||||
)
|
||||
|
||||
context.stream_config = asyncio.get_event_loop().run_until_complete(
|
||||
try:
|
||||
|
||||
_loop_stream_config = asyncio.get_running_loop()
|
||||
|
||||
except RuntimeError:
|
||||
|
||||
_loop_stream_config = asyncio.new_event_loop()
|
||||
|
||||
context.stream_config = _loop_stream_config.run_until_complete(
|
||||
context.route_bridge.downgrade_graph_to_stream(
|
||||
RouteConfig.from_graph_config(
|
||||
GraphConfig(
|
||||
|
||||
@@ -54,9 +54,17 @@ class Agent(ABC):
|
||||
def process_message_sync(
|
||||
self, message: Any, context: dict[str, Any] | None = None
|
||||
) -> Any:
|
||||
return asyncio.get_event_loop().run_until_complete(
|
||||
self.process_message(message, context or {})
|
||||
)
|
||||
try:
|
||||
_loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
_loop = asyncio.new_event_loop()
|
||||
try:
|
||||
return _loop.run_until_complete(
|
||||
self.process_message(message, context or {})
|
||||
)
|
||||
finally:
|
||||
if not _loop.is_running():
|
||||
_loop.close()
|
||||
|
||||
@abstractmethod
|
||||
async def process_message(
|
||||
|
||||
@@ -224,7 +224,10 @@ class RxPyLangGraphBridge:
|
||||
)
|
||||
|
||||
def create_future_task(msg: StreamMessage) -> asyncio.Future[StreamMessage]:
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
task: asyncio.Future[StreamMessage] = asyncio.ensure_future(
|
||||
execute_graph(msg), loop=loop
|
||||
)
|
||||
|
||||
@@ -106,7 +106,10 @@ class Node: # pylint: disable=too-many-instance-attributes
|
||||
|
||||
async def execute(self, state: GraphState) -> dict[str, Any]: # pylint: disable=too-many-branches
|
||||
self.execution_count += 1
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
start_time = loop.time()
|
||||
try:
|
||||
state_updates = {"current_node": self.name}
|
||||
@@ -271,7 +274,10 @@ class Node: # pylint: disable=too-many-instance-attributes
|
||||
if asyncio.iscoroutinefunction(fn):
|
||||
result = await fn(state)
|
||||
else:
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
result = await loop.run_in_executor(None, lambda: fn(state))
|
||||
if asyncio.iscoroutine(result):
|
||||
result = await result
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from rx.scheduler.eventloop import AsyncIOScheduler # type: ignore[attr-defined]
|
||||
@@ -86,7 +86,7 @@ class RouteBridge:
|
||||
last_updated = state.metadata.get("last_updated")
|
||||
if (
|
||||
last_updated
|
||||
and (asyncio.get_event_loop().time() - last_updated) > idle_threshold
|
||||
and (time.time() - last_updated) > idle_threshold
|
||||
):
|
||||
return True
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user