Files
cleveragents-core/features/consolidated_skill.feature
T
HAL9000 f808abff86 chore(ci): fix pre-commit hook failures
Fix JSON syntax errors in .devcontainer/devcontainer.json (removed
invalid JS-style // comments) and .devcontainer/opencode.json (removed
90+ trailing commas). Apply auto-fixes for end-of-file and trailing
whitespace issues across 100+ files. Fix SIM105 ruff violations in
benchmarks/core_circuit_breaker_bench.py (use contextlib.suppress).

Note: The security fix from issue #7478 (validate_path startswith bypass)
was already delivered to master in commit e18ac5f2. This PR as currently
structured is non-atomic (35 commits across 10+ issues) and needs
significant restructure before merge. This commit only addresses the
CI/pre-commit failures.

ISSUES CLOSED: #7478
2026-06-14 09:51:14 -04:00

1237 lines
51 KiB
Gherkin

Feature: Consolidated Skill
Combined scenarios from: skill_context, skill_discovery, skill_flatten, skill_inline_coverage, skill_protocol, skill_resolution
# ============================================================
# Originally from: skill_context.feature
# Feature: Skill Context and Registry
# ============================================================
Scenario: Create SkillContext with plan/project/sandbox info
When I create a skill_context with plan "plan-001" project "proj-alpha" and sandbox "/tmp/sandbox"
Then the skill_context should be created
And the skill_context plan_id should be "plan-001"
And the skill_context project_id should be "proj-alpha"
And the skill_context sandbox_path should be "/tmp/sandbox"
And the skill_context should not be read_only
Scenario: Create read-only SkillContext
When I create a read_only skill_context with plan "plan-002" project "proj-beta"
Then the skill_context should be created
And the skill_context should be read_only
Scenario: SkillContext with custom metadata
When I create a skill_context with metadata key "env" value "staging"
Then the skill_context plan metadata should contain key "env"
And the skill_context plan metadata key "env" should be "staging"
# ---- Resource Resolution ----
Scenario: Resolve bound resources from context
When I create a skill_context with resource "git-checkout" bound to "/repo/checkout"
And I resolve resource "git-checkout" from the skill_context
Then the resolved resource should be "/repo/checkout"
Scenario: Resolve missing resource raises SkillError
When I create a skill_context with no resources
And I try to resolve resource "missing-resource" from the skill_context
Then a skill_context resolution error should be raised
# ---- Change Tracking ----
Scenario: Register tool invocation in change tracker
When I create a skill_context with plan "plan-003" project "proj-gamma" and sandbox "/tmp/sb"
And I register a tool invocation for "local/edit-file" with duration 42.5
Then the skill_context change tracker should have 1 records
And the last change tracker record tool_name should be "local/edit-file"
And the last change tracker record duration_ms should be 42.5
Scenario: Multiple tool invocations are tracked in order
When I create a skill_context with plan "plan-004" project "proj-delta" and sandbox "/tmp/sb2"
And I register a tool invocation for "local/read-file" with duration 10.0
And I register a tool invocation for "local/write-file" with duration 20.0
Then the skill_context change tracker should have 2 records
# ---- Write Guard ----
Scenario: Enforce read-only write guard raises SkillError
When I create a read_only skill_context with plan "plan-005" project "proj-epsilon"
And I try to enforce write guard for tool "local/write-file"
Then a skill_context permission denied error should be raised
And the skill_context permission error tool_name should be "local/write-file"
Scenario: Write guard passes in writable context
When I create a skill_context with plan "plan-006" project "proj-zeta" and sandbox "/tmp/sb3"
And I enforce write guard for tool "local/write-file" in writable context
Then no skill_context error should be raised
# ---- Plan Metadata ----
Scenario: Get plan metadata includes plan_id and project_id
When I create a skill_context with plan "plan-007" project "proj-eta" and sandbox "/tmp/sb4"
Then the skill_context plan metadata should contain key "plan_id"
And the skill_context plan metadata key "plan_id" should be "plan-007"
And the skill_context plan metadata should contain key "project_id"
And the skill_context plan metadata key "project_id" should be "proj-eta"
# ---- SkillRegistry Register/Get/List/Unregister ----
Scenario: SkillRegistry register and get skill
When I create a skill_registry
And I register a skill "local/test-reg" in the registry
And I get skill "local/test-reg" from the registry
Then the registry skill name should be "local/test-reg"
Scenario: SkillRegistry register duplicate raises error
When I create a skill_registry
And I register a skill "local/dup-skill" in the registry
And I try to register a duplicate skill "local/dup-skill"
Then a skill_context validation error should be raised
Scenario: SkillRegistry get missing skill raises error
When I create a skill_registry
And I try to get skill "local/missing" from the registry
Then a skill_context not found error should be raised
Scenario: SkillRegistry list_all returns metadata
When I create a skill_registry
And I register a skill "local/skill-a" in the registry
And I register a skill "local/skill-b" in the registry
And I list all skills from the registry
Then the registry should have 2 skills
And the registry skill list should contain "local/skill-a"
And the registry skill list should contain "local/skill-b"
Scenario: SkillRegistry unregister removes skill
When I create a skill_registry
And I register a skill "local/to-remove" in the registry
And I unregister skill "local/to-remove" from the registry
And I try to get skill "local/to-remove" from the registry
Then a skill_context not found error should be raised
Scenario: SkillRegistry unregister missing skill raises error
When I create a skill_registry
And I try to unregister skill "local/nonexistent" from the registry
Then a skill_context not found error should be raised
# ---- SkillRegistry Tool Resolution ----
Scenario: SkillRegistry resolve_tools through tool references
When I create a skill_registry
And I register a skill "local/resolve-test" with tool refs in the registry
And I resolve tools for "local/resolve-test" from the registry
Then the resolved tools should have 2 entries
And the resolved tools should contain "local/tool-a"
And the resolved tools should contain "local/tool-b"
# ---- SkillRegistry Validation ----
@tdd_issue @tdd_issue_4241 @tdd_expected_fail
Scenario: SkillRegistry validate_skill detects missing tools
When I create a skill_registry with a mock tool registry
And I validate a skill with missing tool refs
Then the validation errors should contain "not found in tool registry"
Scenario: SkillRegistry validate_skill detects missing includes
When I create a skill_registry
And I validate a skill with missing includes
Then the validation errors should contain "is not registered"
Scenario: SkillRegistry validate_skill returns empty for valid skill
When I create a skill_registry
And I validate a skill with no issues
Then the validation errors should be empty
# ============================================================
# Originally from: skill_discovery.feature
# Feature: Agent Skills Discovery
# ============================================================
Scenario: Config key skills.agent_skills_paths is registered
Given the agent skills discovery module is available
When I check the config registry for "skills.agent_skills_paths"
Then the config entry should exist
And the config entry type should be "str"
And the config entry env var should be "CLEVERAGENTS_SKILLS_AGENT_SKILLS_PATHS"
# --- Path parsing ---
Scenario: Parse single agent skills path
Given the agent skills discovery module is available
When I parse agent skills paths "~/.cleveragents/agent_skills"
Then the result should contain 1 path
Scenario: Parse multiple comma-separated paths
Given the agent skills discovery module is available
When I parse agent skills paths "/opt/skills,/home/user/skills"
Then the result should contain 2 paths
Scenario: Parse empty agent skills paths
Given the agent skills discovery module is available
When I parse an empty agent skills path string
Then the result should contain 0 paths
Scenario: Parse paths with whitespace around commas
Given the agent skills discovery module is available
When I parse agent skills paths "/opt/skills , /home/user/skills"
Then the result should contain 2 paths
# --- Discovery ---
Scenario: Discover agent skills from directory with SKILL.md
Given the agent skills discovery module is available
Given a temporary directory with agent skill folders
| folder_name | skill_name | description |
| my-tool | my-tool | A test agent skill |
When I scan the directory for agent skills
Then I should discover 1 agent skill
And the discovered skill "my-tool" should have description "A test agent skill"
Scenario: Discover multiple agent skills
Given the agent skills discovery module is available
Given a temporary directory with agent skill folders
| folder_name | skill_name | description |
| tool-a | tool-a | First tool |
| tool-b | tool-b | Second tool |
| tool-c | tool-c | Third tool |
When I scan the directory for agent skills
Then I should discover 3 agent skills
Scenario: Skip directories without SKILL.md
Given the agent skills discovery module is available
Given a temporary directory with some folders missing SKILL.md
When I scan the directory for agent skills
Then I should discover 0 agent skills
Scenario: Handle non-existent directory gracefully
Given the agent skills discovery module is available
When I run discovery on a non-existent directory
Then the discovery result should have 0 discovered skills
And the discovery result should have errors mentioning "does not exist"
Scenario: Handle SKILL.md without front-matter
Given the agent skills discovery module is available
Given a temporary directory with a SKILL.md that has no front-matter
When I scan the directory for agent skills
Then I should discover 0 agent skills
Scenario: Fall back to folder name when SKILL.md has no name field
Given the agent skills discovery module is available
Given a temporary directory with agent skill folders
| folder_name | skill_name | description |
| fallback-tool | _none_ | Some description |
When I scan the directory for agent skills
Then I should discover 1 agent skill
And the discovered skill should use folder name as name
# --- ToolSpec building ---
Scenario: Build ToolSpec from discovered agent skill
Given the agent skills discovery module is available
Given a discovered agent skill named "my-tool" at "/opt/skills/my-tool"
When I build a ToolSpec from the discovered skill
Then the ToolSpec name should be "agent_skills/my-tool"
And the ToolSpec source should be "agent_skills"
And the ToolSpec source_metadata should contain path "/opt/skills/my-tool"
# --- Registration ---
Scenario: Register discovered agent skills in ToolRegistry
Given the agent skills discovery module is available
Given a ToolRegistry with no existing tools
And a list of 2 discovered agent skills
When I register discovered skills with "skip" conflict strategy
Then 2 tools should be registered in the ToolRegistry
And 0 conflicts should be reported
Scenario: Skip registration on name collision with skip strategy
Given the agent skills discovery module is available
Given a ToolRegistry with an existing tool "agent_skills/collider"
And a discovered agent skill named "collider"
When I register discovered skills with "skip" conflict strategy
Then 0 tools should be registered in the ToolRegistry
And 1 conflict should be reported with tool name "agent_skills/collider"
Scenario: Error on name collision with error strategy
Given the agent skills discovery module is available
Given a ToolRegistry with an existing tool "agent_skills/collider"
And a discovered agent skill named "collider"
When I register discovered skills with "error" conflict strategy
Then a discovery ValueError should be raised mentioning "name collision"
Scenario: Replace existing tool on collision with replace strategy
Given the agent skills discovery module is available
Given a ToolRegistry with an existing tool "agent_skills/collider"
And a discovered agent skill named "collider"
When I register discovered skills with "replace" conflict strategy
Then 1 tool should be registered in the ToolRegistry
And the tool "agent_skills/collider" should have source "agent_skills"
# --- Refresh ---
Scenario: Refresh removes old agent skills and re-discovers
Given the agent skills discovery module is available
Given a SkillRegistryService with a ToolRegistry
And agent skills paths pointing to a directory with 2 skills
When I call discover_and_register
Then 2 agent skills should be registered
When the directory now has 3 skills and I call refresh_agent_skills
Then 3 agent skills should be registered
# --- Edge cases ---
Scenario: Register empty discovered list returns empty
Given the agent skills discovery module is available
Given a ToolRegistry with no existing tools
When I register an empty discovered skills list
Then the registration should return 0 specs and 0 conflicts
Scenario: Scan non-directory path returns empty
Given the agent skills discovery module is available
When I scan a non-directory path for agent skills
Then I should discover 0 agent skills
Scenario: Discover with path that is a file not a directory
Given the agent skills discovery module is available
When I run discovery on a path that is a file
Then the discovery result should have 0 discovered skills
And the discovery result should have errors mentioning "not a directory"
Scenario: SKILL.md with invalid YAML front-matter
Given the agent skills discovery module is available
Given a temporary directory with a SKILL.md that has invalid YAML
When I scan the directory for agent skills
Then I should discover 0 agent skills
Scenario: SKILL.md with empty YAML block
Given the agent skills discovery module is available
Given a temporary directory with a SKILL.md that has empty front-matter
When I scan the directory for agent skills
Then I should discover 0 agent skills
Scenario: SKILL.md with non-dict YAML
Given the agent skills discovery module is available
Given a temporary directory with a SKILL.md that has non-dict YAML
When I scan the directory for agent skills
Then I should discover 0 agent skills
Scenario: Skill with non-string description in front-matter
Given the agent skills discovery module is available
Given a temporary directory with a SKILL.md that has numeric description
When I scan the directory for agent skills
Then I should discover 1 agent skill
Scenario: Skill with empty description falls back to default
Given the agent skills discovery module is available
Given a temporary directory with a SKILL.md that has empty description
When I scan the directory for agent skills
Then I should discover 1 agent skill
And the first discovered skill description should contain "Agent skill from"
Scenario: Noop handler returns expected placeholder
Given the agent skills discovery module is available
When I call the noop handler
Then the noop handler should return status "agent_skill_placeholder"
# --- Source metadata ---
Scenario: Source metadata is included when resolving skill tools
Given the agent skills discovery module is available
Given a registered skill "local/test-skill" with an agent_skill source
When I resolve tools for "local/test-skill"
Then the resolved tools should include agent_skill source entries
# ============================================================
# Originally from: skill_flatten.feature
# Feature: Skill Registry Flattening and Capability Summaries
# ============================================================
@flatten_tool_refs
Scenario: Flattening a skill with named tool refs produces deterministic order
Given a flatten registry skill "local/basic" with tool refs "local/alpha,local/beta,local/gamma"
When I flatten the skill "local/basic"
Then the flatten result should have 3 entries
And the flatten entry at index 0 should be "local/alpha"
And the flatten entry at index 1 should be "local/beta"
And the flatten entry at index 2 should be "local/gamma"
# ---------------------------------------------------------------------------
# Flattening with includes (depth-first ordering)
# ---------------------------------------------------------------------------
@flatten_includes
Scenario: Flattening a skill with includes uses depth-first ordering
Given a flatten registry skill "local/base" with tool refs "local/base-tool"
And a flatten registry skill "local/mid" including "local/base" with refs "local/mid-tool"
And a flatten registry skill "local/top" including "local/mid" with refs "local/top-tool"
When I flatten the skill "local/top"
Then the flatten result should have 3 entries
And the flatten entry at index 0 should be "local/base-tool"
And the flatten entry at index 1 should be "local/mid-tool"
And the flatten entry at index 2 should be "local/top-tool"
# ---------------------------------------------------------------------------
# Flattening with inline tools
# ---------------------------------------------------------------------------
@flatten_inline
Scenario: Flattening a skill with inline tools includes anonymous entries
Given a flatten registry skill "local/inline-skill" with 2 inline tools
When I flatten the skill "local/inline-skill"
Then the flatten result should have 2 entries
And the flatten entry at index 0 should be "local/inline-skill/_anon_0"
And the flatten entry at index 1 should be "local/inline-skill/_anon_1"
And the flatten entry at index 0 should be marked inline
# ---------------------------------------------------------------------------
# Cycle detection with clear error path
# ---------------------------------------------------------------------------
@flatten_cycle
Scenario: Flattening detects cycles and reports a clear path
Given a flatten registry skill "local/cycle-a" including "local/cycle-b" with no refs
And a flatten registry skill "local/cycle-b" including "local/cycle-a" with no refs
When I try to flatten the skill "local/cycle-a"
Then a flatten cycle error should be raised
And the flatten error should mention "local/cycle-a"
And the flatten error should mention "local/cycle-b"
# ---------------------------------------------------------------------------
# Per-include override application
# ---------------------------------------------------------------------------
@flatten_include_overrides
Scenario: Per-include overrides are applied to included tool entries
Given a flatten registry skill "local/child" with tool refs "local/tool-x"
And a flatten registry skill "local/parent" including "local/child" with overrides timeout 600
When I flatten the skill "local/parent"
Then the flatten result should have 1 entries
And the flatten entry "local/tool-x" should have override "timeout" equal to 600
# ---------------------------------------------------------------------------
# Non-overridable field rejection
# ---------------------------------------------------------------------------
@flatten_non_overridable
Scenario: Non-overridable fields are rejected with clear error
Given a flatten registry skill "local/child-nr" with tool refs "local/tool-y"
And a flatten registry skill "local/parent-nr" including "local/child-nr" with overrides on non-overridable field "name"
When I try to flatten the skill "local/parent-nr"
Then a flatten override error should be raised
And the flatten error should mention "name"
And the flatten error should mention "Non-overridable"
# ---------------------------------------------------------------------------
# Capability summary computation
# ---------------------------------------------------------------------------
@flatten_capability_summary
Scenario: Capability summary aggregates read/write/checkpoint/side-effect flags
Given a flatten registry skill "local/cap-skill" with mixed capability inline tools
When I flatten the skill "local/cap-skill" and compute summary
Then the flatten summary total_tools should be 3
And the flatten summary read_only_tools should be 1
And the flatten summary write_tools should be 1
And the flatten summary has_side_effects should be true
And the flatten summary checkpointable_tools should be 2
# ---------------------------------------------------------------------------
# tools() method returns both entries and summary
# ---------------------------------------------------------------------------
@flatten_tools_method
Scenario: The tools() method returns entries and capability summary
Given a flatten skill registry with skill "local/tools-test" having 3 refs
When I call the flatten tools method for "local/tools-test"
Then the flatten tools result should contain 3 entries
And the flatten tools result should include a capability summary
# ---------------------------------------------------------------------------
# validate_plan() with valid plan
# ---------------------------------------------------------------------------
@flatten_validate_plan
Scenario: validate_plan with a valid plan returns no errors
Given a flatten skill registry with skill "local/plan-skill" having 2 refs
When I call flatten validate_plan with skills "local/plan-skill"
Then the flatten validation result should have 0 errors
# ---------------------------------------------------------------------------
# validate_plan() with missing skill
# ---------------------------------------------------------------------------
@flatten_validate_plan_missing
Scenario: validate_plan with a missing skill reports error
Given an empty flatten skill registry
When I call flatten validate_plan with skills "local/nonexistent"
Then the flatten validation result should have 1 errors
And the flatten validation error should mention "local/nonexistent"
# ---------------------------------------------------------------------------
# validate_plan() with cycle in includes
# ---------------------------------------------------------------------------
@flatten_validate_plan_cycle
Scenario: validate_plan detects cycle in includes
Given a flatten registry skill "local/loop-a" including "local/loop-b" with no refs
And a flatten registry skill "local/loop-b" including "local/loop-a" with no refs
And the flatten registry skills are registered in the skill registry
When I call flatten validate_plan with skills "local/loop-a"
Then the flatten validation result should have 1 errors
And the flatten validation error should mention "ycle"
# ---------------------------------------------------------------------------
# De-duplication semantics (last-wins)
# ---------------------------------------------------------------------------
@flatten_dedup
Scenario: De-duplication uses last-wins for entry metadata
Given a flatten registry skill "local/dup-a" with tool refs "local/shared"
And a flatten registry skill "local/dup-b" with tool refs "local/shared"
And a flatten registry skill "local/dup-top" including "local/dup-a,local/dup-b" with no refs
When I flatten the skill "local/dup-top"
Then the flatten result should have 1 entries
And the flatten entry "local/shared" source_skill should be "local/dup-b"
# ---------------------------------------------------------------------------
# Override merging (shallow merge)
# ---------------------------------------------------------------------------
@flatten_override_merge
Scenario: Override merging uses shallow merge semantics
Given a flatten registry skill "local/merge-child" with tool refs "local/merge-tool" and overrides priority 1
And a flatten registry skill "local/merge-parent" including "local/merge-child" with overrides priority 2 and extra key
When I flatten the skill "local/merge-parent"
Then the flatten entry "local/merge-tool" should have override "priority" equal to 2
And the flatten entry "local/merge-tool" should have override "extra" equal to "yes"
# ============================================================
# Originally from: skill_inline_coverage.feature
# Feature: Inline Tool Executor Coverage Boost
# ============================================================
Scenario: Executor rejects non-positive max_runtime_seconds
Given I create an inline executor with invalid max runtime
Then creating the executor should raise a ValueError about runtime
Scenario: Executor rejects non-positive max_output_bytes
Given I create an inline executor with invalid max output
Then creating the executor should raise a ValueError about output
# ---- Property Accessors ----
Scenario: Executor exposes max_runtime_seconds property
Given an inline executor with max runtime 5.0 seconds
Then the executor max_runtime_seconds should be 5.0
Scenario: Executor exposes max_output_bytes property
Given an inline executor with max output 512 bytes
Then the executor max_output_bytes should be 512
# ---- Null Argument Guards ----
Scenario: Execute rejects None tool argument
Given a writable skill context with sandbox "/tmp/inline-sandbox"
And an inline executor with max runtime 5.0 seconds
When I execute with None tool
Then executing should raise a ValueError about tool
Scenario: Execute rejects None context argument
Given an inline tool with code that prints "test"
When I execute with None context
Then executing should raise a ValueError about context
Scenario: Execute rejects None input_data argument
Given an inline tool with code that prints "test"
And a writable skill context with sandbox "/tmp/inline-sandbox"
When I execute with None input_data
Then executing should raise a ValueError about input_data
Scenario: Validate_tool rejects None tool argument
Given an inline executor with max runtime 5.0 seconds
When I validate with None tool
Then validating should raise a ValueError about tool
# ---- Validation Failure Return Path ----
Scenario: Execute returns validation failure without running code
Given an inline tool with no code
And a writable skill context with sandbox "/tmp/inline-sandbox"
When I execute the inline tool
Then the inline result should not be successful
And the inline result error message should contain "Validation failed"
And the inline result duration should be zero
# ---- Sandbox Path Validation ----
Scenario: Execute rejects input paths outside sandbox
Given an inline tool with code that prints "file op"
And a writable skill context with sandbox "/tmp/inline-sandbox"
When I execute the inline tool with path input escaping sandbox
Then the inline result should not be successful
And the inline result error message should contain "escapes sandbox"
Scenario: Execute accepts input paths inside sandbox
Given an inline tool with code that prints "ok"
And a writable skill context with sandbox "/tmp/inline-sandbox"
When I execute the inline tool with path input inside sandbox
Then the inline result should be successful
Scenario: Execute handles invalid path values gracefully
Given an inline tool with code that prints "test"
And a writable skill context with sandbox "/tmp/inline-sandbox"
When I execute the inline tool with an invalid path value
Then the inline result should not be successful
And the inline result error message should contain "Invalid path"
# ============================================================
# Originally from: skill_protocol.feature
# Feature: Skill Protocol Types
# ============================================================
Scenario: SkillErrorType has all expected members
When I list all skill_protocol error types
Then the skill_protocol error types should include "skill_not_found"
And the skill_protocol error types should include "resolution_failure"
And the skill_protocol error types should include "cycle_detected"
And the skill_protocol error types should include "tool_activation_failure"
And the skill_protocol error types should include "tool_execution_failure"
And the skill_protocol error types should include "validation_error"
And the skill_protocol error types should include "permission_denied"
Scenario: SkillErrorType has exactly 7 members
When I list all skill_protocol error types
Then there should be 7 skill_protocol error types
# ---- SkillError Creation ----
Scenario: Create a SkillError with required fields
When I create a skill_protocol error with type "tool_execution_failure" and message "Tool timed out" for skill "local/my-skill"
Then the skill_protocol error should be created
And the skill_protocol error type should be "tool_execution_failure"
And the skill_protocol error message should be "Tool timed out"
And the skill_protocol error skill_name should be "local/my-skill"
And the skill_protocol error tool_name should be none
Scenario: Create a SkillError with optional tool_name
When I create a skill_protocol error with tool_name "local/my-tool"
Then the skill_protocol error should be created
And the skill_protocol error tool_name should be "local/my-tool"
Scenario: Create a SkillError with details
When I create a skill_protocol error with details
Then the skill_protocol error should be created
And the skill_protocol error details should have key "trace_id"
Scenario: SkillError is frozen (immutable)
When I create a skill_protocol error with type "validation_error" and message "Bad input" for skill "local/test"
Then the skill_protocol error should be immutable
# ---- SkillMetadata Creation ----
Scenario: Create SkillMetadata from Skill domain model
When I create a skill_protocol metadata from a basic skill
Then the skill_protocol metadata should be created
And the skill_protocol metadata name should be "local/test-skill"
And the skill_protocol metadata description should be "A test skill"
And the skill_protocol metadata version should be "0.0.0"
And the skill_protocol metadata read_only should be true
And the skill_protocol metadata writes should be false
Scenario: Create SkillMetadata from Skill with tool refs
When I create a skill_protocol metadata from a skill with tool refs
Then the skill_protocol metadata should be created
And the skill_protocol metadata tool_count should be 2
And the skill_protocol metadata source_types should contain "tool_ref"
Scenario: Create SkillMetadata from Skill with inline write tools
When I create a skill_protocol metadata from a skill with write tools
Then the skill_protocol metadata should be created
And the skill_protocol metadata writes should be true
And the skill_protocol metadata read_only should be false
Scenario: Create SkillMetadata from Skill with MCP sources
When I create a skill_protocol metadata from a skill with mcp sources
Then the skill_protocol metadata should be created
And the skill_protocol metadata source_types should contain "mcp"
Scenario: Create SkillMetadata from Skill with agent skill sources
When I create a skill_protocol metadata from a skill with agent skill sources
Then the skill_protocol metadata should be created
And the skill_protocol metadata source_types should contain "agent_skill"
Scenario: Create SkillMetadata with custom version
When I create a skill_protocol metadata with version "1.2.3"
Then the skill_protocol metadata should be created
And the skill_protocol metadata version should be "1.2.3"
Scenario: SkillMetadata is frozen (immutable)
When I create a skill_protocol metadata from a basic skill
Then the skill_protocol metadata should be immutable
Scenario: SkillMetadata from_skill rejects None
When I try to create skill_protocol metadata from None
Then a skill_protocol value error should be raised
# ---- SkillDefinition ----
Scenario: Create a SkillDefinition with resolved tools
When I create a skill_protocol definition with resolved tools
Then the skill_protocol definition should be created
And the skill_protocol definition skill name should be "local/def-skill"
And the skill_protocol definition should have 1 resolved tools
Scenario: Create a SkillDefinition with input and output schemas
When I create a skill_protocol definition with input and output schemas
Then the skill_protocol definition should be created
And the skill_protocol definition input_schema should have key "type"
And the skill_protocol definition output_schema should have key "type"
Scenario: SkillDefinition rejects input_schema without type key
When I try to create a skill_protocol definition with invalid input_schema
Then a skill_protocol validation error should be raised
Scenario: SkillDefinition rejects output_schema without type key
When I try to create a skill_protocol definition with invalid output_schema
Then a skill_protocol validation error should be raised
Scenario: SkillDefinition detects writes inconsistency
When I try to create a skill_protocol definition with writes inconsistency
Then a skill_protocol validation error should be raised
Scenario: SkillDefinition is frozen (immutable)
When I create a skill_protocol definition with resolved tools
Then the skill_protocol definition should be immutable
# ---- SkillResult ----
Scenario: Create a successful SkillResult
When I create a skill_protocol result with success
Then the skill_protocol result should be created
And the skill_protocol result success should be true
And the skill_protocol result skill_name should be "local/my-skill"
And the skill_protocol result tool_name should be "local/my-tool"
And the skill_protocol result error should be none
And the skill_protocol result output_data should be "hello"
Scenario: Create a failed SkillResult with error
When I create a skill_protocol result with failure
Then the skill_protocol result should be created
And the skill_protocol result success should be false
And the skill_protocol result error should not be none
And the skill_protocol result error type should be "tool_execution_failure"
Scenario: Create a SkillResult with duration and resource changes
When I create a skill_protocol result with duration and resource changes
Then the skill_protocol result should be created
And the skill_protocol result duration_ms should be 150.5
And the skill_protocol result should have 1 resource changes
Scenario: SkillResult is frozen (immutable)
When I create a skill_protocol result with success
Then the skill_protocol result should be immutable
# ---- Error Mapping ----
Scenario: Map ValueError with cycle message to CYCLE_DETECTED
When I map a skill_protocol ValueError with message "Cycle detected in skill includes"
Then the skill_protocol mapped error type should be "cycle_detected"
Scenario: Map ValueError with not found message to SKILL_NOT_FOUND
When I map a skill_protocol ValueError with message "Skill not found"
Then the skill_protocol mapped error type should be "skill_not_found"
Scenario: Map generic ValueError to RESOLUTION_FAILURE
When I map a skill_protocol ValueError with message "Something went wrong"
Then the skill_protocol mapped error type should be "resolution_failure"
Scenario: Map PermissionError to PERMISSION_DENIED
When I map a skill_protocol PermissionError
Then the skill_protocol mapped error type should be "permission_denied"
Scenario: Map RuntimeError to TOOL_EXECUTION_FAILURE
When I map a skill_protocol RuntimeError with message "Unexpected failure"
Then the skill_protocol mapped error type should be "tool_execution_failure"
Scenario: Map RuntimeError with activation in message
When I map a skill_protocol RuntimeError with message "activation failed"
Then the skill_protocol mapped error type should be "tool_activation_failure"
Scenario: Error mapping with tool_name populated
When I map a skill_protocol error with tool_name "local/my-tool"
Then the skill_protocol mapped error tool_name should be "local/my-tool"
And the skill_protocol mapped error details should have key "tool_name"
Scenario: Error mapping rejects empty skill_name
When I try to map a skill_protocol error with empty skill_name
Then a skill_protocol value error should be raised
# ---- Writes/Read-Only Metadata Propagation ----
Scenario: Skill with only read-only tools has read_only=True
When I create a skill_protocol metadata from a skill with read_only tools
Then the skill_protocol metadata read_only should be true
And the skill_protocol metadata writes should be false
Scenario: Skill with write tools has writes=True
When I create a skill_protocol metadata from a skill with write tools
Then the skill_protocol metadata writes should be true
And the skill_protocol metadata read_only should be false
Scenario: Empty skill has read_only=True
When I create a skill_protocol metadata from an empty skill
Then the skill_protocol metadata read_only should be true
And the skill_protocol metadata writes should be false
And the skill_protocol metadata tool_count should be 0
# ============================================================
# Originally from: skill_resolution.feature
# Feature: Skill Domain Model and Resolution
# ============================================================
Scenario: Create a skill with valid fields
When I create a skill_model with name "local/code-tools" and description "Code editing tools"
Then the skill_model should be created
And the skill_model name should be "local/code-tools"
And the skill_model description should be "Code editing tools"
And the skill_model namespace should be "local"
And the skill_model short_name should be "code-tools"
Scenario: Create a skill with tool refs
When I create a skill_model with tool refs "local/edit-file,local/read-file"
Then the skill_model should be created
And the skill_model should have 2 tool refs
Scenario: Create a skill with all fields populated
When I create a skill_model with all fields
Then the skill_model should be created
And the skill_model should have 2 tool refs
And the skill_model should have 1 include
And the skill_model should have 1 anonymous tool
And the skill_model should have 1 mcp server
And the skill_model should have 1 agent skill
# ---- Skill Name Validation ----
Scenario: Skill name must match namespace/name pattern
When I try to create a skill_model with invalid name "no-namespace"
Then a skill_model validation error should be raised
And the skill_model error should mention "namespace/name"
Scenario: Skill name rejects spaces
When I try to create a skill_model with invalid name "local/bad name"
Then a skill_model validation error should be raised
Scenario: Skill name rejects double slashes
When I try to create a skill_model with invalid name "local//bad"
Then a skill_model validation error should be raised
Scenario: Skill name allows hyphens and underscores
When I create a skill_model with name "my-ns/my_skill" and description "Test"
Then the skill_model should be created
And the skill_model namespace should be "my-ns"
And the skill_model short_name should be "my_skill"
Scenario: Skill description must not be empty
When I try to create a skill_model with empty description
Then a skill_model validation error should be raised
# ---- Include Resolution (flat) ----
Scenario: Resolve skill with flat includes
Given a skill_resolver registry with "local/basic-tools" containing refs "local/edit-file,local/read-file"
And a skill_resolver root skill "local/code-tools" including "local/basic-tools" with own refs "local/format"
When I resolve the skill_resolver root skill
Then the skill_resolver result should have 3 tools
And the skill_resolver result should contain tool "local/edit-file"
And the skill_resolver result should contain tool "local/read-file"
And the skill_resolver result should contain tool "local/format"
# ---- Include Resolution (nested) ----
Scenario: Resolve skill with nested includes
Given a skill_resolver registry with "local/base" containing refs "local/core-tool"
And a skill_resolver registry with "local/mid" including "local/base" and containing refs "local/mid-tool"
And a skill_resolver root skill "local/top" including "local/mid" with own refs "local/top-tool"
When I resolve the skill_resolver root skill
Then the skill_resolver result should have 3 tools
And the skill_resolver result should contain tool "local/core-tool"
And the skill_resolver result should contain tool "local/mid-tool"
And the skill_resolver result should contain tool "local/top-tool"
# ---- Cycle Detection ----
Scenario: Cycle detection with error message
Given a skill_resolver registry with "local/a" including "local/b"
And a skill_resolver registry with "local/b" including "local/a"
When I try to resolve skill_resolver "local/a"
Then a skill_resolver cycle error should be raised
And the skill_resolver error should mention "local/a"
And the skill_resolver error should mention "local/b"
Scenario: Self-referencing cycle detection
Given a skill_resolver registry with "local/self" including "local/self"
When I try to resolve skill_resolver "local/self"
Then a skill_resolver cycle error should be raised
# ---- De-duplication ----
Scenario: De-duplication behavior last wins for overrides
Given a skill_resolver registry with "local/a" containing refs "local/shared-tool"
And a skill_resolver registry with "local/b" containing refs "local/shared-tool"
And a skill_resolver root skill "local/top" including "local/a,local/b" with no own refs
When I resolve the skill_resolver root skill
Then the skill_resolver result should have 1 tools
And the skill_resolver tool "local/shared-tool" should come from "local/b"
# ---- Override Application ----
Scenario: Override application on tool refs
When I create a skill_model with overrides for "local/edit-file"
And I resolve the overridden skill_model
Then the skill_resolver tool "local/edit-file" should have overrides
# ---- Empty Skill ----
Scenario: Empty skill with no tools or includes
When I create a skill_model with name "local/empty" and description "Empty skill"
And I resolve the empty skill_model
Then the skill_resolver result should have 0 tools
# ---- from_config Loading ----
Scenario: Load skill from config dict
When I load a skill_model from config with name "local/my-skill"
Then the skill_model should be created
And the skill_model name should be "local/my-skill"
Scenario: Load skill from config missing name raises error
When I try to load a skill_model from config missing "name"
Then a skill_model config error should be raised with "name"
Scenario: Load skill from config missing description raises error
When I try to load a skill_model from config missing "description"
Then a skill_model config error should be raised with "description"
Scenario: Load skill from config with full YAML structure
When I load a skill_model from full config
Then the skill_model should be created
And the skill_model should have 2 tool refs
And the skill_model should have 1 include
And the skill_model should have 1 anonymous tool
And the skill_model should have 1 mcp server
And the skill_model should have 1 agent skill
Scenario: Load skill from config with string includes
When I load a skill_model from config with string includes
Then the skill_model should be created
And the skill_model should have 1 include
Scenario: Load skill from config with string agent skills
When I load a skill_model from config with string agent skills
Then the skill_model should be created
And the skill_model should have 1 agent skill
# ---- as_cli_dict Output ----
Scenario: Skill as_cli_dict has required keys
When I create a skill_model and call as_cli_dict
Then the skill_model cli dict should have key "name"
And the skill_model cli dict should have key "description"
And the skill_model cli dict should have key "namespace"
And the skill_model cli dict should have key "short_name"
Scenario: Skill as_cli_dict includes optional sections when set
When I create a skill_model with all fields and call as_cli_dict
Then the skill_model cli dict should have key "tool_refs"
And the skill_model cli dict should have key "includes"
And the skill_model cli dict should have key "anonymous_tools"
And the skill_model cli dict should have key "mcp_servers"
And the skill_model cli dict should have key "agent_skills"
And the skill_model cli dict should have key "overrides"
Scenario: Skill as_cli_dict omits empty sections
When I create a skill_model with name "local/minimal" and description "Minimal"
And I get the skill_model cli dict
Then the skill_model cli dict should not have key "tool_refs"
And the skill_model cli dict should not have key "includes"
And the skill_model cli dict should not have key "anonymous_tools"
And the skill_model cli dict should not have key "mcp_servers"
And the skill_model cli dict should not have key "agent_skills"
And the skill_model cli dict should not have key "overrides"
# ---- Inline Tool Validation ----
Scenario: Inline tool with valid custom source
When I create a skill_model inline tool with source "custom" and code "print('hi')"
Then the skill_model inline tool should be created
Scenario: Inline tool has default timeout of 300
When I create a skill_model inline tool with source "custom" and code "print('hi')"
Then the skill_model inline tool timeout should be 300
Scenario: Inline tool timeout must be >= 1
When I try to create a skill_model inline tool with timeout 0
Then a skill_model validation error should be raised
Scenario: Inline tool description must not be empty
When I try to create a skill_model inline tool with empty description
Then a skill_model validation error should be raised
# ---- MCP Source Validation ----
Scenario: MCP source with specific tools
When I create a skill_model mcp source with server "npx @mcp/server" and tools "read,write"
Then the skill_model mcp source should be created
And the skill_model mcp source should have 2 tools
Scenario: MCP source with all tools (none specified)
When I create a skill_model mcp source with server "npx @mcp/server" and no tools
Then the skill_model mcp source should be created
And the skill_model mcp source tools should be none
Scenario: MCP source with env vars
When I create a skill_model mcp source with env vars
Then the skill_model mcp source should be created
Scenario: MCP source server must not be empty
When I try to create a skill_model mcp source with empty server
Then a skill_model validation error should be raised
# ---- Agent Skill Source Validation ----
Scenario: Agent skill source with valid path
When I create a skill_model agent source with path "./skills/review"
Then the skill_model agent source should be created
Scenario: Agent skill source path must not be empty
When I try to create a skill_model agent source with empty path
Then a skill_model validation error should be raised
# ---- CapabilitySummary Computation ----
Scenario: CapabilitySummary computation for skill with inline tools
When I compute skill_model capability summary for a skill with inline tools
Then the skill_model summary total tools should be 4
And the skill_model summary read_only_tools should be 1
And the skill_model summary write_tools should be 1
And the skill_model summary has_side_effects should be true
And the skill_model summary mcp_sources should be 1
And the skill_model summary agent_skill_sources should be 1
Scenario: CapabilitySummary for empty skill
When I compute skill_model capability summary for an empty skill
Then the skill_model summary total tools should be 0
# ---- SkillToolRef ----
Scenario: SkillToolRef creation
When I create a skill_model tool ref with name "local/my-tool"
Then the skill_model tool ref should be created
# ---- SkillInclude ----
Scenario: SkillInclude creation with overrides
When I create a skill_model include with name "local/base" and overrides
Then the skill_model include should be created
And the skill_model include should have overrides
Scenario: SkillInclude creation without overrides
When I create a skill_model include with name "local/base" without overrides
Then the skill_model include should be created
# ---- Missing included skill ----
Scenario: Resolve with missing included skill raises error
Given a skill_resolver root skill "local/broken" including "local/nonexistent" with no own refs
When I try to resolve skill_resolver "local/broken"
Then a skill_resolver missing skill error should be raised
And the skill_resolver error should mention "local/nonexistent"
# ---- MCP and agent skill resolution ----
Scenario: Resolve skill with MCP and agent skill sources
When I create and resolve a skill_model with mcp and agent sources
Then the skill_resolver result should have 3 tools
# ---- ResolvedToolEntry ----
Scenario: ResolvedToolEntry creation
When I create a skill_resolver resolved tool entry
Then the skill_resolver entry should be created
# ---- Robot Smoke Mirrors (Behave equivalents of robot/skill_resolution.robot) ----
Scenario: Resolver output ordering matches depth-first include order
Given a skill_resolver registry with "local/base-tools" containing refs "local/edit-file,local/read-file"
And a skill_resolver root skill "local/code-tools" including "local/base-tools" with own refs "local/format"
When I resolve the skill_resolver root skill
Then the skill_resolver result should have 3 tools
And the skill_resolver tool at index 0 should be "local/edit-file"
And the skill_resolver tool at index 1 should be "local/read-file"
And the skill_resolver tool at index 2 should be "local/format"
Scenario: Resolver de-duplicates overlapping include tools with last-wins
Given a skill_resolver registry with "local/a" containing refs "local/shared-tool,local/a-only"
And a skill_resolver registry with "local/b" containing refs "local/shared-tool,local/b-only"
And a skill_resolver root skill "local/top" including "local/a,local/b" with no own refs
When I resolve the skill_resolver root skill
Then the skill_resolver result should have 3 tools
And the skill_resolver tool "local/shared-tool" should come from "local/b"
Scenario: Resolver includes inline tools with correct anon keys
When I create and resolve a skill_model with inline tools
Then the skill_resolver result should contain tool "local/inline-test/_anon_0"
And the skill_resolver result should contain tool "local/inline-test/_anon_1"
And the skill_resolver inline tool at index 1 should be marked inline