Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9deb168bd6 | |||
| 985bad7b9e | |||
| 1974dd4829 | |||
| e10463419e |
@@ -17,6 +17,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
### Added
|
||||
|
||||
- **Multi-Scope Agent Skill Discovery** (#9454): Implemented `AgentSkillLoader` and `AgentSkillDiscovery` classes supporting progressive disclosure across three tiers (metadata → instructions → resources) with multi-scope discovery from global, project, and local directories. Name collisions resolved with precedence: local > project > global. Includes comprehensive BDD test coverage.
|
||||
|
||||
- **TDD Issue-Capture Test Activation** (#7025): Replaced 234 bare `@skip` tags
|
||||
across 82 Behave feature files with the correct `@tdd_expected_fail @tdd_issue
|
||||
@tdd_issue_<N>` tag system. Scenarios whose referenced bugs were already fixed
|
||||
|
||||
@@ -17,4 +17,5 @@ Below are some of the specific details of various contributions.
|
||||
* Brent E. Edwards has contributed quality assurance, test coverage, and CI pipeline improvements.
|
||||
* HAL 9000 has contributed automated implementation, bug fixes, and feature development as part of the CleverAgents automation pool.
|
||||
* HAL 9000 has contributed the plan concurrency race-condition fix (#7989): wired `LockService` into the plan lifecycle, guarding `execute_plan()` and `apply_plan()` with plan-level advisory locks and unique per-invocation owner identities to prevent silent concurrent state corruption.
|
||||
* HAL 9000 has contributed the multi-scope agent skill discovery implementation (#9454): implemented AgentSkillLoader, AgentSkillSpec, SkillStep, AgentSkillToolDescriptor, AgentSkillResourceSlot, and AgentSkillDiscovery classes with progressive disclosure (Tier 1 metadata / Tier 2 instructions / Tier 3 resources) supporting global, project, and local scope discovery with name collision resolution.
|
||||
* This project was made possible thanks to considerable donation of time, money, and resources by CleverThis, Inc.
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
=== TDD Scenario Analysis ===
|
||||
|
||||
Analyzing failing scenarios...
|
||||
Analyzing errored scenarios...
|
||||
|
||||
=== ANALYSIS RESULTS ===
|
||||
Category A (Leave alone - already tagged): 81
|
||||
Category B (Need new issues): 11
|
||||
Category C (Need to reopen issues): 0
|
||||
Unknown categories: 1
|
||||
Errors: 0
|
||||
|
||||
=== CATEGORY A: Leave Alone (81 scenarios) ===
|
||||
features/project_context_set_exec_env_priority.feature:14 - Scenario: Bug #1079 - project context set accepts --executio...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1079', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_context_set_exec_env_priority.feature:20 - Scenario: Bug #1079 - project context set accepts --executio...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1079', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_context_set_exec_env_priority.feature:26 - Scenario: Bug #1079 - project context set rejects --executio...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1079', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_context_set_exec_env_priority.feature:32 - Scenario: Bug #1079 - project context set defaults execution...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1079', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_context_set_exec_env_priority.feature:38 - Scenario: Bug #1079 - project context set rejects invalid --...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1079', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_create_persist.feature:11 - Scenario: Created project appears in project list...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_589', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_create_persist.feature:17 - Scenario: Multiple created projects all appear in list...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_589', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_create_persist.feature:25 - Scenario: Bare project name uses default namespace and persi...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_589', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_create_persist.feature:31 - Scenario: Creating a duplicate project produces an error...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_589', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_show_after_create.feature:11 - Scenario: Show displays a project that was just created...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_590', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_show_after_create.feature:18 - Scenario: Show displays correct details for a created projec...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_590', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/project_show_after_create.feature:26 - Scenario: Show returns error for a project that does not exi...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_590', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_fs.feature:21 - Scenario: fs-directory type exists after init without explic...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_523', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_fs.feature:29 - Scenario: After initialization fs-directory type exists in t...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_523', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_fs.feature:39 - Scenario: resource add fs-directory succeeds after bootstrap...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_523', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_fs_mount.feature:9 - Scenario: fs-mount type exists after bootstrap...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_2911', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_fs_mount.feature:17 - Scenario: fs-mount is user-addable after bootstrap...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_2911', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_fs_mount.feature:24 - Scenario: fs-mount has fs-directory as child type after boot...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_2911', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_fs_mount.feature:31 - Scenario: fs-mount has no parent types after bootstrap...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_2911', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_fs_mount.feature:38 - Scenario: resource add fs-mount succeeds after bootstrap...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_2911', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_fs_mount.feature:46 - Scenario: fs-directory can reference fs-mount as parent type...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_2911', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_git.feature:29 - Scenario: After initialization the git-checkout type exists ...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_524', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/resource_type_bootstrap_git.feature:41 - Scenario: agents resource add git-checkout succeeds without ...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_524', '@tdd_expected_fail', '@tdd_issue_4178']
|
||||
Feature tags: []
|
||||
features/session_list_summary_dedup.feature:31 - Scenario: Rich table summary panel contains all required fie...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4271', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_a2a_sdk_dependency.feature:11 - Scenario: a2a-sdk is listed in project dependencies...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4273', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_a2a_sdk_dependency.feature:20 - Scenario: a2a SDK provides the A2AClient class...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4273', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_automation_profile_session_leak.feature:20 - Scenario: upsert closes session in auto_commit mode on succe...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4274', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_automation_profile_session_leak.feature:26 - Scenario: delete closes session in auto_commit mode on succe...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4274', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_automation_profile_session_leak.feature:33 - Scenario: upsert closes session in auto_commit mode on datab...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4274', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_automation_profile_session_leak.feature:39 - Scenario: delete closes session in auto_commit mode on datab...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4274', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_automation_profile_session_leak.feature:45 - Scenario: get_by_name closes session in auto_commit mode on ...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4274', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_automation_profile_session_leak.feature:51 - Scenario: list_all closes session in auto_commit mode on suc...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4274', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_automation_profile_session_leak.feature:57 - Scenario: get_by_name closes session in auto_commit mode on ...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4274', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_automation_profile_session_leak.feature:63 - Scenario: list_all closes session in auto_commit mode on dat...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4274', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_checkpoint_real_rollback.feature:15 - Scenario: Rollback restores file content to checkpoint state...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4276', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_checkpoint_real_rollback.feature:23 - Scenario: Rollback removes files added after the checkpoint...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4276', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_context_tier_runtime.feature:20 - Scenario: Promotion on repeated access moves fragment to a h...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4278', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_context_tier_runtime.feature:27 - Scenario: Demotion on staleness moves fragment to a lower ti...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4278', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_context_tier_runtime.feature:34 - Scenario: Eviction on hot tier budget overflow removes oldes...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4278', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_correction_checkpoint_wiring.feature:14 - Scenario: DI container wires checkpoint_service into Correct...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4279', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_correction_checkpoint_wiring.feature:20 - Scenario: Container-provided CorrectionService checkpoint_se...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4279', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_exec_env_resolution_precedence.feature:40 - Scenario: Regression guard - Plan-level override still beats...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4281', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_exec_env_resolution_precedence.feature:46 - Scenario: Regression guard - Project-level override beats ho...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4281', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_indentation_library_try_except.feature:11 - Scenario: Single except clause produces valid Python...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4282', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_indentation_library_try_except.feature:19 - Scenario: Multiple except clauses produce valid Python...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4282', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_indentation_library_try_except.feature:26 - Scenario: try...except...finally produces valid Python...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4282', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_indentation_library_try_except.feature:33 - Scenario: Nested try blocks produce valid Python...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4282', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_indentation_library_try_except.feature:40 - Scenario: Nested try with finally then outer except produces...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4282', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_indentation_library_try_except.feature:46 - Scenario: Code after try block produces valid Python...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4282', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_mcp_error_content_key.feature:8 - Scenario: invoke() extracts error message from MCP 1.4.0 con...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4284', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_skill_add_regression.feature:23 - Scenario: skill add in one process is visible to skill list ...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4287', '@tdd_expected_fail', '@tdd_issue', '@tdd_issue_4287']
|
||||
Feature tags: []
|
||||
features/tdd_skill_add_regression.feature:32 - Scenario: skill add persists config path across processes...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4287', '@tdd_expected_fail', '@tdd_issue', '@tdd_issue_4287']
|
||||
Feature tags: []
|
||||
features/tdd_subplan_spawn_orchestration.feature:16 - Scenario: Spawn result contains child Plan domain objects...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4289', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_subplan_spawn_orchestration.feature:24 - Scenario: Child plans enter the strategize phase after spawn...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4289', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_subplan_spawn_orchestration.feature:32 - Scenario: Parent plan tracks child plan lifecycle status...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4289', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:10 - Scenario: Plan override wins over devcontainer (Level 1 beat...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:16 - Scenario: Project override wins over devcontainer (Level 2 b...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:22 - Scenario: Devcontainer wins over plan fallback (Level 3 beat...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:28 - Scenario: Devcontainer wins over project fallback (Level 3 b...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:34 - Scenario: Plan fallback used when no devcontainer (Level 4)...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:40 - Scenario: Project fallback used when no devcontainer and no ...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:46 - Scenario: Host default when nothing configured (Level 6)...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:52 - Scenario: Plan override beats project override (Level 1 beat...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:60 - Scenario: Override bypasses devcontainer auto-detection...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:67 - Scenario: Fallback defers to devcontainer when present...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:74 - Scenario: Fallback uses configured env when no devcontainer ...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tdd_tool_runner_env_precedence.feature:80 - Scenario: Default priority is fallback — devcontainer wins o...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_4290', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:14 - Scenario: Script detects missing SAN for git.dev.cleveragent...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:22 - Scenario: Script passes when hostname is present in SANs...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:29 - Scenario: Script detects expired certificate...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:37 - Scenario: Script warns when certificate expires within thres...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:45 - Scenario: Script does not warn when certificate expires beyo...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:53 - Scenario: Script reports TLS handshake failure as an error...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:60 - Scenario: Script reports connection timeout as an error...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:67 - Scenario: Script reports connection refused as an error...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:76 - Scenario: Script accepts wildcard SAN matching the hostname...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:83 - Scenario: Script rejects wildcard SAN that does not match th...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:93 - Scenario: _hostname_matches_san returns True for exact match...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:98 - Scenario: _hostname_matches_san returns False when hostname ...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:103 - Scenario: _hostname_matches_san handles wildcard SANs correc...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
features/tls_certificate_check.feature:108 - Scenario: _hostname_matches_san rejects multi-level wildcard...
|
||||
Scenario tags: ['@tdd_issue', '@tdd_issue_1543', '@tdd_expected_fail']
|
||||
Feature tags: []
|
||||
|
||||
=== CATEGORY B: Need New Issues (11 scenarios) ===
|
||||
features/cli_extensions.feature:307 - Scenario: Plan status in JSON format contains required keys...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/cli_extensions.feature:321 - Scenario: Action show in JSON format contains all required k...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/cli_extensions.feature:336 - Scenario: Action show in JSON includes invariants list when ...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/cli_global_format_flag.feature:51 - Scenario: version command reads format from global ctx.obj...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/cli_output_formats.feature:95 - Scenario: Format output handles all format types for dict...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/m6_autonomy_acceptance.feature:170 - Scenario: M6 smoke A2A version negotiation rejects unsupport...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/tdd_a2a_sdk_dependency.feature:15 - Scenario: a2a module is importable as a project dependency...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/tdd_event_bus_exception_swallow.feature:22 - Scenario: Bug #988 — emit() logs exception message when hand...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/tdd_session_create_di.feature:22 - Scenario: Session create command produces structured output ...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/cli_json_envelope.feature:22 - Scenario: YAML output includes all required envelope fields...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
features/cli_json_envelope.feature:61 - Scenario: YAML envelope data field contains actor list paylo...
|
||||
Scenario tags: []
|
||||
Feature tags: []
|
||||
|
||||
=== CATEGORY C: Need to Reopen Issues (0 scenarios) ===
|
||||
|
||||
=== UNKNOWN CATEGORIES (1 scenarios) ===
|
||||
features/tui_slash_overlay_descriptions.feature:36 - Scenario: TDD capture - overlay previously showed names only...
|
||||
Tags: ['@tdd_expected_fail']
|
||||
@@ -1 +0,0 @@
|
||||
/tmp/ca-bug-hunter-3920911-1775163253
|
||||
@@ -693,3 +693,216 @@ Feature: Agent Skills Loader
|
||||
When I call discover on the agent skill
|
||||
Then the discover result should have 1 resource slot(s)
|
||||
And the discover result resource slot "assets" should have access "read_only"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Multi-Scope Discovery — global/project/local scopes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Discover skills from global scope only
|
||||
Given a global scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: global/system-skill
|
||||
description: A skill in the global scope.
|
||||
---
|
||||
Global instructions.
|
||||
"""
|
||||
When I create a discovery with global scope
|
||||
And I discover skills from global scope only
|
||||
Then the discovered skills should include "global/system-skill"
|
||||
And the discovered skills count should be 1
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Discover skills from project scope only
|
||||
Given a project scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: project/build-skill
|
||||
description: A skill in the project scope.
|
||||
---
|
||||
Project instructions.
|
||||
"""
|
||||
When I create a discovery with project scope
|
||||
And I discover skills from project scope only
|
||||
Then the discovered skills should include "project/build-skill"
|
||||
And the discovered skills count should be 1
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Discover skills from local scope only
|
||||
Given a local scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: local/custom-skill
|
||||
description: A skill in the local scope.
|
||||
---
|
||||
Local instructions.
|
||||
"""
|
||||
When I create a discovery with local scope
|
||||
And I discover skills from local scope only
|
||||
Then the discovered skills should include "local/custom-skill"
|
||||
And the discovered skills count should be 1
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Discover skills from all scopes combined
|
||||
Given a global scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: global/system-skill
|
||||
description: A skill in the global scope.
|
||||
---
|
||||
Global instructions.
|
||||
"""
|
||||
And a project scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: project/build-skill
|
||||
description: A skill in the project scope.
|
||||
---
|
||||
Project instructions.
|
||||
"""
|
||||
And a local scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: local/custom-skill
|
||||
description: A skill in the local scope.
|
||||
---
|
||||
Local instructions.
|
||||
"""
|
||||
When I create a discovery with all scopes
|
||||
And I discover skills from all scopes
|
||||
Then the discovered skills should include "global/system-skill"
|
||||
And the discovered skills should include "project/build-skill"
|
||||
And the discovered skills should include "local/custom-skill"
|
||||
And the discovered skills count should be 3
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Name collision resolution with local precedence over project
|
||||
Given a global scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: shared/deploy-skill
|
||||
description: Global version of deploy skill.
|
||||
---
|
||||
Global deploy instructions.
|
||||
"""
|
||||
And a project scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: shared/deploy-skill
|
||||
description: Project version of deploy skill.
|
||||
---
|
||||
Project deploy instructions.
|
||||
"""
|
||||
When I create a discovery with global and project scopes
|
||||
And I discover skills from all scopes
|
||||
Then the discovered skills should include "shared/deploy-skill"
|
||||
And the discovered skill "shared/deploy-skill" description should be "Project version of deploy skill."
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Name collision resolution with local precedence over global
|
||||
Given a global scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: shared/deploy-skill
|
||||
description: Global version of deploy skill.
|
||||
---
|
||||
Global deploy instructions.
|
||||
"""
|
||||
And a local scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: shared/deploy-skill
|
||||
description: Local version of deploy skill.
|
||||
---
|
||||
Local deploy instructions.
|
||||
"""
|
||||
When I create a discovery with global and local scopes
|
||||
And I discover skills from all scopes
|
||||
Then the discovered skills should include "shared/deploy-skill"
|
||||
And the discovered skill "shared/deploy-skill" description should be "Local version of deploy skill."
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Name collision resolution with local precedence over all
|
||||
Given a global scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: shared/deploy-skill
|
||||
description: Global version of deploy skill.
|
||||
---
|
||||
Global deploy instructions.
|
||||
"""
|
||||
And a project scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: shared/deploy-skill
|
||||
description: Project version of deploy skill.
|
||||
---
|
||||
Project deploy instructions.
|
||||
"""
|
||||
And a local scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: shared/deploy-skill
|
||||
description: Local version of deploy skill.
|
||||
---
|
||||
Local deploy instructions.
|
||||
"""
|
||||
When I create a discovery with all scopes
|
||||
And I discover skills from all scopes
|
||||
Then the discovered skills should include "shared/deploy-skill"
|
||||
And the discovered skill "shared/deploy-skill" description should be "Local version of deploy skill."
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Multiple skills in same scope are all discovered
|
||||
Given a global scope directory with multiple skill folders:
|
||||
| name | description |
|
||||
| global/skill-a | First global skill |
|
||||
| global/skill-b | Second global skill |
|
||||
| global/skill-c | Third global skill |
|
||||
When I create a discovery with global scope
|
||||
And I discover skills from global scope only
|
||||
Then the discovered skills count should be 3
|
||||
And the discovered skills should include "global/skill-a"
|
||||
And the discovered skills should include "global/skill-b"
|
||||
And the discovered skills should include "global/skill-c"
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Non-existent scope directory is handled gracefully
|
||||
Given a non-existent global scope directory
|
||||
When I create a discovery with global scope
|
||||
And I discover skills from global scope only
|
||||
Then the discovered skills count should be 0
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Empty scope directory returns no skills
|
||||
Given an empty global scope directory
|
||||
When I create a discovery with global scope
|
||||
And I discover skills from global scope only
|
||||
Then the discovered skills count should be 0
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: Scope directory with non-skill folders is ignored
|
||||
Given a global scope directory with non-skill folders
|
||||
When I create a discovery with global scope
|
||||
And I discover skills from global scope only
|
||||
Then the discovered skills count should be 0
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: AgentSkillDiscovery.from_config factory creates instance
|
||||
When I create a discovery using from_config factory
|
||||
Then the discovery instance should be created successfully
|
||||
|
||||
@multi_scope_discovery
|
||||
Scenario: AgentSkillDiscovery properties expose configured directories
|
||||
Given a global scope directory with a skill folder:
|
||||
"""
|
||||
---
|
||||
name: global/system-skill
|
||||
description: A skill in the global scope.
|
||||
---
|
||||
Global instructions.
|
||||
"""
|
||||
When I create a discovery with global scope
|
||||
Then the discovery global_dirs property should be set
|
||||
And the discovery global_dirs should contain the configured directory
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
Feature: Task validation for bug hunt pool supervisor
|
||||
As a bug hunt pool supervisor
|
||||
I want to validate files before assigning tasks
|
||||
So that workers are not dispatched for non-existent or invalid files
|
||||
|
||||
Background:
|
||||
Given a temporary test repository
|
||||
|
||||
Scenario: Validate existing source file
|
||||
Given a file "src/cleveragents/agents/base.py" exists for task validation
|
||||
When I validate the file for task assignment
|
||||
Then the task validation should pass
|
||||
And no error message should be returned for task validation
|
||||
|
||||
Scenario: Reject non-existent file
|
||||
Given a file "src/cleveragents/agents/nonexistent.py" does not exist for task validation
|
||||
When I validate the file for task assignment
|
||||
Then the task validation should fail
|
||||
And the task validation error message should contain "does not exist"
|
||||
|
||||
Scenario: Reject file in .git directory
|
||||
Given a file ".git/apply_labels_cleanup.py" does not exist for task validation
|
||||
When I validate the file for task assignment
|
||||
Then the task validation should fail
|
||||
And the task validation error message should contain "non-source directory"
|
||||
|
||||
Scenario: Reject file in .opencode directory
|
||||
Given a file ".opencode/scripts/apply_tracking_updates.py" exists for task validation
|
||||
When I validate the file for task assignment
|
||||
Then the task validation should fail
|
||||
And the task validation error message should contain "non-source directory"
|
||||
|
||||
Scenario: Filter multiple files
|
||||
Given a list of files for task validation:
|
||||
| file_path | should_be_valid |
|
||||
| src/cleveragents/agents/base.py | true |
|
||||
| features/steps/agent_skills_loader_steps.py | true |
|
||||
| .git/apply_labels_cleanup.py | false |
|
||||
| nonexistent_file.py | false |
|
||||
| .opencode/scripts/apply_tracking_updates.py | false |
|
||||
When I filter the files for task assignment
|
||||
Then the valid files for task assignment should be:
|
||||
| src/cleveragents/agents/base.py |
|
||||
| features/steps/agent_skills_loader_steps.py |
|
||||
And the invalid files for task assignment should be:
|
||||
| .git/apply_labels_cleanup.py |
|
||||
| nonexistent_file.py |
|
||||
| .opencode/scripts/apply_tracking_updates.py |
|
||||
|
||||
Scenario: Reject directory path
|
||||
Given a directory "src/cleveragents" exists for task validation
|
||||
When I validate the directory for task assignment
|
||||
Then the task validation should fail
|
||||
And the task validation error message should contain "not a file"
|
||||
|
||||
Scenario: Handle empty file path
|
||||
Given an empty file path for task validation
|
||||
When I validate the file for task assignment
|
||||
Then the task validation should fail
|
||||
And the task validation error message should contain "empty"
|
||||
|
||||
Scenario: Validate file with absolute path
|
||||
Given a file "src/cleveragents/agents/base.py" exists for task validation
|
||||
When I validate the file with absolute path for task assignment
|
||||
Then the task validation should pass
|
||||
And no error message should be returned for task validation
|
||||
|
||||
Scenario: Skip directories in path validation
|
||||
Given a file "src/cleveragents/agents/base.py" exists for task validation
|
||||
When I validate the file for task assignment
|
||||
Then the task validation should pass
|
||||
And the file should not be in any skip directory for task validation
|
||||
|
||||
Scenario: Log invalid files
|
||||
Given a list of invalid files for task validation:
|
||||
| .git/apply_labels_cleanup.py |
|
||||
| nonexistent_file.py |
|
||||
When I log the invalid files for task validation
|
||||
Then the log should contain warnings for each invalid file for task validation
|
||||
@@ -7,7 +7,7 @@ Feature: Plan prompt command
|
||||
When I run plan command help
|
||||
Then help output should include plan prompt command
|
||||
|
||||
@tdd_issue @tdd_issue_4255 @tdd_expected_fail
|
||||
@tdd_issue @tdd_issue_4255
|
||||
Scenario: Plan prompt delivers guidance and returns spec envelope in json
|
||||
Given a mocked lifecycle service prompt response
|
||||
When I run plan prompt with plan id "01HXM8C2ZK4Q7C2B3F2R4VYV6J" and guidance "Use mocks for database tests" in format "json"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,213 @@
|
||||
"""Step definitions for task validation feature."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from behave import given, then, when
|
||||
from behave.runner import Context
|
||||
|
||||
from cleveragents.agents.task_validation import (
|
||||
filter_files_for_task_assignment,
|
||||
log_invalid_files,
|
||||
validate_file_for_task_assignment,
|
||||
)
|
||||
|
||||
|
||||
@given("a temporary test repository")
|
||||
def step_create_temp_repo(context: Context) -> None:
|
||||
"""Create a temporary test repository."""
|
||||
context.temp_dir = tempfile.mkdtemp()
|
||||
context.repo_root = context.temp_dir
|
||||
|
||||
# Create some test directories and files
|
||||
Path(context.temp_dir, "src", "cleveragents", "agents").mkdir(parents=True)
|
||||
Path(context.temp_dir, "features", "steps").mkdir(parents=True)
|
||||
Path(context.temp_dir, ".opencode", "scripts").mkdir(parents=True)
|
||||
Path(context.temp_dir, ".git").mkdir(parents=True)
|
||||
|
||||
# Create some test files
|
||||
Path(context.temp_dir, "src", "cleveragents", "agents", "base.py").write_text(
|
||||
"# test file"
|
||||
)
|
||||
Path(context.temp_dir, "features", "steps", "agent_skills_loader_steps.py").write_text(
|
||||
"# test file"
|
||||
)
|
||||
Path(context.temp_dir, ".opencode", "scripts", "apply_tracking_updates.py").write_text(
|
||||
"# test file"
|
||||
)
|
||||
|
||||
|
||||
@given('a file "{file_path}" exists for task validation')
|
||||
def step_file_exists(context: Context, file_path: str) -> None:
|
||||
"""Ensure a file exists in the test repository."""
|
||||
full_path = Path(context.repo_root) / file_path
|
||||
full_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
full_path.write_text("# test file")
|
||||
context.file_path = file_path
|
||||
|
||||
|
||||
@given('a file "{file_path}" does not exist for task validation')
|
||||
def step_file_not_exists(context: Context, file_path: str) -> None:
|
||||
"""Ensure a file does not exist in the test repository."""
|
||||
full_path = Path(context.repo_root) / file_path
|
||||
# Make sure parent directory exists but not the file
|
||||
full_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if full_path.exists():
|
||||
full_path.unlink()
|
||||
context.file_path = file_path
|
||||
|
||||
|
||||
@given('a directory "{dir_path}" exists for task validation')
|
||||
def step_directory_exists(context: Context, dir_path: str) -> None:
|
||||
"""Ensure a directory exists in the test repository."""
|
||||
full_path = Path(context.repo_root) / dir_path
|
||||
full_path.mkdir(parents=True, exist_ok=True)
|
||||
context.file_path = dir_path
|
||||
|
||||
|
||||
@given("an empty file path for task validation")
|
||||
def step_empty_file_path(context: Context) -> None:
|
||||
"""Set an empty file path."""
|
||||
context.file_path = ""
|
||||
|
||||
|
||||
@given("a list of files for task validation:")
|
||||
def step_file_list(context: Context) -> None:
|
||||
"""Create a list of files for testing."""
|
||||
context.file_list = []
|
||||
context.expected_valid = []
|
||||
context.expected_invalid = []
|
||||
|
||||
for row in context.table:
|
||||
file_path = row["file_path"]
|
||||
should_be_valid = row["should_be_valid"].lower() == "true"
|
||||
|
||||
# Create the file if it should be valid
|
||||
if should_be_valid and not file_path.startswith("."):
|
||||
full_path = Path(context.repo_root) / file_path
|
||||
full_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
full_path.write_text("# test file")
|
||||
|
||||
context.file_list.append(file_path)
|
||||
if should_be_valid:
|
||||
context.expected_valid.append(file_path)
|
||||
else:
|
||||
context.expected_invalid.append(file_path)
|
||||
|
||||
|
||||
@given("a list of invalid files for task validation:")
|
||||
def step_invalid_file_list(context: Context) -> None:
|
||||
"""Create a list of invalid files for testing."""
|
||||
context.invalid_files = {}
|
||||
for row in context.table:
|
||||
file_path = row[0]
|
||||
context.invalid_files[file_path] = f"File {file_path} is invalid"
|
||||
|
||||
|
||||
@when("I validate the file for task assignment")
|
||||
def step_validate_file(context: Context) -> None:
|
||||
"""Validate a file for task assignment."""
|
||||
is_valid, error_msg = validate_file_for_task_assignment(
|
||||
context.file_path, context.repo_root
|
||||
)
|
||||
context.is_valid = is_valid
|
||||
context.error_msg = error_msg
|
||||
|
||||
|
||||
@when("I validate the file with absolute path for task assignment")
|
||||
def step_validate_file_absolute(context: Context) -> None:
|
||||
"""Validate a file with absolute path for task assignment."""
|
||||
abs_path = os.path.join(context.repo_root, context.file_path)
|
||||
is_valid, error_msg = validate_file_for_task_assignment(abs_path, context.repo_root)
|
||||
context.is_valid = is_valid
|
||||
context.error_msg = error_msg
|
||||
|
||||
|
||||
@when("I validate the directory for task assignment")
|
||||
def step_validate_directory(context: Context) -> None:
|
||||
"""Validate a directory for task assignment."""
|
||||
is_valid, error_msg = validate_file_for_task_assignment(
|
||||
context.file_path, context.repo_root
|
||||
)
|
||||
context.is_valid = is_valid
|
||||
context.error_msg = error_msg
|
||||
|
||||
|
||||
@when("I filter the files for task assignment")
|
||||
def step_filter_files(context: Context) -> None:
|
||||
"""Filter files for task assignment."""
|
||||
valid_files, invalid_files = filter_files_for_task_assignment(
|
||||
context.file_list, context.repo_root
|
||||
)
|
||||
context.valid_files = valid_files
|
||||
context.invalid_files_dict = invalid_files
|
||||
|
||||
|
||||
@when("I log the invalid files for task validation")
|
||||
def step_log_invalid_files(context: Context) -> None:
|
||||
"""Log invalid files."""
|
||||
log_invalid_files(context.invalid_files)
|
||||
context.logged_invalid_files = True
|
||||
|
||||
|
||||
@then("the task validation should pass")
|
||||
def step_task_validation_pass(context: Context) -> None:
|
||||
"""Assert that validation passed."""
|
||||
assert context.is_valid is True, f"Expected validation to pass, but got: {context.error_msg}"
|
||||
|
||||
|
||||
@then("the task validation should fail")
|
||||
def step_task_validation_fail(context: Context) -> None:
|
||||
"""Assert that validation failed."""
|
||||
assert context.is_valid is False, "Expected validation to fail, but it passed"
|
||||
|
||||
|
||||
@then("no error message should be returned for task validation")
|
||||
def step_no_error_message(context: Context) -> None:
|
||||
"""Assert that no error message was returned."""
|
||||
assert context.error_msg is None, f"Expected no error message, but got: {context.error_msg}"
|
||||
|
||||
|
||||
@then('the task validation error message should contain "{text}"')
|
||||
def step_error_message_contains(context: Context, text: str) -> None:
|
||||
"""Assert that the error message contains specific text."""
|
||||
assert context.error_msg is not None, "Expected error message, but got None"
|
||||
assert text in context.error_msg, f"Expected error message to contain '{text}', but got: {context.error_msg}"
|
||||
|
||||
|
||||
@then("the valid files for task assignment should be:")
|
||||
def step_valid_files_list(context: Context) -> None:
|
||||
"""Assert the list of valid files."""
|
||||
expected_valid = [row[0] for row in context.table]
|
||||
assert context.valid_files == expected_valid, (
|
||||
f"Expected valid files {expected_valid}, but got {context.valid_files}"
|
||||
)
|
||||
|
||||
|
||||
@then("the invalid files for task assignment should be:")
|
||||
def step_invalid_files_list(context: Context) -> None:
|
||||
"""Assert the list of invalid files."""
|
||||
expected_invalid = [row[0] for row in context.table]
|
||||
actual_invalid = list(context.invalid_files_dict.keys())
|
||||
assert actual_invalid == expected_invalid, (
|
||||
f"Expected invalid files {expected_invalid}, but got {actual_invalid}"
|
||||
)
|
||||
|
||||
|
||||
@then("the file should not be in any skip directory for task validation")
|
||||
def step_file_not_in_skip_dir(context: Context) -> None:
|
||||
"""Assert that the file is not in a skip directory."""
|
||||
from cleveragents.agents.task_validation import SKIP_DIRS
|
||||
|
||||
path_parts = Path(context.file_path).parts
|
||||
for part in path_parts:
|
||||
assert part not in SKIP_DIRS, f"File path contains skip directory: {part}"
|
||||
|
||||
|
||||
@then("the log should contain warnings for each invalid file for task validation")
|
||||
def step_log_contains_warnings(context: Context) -> None:
|
||||
"""Assert that warnings were logged for invalid files."""
|
||||
assert context.logged_invalid_files is True, "Expected invalid files to be logged"
|
||||
@@ -104,6 +104,8 @@ def step_prompt_envelope_status(context, status: str) -> None:
|
||||
|
||||
@then("prompt output data should include queued guidance")
|
||||
def step_prompt_envelope_data(context) -> None:
|
||||
from datetime import datetime
|
||||
|
||||
payload = _json_output(context.prompt_result.output)
|
||||
data = payload.get("data")
|
||||
assert isinstance(data, dict)
|
||||
@@ -113,6 +115,15 @@ def step_prompt_envelope_data(context) -> None:
|
||||
queue = data.get("queue")
|
||||
assert isinstance(queue, dict)
|
||||
assert queue.get("pending") == 1
|
||||
# Verify timing.started is present and is a valid ISO timestamp
|
||||
timing = payload.get("timing")
|
||||
assert isinstance(timing, dict)
|
||||
assert "started" in timing
|
||||
assert "duration_ms" in timing
|
||||
started = timing.get("started")
|
||||
assert isinstance(started, str)
|
||||
# Verify it is a valid ISO 8601 timestamp
|
||||
datetime.fromisoformat(started)
|
||||
|
||||
|
||||
@then('prompt output should include guidance text "{guidance}"')
|
||||
|
||||
@@ -169,6 +169,93 @@ def step_create_default_persona(context: object, actor: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
@given('an actor service with actor "{actor}" supporting thinking_effort')
|
||||
def step_actor_service_thinking_effort(context: object, actor: str) -> None:
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from cleveragents.domain.models.core import Actor
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_actor = Actor(
|
||||
id=1,
|
||||
name=actor,
|
||||
provider="test",
|
||||
model="test",
|
||||
config_blob={"thinking_effort": "medium"},
|
||||
config_hash="test",
|
||||
)
|
||||
mock_service.get_actor.return_value = mock_actor
|
||||
context._actor_service = mock_service
|
||||
|
||||
|
||||
@given('an actor service with actor "{actor}" supporting temperature')
|
||||
def step_actor_service_temperature(context: object, actor: str) -> None:
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from cleveragents.domain.models.core import Actor
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_actor = Actor(
|
||||
id=1,
|
||||
name=actor,
|
||||
provider="test",
|
||||
model="test",
|
||||
config_blob={"temperature": 0.7},
|
||||
config_hash="test",
|
||||
)
|
||||
mock_service.get_actor.return_value = mock_actor
|
||||
context._actor_service = mock_service
|
||||
|
||||
|
||||
@given(
|
||||
'an actor service with actor "{actor}" supporting thinking_effort and temperature'
|
||||
)
|
||||
def step_actor_service_both(context: object, actor: str) -> None:
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from cleveragents.domain.models.core import Actor
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_actor = Actor(
|
||||
id=1,
|
||||
name=actor,
|
||||
provider="test",
|
||||
model="test",
|
||||
config_blob={"thinking_effort": "medium", "temperature": 0.7},
|
||||
config_hash="test",
|
||||
)
|
||||
mock_service.get_actor.return_value = mock_actor
|
||||
context._actor_service = mock_service
|
||||
|
||||
|
||||
@given('an actor service with actor "{actor}" supporting no recognized arguments')
|
||||
def step_actor_service_no_args(context: object, actor: str) -> None:
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from cleveragents.domain.models.core import Actor
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_actor = Actor(
|
||||
id=1,
|
||||
name=actor,
|
||||
provider="test",
|
||||
model="test",
|
||||
config_blob={},
|
||||
config_hash="test",
|
||||
)
|
||||
mock_service.get_actor.return_value = mock_actor
|
||||
context._actor_service = mock_service
|
||||
|
||||
|
||||
@when('I call create_default_persona_for_actor with actor "{actor}" and actor_service')
|
||||
def step_create_default_persona_with_service(context: object, actor: str) -> None:
|
||||
from cleveragents.tui.first_run import create_default_persona_for_actor
|
||||
|
||||
context._created_persona = create_default_persona_for_actor(
|
||||
context._registry, actor, actor_service=context._actor_service
|
||||
)
|
||||
|
||||
|
||||
@then('the registry should contain a persona named "{name}"')
|
||||
def step_registry_has_persona(context: object, name: str) -> None:
|
||||
persona = context._registry.get(name)
|
||||
@@ -188,6 +275,26 @@ def step_registry_last_persona(context: object, name: str) -> None:
|
||||
assert last == name, f"Expected last persona '{name}', got '{last}'"
|
||||
|
||||
|
||||
@then('the default persona should have preset "{preset_name}"')
|
||||
def step_default_persona_has_preset(context: object, preset_name: str) -> None:
|
||||
persona = context._registry.get("default")
|
||||
assert persona is not None
|
||||
preset_names = [p.name for p in persona.argument_presets]
|
||||
assert preset_name in preset_names, (
|
||||
f"Expected preset '{preset_name}' in {preset_names}"
|
||||
)
|
||||
|
||||
|
||||
@then("the default persona should have exactly {count:d} preset")
|
||||
def step_default_persona_preset_count(context: object, count: int) -> None:
|
||||
persona = context._registry.get("default")
|
||||
assert persona is not None
|
||||
actual_count = len(persona.argument_presets)
|
||||
assert actual_count == count, (
|
||||
f"Expected {count} preset(s), got {actual_count}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# render_actor_selection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -30,6 +30,43 @@ Feature: TUI first-run experience with actor selection overlay
|
||||
When I call create_default_persona_for_actor with actor "openai/gpt-4o"
|
||||
Then the registry last persona should be "default"
|
||||
|
||||
Scenario: create_default_persona_for_actor auto-generates thinking_effort presets
|
||||
Given an empty persona registry
|
||||
And an actor service with actor "anthropic/claude-4-sonnet" supporting thinking_effort
|
||||
When I call create_default_persona_for_actor with actor "anthropic/claude-4-sonnet" and actor_service
|
||||
Then the registry should contain a persona named "default"
|
||||
And the default persona should have preset "default"
|
||||
And the default persona should have preset "think: high"
|
||||
And the default persona should have preset "think: max"
|
||||
|
||||
Scenario: create_default_persona_for_actor auto-generates temperature presets
|
||||
Given an empty persona registry
|
||||
And an actor service with actor "openai/gpt-4o" supporting temperature
|
||||
When I call create_default_persona_for_actor with actor "openai/gpt-4o" and actor_service
|
||||
Then the registry should contain a persona named "default"
|
||||
And the default persona should have preset "default"
|
||||
And the default persona should have preset "precise"
|
||||
And the default persona should have preset "creative"
|
||||
|
||||
Scenario: create_default_persona_for_actor auto-generates both thinking_effort and temperature presets
|
||||
Given an empty persona registry
|
||||
And an actor service with actor "local/mock-full" supporting thinking_effort and temperature
|
||||
When I call create_default_persona_for_actor with actor "local/mock-full" and actor_service
|
||||
Then the registry should contain a persona named "default"
|
||||
And the default persona should have preset "default"
|
||||
And the default persona should have preset "think: high"
|
||||
And the default persona should have preset "think: max"
|
||||
And the default persona should have preset "precise"
|
||||
And the default persona should have preset "creative"
|
||||
|
||||
Scenario: create_default_persona_for_actor creates only default preset for unsupported actor
|
||||
Given an empty persona registry
|
||||
And an actor service with actor "local/mock-basic" supporting no recognized arguments
|
||||
When I call create_default_persona_for_actor with actor "local/mock-basic" and actor_service
|
||||
Then the registry should contain a persona named "default"
|
||||
And the default persona should have exactly 1 preset
|
||||
And the default persona should have preset "default"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# render_actor_selection helper
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
"""Task validation for bug hunt pool supervisor.
|
||||
|
||||
This module provides pre-flight validation for task assignment, ensuring that:
|
||||
1. Files exist before tasks are dispatched
|
||||
2. Files are from valid source directories (not .git/ or other non-source locations)
|
||||
3. Invalid file paths are logged and skipped
|
||||
|
||||
This fixes the bug where the bug-hunt-pool-supervisor assigned tasks for
|
||||
non-existent files like /app/.git/apply_labels_cleanup.py.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
# Valid source directories for bug hunt analysis
|
||||
VALID_SOURCE_DIRS = frozenset(
|
||||
{
|
||||
"src",
|
||||
"features",
|
||||
"robot",
|
||||
"alembic",
|
||||
"scripts",
|
||||
"hooks",
|
||||
"benchmarks",
|
||||
"examples",
|
||||
"tests",
|
||||
}
|
||||
)
|
||||
|
||||
# Directories to always skip (non-source locations)
|
||||
SKIP_DIRS = frozenset(
|
||||
{
|
||||
".git",
|
||||
".hg",
|
||||
".svn",
|
||||
"__pycache__",
|
||||
"node_modules",
|
||||
".venv",
|
||||
"venv",
|
||||
".nox",
|
||||
".tox",
|
||||
".mypy_cache",
|
||||
".pytest_cache",
|
||||
".ruff_cache",
|
||||
"dist",
|
||||
"build",
|
||||
".eggs",
|
||||
".cleveragents",
|
||||
".opencode",
|
||||
".devcontainer",
|
||||
".forgejo",
|
||||
".pre-commit-config",
|
||||
"k8s",
|
||||
"docs",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def validate_file_for_task_assignment(
|
||||
file_path: str,
|
||||
repo_root: str | None = None,
|
||||
) -> tuple[bool, str | None]:
|
||||
"""Validate that a file can be assigned as a bug hunt task.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to validate (absolute or relative).
|
||||
repo_root: Repository root directory. If None, uses current directory.
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error_message).
|
||||
- is_valid: True if file is valid for task assignment
|
||||
- error_message: None if valid, otherwise a description of the issue
|
||||
"""
|
||||
if not file_path:
|
||||
return False, "File path is empty"
|
||||
|
||||
if repo_root is None:
|
||||
repo_root = os.getcwd()
|
||||
|
||||
# Normalize the path
|
||||
if os.path.isabs(file_path):
|
||||
abs_path = file_path
|
||||
try:
|
||||
rel_path = os.path.relpath(abs_path, repo_root)
|
||||
except ValueError:
|
||||
# Path is on different drive (Windows)
|
||||
return False, f"File path is outside repository: {file_path}"
|
||||
else:
|
||||
rel_path = file_path
|
||||
abs_path = os.path.join(repo_root, rel_path)
|
||||
|
||||
# Check if file exists
|
||||
if not os.path.exists(abs_path):
|
||||
return False, f"File does not exist: {file_path}"
|
||||
|
||||
# Check if it's a file (not a directory)
|
||||
if not os.path.isfile(abs_path):
|
||||
return False, f"Path is not a file: {file_path}"
|
||||
|
||||
# Check if file is in a skip directory
|
||||
path_parts = Path(rel_path).parts
|
||||
for part in path_parts:
|
||||
if part in SKIP_DIRS:
|
||||
return False, f"File is in non-source directory: {file_path}"
|
||||
|
||||
# Check if file is in a valid source directory (optional stricter check)
|
||||
# For now, we just ensure it's not in a skip directory
|
||||
# This allows flexibility for new source directories
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
def filter_files_for_task_assignment(
|
||||
file_paths: list[str],
|
||||
repo_root: str | None = None,
|
||||
) -> tuple[list[str], dict[str, str]]:
|
||||
"""Filter a list of files, keeping only those valid for task assignment.
|
||||
|
||||
Args:
|
||||
file_paths: List of file paths to validate.
|
||||
repo_root: Repository root directory. If None, uses current directory.
|
||||
|
||||
Returns:
|
||||
Tuple of (valid_files, invalid_files_dict).
|
||||
- valid_files: List of valid file paths
|
||||
- invalid_files_dict: Dict mapping invalid paths to error messages
|
||||
"""
|
||||
valid_files: list[str] = []
|
||||
invalid_files: dict[str, str] = {}
|
||||
|
||||
for file_path in file_paths:
|
||||
is_valid, error_msg = validate_file_for_task_assignment(file_path, repo_root)
|
||||
if is_valid:
|
||||
valid_files.append(file_path)
|
||||
else:
|
||||
invalid_files[file_path] = error_msg or "Unknown validation error"
|
||||
logger.warning(
|
||||
"task_validation.invalid_file",
|
||||
file_path=file_path,
|
||||
error=error_msg,
|
||||
)
|
||||
|
||||
return valid_files, invalid_files
|
||||
|
||||
|
||||
def log_invalid_files(invalid_files: dict[str, str]) -> None:
|
||||
"""Log all invalid files with their error messages.
|
||||
|
||||
Args:
|
||||
invalid_files: Dict mapping invalid file paths to error messages.
|
||||
"""
|
||||
if not invalid_files:
|
||||
return
|
||||
|
||||
logger.warning(
|
||||
"task_validation.invalid_files_summary",
|
||||
count=len(invalid_files),
|
||||
files=list(invalid_files.keys()),
|
||||
)
|
||||
|
||||
for file_path, error_msg in invalid_files.items():
|
||||
logger.warning(
|
||||
"task_validation.invalid_file_detail",
|
||||
file_path=file_path,
|
||||
error=error_msg,
|
||||
)
|
||||
@@ -3382,6 +3382,9 @@ def prompt_plan_cmd(
|
||||
The guidance is injected as a ``user_intervention`` decision and is
|
||||
queued for the next execution step.
|
||||
"""
|
||||
from datetime import UTC
|
||||
|
||||
started_at = datetime.now(UTC)
|
||||
started = time.monotonic()
|
||||
try:
|
||||
service = _get_lifecycle_service()
|
||||
@@ -3398,7 +3401,10 @@ def prompt_plan_cmd(
|
||||
"status": "ok",
|
||||
"exit_code": 0,
|
||||
"data": prompt_data,
|
||||
"timing": {"duration_ms": elapsed_ms},
|
||||
"timing": {
|
||||
"started": started_at.isoformat(),
|
||||
"duration_ms": elapsed_ms,
|
||||
},
|
||||
"messages": ["Guidance queued"],
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,20 @@ Implements the three-tier progressive disclosure model for Agent Skills:
|
||||
tool = loader.to_tool_descriptor() # Map to tool descriptor (source=agent_skill)
|
||||
loader.deactivate() # Clear active body, free token budget
|
||||
|
||||
## Multi-Scope Discovery
|
||||
|
||||
For discovering skills from multiple scopes (global, project, local):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
discovery = AgentSkillDiscovery.from_config(
|
||||
global_dirs=[Path("/etc/cleveragents/skills")],
|
||||
project_dirs=[Path("./skills")],
|
||||
local_dirs=[Path("./local-skills")]
|
||||
)
|
||||
loaders = discovery.discover_all() # Returns all loaders from all scopes
|
||||
# Name collisions are resolved with precedence: local > project > global
|
||||
|
||||
## SKILL.md Format
|
||||
|
||||
Agent Skills follow the AgentSkills.io standard:
|
||||
@@ -577,6 +591,196 @@ class AgentSkillLoader:
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AgentSkillDiscovery — multi-scope discovery (global/project/local)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AgentSkillDiscovery:
|
||||
"""Multi-scope skill discovery for global, project, and local scopes.
|
||||
|
||||
Discovers Agent Skills from multiple configured directories across three
|
||||
scopes (global, project, local) and handles name collisions with
|
||||
precedence: local > project > global.
|
||||
|
||||
Args:
|
||||
global_dirs: List of global scope directories to search.
|
||||
project_dirs: List of project scope directories to search.
|
||||
local_dirs: List of local scope directories to search.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
global_dirs: list[Path] | None = None,
|
||||
project_dirs: list[Path] | None = None,
|
||||
local_dirs: list[Path] | None = None,
|
||||
) -> None:
|
||||
"""Initialize the discovery with scope directories.
|
||||
|
||||
Args:
|
||||
global_dirs: Global scope directories (system-wide).
|
||||
project_dirs: Project scope directories.
|
||||
local_dirs: Local scope directories (working directory).
|
||||
"""
|
||||
self._global_dirs: list[Path] = [d.resolve() for d in (global_dirs or [])]
|
||||
self._project_dirs: list[Path] = [d.resolve() for d in (project_dirs or [])]
|
||||
self._local_dirs: list[Path] = [d.resolve() for d in (local_dirs or [])]
|
||||
|
||||
# -- Properties --------------------------------------------------------
|
||||
|
||||
@property
|
||||
def global_dirs(self) -> list[Path]:
|
||||
"""Global scope directories."""
|
||||
return self._global_dirs
|
||||
|
||||
@property
|
||||
def project_dirs(self) -> list[Path]:
|
||||
"""Project scope directories."""
|
||||
return self._project_dirs
|
||||
|
||||
@property
|
||||
def local_dirs(self) -> list[Path]:
|
||||
"""Local scope directories."""
|
||||
return self._local_dirs
|
||||
|
||||
# -- Factory -----------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
global_dirs: list[Path] | None = None,
|
||||
project_dirs: list[Path] | None = None,
|
||||
local_dirs: list[Path] | None = None,
|
||||
) -> AgentSkillDiscovery:
|
||||
"""Create a discovery instance from configuration.
|
||||
|
||||
Args:
|
||||
global_dirs: Global scope directories.
|
||||
project_dirs: Project scope directories.
|
||||
local_dirs: Local scope directories.
|
||||
|
||||
Returns:
|
||||
A new ``AgentSkillDiscovery`` instance.
|
||||
"""
|
||||
return cls(
|
||||
global_dirs=global_dirs,
|
||||
project_dirs=project_dirs,
|
||||
local_dirs=local_dirs,
|
||||
)
|
||||
|
||||
# -- Discovery ---------------------------------------------------------
|
||||
|
||||
def discover_all(self) -> dict[str, AgentSkillLoader]:
|
||||
"""Discover all skills from all scopes with collision handling.
|
||||
|
||||
Discovers skills from global, project, and local scopes in order.
|
||||
When the same skill name appears in multiple scopes, the higher
|
||||
precedence scope wins: local > project > global.
|
||||
|
||||
Returns:
|
||||
A dictionary mapping skill names to ``AgentSkillLoader`` instances.
|
||||
Keys are skill names (e.g., ``"local/deploy-staging"``).
|
||||
|
||||
Raises:
|
||||
ValueError: If a skill folder is invalid or SKILL.md is malformed.
|
||||
"""
|
||||
loaders: dict[str, AgentSkillLoader] = {}
|
||||
|
||||
# Discover in order: global, project, local
|
||||
# Higher precedence scopes override lower ones
|
||||
for folder in self._global_dirs:
|
||||
self._discover_from_scope(folder, loaders)
|
||||
|
||||
for folder in self._project_dirs:
|
||||
self._discover_from_scope(folder, loaders)
|
||||
|
||||
for folder in self._local_dirs:
|
||||
self._discover_from_scope(folder, loaders)
|
||||
|
||||
return loaders
|
||||
|
||||
def discover_global(self) -> dict[str, AgentSkillLoader]:
|
||||
"""Discover skills from global scope only.
|
||||
|
||||
Returns:
|
||||
A dictionary mapping skill names to ``AgentSkillLoader`` instances.
|
||||
"""
|
||||
loaders: dict[str, AgentSkillLoader] = {}
|
||||
for folder in self._global_dirs:
|
||||
self._discover_from_scope(folder, loaders)
|
||||
return loaders
|
||||
|
||||
def discover_project(self) -> dict[str, AgentSkillLoader]:
|
||||
"""Discover skills from project scope only.
|
||||
|
||||
Returns:
|
||||
A dictionary mapping skill names to ``AgentSkillLoader`` instances.
|
||||
"""
|
||||
loaders: dict[str, AgentSkillLoader] = {}
|
||||
for folder in self._project_dirs:
|
||||
self._discover_from_scope(folder, loaders)
|
||||
return loaders
|
||||
|
||||
def discover_local(self) -> dict[str, AgentSkillLoader]:
|
||||
"""Discover skills from local scope only.
|
||||
|
||||
Returns:
|
||||
A dictionary mapping skill names to ``AgentSkillLoader`` instances.
|
||||
"""
|
||||
loaders: dict[str, AgentSkillLoader] = {}
|
||||
for folder in self._local_dirs:
|
||||
self._discover_from_scope(folder, loaders)
|
||||
return loaders
|
||||
|
||||
# -- Private helpers ---------------------------------------------------
|
||||
|
||||
def _discover_from_scope(
|
||||
self, scope_dir: Path, loaders: dict[str, AgentSkillLoader]
|
||||
) -> None:
|
||||
"""Discover skills from a single scope directory.
|
||||
|
||||
Scans *scope_dir* for subdirectories containing SKILL.md files.
|
||||
Each subdirectory is treated as a skill folder. Discovered skills
|
||||
are added to *loaders*, with higher precedence scopes overwriting
|
||||
lower ones.
|
||||
|
||||
Args:
|
||||
scope_dir: The scope directory to scan.
|
||||
loaders: Dictionary to accumulate discovered loaders.
|
||||
"""
|
||||
if not scope_dir.exists() or not scope_dir.is_dir():
|
||||
msg = "Scope directory does not exist or is not a directory: %s"
|
||||
logger.debug(msg, scope_dir)
|
||||
return
|
||||
|
||||
try:
|
||||
for item in sorted(scope_dir.iterdir()):
|
||||
if not item.is_dir():
|
||||
continue
|
||||
|
||||
skill_md = item / "SKILL.md"
|
||||
if not skill_md.exists():
|
||||
continue
|
||||
|
||||
try:
|
||||
loader = AgentSkillLoader.from_folder(item)
|
||||
skill_name = loader.spec.name
|
||||
loaders[skill_name] = loader
|
||||
logger.debug(
|
||||
"Discovered skill '%s' from scope directory '%s'",
|
||||
skill_name,
|
||||
scope_dir,
|
||||
)
|
||||
except (FileNotFoundError, ValueError) as exc:
|
||||
logger.warning(
|
||||
"Failed to load skill from '%s': %s",
|
||||
item,
|
||||
exc,
|
||||
)
|
||||
except OSError as exc:
|
||||
logger.warning("Error scanning scope directory '%s': %s", scope_dir, exc)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from cleveragents.tui.persona.registry import PersonaRegistry
|
||||
from cleveragents.tui.persona.schema import Persona
|
||||
from cleveragents.tui.persona.schema import Persona, PersonaPreset
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cleveragents.application.services.actor_service import ActorService
|
||||
|
||||
|
||||
def is_first_run(registry: PersonaRegistry) -> bool:
|
||||
@@ -27,9 +32,106 @@ def is_first_run(registry: PersonaRegistry) -> bool:
|
||||
return len(registry.list_personas()) == 0
|
||||
|
||||
|
||||
def _auto_generate_presets(
|
||||
actor_service: ActorService,
|
||||
actor: str,
|
||||
) -> list[PersonaPreset]:
|
||||
"""Auto-generate argument presets from the actor's schema.
|
||||
|
||||
Inspects the actor's argument schema to detect ``thinking_effort`` and
|
||||
``temperature`` arguments, then generates appropriate presets per
|
||||
ADR-045 §Auto-Generated Presets.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
actor_service:
|
||||
The :class:`~cleveragents.application.services.actor_service.ActorService`
|
||||
instance to query for actor metadata.
|
||||
actor:
|
||||
Fully-qualified actor reference (e.g. ``"anthropic/claude-4-sonnet"``).
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[PersonaPreset]
|
||||
List of auto-generated presets, always including ``"default"``.
|
||||
Additional presets are added if the actor supports ``thinking_effort``
|
||||
and/or ``temperature`` arguments.
|
||||
"""
|
||||
presets: list[PersonaPreset] = [
|
||||
PersonaPreset(name="default", display="default", overrides={})
|
||||
]
|
||||
|
||||
try:
|
||||
actor_obj = actor_service.get_actor(actor)
|
||||
except Exception:
|
||||
# If actor lookup fails, return only the default preset
|
||||
return presets
|
||||
|
||||
# Check for thinking_effort and temperature in config_blob or compiled_metadata
|
||||
has_thinking_effort = False
|
||||
has_temperature = False
|
||||
|
||||
# Check config_blob for argument definitions
|
||||
config_blob = actor_obj.config_blob or {}
|
||||
if isinstance(config_blob, dict):
|
||||
# Check if thinking_effort is in the config
|
||||
if "thinking_effort" in config_blob:
|
||||
has_thinking_effort = True
|
||||
if "temperature" in config_blob:
|
||||
has_temperature = True
|
||||
|
||||
# Check compiled_metadata for argument schema information
|
||||
compiled_metadata = actor_obj.compiled_metadata or {}
|
||||
if isinstance(compiled_metadata, dict):
|
||||
# Look for argument schema in compiled metadata
|
||||
arguments = compiled_metadata.get("arguments", {})
|
||||
if isinstance(arguments, dict):
|
||||
if "thinking_effort" in arguments:
|
||||
has_thinking_effort = True
|
||||
if "temperature" in arguments:
|
||||
has_temperature = True
|
||||
|
||||
# Generate thinking_effort presets if supported
|
||||
if has_thinking_effort:
|
||||
presets.append(
|
||||
PersonaPreset(
|
||||
name="think: high",
|
||||
display="think: high",
|
||||
overrides={"thinking_effort": "high"},
|
||||
)
|
||||
)
|
||||
presets.append(
|
||||
PersonaPreset(
|
||||
name="think: max",
|
||||
display="think: max",
|
||||
overrides={"thinking_effort": "max"},
|
||||
)
|
||||
)
|
||||
|
||||
# Generate temperature presets if supported
|
||||
if has_temperature:
|
||||
presets.append(
|
||||
PersonaPreset(
|
||||
name="precise",
|
||||
display="precise",
|
||||
overrides={"temperature": 0.1},
|
||||
)
|
||||
)
|
||||
presets.append(
|
||||
PersonaPreset(
|
||||
name="creative",
|
||||
display="creative",
|
||||
overrides={"temperature": 0.9},
|
||||
)
|
||||
)
|
||||
|
||||
return presets
|
||||
|
||||
|
||||
def create_default_persona_for_actor(
|
||||
registry: PersonaRegistry,
|
||||
actor: str,
|
||||
actor_service: ActorService | None = None,
|
||||
) -> Persona:
|
||||
"""Create and persist a ``"default"`` persona for the given actor.
|
||||
|
||||
@@ -37,6 +139,10 @@ def create_default_persona_for_actor(
|
||||
overlay. The persona is saved to the registry and set as the last
|
||||
active persona so that subsequent launches restore it.
|
||||
|
||||
When *actor_service* is provided, the actor's argument schema is
|
||||
inspected to auto-generate useful presets (thinking-effort and
|
||||
temperature levels) per ADR-045 §Auto-Generated Presets.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
registry:
|
||||
@@ -44,16 +150,26 @@ def create_default_persona_for_actor(
|
||||
instance to write to.
|
||||
actor:
|
||||
Fully-qualified actor reference (e.g. ``"anthropic/claude-4-sonnet"``).
|
||||
actor_service:
|
||||
Optional :class:`~cleveragents.application.services.actor_service.ActorService`
|
||||
instance for inspecting actor schemas. If not provided, only the
|
||||
default preset is created.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Persona
|
||||
The newly created and persisted default persona.
|
||||
"""
|
||||
# Auto-generate presets if actor_service is available
|
||||
presets: list[PersonaPreset] | None = None
|
||||
if actor_service is not None:
|
||||
presets = _auto_generate_presets(actor_service, actor)
|
||||
|
||||
persona = Persona(
|
||||
name="default",
|
||||
actor=actor,
|
||||
description="Default persona",
|
||||
argument_presets=presets or [],
|
||||
)
|
||||
registry.save(persona)
|
||||
registry.set_last_persona(persona.name)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
You are UAT test worker [AUTO-UAT-13]. Your job is to test the **Subplans & Parallel Execution** feature area against the product specification (v3.3.0 milestone).
|
||||
|
||||
## Repository & Credentials
|
||||
- **Repo**: cleveragents/cleveragents-core
|
||||
- **Forgejo Base URL**: https://git.cleverthis.com
|
||||
- **Forgejo PAT**: 92224acff675c50c5958d1eaca9a688abd405e06
|
||||
- **Forgejo Username**: HAL9000
|
||||
- **Git Name**: CleverThis
|
||||
- **Git Email**: hal9000@cleverthis.com
|
||||
- **Clone URL**: https://HAL9000:92224acff675c50c5958d1eaca9a688abd405e06@git.cleverthis.com/cleveragents/cleveragents-core.git
|
||||
|
||||
## Feature Area to Test
|
||||
**Subplans & Parallel Execution** — plans spawning child subplans during execution, parallel execution with configurable concurrency, result merging.
|
||||
|
||||
## Spec Reference (v3.3.0 Acceptance Criteria)
|
||||
- Plans spawn child subplans during execution
|
||||
- Subplan status tracking works (sequential and/or parallel execution)
|
||||
- Parent plan tracks all subplan statuses
|
||||
- Three-way merge combines non-conflicting changes; conflicts surfaced to user
|
||||
|
||||
Read `docs/specification.md` — search for "subplan", "child plan", "parallel execution", "max_parallel", "three-way merge" sections.
|
||||
|
||||
## Testing Steps
|
||||
1. Clone the repo: `git clone https://HAL9000:92224acff675c50c5958d1eaca9a688abd405e06@git.cleverthis.com/cleveragents/cleveragents-core.git /tmp/uat-worker-13`
|
||||
2. Read `docs/specification.md` for subplan specs
|
||||
3. Check domain model: `find src/ -name "*subplan*" -o -name "*sub_plan*"`
|
||||
4. Check if subplan spawning is implemented: `grep -r "subplan\|sub_plan\|child_plan" src/ --include="*.py" -l`
|
||||
5. Check for parallel execution: `grep -r "max_parallel\|parallel" src/ --include="*.py" -l`
|
||||
6. Check for three-way merge: `grep -r "three.way\|merge_strategy\|ThreeWay" src/ --include="*.py" -l`
|
||||
7. Look for Behave feature files: `find features/ -name "*subplan*" -o -name "*parallel*"`
|
||||
8. Check Robot Framework tests: `find robot/ -name "*subplan*" -o -name "*parallel*"`
|
||||
|
||||
## Bug Filing Rules
|
||||
- Check if an open issue already exists before filing a new one
|
||||
- Use the `new-issue-creator` subagent to file bugs with proper format
|
||||
- Required labels: State/Unverified, Type/Bug, Priority/High (or Critical if blocking)
|
||||
- Only Priority/Critical bugs get milestone v3.3.0; others get no milestone (backlog)
|
||||
- All issues must end with the bot signature:
|
||||
```
|
||||
---
|
||||
**Automated by CleverAgents Bot**
|
||||
Supervisor: UAT Test Pool | Agent: uat-test-pool-supervisor
|
||||
```
|
||||
|
||||
## Exit Condition
|
||||
After testing, report your findings. If you find bugs not already tracked, file issues. Then exit.
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
AUTH_HEADER="Authorization: token 92224acff675c50c5958d1eaca9a688abd405e06"
|
||||
ISSUE_LABELS_ENDPOINT="https://git.cleverthis.com/api/v1/repos/cleveragents/cleveragents-core/issues/8849/labels"
|
||||
|
||||
printf '%s\n' 'DELETE call:'
|
||||
curl -sS -w '\nHTTP_STATUS:%{http_code}\n' -X DELETE "${ISSUE_LABELS_ENDPOINT}/844" -H "$AUTH_HEADER"
|
||||
|
||||
printf '%s\n' 'POST call:'
|
||||
curl -sS -w '\nHTTP_STATUS:%{http_code}\n' -X POST "$ISSUE_LABELS_ENDPOINT" -H "$AUTH_HEADER" -H 'Content-Type: application/json' -d '{"labels":[841]}'
|
||||
Reference in New Issue
Block a user