Compare commits

...

4 Commits

Author SHA1 Message Date
HAL9000 9deb168bd6 fix(skills): implement multi-scope agent skill discovery for global, project, and local tiers
CI / helm (pull_request) Successful in 47s
CI / lint (pull_request) Failing after 1m6s
CI / build (pull_request) Successful in 1m7s
CI / typecheck (pull_request) Successful in 1m40s
CI / quality (pull_request) Successful in 1m38s
CI / security (pull_request) Successful in 1m48s
CI / coverage (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 29s
CI / e2e_tests (pull_request) Successful in 3m47s
CI / integration_tests (pull_request) Successful in 3m48s
CI / unit_tests (pull_request) Failing after 4m48s
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s
Implements AgentSkillDiscovery class to support discovering Agent Skills from
multiple configured directories across three scopes (global, project, local).
Handles name collisions with precedence: local > project > global.

Adds comprehensive BDD test coverage for multi-scope discovery scenarios including:
- Global-only, project-only, and local-only discovery
- Combined discovery from all scopes
- Name collision resolution with proper precedence
- Non-existent and empty scope directory handling
- Multiple skills in same scope discovery

ISSUES CLOSED: #9369
2026-05-06 07:54:50 +00:00
HAL9000 985bad7b9e fix(agents): add pre-flight file existence check to bug-hunt-pool-supervisor task assignment
Add task_validation module that validates files before task assignment:
- Checks file existence before dispatching tasks
- Filters out files from non-source directories (.git/, .opencode/, etc.)
- Logs invalid files with descriptive error messages
- Provides filter_files_for_task_assignment() for bulk validation

Add comprehensive BDD tests covering:
- Valid source file validation
- Non-existent file rejection
- Skip directory filtering (.git/, .opencode/, etc.)
- Directory path rejection
- Empty path handling
- Absolute path validation
- Bulk file filtering

This fixes the bug where bug-hunt-pool-supervisor assigned tasks for
non-existent files like /app/.git/apply_labels_cleanup.py.

ISSUES CLOSED: #9365
2026-04-14 18:03:45 +00:00
HAL9000 1974dd4829 fix(tui): auto-generate thinking-effort presets from actor schema in create_default_persona_for_actor
Implements ADR-045 §Auto-Generated Presets by inspecting the actor's argument schema
to detect thinking_effort and temperature arguments, then auto-generating appropriate
presets for first-run personas.

Changes:
- Add _auto_generate_presets() helper to inspect actor schema and generate presets
- Update create_default_persona_for_actor() to accept optional actor_service parameter
- Auto-generate "think: high" and "think: max" presets for actors with thinking_effort
- Auto-generate "precise" and "creative" presets for actors with temperature
- Add comprehensive BDD test coverage for all preset generation scenarios

ISSUES CLOSED: #9360
2026-04-14 18:01:39 +00:00
HAL9000 e10463419e fix(cli): add timing.started ISO timestamp to plan prompt JSON envelope
CI / push-validation (pull_request) Successful in 9s
CI / helm (pull_request) Successful in 25s
CI / lint (pull_request) Failing after 40s
CI / typecheck (pull_request) Successful in 55s
CI / build (pull_request) Successful in 3m18s
CI / quality (pull_request) Successful in 3m41s
CI / integration_tests (pull_request) Successful in 4m0s
CI / security (pull_request) Successful in 4m27s
CI / coverage (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 5m43s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 7m43s
CI / status-check (pull_request) Failing after 2s
- Capture started_at timestamp using datetime.now(UTC) before service call
- Add timing.started field to JSON envelope with ISO 8601 format
- Update step definitions to verify timing.started is present and valid
- Remove @tdd_expected_fail tag from plan_prompt_command.feature scenario

Fixes #9353
2026-04-14 17:57:53 +00:00
18 changed files with 2038 additions and 787 deletions
+2
View File
@@ -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
+1
View File
@@ -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.
-297
View File
@@ -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
View File
@@ -1 +0,0 @@
/tmp/ca-bug-hunter-3920911-1775163253
+213
View File
@@ -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
+79
View File
@@ -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
+1 -1
View File
@@ -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}"')
+107
View File
@@ -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
# ---------------------------------------------------------------------------
+37
View File
@@ -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
# -----------------------------------------------------------------------
+172
View File
@@ -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,
)
+7 -1
View File
@@ -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
# ---------------------------------------------------------------------------
+117 -1
View File
@@ -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)
+46
View File
@@ -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.
+11
View File
@@ -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]}'