From 7ecc8fb27e788c18c20908035b201ccf5fb4b4a3 Mon Sep 17 00:00:00 2001 From: Daniele Martinoli Date: Thu, 26 Feb 2026 11:12:12 +0100 Subject: [PATCH 01/10] compliance checker Signed-off-by: Daniele Martinoli --- .github/copilot-instructions.md | 2 +- CLAUDE.md | 491 +---------------------------- Makefile | 10 +- README.md | 33 +- SKILL_DESIGN_PRINCIPLES.md | 493 ++++++++++++++++++++++++++++++ rh-sre/skills/cve-impact/SKILL.md | 2 + scripts/validate_skill_design.py | 437 ++++++++++++++++++++++++++ 7 files changed, 974 insertions(+), 494 deletions(-) create mode 100644 SKILL_DESIGN_PRINCIPLES.md create mode 100644 scripts/validate_skill_design.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 79ce5378..09c11f73 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -88,7 +88,7 @@ Same YAML frontmatter requirement (`name` + `description`). Agents orchestrate s **NEVER hardcode credentials.** Always use `${ENV_VAR}` references. Gitleaks runs as a pre-commit hook and in CI. -## Design Principles (from CLAUDE.md) +## Design Principles (from SKILL_DESIGN_PRINCIPLES.md) 1. **Skills encapsulate tools** — never call MCP tools directly; always go through a skill. 2. **Agents orchestrate skills** — complex workflows delegate to specialized skills. diff --git a/CLAUDE.md b/CLAUDE.md index 60f654d0..0e14d12a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,494 +59,7 @@ Each pack follows this structure: ## Design Principles for Skills and Agents -### 1. Document Consultation Transparency - -When a skill or agent consults documentation (from `docs/` or skill/agent files), it **MUST**: -1. **Actually read the file** using the Read tool to load it into context -2. **Then declare** the consultation to the user - -**CRITICAL**: Document consultation means READING the file, not just claiming to have read it. - -**Required Implementation**: -```markdown -**Document Consultation** (REQUIRED): -1. **Action**: Read [filename.md](path/to/filename.md) using the Read tool to understand [specific topic] -2. **Output to user**: "I consulted [filename.md](path/to/filename.md) to understand [specific topic]." -``` - -**❌ WRONG - Transparency Theater** (just claims, no actual reading): -```markdown -**Document Consultation** (output to user): -``` -I consulted [filename.md](path/to/filename.md) to understand [topic]. -``` -``` - -**✅ CORRECT - Actual Consultation** (reads first, then declares): -```markdown -**Document Consultation** (REQUIRED): -1. **Action**: Read [cvss-scoring.md](../../docs/references/cvss-scoring.md) using the Read tool to understand CVSS severity mapping -2. **Output to user**: "I consulted [cvss-scoring.md](../../docs/references/cvss-scoring.md) to understand CVSS severity mapping." -``` - -**Examples in execution**: -- Read `docs/references/cvss-scoring.md` → "I consulted [cvss-scoring.md](rh-sre/docs/references/cvss-scoring.md) to verify the CVSS severity mapping." -- Read `skills/playbook-generator/SKILL.md` → "I consulted [playbook-generator/SKILL.md](rh-sre/skills/playbook-generator/SKILL.md) to understand playbook generation parameters." - -**Rationale**: -- **Substance**: Ensures AI actually enriches its context with domain knowledge -- **Transparency**: Users understand the AI's knowledge sources -- **Auditability**: The execution-summary skill can track actual Read tool calls - -### 2. Precise Parameter Specification - -Skills MUST specify **exact parameters** when instructing agents to use tools, ensuring first-attempt success. - -**CRITICAL**: Document consultation must be specified BEFORE tool parameters to ensure it happens first. - -**❌ Bad Example - Vague parameters**: -``` -Use get_cve tool with the CVE ID -``` - -**❌ Bad Example - Wrong parameters**: -``` -**MCP Tool**: get_cves - -**Parameters**: -- severity: ["Critical", "Important"] -- sort_by: "cvss_score" -``` -(Actual tool uses `impact: "7,6"` and `sort: "-cvss_score"`) - -**✅ Good Example - Correct structure with document consultation first**: -``` -**CRITICAL**: Document consultation MUST happen BEFORE tool invocation. - -**Document Consultation** (REQUIRED - Execute FIRST): -1. **Action**: Read [vulnerability-logic.md](../../docs/insights/vulnerability-logic.md) using the Read tool -2. **Output to user**: "I consulted [vulnerability-logic.md]..." - -**MCP Tool**: `get_cves` or `vulnerability__get_cves` (from lightspeed-mcp) - -**Parameters**: -- impact: "7,6" (string with comma-separated impact levels: 7=Important, 6=Moderate, 5=Low) -- sort: "-cvss_score" (use - prefix for descending; valid fields: "cvss_score", "public_date") -- limit: 20 (maximum number of CVEs to return) -``` - -**Rationale**: -- **Ordering**: Document consultation before parameters ensures it's executed first -- **Precision**: Exact parameter names and formats prevent tool errors -- **Examples**: Value examples (e.g., "7,6") show correct format -- **Determinism**: First-attempt success reduces wasted cycles - -### 3. Skill Precedence and Conciseness - -**Precedence Rule**: Skills > Tools (always invoke skills, not raw MCP tools) - -**Conciseness Requirement**: Skill descriptions (loaded at agent start time) must be: -- **Under 500 tokens** for the YAML frontmatter description field -- **Focus on "when to use"** with 3-5 concrete examples -- **Defer implementation details** to the skill body (not frontmatter) - -**Example**: -```yaml ---- -name: cve-impact -description: | - Analyze CVE impact across the fleet without immediate remediation. - - Use when: - - "What are the most critical vulnerabilities?" - - "Show CVEs affecting my systems" - - "List high-severity CVEs" - - NOT for remediation actions (use remediator agent instead). -model: inherit ---- -``` - -**Rationale**: Minimizes token usage at agent initialization while maintaining clarity. - -### 4. Dependencies Declaration - -Every skill MUST include a **Dependencies** section listing: -- **Skills**: Other skills this skill may invoke -- **MCP Tools**: Specific tools from MCP servers -- **MCP Servers**: Required MCP server names -- **Documentation**: Reference docs for context - -**Required Format**: -```markdown -## Dependencies - -### Required MCP Servers -- `lightspeed-mcp` - Red Hat Lightspeed platform access - -### Required MCP Tools -- `vulnerability__get_cves` (from lightspeed-mcp) - List CVEs -- `vulnerability__get_cve` (from lightspeed-mcp) - Get CVE details - -### Related Skills -- `cve-validation` - Validate CVEs before impact analysis -- `fleet-inventory` - Identify affected systems - -### Reference Documentation -- [cvss-scoring.md](docs/references/cvss-scoring.md) - CVSS severity mappings -- [insights-api.md](docs/insights/insights-api.md) - API usage patterns -``` - -**Rationale**: Makes dependencies explicit for debugging and ensures proper error handling. - -### 5. Human-in-the-Loop Requirements - -Skills performing **critical operations** MUST include this section: - -**Required Section**: -```markdown -## Critical: Human-in-the-Loop Requirements - -This skill requires explicit user confirmation at the following steps: - -1. **Before Tool Invocation** [if applicable] - - Ask: "Should I proceed with [specific action]?" - - Wait for user confirmation - -2. **Before Destructive Actions** [if applicable] - - Display preview of changes - - Ask: "Review the changes above. Should I execute this?" - - Wait for explicit "yes" or "proceed" - -3. **After Each Major Step** [if applicable] - - Report results - - Ask: "Continue to next step?" - -**Never assume approval** - always wait for explicit user confirmation. -``` - -**When to Use**: -- Playbook execution (ansible-mcp-server) -- System modifications (package updates, config changes) -- Multi-system operations (batch remediation) -- Data deletion or irreversible actions - -**Rationale**: Prevents unintended automation; maintains user control over critical operations. - -### 6. Mandatory Skill Sections - -Every skill MUST include these sections in order: - -#### Template Structure: -```markdown ---- -name: skill-name -description: | - [Concise when-to-use with 3-5 examples] -model: inherit|sonnet|haiku -color: red|blue|green|yellow ---- - -# [Skill Name] - -## Prerequisites - -**Required MCP Servers**: `server-name` ([setup guide](link)) -**Required MCP Tools**: `tool_name` (from server-name) -**Environment Variables**: `VAR_NAME` (if applicable) - -**Verification**: -Before executing, verify MCP server availability: -1. Check `server-name` is configured in `.mcp.json` -2. Verify environment variables are set -3. If missing: Report to user with setup instructions - -**Human Notification on Failure**: -If prerequisites are not met: -- ❌ "Cannot proceed: MCP server `server-name` is not available" -- 📋 "Setup required: [link to setup guide]" -- ❓ "How would you like to proceed? (setup now / skip / abort)" -- ⏸️ Wait for user decision - -## When to Use This Skill - -Use this skill when: -- [Specific scenario 1] -- [Specific scenario 2] -- [Specific scenario 3] - -Do NOT use when: -- [Anti-pattern 1] → Use [alternative] instead -- [Anti-pattern 2] → Use [alternative] instead - -## Workflow - -### Step 1: [Action Name] - -**CRITICAL**: Document consultation MUST happen BEFORE tool invocation. - -**Document Consultation** (REQUIRED - Execute FIRST): -1. **Action**: Read [doc.md](../../docs/category/doc.md) using the Read tool to understand [specific topic] -2. **Output to user**: "I consulted [doc.md](../../docs/category/doc.md) to understand [specific topic]." - -**MCP Tool**: `tool_name` or `toolset__tool_name` (from server-name) - -**Parameters**: -- `param1`: [exact specification with example - see Design Principle #2] - - Example: `"CVE-2024-1234"` -- `param2`: [exact specification with example] - - Example: `true` (description of what this does) - -**Expected Output**: [describe what the tool returns] - -**Error Handling**: -- If [error condition]: [how to handle] - -### Step 2: [Next Action] - -[Continue pattern...] - -## Dependencies - -[As specified in principle #4] - -## Critical: Human-in-the-Loop Requirements - -[As specified in principle #5, if applicable] - -## Example Usage - -[Concrete example with user query and skill response] -``` - -**Rationale**: Standardizes skill structure for consistency and completeness. - -### 7. MCP Server Availability Verification - -The **Prerequisites** section MUST include verification logic: - -**CRITICAL SECURITY CONSTRAINT**: -- **NEVER print environment variable values in user-visible output** -- When checking if env vars are set, only report presence/absence -- Do NOT use `echo $VAR_NAME` or display actual credential values -- Protect sensitive data like API keys, tokens, secrets, passwords - -**❌ WRONG - Exposes credentials**: -```bash -echo $LIGHTSPEED_CLIENT_SECRET # Shows actual secret value -``` - -**✅ CORRECT - Check without exposing**: -```bash -# Check if set (exit code only, no output) -test -n "$LIGHTSPEED_CLIENT_SECRET" - -# Or check and report boolean result -if [ -n "$LIGHTSPEED_CLIENT_SECRET" ]; then - echo "✓ LIGHTSPEED_CLIENT_SECRET is set" -else - echo "✗ LIGHTSPEED_CLIENT_SECRET is not set" -fi -``` - -**In User-Visible Messages**: -``` -✓ Environment variable LIGHTSPEED_CLIENT_ID is set -✓ Environment variable LIGHTSPEED_CLIENT_SECRET is set -``` - -**NEVER show**: -``` -LIGHTSPEED_CLIENT_SECRET=sk-abc123-xyz789-... ❌ SECURITY VIOLATION -``` - -**Rationale**: Prevents accidental credential exposure in conversation history, logs, or screenshots. - ---- - -**Required Pattern**: -```markdown -## Prerequisites - -**Required MCP Servers**: `lightspeed-mcp` ([setup guide](https://console.redhat.com/)) -**Required MCP Tools**: -- `vulnerability__get_cves` -- `vulnerability__get_cve` - -**Verification Steps**: -1. **Check MCP Server Configuration** - - Verify `lightspeed-mcp` exists in `.mcp.json` - - If missing → Proceed to Human Notification - -2. **Check Environment Variables** - - Verify `LIGHTSPEED_CLIENT_ID` is set - - Verify `LIGHTSPEED_CLIENT_SECRET` is set - - If missing → Proceed to Human Notification - -3. **Test MCP Server Connection** (optional, for critical skills) - - Attempt simple tool call (e.g., `get_mcp_version`) - - If fails → Proceed to Human Notification - -**Human Notification Protocol**: - -When prerequisites fail, the skill MUST: - -1. **Stop Execution Immediately** - Do not attempt tool calls -2. **Report Clear Error**: - ``` - ❌ Cannot execute [skill-name]: MCP server `lightspeed-mcp` is not available - - 📋 Setup Instructions: - 1. Add lightspeed-mcp to `.mcp.json` (see: [setup guide]) - 2. Set environment variables: - export LIGHTSPEED_CLIENT_ID="your-id" - export LIGHTSPEED_CLIENT_SECRET="your-secret" - 3. Restart Claude Code to reload MCP servers - - 🔗 Documentation: [link to MCP server docs] - ``` - -3. **Request User Decision**: - ``` - ❓ How would you like to proceed? - - Options: - - "setup" - I'll help you configure the MCP server now - - "skip" - Skip this skill and continue with alternative approach - - "abort" - Stop the workflow entirely - - Please respond with your choice. - ``` - -4. **Wait for Explicit User Input** - Do not proceed automatically - -**Error Message Templates**: - -- Missing MCP Server: - ``` - ❌ MCP server `{server_name}` not configured in .mcp.json - 📋 Add server configuration: [setup guide link] - ``` - -- Missing Environment Variable: - ``` - ❌ Environment variable `{VAR_NAME}` not set - 📋 Set variable: export {VAR_NAME}="your-value" - - ⚠️ SECURITY: Never expose actual values in output or logs - ``` - -- Connection Failure: - ``` - ❌ Cannot connect to `{server_name}` MCP server - 📋 Possible causes: - - Container not running (run: podman ps) - - Network issues (check: podman logs) - - Invalid credentials (verify env vars) - ``` -``` - -**Rationale**: Provides graceful degradation and clear user guidance when dependencies are missing. - -### Skill File Format - -Skills MUST follow the structure defined in **Design Principle #6** above. Here's a minimal template: - -```yaml ---- -name: skill-name -description: | - [Concise when-to-use with 3-5 examples - under 500 tokens] -model: inherit|sonnet|haiku -color: red|blue|green|yellow ---- - -# [Skill Name] - -## Prerequisites -[As defined in Design Principle #7 - with verification and human notification] - -## When to Use This Skill -[Clear use cases and anti-patterns] - -## Workflow -### Step 1: [Action] - -**CRITICAL**: Document consultation MUST happen BEFORE tool invocation. - -**Document Consultation** (REQUIRED - Execute FIRST): -1. **Action**: Read [doc.md](path/to/doc.md) using the Read tool to understand [topic] -2. **Output to user**: "I consulted [doc.md](path/to/doc.md) to understand [topic]." - -**MCP Tool**: `tool_name` or `toolset__tool_name` (from server-name) - -**Parameters**: -- param1: "value" (exact format with example - Design Principle #2) -- param2: true (description of what this does) - -[Implementation details] - -## Dependencies -[As defined in Design Principle #4] - -## Critical: Human-in-the-Loop Requirements -[If applicable - Design Principle #5] -``` - -**Important**: See **Design Principles for Skills and Agents** section above for complete requirements and rationale. - -### Agent File Format - -Agents MUST follow similar principles as skills, with focus on skill orchestration: - -```yaml ---- -name: agent-name -description: | - When to use this agent vs skills - [Concise with 3-5 examples - under 500 tokens] -model: inherit -color: red -tools: ["All"] ---- - -# [Agent Name] - -## Prerequisites -[MCP servers and skills this agent depends on - Design Principle #7] - -## When to Use This Agent -[Multi-step workflows requiring orchestration] - -## Workflow - -### 1. Step Name -**Invoke the skill-name skill**: -``` -Skill: skill-name -Args: [Precise parameters - Design Principle #2] -``` - -**Document Consultation** (if needed): -I consulted [filename.md](path/to/filename.md) to understand [topic]. -[Design Principle #1] - -**Human Confirmation** (if critical): -Ask: "Should I proceed with [action]?" -Wait for confirmation. -[Design Principle #5] - -### 2. Next Step -[Continue orchestration pattern...] - -## Dependencies -[Skills, tools, docs this agent uses - Design Principle #4] - -## Critical: Human-in-the-Loop Requirements -[For agents performing critical operations - Design Principle #5] -``` - -**Important**: Agents inherit the same design principles as skills. See **Design Principles for Skills and Agents** section above. +See [SKILL_DESIGN_PRINCIPLES.md](SKILL_DESIGN_PRINCIPLES.md) for the complete design principles, templates, and rationale. Validate compliance with `make validate-skill-design`. ### MCP Server Integration @@ -727,4 +240,4 @@ When creating new collections, follow the pattern that best matches your needs: 14. **Persona-focused design** - Each collection serves specific user roles 15. **Concise skill descriptions** - Keep YAML frontmatter under 500 tokens (Design Principle #3) -**See**: **Design Principles for Skills and Agents** section for detailed requirements and templates. +**See**: [SKILL_DESIGN_PRINCIPLES.md](SKILL_DESIGN_PRINCIPLES.md) for detailed requirements and templates. diff --git a/Makefile b/Makefile index 54247296..47e3fa60 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ -.PHONY: help install validate generate serve clean test test-full check-uv +.PHONY: help install validate validate-skill-design generate serve clean test test-full check-uv help: @echo "agentic-collections Documentation Generator" @echo "" @echo "Available targets:" - @echo " install - Install Python dependencies (requires uv)" - @echo " validate - Validate pack structure" + @echo " install - Install Python dependencies (requires uv)" + @echo " validate - Validate pack structure (plugin.json, .mcp.json, frontmatter)" + @echo " validate-skill-design - Validate skills (use PACK=rh-sre for a specific pack)" @echo " generate - Generate docs/data.json" @echo " serve - Start local server on http://localhost:8000" @echo " test - Quick test (validate + generate + verify)" @@ -39,6 +40,9 @@ validate: check-uv @uv run python scripts/validate_structure.py @echo "✓ Validation passed!" +validate-skill-design: check-uv + @uv run python scripts/validate_skill_design.py $(if $(PACK),$(PACK)) + generate: check-uv @echo "Generating documentation..." @uv run python scripts/build_website.py diff --git a/README.md b/README.md index 49affa00..336d5575 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,14 @@ Generate and view documentation locally: # Install dependencies (first time only) make install -# Validate pack structure +# Validate pack structure (plugin.json, .mcp.json, frontmatter) make validate +# Validate skills against Design Principles (SKILL_DESIGN_PRINCIPLES.md) +make validate-skill-design +# Or validate a specific pack: +make validate-skill-design PACK=rh-sre + # Generate docs/data.json make generate @@ -55,6 +60,32 @@ Updates are automatically deployed to GitHub Pages when changes are pushed to ma For more details, see [docs/README.md](docs/README.md). +### Skill Design Validation + +The `validate-skill-design` target checks skills against the [SKILL_DESIGN_PRINCIPLES.md](SKILL_DESIGN_PRINCIPLES.md) defined in CLAUDE.md. Use it when creating or modifying skills to ensure compliance with: + +- Document consultation transparency (DP1) +- Parameter specification and ordering (DP2) +- Description conciseness (DP3) +- Dependencies declaration (DP4) +- Human-in-the-loop requirements for critical operations (DP5) +- Mandatory sections (Prerequisites, When to Use, Workflow) (DP6) +- Credential security (no `echo $VAR` exposure) (DP7) + +```bash +# Validate all packs +make validate-skill-design + +# Validate a specific pack only +make validate-skill-design PACK=rh-sre + +# Treat warnings as errors +uv run python scripts/validate_skill_design.py --warnings-as-errors + +# Skip missing 'model' field (for legacy skills) +uv run python scripts/validate_skill_design.py --skip-missing-model +``` + ## Security This repository uses [gitleaks](https://github.com/gitleaks/gitleaks) to prevent accidental commits of sensitive data. diff --git a/SKILL_DESIGN_PRINCIPLES.md b/SKILL_DESIGN_PRINCIPLES.md new file mode 100644 index 00000000..fa847537 --- /dev/null +++ b/SKILL_DESIGN_PRINCIPLES.md @@ -0,0 +1,493 @@ +# Design Principles for Skills and Agents + +This document defines the design principles for creating skills and agents in agentic collections. It is referenced from [CLAUDE.md](CLAUDE.md#design-principles-for-skills-and-agents). Validate compliance with `make validate-skill-design` (see [README.md](README.md#skill-design-validation)). + +## 1. Document Consultation Transparency + +When a skill or agent consults documentation (from `docs/` or skill/agent files), it **MUST**: +1. **Actually read the file** using the Read tool to load it into context +2. **Then declare** the consultation to the user + +**CRITICAL**: Document consultation means READING the file, not just claiming to have read it. + +**Required Implementation**: +```markdown +**Document Consultation** (REQUIRED): +1. **Action**: Read [filename.md](path/to/filename.md) using the Read tool to understand [specific topic] +2. **Output to user**: "I consulted [filename.md](path/to/filename.md) to understand [specific topic]." +``` + +**❌ WRONG - Transparency Theater** (just claims, no actual reading): +```markdown +**Document Consultation** (output to user): +``` +I consulted [filename.md](path/to/filename.md) to understand [topic]. +``` +``` + +**✅ CORRECT - Actual Consultation** (reads first, then declares): +```markdown +**Document Consultation** (REQUIRED): +1. **Action**: Read [cvss-scoring.md](../../docs/references/cvss-scoring.md) using the Read tool to understand CVSS severity mapping +2. **Output to user**: "I consulted [cvss-scoring.md](../../docs/references/cvss-scoring.md) to understand CVSS severity mapping." +``` + +**Examples in execution**: +- Read `docs/references/cvss-scoring.md` → "I consulted [cvss-scoring.md](rh-sre/docs/references/cvss-scoring.md) to verify the CVSS severity mapping." +- Read `skills/playbook-generator/SKILL.md` → "I consulted [playbook-generator/SKILL.md](rh-sre/skills/playbook-generator/SKILL.md) to understand playbook generation parameters." + +**Rationale**: +- **Substance**: Ensures AI actually enriches its context with domain knowledge +- **Transparency**: Users understand the AI's knowledge sources +- **Auditability**: The execution-summary skill can track actual Read tool calls + +## 2. Precise Parameter Specification + +Skills MUST specify **exact parameters** when instructing agents to use tools, ensuring first-attempt success. + +**CRITICAL**: Document consultation must be specified BEFORE tool parameters to ensure it happens first. + +**❌ Bad Example - Vague parameters**: +``` +Use get_cve tool with the CVE ID +``` + +**❌ Bad Example - Wrong parameters**: +``` +**MCP Tool**: get_cves + +**Parameters**: +- severity: ["Critical", "Important"] +- sort_by: "cvss_score" +``` +(Actual tool uses `impact: "7,6"` and `sort: "-cvss_score"`) + +**✅ Good Example - Correct structure with document consultation first**: +``` +**CRITICAL**: Document consultation MUST happen BEFORE tool invocation. + +**Document Consultation** (REQUIRED - Execute FIRST): +1. **Action**: Read [vulnerability-logic.md](../../docs/insights/vulnerability-logic.md) using the Read tool +2. **Output to user**: "I consulted [vulnerability-logic.md]..." + +**MCP Tool**: `get_cves` or `vulnerability__get_cves` (from lightspeed-mcp) + +**Parameters**: +- impact: "7,6" (string with comma-separated impact levels: 7=Important, 6=Moderate, 5=Low) +- sort: "-cvss_score" (use - prefix for descending; valid fields: "cvss_score", "public_date") +- limit: 20 (maximum number of CVEs to return) +``` + +**Rationale**: +- **Ordering**: Document consultation before parameters ensures it's executed first +- **Precision**: Exact parameter names and formats prevent tool errors +- **Examples**: Value examples (e.g., "7,6") show correct format +- **Determinism**: First-attempt success reduces wasted cycles + +## 3. Skill Precedence and Conciseness + +**Precedence Rule**: Skills > Tools (always invoke skills, not raw MCP tools) + +**Conciseness Requirement**: Skill descriptions (loaded at agent start time) must be: +- **Under 500 tokens** for the YAML frontmatter description field +- **Focus on "when to use"** with 3-5 concrete examples +- **Defer implementation details** to the skill body (not frontmatter) + +**Example**: +```yaml +--- +name: cve-impact +description: | + Analyze CVE impact across the fleet without immediate remediation. + + Use when: + - "What are the most critical vulnerabilities?" + - "Show CVEs affecting my systems" + - "List high-severity CVEs" + + NOT for remediation actions (use remediator agent instead). +model: inherit +--- +``` + +**Rationale**: Minimizes token usage at agent initialization while maintaining clarity. + +## 4. Dependencies Declaration + +Every skill MUST include a **Dependencies** section listing: +- **Skills**: Other skills this skill may invoke +- **MCP Tools**: Specific tools from MCP servers +- **MCP Servers**: Required MCP server names +- **Documentation**: Reference docs for context + +**Required Format**: +```markdown +## Dependencies + +### Required MCP Servers +- `lightspeed-mcp` - Red Hat Lightspeed platform access + +### Required MCP Tools +- `vulnerability__get_cves` (from lightspeed-mcp) - List CVEs +- `vulnerability__get_cve` (from lightspeed-mcp) - Get CVE details + +### Related Skills +- `cve-validation` - Validate CVEs before impact analysis +- `fleet-inventory` - Identify affected systems + +### Reference Documentation +- [cvss-scoring.md](docs/references/cvss-scoring.md) - CVSS severity mappings +- [insights-api.md](docs/insights/insights-api.md) - API usage patterns +``` + +**Rationale**: Makes dependencies explicit for debugging and ensures proper error handling. + +## 5. Human-in-the-Loop Requirements + +Skills performing **critical operations** MUST include this section: + +**Required Section**: +```markdown +## Critical: Human-in-the-Loop Requirements + +This skill requires explicit user confirmation at the following steps: + +1. **Before Tool Invocation** [if applicable] + - Ask: "Should I proceed with [specific action]?" + - Wait for user confirmation + +2. **Before Destructive Actions** [if applicable] + - Display preview of changes + - Ask: "Review the changes above. Should I execute this?" + - Wait for explicit "yes" or "proceed" + +3. **After Each Major Step** [if applicable] + - Report results + - Ask: "Continue to next step?" + +**Never assume approval** - always wait for explicit user confirmation. +``` + +**When to Use**: +- Playbook execution (ansible-mcp-server) +- System modifications (package updates, config changes) +- Multi-system operations (batch remediation) +- Data deletion or irreversible actions + +**Rationale**: Prevents unintended automation; maintains user control over critical operations. + +## 6. Mandatory Skill Sections + +Every skill MUST include these sections in order: + +### Template Structure: + +```markdown +--- +name: skill-name +description: | + [Concise when-to-use with 3-5 examples] +model: inherit|sonnet|haiku +color: red|blue|green|yellow +--- + +# [Skill Name] + +## Prerequisites + +**Required MCP Servers**: `server-name` ([setup guide](link)) +**Required MCP Tools**: `tool_name` (from server-name) +**Environment Variables**: `VAR_NAME` (if applicable) + +**Verification**: +Before executing, verify MCP server availability: +1. Check `server-name` is configured in `.mcp.json` +2. Verify environment variables are set +3. If missing: Report to user with setup instructions + +**Human Notification on Failure**: +If prerequisites are not met: +- ❌ "Cannot proceed: MCP server `server-name` is not available" +- 📋 "Setup required: [link to setup guide]" +- ❓ "How would you like to proceed? (setup now / skip / abort)" +- ⏸️ Wait for user decision + +## When to Use This Skill + +Use this skill when: +- [Specific scenario 1] +- [Specific scenario 2] +- [Specific scenario 3] + +Do NOT use when: +- [Anti-pattern 1] → Use [alternative] instead +- [Anti-pattern 2] → Use [alternative] instead + +## Workflow + +### Step 1: [Action Name] + +**CRITICAL**: Document consultation MUST happen BEFORE tool invocation. + +**Document Consultation** (REQUIRED - Execute FIRST): +1. **Action**: Read [doc.md](../../docs/category/doc.md) using the Read tool to understand [specific topic] +2. **Output to user**: "I consulted [doc.md](../../docs/category/doc.md) to understand [specific topic]." + +**MCP Tool**: `tool_name` or `toolset__tool_name` (from server-name) + +**Parameters**: +- `param1`: [exact specification with example - see Design Principle #2] + - Example: `"CVE-2024-1234"` +- `param2`: [exact specification with example] + - Example: `true` (description of what this does) + +**Expected Output**: [describe what the tool returns] + +**Error Handling**: +- If [error condition]: [how to handle] + +### Step 2: [Next Action] + +[Continue pattern...] + +## Dependencies + +[As specified in principle #4] + +## Critical: Human-in-the-Loop Requirements + +[As specified in principle #5, if applicable] + +## Example Usage + +[Concrete example with user query and skill response] +``` + +**Rationale**: Standardizes skill structure for consistency and completeness. + +## 7. MCP Server Availability Verification + +The **Prerequisites** section MUST include verification logic: + +**CRITICAL SECURITY CONSTRAINT**: +- **NEVER print environment variable values in user-visible output** +- When checking if env vars are set, only report presence/absence +- Do NOT use `echo $VAR_NAME` or display actual credential values +- Protect sensitive data like API keys, tokens, secrets, passwords + +**❌ WRONG - Exposes credentials**: +```bash +echo $LIGHTSPEED_CLIENT_SECRET # Shows actual secret value +``` + +**✅ CORRECT - Check without exposing**: +```bash +# Check if set (exit code only, no output) +test -n "$LIGHTSPEED_CLIENT_SECRET" + +# Or check and report boolean result +if [ -n "$LIGHTSPEED_CLIENT_SECRET" ]; then + echo "✓ LIGHTSPEED_CLIENT_SECRET is set" +else + echo "✗ LIGHTSPEED_CLIENT_SECRET is not set" +fi +``` + +**In User-Visible Messages**: +``` +✓ Environment variable LIGHTSPEED_CLIENT_ID is set +✓ Environment variable LIGHTSPEED_CLIENT_SECRET is set +``` + +**NEVER show**: +``` +LIGHTSPEED_CLIENT_SECRET=sk-abc123-xyz789-... ❌ SECURITY VIOLATION +``` + +**Rationale**: Prevents accidental credential exposure in conversation history, logs, or screenshots. + +--- + +**Required Pattern**: +```markdown +## Prerequisites + +**Required MCP Servers**: `lightspeed-mcp` ([setup guide](https://console.redhat.com/)) +**Required MCP Tools**: +- `vulnerability__get_cves` +- `vulnerability__get_cve` + +**Verification Steps**: +1. **Check MCP Server Configuration** + - Verify `lightspeed-mcp` exists in `.mcp.json` + - If missing → Proceed to Human Notification + +2. **Check Environment Variables** + - Verify `LIGHTSPEED_CLIENT_ID` is set + - Verify `LIGHTSPEED_CLIENT_SECRET` is set + - If missing → Proceed to Human Notification + +3. **Test MCP Server Connection** (optional, for critical skills) + - Attempt simple tool call (e.g., `get_mcp_version`) + - If fails → Proceed to Human Notification + +**Human Notification Protocol**: + +When prerequisites fail, the skill MUST: + +1. **Stop Execution Immediately** - Do not attempt tool calls +2. **Report Clear Error**: + ``` + ❌ Cannot execute [skill-name]: MCP server `lightspeed-mcp` is not available + + 📋 Setup Instructions: + 1. Add lightspeed-mcp to `.mcp.json` (see: [setup guide]) + 2. Set environment variables: + export LIGHTSPEED_CLIENT_ID="your-id" + export LIGHTSPEED_CLIENT_SECRET="your-secret" + 3. Restart Claude Code to reload MCP servers + + 🔗 Documentation: [link to MCP server docs] + ``` + +3. **Request User Decision**: + ``` + ❓ How would you like to proceed? + + Options: + - "setup" - I'll help you configure the MCP server now + - "skip" - Skip this skill and continue with alternative approach + - "abort" - Stop the workflow entirely + + Please respond with your choice. + ``` + +4. **Wait for Explicit User Input** - Do not proceed automatically + +**Error Message Templates**: + +- Missing MCP Server: + ``` + ❌ MCP server `{server_name}` not configured in .mcp.json + 📋 Add server configuration: [setup guide link] + ``` + +- Missing Environment Variable: + ``` + ❌ Environment variable `{VAR_NAME}` not set + 📋 Set variable: export {VAR_NAME}="your-value" + + ⚠️ SECURITY: Never expose actual values in output or logs + ``` + +- Connection Failure: + ``` + ❌ Cannot connect to `{server_name}` MCP server + 📋 Possible causes: + - Container not running (run: podman ps) + - Network issues (check: podman logs) + - Invalid credentials (verify env vars) + ``` +``` + +**Rationale**: Provides graceful degradation and clear user guidance when dependencies are missing. + +## Skill File Format + +Skills MUST follow the structure defined in **Design Principle #6** above. Here's a minimal template: + +```yaml +--- +name: skill-name +description: | + [Concise when-to-use with 3-5 examples - under 500 tokens] +model: inherit|sonnet|haiku +color: red|blue|green|yellow +--- + +# [Skill Name] + +## Prerequisites +[As defined in Design Principle #7 - with verification and human notification] + +## When to Use This Skill +[Clear use cases and anti-patterns] + +## Workflow +### Step 1: [Action] + +**CRITICAL**: Document consultation MUST happen BEFORE tool invocation. + +**Document Consultation** (REQUIRED - Execute FIRST): +1. **Action**: Read [doc.md](path/to/doc.md) using the Read tool to understand [topic] +2. **Output to user**: "I consulted [doc.md](path/to/doc.md) to understand [topic]." + +**MCP Tool**: `tool_name` or `toolset__tool_name` (from server-name) + +**Parameters**: +- param1: "value" (exact format with example - Design Principle #2) +- param2: true (description of what this does) + +[Implementation details] + +## Dependencies +[As defined in Design Principle #4] + +## Critical: Human-in-the-Loop Requirements +[If applicable - Design Principle #5] +``` + +**Important**: See this document for complete requirements and rationale. + +## Agent File Format + +Agents MUST follow similar principles as skills, with focus on skill orchestration: + +```yaml +--- +name: agent-name +description: | + When to use this agent vs skills + [Concise with 3-5 examples - under 500 tokens] +model: inherit +color: red +tools: ["All"] +--- + +# [Agent Name] + +## Prerequisites +[MCP servers and skills this agent depends on - Design Principle #7] + +## When to Use This Agent +[Multi-step workflows requiring orchestration] + +## Workflow + +### 1. Step Name +**Invoke the skill-name skill**: +``` +Skill: skill-name +Args: [Precise parameters - Design Principle #2] +``` + +**Document Consultation** (if needed): +I consulted [filename.md](path/to/filename.md) to understand [topic]. +[Design Principle #1] + +**Human Confirmation** (if critical): +Ask: "Should I proceed with [action]?" +Wait for confirmation. +[Design Principle #5] + +### 2. Next Step +[Continue orchestration pattern...] + +## Dependencies +[Skills, tools, docs this agent uses - Design Principle #4] + +## Critical: Human-in-the-Loop Requirements +[For agents performing critical operations - Design Principle #5] +``` + +**Important**: Agents inherit the same design principles as skills. See this document for complete requirements. diff --git a/rh-sre/skills/cve-impact/SKILL.md b/rh-sre/skills/cve-impact/SKILL.md index a400f2eb..8301d69e 100644 --- a/rh-sre/skills/cve-impact/SKILL.md +++ b/rh-sre/skills/cve-impact/SKILL.md @@ -20,6 +20,8 @@ description: | This skill orchestrates MCP tools (get_cves, get_cve, get_cve_systems) to provide comprehensive CVE analysis with Red Hat Lightspeed context. When users ask for remediation after seeing the analysis, invoke the `sre-agents:remediator` agent. **IMPORTANT**: ALWAYS use this skill instead of calling get_cves or other vulnerability MCP tools directly. + + a change --- # CVE Impact Analysis Skill diff --git a/scripts/validate_skill_design.py b/scripts/validate_skill_design.py new file mode 100644 index 00000000..5db50923 --- /dev/null +++ b/scripts/validate_skill_design.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python3 +""" +Validate skills against SKILL_DESIGN_PRINCIPLES.md. + +Design principles checked: + DP1: Document Consultation - correct format (Action: Read, Output to user) + DP2: Parameter order - Document Consultation before MCP Tool/Parameters + DP3: Conciseness - description length, "Use when" examples + DP4: Dependencies - section with Required MCP Servers/Tools, Related Skills, Reference Docs + DP5: Human-in-the-Loop - critical skills should have this section + DP6: Mandatory sections - Prerequisites, When to Use This Skill, Workflow + DP7: Credential security - no echo $VAR (except in anti-pattern examples) + +Cannot validate: runtime behavior (AI actually reading docs), parameter correctness vs MCP schemas. +""" + +from __future__ import annotations + +import argparse +import re +import sys +from dataclasses import dataclass, field +from pathlib import Path +from typing import Iterator + +import yaml + +# Design principle constants +MAX_DESCRIPTION_TOKENS = 500 +# Rough token estimate: ~4 chars per token for English +CHARS_PER_TOKEN = 4 +MAX_DESCRIPTION_CHARS = MAX_DESCRIPTION_TOKENS * CHARS_PER_TOKEN + +# Skills that perform critical operations (require Human-in-the-Loop section) +CRITICAL_SKILL_KEYWORDS = [ + "executor", + "playbook-executor", + "job-template-creator", + "remediator", +] + +# Required section order (DP6) +REQUIRED_SECTIONS = [ + "Prerequisites", + "When to Use This Skill", + "Workflow", +] + +# Dependencies subsections (DP4) +DEPENDENCY_SUBSECTIONS = [ + "Required MCP Servers", + "Required MCP Tools", + "Related Skills", + "Reference Documentation", +] + +# Credential exposure patterns (DP7) +CREDENTIAL_EXPOSURE_PATTERN = re.compile( + r"echo\s+\$\{?[A-Za-z_][A-Za-z0-9_]*\}?", + re.MULTILINE, +) + +# Anti-pattern context: if echo $VAR appears near these, it may be documenting the wrong way +ANTI_PATTERN_MARKERS = ["WRONG", "NEVER", "❌", "don't", "do not", "exposes credentials"] + + +@dataclass +class ValidationResult: + """Result of validating a single skill.""" + + path: Path + errors: list[str] = field(default_factory=list) + warnings: list[str] = field(default_factory=list) + + @property + def is_valid(self) -> bool: + return len(self.errors) == 0 + + +def find_skill_files(pack_dirs: list[str]) -> Iterator[Path]: + """Yield paths to all SKILL.md files in pack directories.""" + for pack_dir in pack_dirs: + skills_dir = Path(pack_dir) / "skills" + if skills_dir.exists(): + yield from skills_dir.glob("*/SKILL.md") + + +def extract_frontmatter(content: str) -> tuple[dict | None, str]: + """Extract YAML frontmatter and body from markdown content.""" + match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL) + if not match: + return None, content + try: + frontmatter = yaml.safe_load(match.group(1)) + body = content[match.end() :] + return frontmatter, body + except yaml.YAMLError: + return None, content + + +def check_dp1_document_consultation(body: str, result: ValidationResult) -> None: + """ + DP1: Document Consultation Transparency. + - If Document Consultation appears, it must have Action: Read and Output to user. + - Flag 'Transparency Theater' (output-only without Action). + """ + doc_consult_blocks = re.findall( + r"\*\*Document Consultation\*\*[^*]*(?:\*\*[^*]*\*\*[^*]*)*", + body, + re.DOTALL, + ) + + for block in doc_consult_blocks: + has_action_read = "Read [" in block or "Read [" in block.replace("\n", " ") + has_output = "Output to user" in block or "I consulted" in block + + # Transparency Theater: has output declaration but no Action + if "Output to user" in block or "output to user" in block.lower(): + if not has_action_read and "Action" not in block: + result.warnings.append( + "DP1: Document Consultation may be 'Transparency Theater' " + "(output declared but no 'Action: Read' - ensure AI actually reads the file)" + ) + + if has_action_read and not has_output: + result.warnings.append( + "DP1: Document Consultation has Action but missing 'Output to user' declaration" + ) + + +def check_dp2_parameter_order(body: str, result: ValidationResult) -> None: + """ + DP2: Document consultation must appear BEFORE MCP Tool/Parameters. + Check workflow steps that have both. + """ + # Find workflow steps (### Step N or #### Option) + step_pattern = re.compile( + r"(###+ [^\n]+\n)(.*?)(?=###+ |\Z)", + re.DOTALL, + ) + + for match in step_pattern.finditer(body): + step_content = match.group(2) + has_mcp_tool = "MCP Tool" in step_content or "**MCP Tool**" in step_content + has_params = "**Parameters**" in step_content or "Parameters:" in step_content + has_doc_consult = "Document Consultation" in step_content + + if (has_mcp_tool or has_params) and has_doc_consult: + doc_pos = step_content.find("Document Consultation") + tool_pos = step_content.find("MCP Tool") + params_pos = step_content.find("Parameters") + + if tool_pos >= 0 and doc_pos > tool_pos: + result.errors.append( + "DP2: Document Consultation must appear BEFORE MCP Tool in workflow step" + ) + if params_pos >= 0 and doc_pos > params_pos and "Parameters" in step_content: + result.errors.append( + "DP2: Document Consultation must appear BEFORE Parameters in workflow step" + ) + + +def check_dp3_conciseness(frontmatter: dict | None, result: ValidationResult) -> None: + """ + DP3: Skill Precedence and Conciseness. + - Description under 500 tokens. + - Focus on 'when to use' with 3-5 examples. + """ + if not frontmatter or "description" not in frontmatter: + return + + desc = frontmatter["description"] + if isinstance(desc, list): + desc = "\n".join(desc) + desc_str = str(desc).strip() + + # Token estimate + char_count = len(desc_str) + if char_count > MAX_DESCRIPTION_CHARS: + result.warnings.append( + f"DP3: Description may exceed 500 tokens " + f"(~{char_count // CHARS_PER_TOKEN} tokens, {char_count} chars). " + "Keep frontmatter concise; defer details to skill body." + ) + + # Use when examples + if "Use when" not in desc_str and "use when" not in desc_str.lower(): + result.warnings.append( + "DP3: Description should include 'Use when' with 3-5 concrete examples" + ) + + +def check_dp4_dependencies(body: str, result: ValidationResult) -> None: + """ + DP4: Dependencies Declaration. + Must have ## Dependencies with required subsections. + """ + if "## Dependencies" not in body: + result.errors.append("DP4: Missing '## Dependencies' section") + return + + # Use (?=\n## |\Z) to avoid matching "## " inside "### " (subsection headers) + deps_section = re.search( + r"## Dependencies\s*\n(.*?)(?=\n## |\Z)", + body, + re.DOTALL, + ) + if not deps_section: + return + + section_content = deps_section.group(1) + for subsection in DEPENDENCY_SUBSECTIONS: + if subsection not in section_content: + result.warnings.append( + f"DP4: Dependencies section should include '### {subsection}'" + ) + + +def check_dp5_human_in_loop( + name: str, body: str, result: ValidationResult +) -> None: + """ + DP5: Human-in-the-Loop Requirements. + Critical skills (executor, playbook, etc.) must have this section. + """ + is_critical = any(kw in name.lower() for kw in CRITICAL_SKILL_KEYWORDS) + has_section = "Human-in-the-Loop" in body or "Human-in-the-Loop Requirements" in body + + if is_critical and not has_section: + result.warnings.append( + "DP5: Skill performs critical operations (execution/modification). " + "Consider adding '## Critical: Human-in-the-Loop Requirements' section." + ) + + +def check_dp6_mandatory_sections(body: str, result: ValidationResult) -> None: + """ + DP6: Mandatory Skill Sections. + Must have Prerequisites, When to Use This Skill, Workflow in order. + """ + section_headings = re.findall(r"^## ([^\n#]+)", body, re.MULTILINE) + + for required in REQUIRED_SECTIONS: + if required not in section_headings: + result.errors.append(f"DP6: Missing required section '## {required}'") + return + + # Check order + indices = [] + for i, heading in enumerate(section_headings): + for req in REQUIRED_SECTIONS: + if req in heading or heading.strip() == req: + indices.append((REQUIRED_SECTIONS.index(req), i)) + break + + if len(indices) >= 2: + indices.sort(key=lambda x: x[0]) + for i in range(1, len(indices)): + if indices[i][1] < indices[i - 1][1]: + result.warnings.append( + f"DP6: Required sections should appear in order: " + f"{', '.join(REQUIRED_SECTIONS)}" + ) + break + + +def check_dp7_credential_exposure(body: str, result: ValidationResult) -> None: + """ + DP7: MCP Server Availability Verification - no credential exposure. + Flag echo $VAR unless it's in an anti-pattern example (WRONG, NEVER, ❌). + """ + lines = body.split("\n") + in_code_block = False + code_block_context = "" + + for i, line in enumerate(lines): + if line.strip().startswith("```"): + in_code_block = not in_code_block + if in_code_block: + code_block_context = "" + continue + + if in_code_block: + code_block_context += line + "\n" + else: + code_block_context = line + + match = CREDENTIAL_EXPOSURE_PATTERN.search(line) + if match: + # Check if this is in a "wrong example" context + context_before = "\n".join(lines[max(0, i - 10) : i]).lower() + is_anti_pattern = any( + marker.lower() in context_before for marker in ANTI_PATTERN_MARKERS + ) + if not is_anti_pattern: + result.errors.append( + f"DP7: Potential credential exposure at line {i + 1}: " + f"'{match.group().strip()}'. " + "Never echo env vars; use 'test -n \"$VAR\"' or report presence/absence only." + ) + + +def check_frontmatter_fields( + frontmatter: dict | None, + result: ValidationResult, + *, + skip_model: bool = False, +) -> None: + """Check required frontmatter fields (name, description, model, color).""" + if not frontmatter: + result.errors.append("Missing or invalid YAML frontmatter") + return + + required = ["name", "description"] + if not skip_model: + required.append("model") + for field_name in required: + if field_name not in frontmatter: + result.errors.append(f"Frontmatter missing required field: {field_name}") + + if "color" in frontmatter: + valid_colors = {"red", "blue", "green", "yellow", "cyan"} + if frontmatter["color"].lower() not in valid_colors: + result.warnings.append( + f"Frontmatter 'color' should be one of: {', '.join(valid_colors)}" + ) + + +def validate_skill(skill_path: Path, *, skip_model: bool = False) -> ValidationResult: + """Run all design principle checks on a skill file.""" + result = ValidationResult(path=skill_path) + + try: + content = skill_path.read_text(encoding="utf-8") + except Exception as e: + result.errors.append(f"Could not read file: {e}") + return result + + frontmatter, body = extract_frontmatter(content) + + check_frontmatter_fields(frontmatter, result, skip_model=skip_model) + check_dp1_document_consultation(body, result) + check_dp2_parameter_order(body, result) + check_dp3_conciseness(frontmatter, result) + check_dp4_dependencies(body, result) + check_dp5_human_in_loop( + frontmatter.get("name", "") if frontmatter else "", body, result + ) + check_dp6_mandatory_sections(body, result) + check_dp7_credential_exposure(body, result) + + return result + + +def main() -> int: + """Main entry point.""" + parser = argparse.ArgumentParser( + description="Validate skills against SKILL_DESIGN_PRINCIPLES.md" + ) + parser.add_argument( + "paths", + nargs="*", + default=["rh-sre", "rh-developer", "ocp-admin", "rh-support-engineer", "rh-virt"], + help="Pack directories or specific SKILL.md paths to validate", + ) + parser.add_argument( + "--warnings-as-errors", + action="store_true", + help="Treat warnings as errors", + ) + parser.add_argument( + "--skip-missing-model", + action="store_true", + help="Do not fail on missing 'model' frontmatter (for packs with legacy skills)", + ) + args = parser.parse_args() + + # Resolve paths to skill files + skill_files: list[Path] = [] + for p in args.paths: + path = Path(p) + if path.is_file() and path.name == "SKILL.md": + skill_files.append(path) + elif path.is_dir(): + skills_dir = path / "skills" + if skills_dir.exists(): + skill_files.extend(skills_dir.glob("*/SKILL.md")) + else: + # Maybe it's a pack name + pack_path = Path(p) + if (pack_path / "skills").exists(): + skill_files.extend((pack_path / "skills").glob("*/SKILL.md")) + + if not skill_files: + print("No SKILL.md files found to validate.") + return 0 + + print("🔍 Validating skills against Design Principles...") + print() + + all_errors: list[tuple[Path, str]] = [] + all_warnings: list[tuple[Path, str]] = [] + + for skill_path in sorted(skill_files): + result = validate_skill(skill_path, skip_model=args.skip_missing_model) + rel_path = skill_path.relative_to(Path.cwd()) if skill_path.is_relative_to(Path.cwd()) else skill_path + + if result.errors: + print(f" {rel_path}: ❌") + for err in result.errors: + all_errors.append((skill_path, err)) + print(f" • {err}") + elif result.warnings: + print(f" {rel_path}: ⚠️") + for warn in result.warnings: + all_warnings.append((skill_path, warn)) + if args.warnings_as_errors: + all_errors.append((skill_path, f"[WARN] {warn}")) + print(f" • {warn}") + else: + print(f" {rel_path}: ✓") + + print() + + if all_errors or (args.warnings_as_errors and all_warnings): + print("❌ Validation failed") + return 1 + + if all_warnings: + print("✅ Validation passed (with warnings)") + else: + print("✅ All skills validated successfully") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 15ab32a4cb16ace59fc7d6b186aa05cafc821d8a Mon Sep 17 00:00:00 2001 From: Daniele Martinoli Date: Thu, 26 Feb 2026 12:20:42 +0100 Subject: [PATCH 02/10] CI compliance check Signed-off-by: Daniele Martinoli --- .claude/skills/compliance-checker/SKILL.md | 58 +++++++++++++++++++ .cursor/skills | 1 + ...alidate-packs.yml => compliance-check.yml} | 15 ++++- Makefile | 12 ++-- README.md | 9 ++- rh-virt/SKILL_TEMPLATE.md | 12 ++-- scripts/ci-validate-changed-skills.sh | 41 +++++++++++++ 7 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 .claude/skills/compliance-checker/SKILL.md create mode 120000 .cursor/skills rename .github/workflows/{validate-packs.yml => compliance-check.yml} (50%) create mode 100755 scripts/ci-validate-changed-skills.sh diff --git a/.claude/skills/compliance-checker/SKILL.md b/.claude/skills/compliance-checker/SKILL.md new file mode 100644 index 00000000..f944caa2 --- /dev/null +++ b/.claude/skills/compliance-checker/SKILL.md @@ -0,0 +1,58 @@ +--- +name: compliance-checker +description: | + Run skill design compliance validation for agentic collections. Use when the user asks to: + - "Check skill compliance" / "Validate skill design" / "Run compliance check" + - "Verify my skills follow design principles" + - Before committing skill changes + + Runs the validate_skill_design.py script against SKILL_DESIGN_PRINCIPLES.md. +--- + +# Skill Design Compliance Checker + +Run the programmatic compliance check for skills in this agentic-collections repository. + +## When to Use + +Invoke this skill when the user wants to: +- Validate skills against [SKILL_DESIGN_PRINCIPLES.md](SKILL_DESIGN_PRINCIPLES.md) +- Check compliance before committing or opening a PR +- Verify design principle adherence (DP1–DP7) + +## Instructions + +1. **Run from project root** (the workspace root). + +2. **Preferred: validate only changed skills** (recommended for local dev): + ```bash + make validate-skill-design-changed + ``` + This validates only staged and unstaged skill changes. + +3. **Alternative: validate all skills or a specific pack**: + ```bash + make validate-skill-design + # Or validate a specific pack: + make validate-skill-design PACK=rh-sre + ``` + +4. **Report results** to the user: + - If validation passes: report success + - If validation fails: list the errors and suggest fixes per the design principles + +## Dependencies + +- `uv` must be installed (run `make install` if needed) +- Script: `scripts/validate_skill_design.py` +- Reference: [SKILL_DESIGN_PRINCIPLES.md](../../../SKILL_DESIGN_PRINCIPLES.md) + +## Design Principles Checked + +- DP1: Document consultation transparency +- DP2: Parameter specification and ordering +- DP3: Description conciseness (≤500 tokens) +- DP4: Dependencies declaration +- DP5: Human-in-the-loop for critical skills +- DP6: Mandatory sections (Prerequisites, When to Use, Workflow) +- DP7: Credential security (no `echo $VAR`) diff --git a/.cursor/skills b/.cursor/skills new file mode 120000 index 00000000..454b8427 --- /dev/null +++ b/.cursor/skills @@ -0,0 +1 @@ +../.claude/skills \ No newline at end of file diff --git a/.github/workflows/validate-packs.yml b/.github/workflows/compliance-check.yml similarity index 50% rename from .github/workflows/validate-packs.yml rename to .github/workflows/compliance-check.yml index 037125fa..194b4d8b 100644 --- a/.github/workflows/validate-packs.yml +++ b/.github/workflows/compliance-check.yml @@ -6,10 +6,12 @@ on: branches: [main] jobs: - validate: + compliance-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 @@ -19,8 +21,17 @@ jobs: - name: Install uv run: curl -LsSf https://astral.sh/uv/install.sh | sh - - name: Install dependencies and validate + - name: Install dependencies and validate structure run: | source $HOME/.cargo/env make install make validate + + - name: Validate skill design (changed skills only) + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_BASE_REF: ${{ github.base_ref }} + GITHUB_EVENT_BEFORE: ${{ github.event.before }} + run: | + source $HOME/.cargo/env + ./scripts/ci-validate-changed-skills.sh diff --git a/Makefile b/Makefile index 47e3fa60..6cf28961 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,13 @@ -.PHONY: help install validate validate-skill-design generate serve clean test test-full check-uv +.PHONY: help install validate validate-skill-design validate-skill-design-changed generate serve clean test test-full check-uv help: @echo "agentic-collections Documentation Generator" @echo "" @echo "Available targets:" - @echo " install - Install Python dependencies (requires uv)" - @echo " validate - Validate pack structure (plugin.json, .mcp.json, frontmatter)" - @echo " validate-skill-design - Validate skills (use PACK=rh-sre for a specific pack)" + @echo " install - Install Python dependencies (requires uv)" + @echo " validate - Validate pack structure (plugin.json, .mcp.json, frontmatter)" + @echo " validate-skill-design - Validate all skills (use PACK=rh-sre for a specific pack)" + @echo " validate-skill-design-changed - Validate only changed skills (staged + unstaged, for local dev)" @echo " generate - Generate docs/data.json" @echo " serve - Start local server on http://localhost:8000" @echo " test - Quick test (validate + generate + verify)" @@ -43,6 +44,9 @@ validate: check-uv validate-skill-design: check-uv @uv run python scripts/validate_skill_design.py $(if $(PACK),$(PACK)) +validate-skill-design-changed: check-uv + @VALIDATE_INCLUDE_UNCOMMITTED=1 ./scripts/ci-validate-changed-skills.sh + generate: check-uv @echo "Generating documentation..." @uv run python scripts/build_website.py diff --git a/README.md b/README.md index 336d5575..d9690b42 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,10 @@ make install make validate # Validate skills against Design Principles (SKILL_DESIGN_PRINCIPLES.md) +# Validate only changed skills (staged + unstaged) - recommended for local dev: +make validate-skill-design-changed +# Or validate all skills / a specific pack: make validate-skill-design -# Or validate a specific pack: make validate-skill-design PACK=rh-sre # Generate docs/data.json @@ -62,7 +64,7 @@ For more details, see [docs/README.md](docs/README.md). ### Skill Design Validation -The `validate-skill-design` target checks skills against the [SKILL_DESIGN_PRINCIPLES.md](SKILL_DESIGN_PRINCIPLES.md) defined in CLAUDE.md. Use it when creating or modifying skills to ensure compliance with: +The `validate-skill-design` and `validate-skill-design-changed` targets check skills against the [Design Principles](SKILL_DESIGN_PRINCIPLES.md) referenced from CLAUDE.md. **CI runs this validation automatically** on pull requests and pushes to main, but only for changed skills. For local development, use `validate-skill-design-changed` to validate only your modified skills (staged + unstaged). Use `validate-skill-design` for full validation or a specific pack. Ensure compliance with: - Document consultation transparency (DP1) - Parameter specification and ordering (DP2) @@ -73,6 +75,9 @@ The `validate-skill-design` target checks skills against the [SKILL_DESIGN_PRINC - Credential security (no `echo $VAR` exposure) (DP7) ```bash +# Validate only changed skills (staged + unstaged) - recommended for local dev +make validate-skill-design-changed + # Validate all packs make validate-skill-design diff --git a/rh-virt/SKILL_TEMPLATE.md b/rh-virt/SKILL_TEMPLATE.md index c8ecee53..36458331 100644 --- a/rh-virt/SKILL_TEMPLATE.md +++ b/rh-virt/SKILL_TEMPLATE.md @@ -6,11 +6,11 @@ This template provides the standardized structure for all skills in the `rh-virt This template implements: - **Repository Standards**: rh-virt collection-specific patterns and conventions -- **Claude Guidelines**: Official skill structure from `/CLAUDE.md` (Design Principles #1-7) +- **Claude Guidelines**: Official skill structure from [SKILL_DESIGN_PRINCIPLES.md](/SKILL_DESIGN_PRINCIPLES.md) (Design Principles #1-7) - **MCP Integration**: OpenShift Virtualization MCP server tool usage patterns - **Human-in-the-Loop**: Safety confirmations for critical operations -**Reference**: See `/CLAUDE.md` "Design Principles for Skills and Agents" for complete rationale. +**Reference**: See [SKILL_DESIGN_PRINCIPLES.md](/SKILL_DESIGN_PRINCIPLES.md) for complete rationale. ## Quick Start @@ -180,7 +180,7 @@ Please respond with your choice. **Purpose**: Step-by-step instructions for executing this skill. -**Requirements** (from CLAUDE.md Design Principles #1-2): +**Requirements** (from SKILL_DESIGN_PRINCIPLES.md Design Principles #1-2): - Document Consultation BEFORE tool invocation (REQUIRED when relevant troubleshooting docs exist) - Precise parameter specifications with examples - Clear error handling for each step @@ -591,7 +591,7 @@ Sections MUST appear in this exact order: - [ ] Includes Human Notification Protocol for failures - [ ] **SECURITY**: Never exposes credential values in output -### 4. Workflow Section (Design Principles #1-2) +### 4. Workflow Section (SKILL_DESIGN_PRINCIPLES.md #1-2) - [ ] **Document Consultation** pattern included when relevant troubleshooting docs exist - REQUIRED when skill relates to documented error scenarios @@ -709,7 +709,7 @@ See `SKILLS_CHECKLIST.md` for the complete validation guide with scoring criteri ### Claude Code Compliance -5. **Follow Design Principles**: Reference `/CLAUDE.md` for rationale +5. **Follow Design Principles**: Reference [SKILL_DESIGN_PRINCIPLES.md](/SKILL_DESIGN_PRINCIPLES.md) for rationale - **Principle #1**: Document Consultation (read docs before tools) - **Principle #2**: Precise Parameters (exact formats with examples) - **Principle #3**: Concise Descriptions (under 500 tokens) @@ -787,7 +787,7 @@ See `SKILLS_CHECKLIST.md` for the complete validation guide with scoring criteri ## Claude Code Design Principles Reference -For complete details, see `/CLAUDE.md` "Design Principles for Skills and Agents" section. +For complete details, see [SKILL_DESIGN_PRINCIPLES.md](/SKILL_DESIGN_PRINCIPLES.md). ### Principle #1: Document Consultation Transparency diff --git a/scripts/ci-validate-changed-skills.sh b/scripts/ci-validate-changed-skills.sh new file mode 100755 index 00000000..4b3e3f7c --- /dev/null +++ b/scripts/ci-validate-changed-skills.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Validate only changed skills in CI (PRs and pushes to main). +# Exits 0 if no skills changed or all changed skills pass validation. +# +# Local dev: set VALIDATE_INCLUDE_UNCOMMITTED=1 to include staged and unstaged changes. + +set -e + +if [ -n "$VALIDATE_INCLUDE_UNCOMMITTED" ]; then + # Local dev: include staged + unstaged changes vs HEAD + DIFF_CMD="git diff --name-only HEAD" +elif [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + # Three-dot diff: merge-base(base, HEAD)..HEAD = changes in the PR + BASE_REF="${GITHUB_BASE_REF:-main}" + git fetch origin "$BASE_REF" 2>/dev/null || true + DIFF_CMD="git diff --name-only origin/$BASE_REF...HEAD" +elif [ "$GITHUB_EVENT_NAME" = "push" ]; then + # Push event: diff between before and after + BEFORE="${GITHUB_EVENT_BEFORE:-}" + if [ -z "$BEFORE" ] || [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then + echo "No base commit for diff, skipping skill design validation" + exit 0 + fi + DIFF_CMD="git diff --name-only $BEFORE HEAD" +else + # Default: local dev, include uncommitted changes + DIFF_CMD="git diff --name-only HEAD" +fi + +CHANGED=$($DIFF_CMD 2>/dev/null | grep -E '^[^/]+/skills/[^/]+/SKILL\.md$' || true) + +if [ -z "$CHANGED" ]; then + echo "No skills changed, skipping skill design validation" + exit 0 +fi + +echo "Validating changed skills:" +echo "$CHANGED" | sed 's/^/ - /' + +# Run validator on changed skills only +uv run python scripts/validate_skill_design.py $(echo "$CHANGED" | tr '\n' ' ') From 8349f7a6a44f8d343dcff6255fbd65c2a77eae3c Mon Sep 17 00:00:00 2001 From: Daniele Martinoli Date: Thu, 26 Feb 2026 12:25:42 +0100 Subject: [PATCH 03/10] reverted test change Signed-off-by: Daniele Martinoli --- rh-sre/skills/cve-impact/SKILL.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/rh-sre/skills/cve-impact/SKILL.md b/rh-sre/skills/cve-impact/SKILL.md index 8301d69e..a400f2eb 100644 --- a/rh-sre/skills/cve-impact/SKILL.md +++ b/rh-sre/skills/cve-impact/SKILL.md @@ -20,8 +20,6 @@ description: | This skill orchestrates MCP tools (get_cves, get_cve, get_cve_systems) to provide comprehensive CVE analysis with Red Hat Lightspeed context. When users ask for remediation after seeing the analysis, invoke the `sre-agents:remediator` agent. **IMPORTANT**: ALWAYS use this skill instead of calling get_cves or other vulnerability MCP tools directly. - - a change --- # CVE Impact Analysis Skill From 1df12f8688160246c548fd533a2e65452ec65195 Mon Sep 17 00:00:00 2001 From: Daniele Martinoli Date: Thu, 26 Feb 2026 13:54:43 +0100 Subject: [PATCH 04/10] Enhance skill documentation and validation - Updated CLAUDE.md to clarify YAML frontmatter requirements, including optional metadata fields. - Revised README.md by removing legacy model field skip instructions. - Expanded SKILL_DESIGN_PRINCIPLES.md with details on root-level frontmatter and standard color values. - Modified compliance-checker SKILL.md to improve clarity in usage instructions and workflow. - Updated copilot-instructions.md to reflect new frontmatter requirements. - Adjusted SKILL_TEMPLATE.md to include new metadata fields and color options. - Enhanced validate_skill_design.py to enforce new frontmatter rules and remove legacy model handling. Signed-off-by: Daniele Martinoli --- .claude/skills/compliance-checker/SKILL.md | 6 +-- .github/copilot-instructions.md | 4 +- CLAUDE.md | 2 +- README.md | 2 - SKILL_DESIGN_PRINCIPLES.md | 38 +++++++++++++++-- rh-virt/SKILL_TEMPLATE.md | 6 ++- scripts/validate_skill_design.py | 48 ++++++++++++---------- 7 files changed, 73 insertions(+), 33 deletions(-) diff --git a/.claude/skills/compliance-checker/SKILL.md b/.claude/skills/compliance-checker/SKILL.md index f944caa2..092df38c 100644 --- a/.claude/skills/compliance-checker/SKILL.md +++ b/.claude/skills/compliance-checker/SKILL.md @@ -13,14 +13,14 @@ description: | Run the programmatic compliance check for skills in this agentic-collections repository. -## When to Use +## When to Use This Skill Invoke this skill when the user wants to: - Validate skills against [SKILL_DESIGN_PRINCIPLES.md](SKILL_DESIGN_PRINCIPLES.md) - Check compliance before committing or opening a PR - Verify design principle adherence (DP1–DP7) -## Instructions +## Workflow 1. **Run from project root** (the workspace root). @@ -55,4 +55,4 @@ Invoke this skill when the user wants to: - DP4: Dependencies declaration - DP5: Human-in-the-loop for critical skills - DP6: Mandatory sections (Prerequisites, When to Use, Workflow) -- DP7: Credential security (no `echo $VAR`) +- DP7: Credential security (avoid echoing env vars) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 09c11f73..21671bc7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -53,7 +53,7 @@ Packs known to the validator are listed in `scripts/validate_structure.py` (`PAC ### Skills — `skills//SKILL.md` -Must start with valid YAML frontmatter containing at minimum `name` and `description`: +Must start with valid YAML frontmatter. Root-level fields: `name`, `description`, `model`, `color`. Use `metadata` for custom fields (author, priority). See SKILL_DESIGN_PRINCIPLES.md for the 2026 Agentic Skills structure. ```yaml --- @@ -63,6 +63,8 @@ description: | Keep under 500 tokens. model: inherit color: blue +metadata: + author: "team-name" --- ``` diff --git a/CLAUDE.md b/CLAUDE.md index 0e14d12a..b6056eb9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -152,7 +152,7 @@ last_updated: YYYY-MM-DD ### Adding a Skill 1. Create `skills//SKILL.md` -2. Define YAML frontmatter (name, description, model, color) +2. Define YAML frontmatter with root-level fields (name, description, model, color) and optional `metadata` for custom fields (author, priority, etc.). See [SKILL_DESIGN_PRINCIPLES.md](SKILL_DESIGN_PRINCIPLES.md) for the 2026 Agentic Skills structure. 3. Document workflow with MCP tool references 4. Include concrete examples 5. Test with `Skill` tool invocation diff --git a/README.md b/README.md index d9690b42..3aad5c8c 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,6 @@ make validate-skill-design PACK=rh-sre # Treat warnings as errors uv run python scripts/validate_skill_design.py --warnings-as-errors -# Skip missing 'model' field (for legacy skills) -uv run python scripts/validate_skill_design.py --skip-missing-model ``` ## Security diff --git a/SKILL_DESIGN_PRINCIPLES.md b/SKILL_DESIGN_PRINCIPLES.md index fa847537..6b64f055 100644 --- a/SKILL_DESIGN_PRINCIPLES.md +++ b/SKILL_DESIGN_PRINCIPLES.md @@ -106,12 +106,33 @@ description: | - "List high-severity CVEs" NOT for remediation actions (use remediator agent instead). -model: inherit +model: inherit # Root: Runtime - host needs before skill runs +color: blue # Root: UX - IDE sidebar/terminal theme --- ``` **Rationale**: Minimizes token usage at agent initialization while maintaining clarity. +### Root-Level Frontmatter (2026 Agentic Skills Standard) + +Primary UI and runtime configuration fields belong at the **root level** of YAML frontmatter so the IDE or Agent Host can parse them without traversing nested blocks. + +| Field Type | Location | Examples | +|------------|----------|----------| +| Runtime | Root | `model`, `allowed-tools` | +| UX/UI | Root | `color`, `icon`, `version` | +| Custom | `metadata` | `author`, `priority`, `compliance` | + +**Standard Color Values** (Cursor, Claude Code): + +| Color | Use Case | +|-------|----------| +| blue, cyan | Analysis, read-only | +| green | Success, deployment | +| yellow | Caution, validation | +| red | Critical, security, remediation | +| magenta | Creative, generation | + ## 4. Dependencies Declaration Every skill MUST include a **Dependencies** section listing: @@ -188,7 +209,11 @@ name: skill-name description: | [Concise when-to-use with 3-5 examples] model: inherit|sonnet|haiku -color: red|blue|green|yellow +color: red|blue|green|yellow|cyan|magenta +version: 1.0.0 +metadata: + author: "team-name" + priority: "high" --- # [Skill Name] @@ -402,7 +427,11 @@ name: skill-name description: | [Concise when-to-use with 3-5 examples - under 500 tokens] model: inherit|sonnet|haiku -color: red|blue|green|yellow +color: red|blue|green|yellow|cyan|magenta +version: 1.0.0 +metadata: + author: "team-name" + priority: "high" --- # [Skill Name] @@ -451,6 +480,9 @@ description: | [Concise with 3-5 examples - under 500 tokens] model: inherit color: red +version: 1.0.0 +metadata: + author: "team-name" tools: ["All"] --- diff --git a/rh-virt/SKILL_TEMPLATE.md b/rh-virt/SKILL_TEMPLATE.md index 36458331..565a7c40 100644 --- a/rh-virt/SKILL_TEMPLATE.md +++ b/rh-virt/SKILL_TEMPLATE.md @@ -88,8 +88,11 @@ description: | # REQUIRED: Must be under 500 tokens total model: inherit # REQUIRED: Always "inherit" unless special case # Only use "sonnet" or "haiku" if skill needs specific model -color: # REQUIRED: red|yellow|blue|green|cyan - see Color Guide below +color: # REQUIRED: red|yellow|blue|green|cyan|magenta - see Color Guide below # Indicates operation risk level for user safety +metadata: # Optional: Custom fields (2026 Agentic Skills standard) + author: "team-name" + priority: "high" --- # / Skill @@ -550,6 +553,7 @@ Use the following color codes based on operation characteristics: | **blue** | Reversible state changes | vm-lifecycle-manager, vm-clone | | **yellow** | Destructive but recoverable operations | vm-snapshot-delete | | **red** | Irreversible/critical operations (data loss risk) | vm-delete, vm-snapshot-restore | +| **magenta** | Creative, generation tasks | content generation, templating | --- diff --git a/scripts/validate_skill_design.py b/scripts/validate_skill_design.py index 5db50923..d75140e2 100644 --- a/scripts/validate_skill_design.py +++ b/scripts/validate_skill_design.py @@ -39,8 +39,13 @@ "remediator", ] -# Required section order (DP6) +# Required sections (DP6) - Prerequisites is optional REQUIRED_SECTIONS = [ + "When to Use This Skill", + "Workflow", +] +# Expected order when all present +ORDERED_SECTIONS = [ "Prerequisites", "When to Use This Skill", "Workflow", @@ -236,7 +241,8 @@ def check_dp5_human_in_loop( def check_dp6_mandatory_sections(body: str, result: ValidationResult) -> None: """ DP6: Mandatory Skill Sections. - Must have Prerequisites, When to Use This Skill, Workflow in order. + Must have When to Use This Skill, Workflow. Prerequisites is optional. + When present, sections must appear in order: Prerequisites, When to Use, Workflow. """ section_headings = re.findall(r"^## ([^\n#]+)", body, re.MULTILINE) @@ -245,12 +251,12 @@ def check_dp6_mandatory_sections(body: str, result: ValidationResult) -> None: result.errors.append(f"DP6: Missing required section '## {required}'") return - # Check order + # Check order for sections that are present (Prerequisites, When to Use, Workflow) indices = [] for i, heading in enumerate(section_headings): - for req in REQUIRED_SECTIONS: + for req in ORDERED_SECTIONS: if req in heading or heading.strip() == req: - indices.append((REQUIRED_SECTIONS.index(req), i)) + indices.append((ORDERED_SECTIONS.index(req), i)) break if len(indices) >= 2: @@ -258,8 +264,8 @@ def check_dp6_mandatory_sections(body: str, result: ValidationResult) -> None: for i in range(1, len(indices)): if indices[i][1] < indices[i - 1][1]: result.warnings.append( - f"DP6: Required sections should appear in order: " - f"{', '.join(REQUIRED_SECTIONS)}" + f"DP6: Sections should appear in order: " + f"{', '.join(ORDERED_SECTIONS)}" ) break @@ -303,30 +309,33 @@ def check_dp7_credential_exposure(body: str, result: ValidationResult) -> None: def check_frontmatter_fields( frontmatter: dict | None, result: ValidationResult, - *, - skip_model: bool = False, ) -> None: - """Check required frontmatter fields (name, description, model, color).""" + """Check required frontmatter fields (name, description). Model is optional.""" if not frontmatter: result.errors.append("Missing or invalid YAML frontmatter") return required = ["name", "description"] - if not skip_model: - required.append("model") for field_name in required: if field_name not in frontmatter: result.errors.append(f"Frontmatter missing required field: {field_name}") if "color" in frontmatter: - valid_colors = {"red", "blue", "green", "yellow", "cyan"} + valid_colors = {"red", "blue", "green", "yellow", "cyan", "magenta"} if frontmatter["color"].lower() not in valid_colors: result.warnings.append( - f"Frontmatter 'color' should be one of: {', '.join(valid_colors)}" + f"Frontmatter 'color' should be one of: {', '.join(sorted(valid_colors))}" ) + if "metadata" in frontmatter: + meta = frontmatter["metadata"] + if not isinstance(meta, dict): + result.warnings.append( + "Frontmatter 'metadata' should be a key-value map (dict), not a string or list" + ) -def validate_skill(skill_path: Path, *, skip_model: bool = False) -> ValidationResult: + +def validate_skill(skill_path: Path) -> ValidationResult: """Run all design principle checks on a skill file.""" result = ValidationResult(path=skill_path) @@ -338,7 +347,7 @@ def validate_skill(skill_path: Path, *, skip_model: bool = False) -> ValidationR frontmatter, body = extract_frontmatter(content) - check_frontmatter_fields(frontmatter, result, skip_model=skip_model) + check_frontmatter_fields(frontmatter, result) check_dp1_document_consultation(body, result) check_dp2_parameter_order(body, result) check_dp3_conciseness(frontmatter, result) @@ -368,11 +377,6 @@ def main() -> int: action="store_true", help="Treat warnings as errors", ) - parser.add_argument( - "--skip-missing-model", - action="store_true", - help="Do not fail on missing 'model' frontmatter (for packs with legacy skills)", - ) args = parser.parse_args() # Resolve paths to skill files @@ -402,7 +406,7 @@ def main() -> int: all_warnings: list[tuple[Path, str]] = [] for skill_path in sorted(skill_files): - result = validate_skill(skill_path, skip_model=args.skip_missing_model) + result = validate_skill(skill_path) rel_path = skill_path.relative_to(Path.cwd()) if skill_path.is_relative_to(Path.cwd()) else skill_path if result.errors: From 738c978b842cc25117f39808c31563d65fc9cbcc Mon Sep 17 00:00:00 2001 From: Daniele Martinoli <86618610+dmartinol@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:04:00 +0100 Subject: [PATCH 05/10] Update scripts/ci-validate-changed-skills.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/ci-validate-changed-skills.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci-validate-changed-skills.sh b/scripts/ci-validate-changed-skills.sh index 4b3e3f7c..2eb07278 100755 --- a/scripts/ci-validate-changed-skills.sh +++ b/scripts/ci-validate-changed-skills.sh @@ -27,7 +27,7 @@ else DIFF_CMD="git diff --name-only HEAD" fi -CHANGED=$($DIFF_CMD 2>/dev/null | grep -E '^[^/]+/skills/[^/]+/SKILL\.md$' || true) +CHANGED=$($DIFF_CMD 2>/dev/null | grep -E '^([^/]+/skills/[^/]+/SKILL\.md|\.claude/skills/[^/]+/SKILL\.md)$' || true) if [ -z "$CHANGED" ]; then echo "No skills changed, skipping skill design validation" From 52a0161fe0361515b253be59aa70344d644b13f9 Mon Sep 17 00:00:00 2001 From: Daniele Martinoli Date: Thu, 26 Feb 2026 14:10:11 +0100 Subject: [PATCH 06/10] using pyproject.toml Signed-off-by: Daniele Martinoli --- Makefile | 4 +- pyproject.toml | 9 ++ scripts/requirements.txt | 2 - uv.lock | 240 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 pyproject.toml delete mode 100644 scripts/requirements.txt create mode 100644 uv.lock diff --git a/Makefile b/Makefile index 6cf28961..e060f031 100644 --- a/Makefile +++ b/Makefile @@ -31,9 +31,7 @@ check-uv: install: check-uv @echo "Installing Python dependencies with uv..." - @uv venv - @echo "Installing Python dependencies with uv..." - @uv pip install -q -r scripts/requirements.txt + @uv sync @echo "Dependencies installed in isolated environment!" validate: check-uv diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..06bb0f89 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "agentic-collections" +version = "0.1.0" +description = "Agentic collections for Red Hat platforms (skills, agents, MCP)" +requires-python = ">=3.11" +dependencies = [ + "PyYAML>=6.0", + "jsonschema>=4.0", +] diff --git a/scripts/requirements.txt b/scripts/requirements.txt deleted file mode 100644 index 9aeb7ba4..00000000 --- a/scripts/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -PyYAML>=6.0 -jsonschema>=4.0 diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..9e071254 --- /dev/null +++ b/uv.lock @@ -0,0 +1,240 @@ +version = 1 +revision = 2 +requires-python = ">=3.11" + +[[package]] +name = "agentic-collections" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "jsonschema" }, + { name = "pyyaml" }, +] + +[package.metadata] +requires-dist = [ + { name = "jsonschema", specifier = ">=4.0" }, + { name = "pyyaml", specifier = ">=6.0" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] From 77582dc4f9472f7676ce9cbe6d95cef82d1084fe Mon Sep 17 00:00:00 2001 From: Daniele Martinoli <86618610+dmartinol@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:11:56 +0100 Subject: [PATCH 07/10] Update SKILL_DESIGN_PRINCIPLES.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- SKILL_DESIGN_PRINCIPLES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SKILL_DESIGN_PRINCIPLES.md b/SKILL_DESIGN_PRINCIPLES.md index 6b64f055..ea6591ae 100644 --- a/SKILL_DESIGN_PRINCIPLES.md +++ b/SKILL_DESIGN_PRINCIPLES.md @@ -1,6 +1,6 @@ # Design Principles for Skills and Agents -This document defines the design principles for creating skills and agents in agentic collections. It is referenced from [CLAUDE.md](CLAUDE.md#design-principles-for-skills-and-agents). Validate compliance with `make validate-skill-design` (see [README.md](README.md#skill-design-validation)). +This document defines the design principles for creating skills and agents in agentic collections. It is referenced from [CLAUDE.md](CLAUDE.md). Validate compliance with `make validate-skill-design` (see [README.md](README.md#skill-design-validation)). ## 1. Document Consultation Transparency From b357cbc6d5789323d3a45a2ab2d0c43e4f2d38da Mon Sep 17 00:00:00 2001 From: Daniele Martinoli <86618610+dmartinol@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:12:33 +0100 Subject: [PATCH 08/10] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3aad5c8c..add2d02a 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ For more details, see [docs/README.md](docs/README.md). ### Skill Design Validation -The `validate-skill-design` and `validate-skill-design-changed` targets check skills against the [Design Principles](SKILL_DESIGN_PRINCIPLES.md) referenced from CLAUDE.md. **CI runs this validation automatically** on pull requests and pushes to main, but only for changed skills. For local development, use `validate-skill-design-changed` to validate only your modified skills (staged + unstaged). Use `validate-skill-design` for full validation or a specific pack. Ensure compliance with: +Use the `validate-skill-design` and `validate-skill-design-changed` targets to check skills against the [Design Principles](SKILL_DESIGN_PRINCIPLES.md) referenced from CLAUDE.md. **CI runs this validation automatically** on pull requests and pushes to main, but only for changed skills. For local development, run `validate-skill-design-changed` to validate only your modified skills (staged + unstaged). To perform full validation or validate a specific pack, run `validate-skill-design`. Ensure compliance with: - Document consultation transparency (DP1) - Parameter specification and ordering (DP2) From abda313396d4127353fef89a10060418d5578b6c Mon Sep 17 00:00:00 2001 From: Daniele Martinoli <86618610+dmartinol@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:14:23 +0100 Subject: [PATCH 09/10] Update SKILL_DESIGN_PRINCIPLES.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- SKILL_DESIGN_PRINCIPLES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SKILL_DESIGN_PRINCIPLES.md b/SKILL_DESIGN_PRINCIPLES.md index ea6591ae..eeabd683 100644 --- a/SKILL_DESIGN_PRINCIPLES.md +++ b/SKILL_DESIGN_PRINCIPLES.md @@ -106,8 +106,8 @@ description: | - "List high-severity CVEs" NOT for remediation actions (use remediator agent instead). -model: inherit # Root: Runtime - host needs before skill runs -color: blue # Root: UX - IDE sidebar/terminal theme + model: inherit # Root: Runtime configuration required before skill execution + color: blue # Root: UX - IDE sidebar/terminal theme --- ``` From 52da58057608254d88d3dbd97fce804582fc95be Mon Sep 17 00:00:00 2001 From: Daniele Martinoli <86618610+dmartinol@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:14:39 +0100 Subject: [PATCH 10/10] Update rh-virt/SKILL_TEMPLATE.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- rh-virt/SKILL_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rh-virt/SKILL_TEMPLATE.md b/rh-virt/SKILL_TEMPLATE.md index 565a7c40..eb29425a 100644 --- a/rh-virt/SKILL_TEMPLATE.md +++ b/rh-virt/SKILL_TEMPLATE.md @@ -553,7 +553,7 @@ Use the following color codes based on operation characteristics: | **blue** | Reversible state changes | vm-lifecycle-manager, vm-clone | | **yellow** | Destructive but recoverable operations | vm-snapshot-delete | | **red** | Irreversible/critical operations (data loss risk) | vm-delete, vm-snapshot-restore | -| **magenta** | Creative, generation tasks | content generation, templating | +| **magenta** | Creative, generation | content generation, templating | ---