Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .url-check-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# URL patterns to skip during liveness checks.
# Each line is a substring match — if the URL contains this string, it's skipped.

# Localhost / example / placeholder URLs
https://localhost
https://127.0.0.1
https://example.com
https://your-
https://my-
https://myapp.

# CloudFormation / SAM template variables in URLs
${AWS::

# API endpoints (not browsable)
https://maps.geo.
https://places.geo.
https://routes.geo.
https://geofencing.geo.
https://tracking.geo.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ To send us a pull request, please:
5. Send us a pull request, answering any default questions in the pull request interface.
6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.

GitHub provides additional documentation on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
GitHub provides additional documentation on [forking a repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) and [creating a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).

## Finding Contributions to Work On

Expand Down
2 changes: 1 addition & 1 deletion docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ GitHub Actions workflows can occasionally fail due to intermittent issues (netwo
1. Open the failed check from your pull request's **Checks** tab.
2. Click **Re-run failed jobs**.

See [Re-running workflows and jobs](https://docs.github.com/en/actions/managing-workflow-runs/re-running-workflows-and-jobs) for details.
See [Re-running workflows and jobs](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/re-run-workflows-and-jobs) for details.

#### Via the `gh` CLI

Expand Down
35 changes: 34 additions & 1 deletion mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
min_version = "2026.2.4"

[tools]
uv = "0.10"
node = "24"
"npm:markdownlint-cli2" = "0.17"
"npm:ajv-cli" = "5"
Expand Down Expand Up @@ -65,6 +66,37 @@ run = [
{ task = "lint:cross-refs"}
]

# =============
# VALIDATION
# =============

[tasks."validate:refs"]
description = "Detect broken links and orphaned reference files"
run = "uv run tools/validate-references.py"

[tasks."validate:size"]
description = "Check SKILL.md sizes and flag extraction candidates"
run = "uv run tools/validate-size.py"

[tasks."validate:urls"]
description = "Check HTTPS URLs in markdown files return 200 (network-dependent, not in build)"
run = "uv run tools/validate-urls.py"

[tasks.validate]
description = "Run all validation checks"
run = [
{ task = "validate:refs" },
{ task = "validate:size" }
]

# =============
# SCAFFOLDING
# =============

[tasks."init:skill"]
description = "Scaffold a new skill directory"
run = "uv run tools/init-skill.py"

# =========
# SECURITY
# =========
Expand Down Expand Up @@ -114,9 +146,10 @@ run = [
# ===============

[tasks.build]
description = "Complete build: lint, format, security scans"
description = "Complete build: lint, format, validate, security scans"
run = [
{ task = "lint" },
{ task = "fmt:check" },
{ task = "validate" },
{ task = "security"}
]
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ Integrates with the [AWS MCP Server](https://docs.aws.amazon.com/aws-mcp/latest/

- [Amazon Location Service Developer Guide](https://docs.aws.amazon.com/location/latest/developerguide/)
- [Amazon Location Service API Reference](https://docs.aws.amazon.com/location/latest/APIReference/)
- [Amazon Location Service Samples Repository](https://github.com/aws-geospatial/amazon-location-samples)
- [Amazon Location Service Samples](https://github.com/aws-geospatial)

## Reference Files

Expand Down
2 changes: 1 addition & 1 deletion plugins/aws-serverless/skills/aws-lambda/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,6 @@ When not specified, ALWAYS use CDK

- [AWS SAM Documentation](https://docs.aws.amazon.com/serverless-application-model/)
- [AWS Lambda Documentation](https://docs.aws.amazon.com/lambda/)
- [AWS Lambda Powertools](https://docs.powertools.aws.dev/lambda/)
- [AWS Lambda Powertools](https://docs.aws.amazon.com/powertools/)
- [AWS CDK Documentation](https://docs.aws.amazon.com/cdk/)
- [AWS Serverless MCP Server](https://github.com/awslabs/mcp/tree/main/src/aws-serverless-mcp-server)
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Globals:
OTEL_TRACES_SAMPLER: xray
```

Layer ARNs vary by runtime and architecture. Check the [ADOT Lambda layer documentation](https://aws-otel.github.io/docs/getting-started/lambda) for the correct ARN for your runtime (Python, Node.js, Java, .NET) and architecture (amd64, arm64).
Layer ARNs vary by runtime and architecture. Check the [ADOT Lambda layer documentation](https://aws-otel.github.io/docs/getting-started/lambda/) for the correct ARN for your runtime (Python, Node.js, Java, .NET) and architecture (amd64, arm64).

**SLO configuration** happens in the CloudWatch console or via API after deployment — define SLIs (latency percentile, error rate, availability) and set objectives with burn rate alerting.

Expand Down
10 changes: 9 additions & 1 deletion schemas/skill-frontmatter.schema.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$id": "https://awslabs.github.io/agent-plugins/schemas/skill-frontmatter.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"additionalProperties": false,
"description": "Schema for YAML frontmatter in SKILL.md files",
"properties": {
"agent": {
Expand Down Expand Up @@ -33,6 +33,14 @@
"description": "Prevent Claude from auto-loading",
"type": "boolean"
},
"license": {
"description": "SPDX license identifier",
"type": "string"
},
"metadata": {
"description": "Plugin-specific metadata (author, version, etc.)",
"type": "object"
},
"model": {
"description": "Model to use when skill is active",
"type": "string"
Expand Down
129 changes: 129 additions & 0 deletions tools/init-skill.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///
"""Scaffold a new skill directory with correct SKILL.md template."""

import re
import sys
from pathlib import Path
from string import Template

ROOT = Path(__file__).resolve().parent.parent
PLUGINS_DIR = ROOT / "plugins"
NAME_RE = re.compile(r"^[a-z][a-z0-9-]*$")

SKILL_TEMPLATE = Template("""\
---
name: $name
description: >
$description
---

# $title

## When to Use

[FILL: Describe the specific scenarios and contexts where this skill applies.]

## Core Concepts

[FILL: Key concepts, terminology, and mental models.]

## Quick Reference

[FILL: The most commonly needed patterns, commands, or code snippets.]

## Anti-Patterns

[FILL: Common mistakes and what to do instead.]

## References

- `references/` — [FILL: Describe what detailed reference material is available.]
""")


def main() -> int:
if len(sys.argv) < 4:
print(f"Usage: uv run {sys.argv[0]} <plugin-name> <skill-name> <description>")
print(
f"Example: uv run {sys.argv[0]} deploy-on-aws my-skill"
" 'Brief description. Use when the user asks to...'"
)
return 1

plugin_name = sys.argv[1]
skill_name = sys.argv[2]
description = sys.argv[3]

# Validate plugin exists and is under PLUGINS_DIR (no path traversal)
plugin_dir = (PLUGINS_DIR / plugin_name).resolve()
if not plugin_dir.is_relative_to(PLUGINS_DIR.resolve()):
print(f"Error: plugin name must not contain path traversal: '{plugin_name}'")
return 1
if not plugin_dir.is_dir():
print(f"Error: plugin '{plugin_name}' not found at {plugin_dir}")
available = sorted(
d.name for d in PLUGINS_DIR.iterdir() if d.is_dir() and not d.name.startswith(".")
)
if available:
print(f"Available plugins: {', '.join(available)}")
return 1

# Validate skill name format
if not NAME_RE.match(skill_name):
print(f"Error: '{skill_name}' is not a valid skill name")
print("Must be kebab-case (lowercase letters, digits, hyphens)")
return 1

if len(skill_name) > 64:
print(f"Error: skill name exceeds 64 characters (current: {len(skill_name)})")
return 1

if "--" in skill_name:
print(f"Error: '{skill_name}' contains consecutive hyphens")
return 1

# Check reserved words
if re.search(r"\b(anthropic|claude)\b", skill_name, re.IGNORECASE):
print(f"Error: '{skill_name}' contains reserved word")
return 1

# Check skill doesn't already exist
skills_dir = plugin_dir / "skills"
skill_dir = skills_dir / skill_name
if skill_dir.exists():
print(f"Error: skill '{skill_name}' already exists at {skill_dir}")
return 1

# Warn about trigger phrase
if "Use when" not in description and "Use this" not in description:
print("Warning: description should contain 'Use when' or 'Use this' trigger phrase")

# Create structure
title = skill_name.replace("-", " ").title()
skill_dir.mkdir(parents=True)
(skill_dir / "SKILL.md").write_text(
SKILL_TEMPLATE.safe_substitute(name=skill_name, description=description, title=title),
encoding="utf-8",
)
refs_dir = skill_dir / "references"
refs_dir.mkdir()
(refs_dir / ".gitkeep").touch()

rel_path = skill_dir.relative_to(ROOT)
print(f"Created skill '{skill_name}' in plugin '{plugin_name}':")
print(f" {rel_path}/SKILL.md")
print(f" {rel_path}/references/.gitkeep")
print()
print("Next steps:")
print(f" 1. Edit {rel_path}/SKILL.md — fill in the [FILL] sections")
print(f" 2. Add reference files to {rel_path}/references/")
print(f" 3. Run: mise run validate")

return 0


if __name__ == "__main__":
sys.exit(main())
48 changes: 48 additions & 0 deletions tools/markdownlint-frontmatter.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,47 @@
* Required:
* - name: skill identifier (kebab-case)
* - description: when to use this skill (min 20 chars)
*
* Also enforces a property whitelist per skill-frontmatter.schema.json.
*/

"use strict";

const path = require("path");
const fs = require("fs");

// Allowed frontmatter properties — derived from skill-frontmatter.schema.json
// at runtime to avoid drift between the schema and this rule.
function loadAllowedProperties() {
const fallback = new Set([
"name",
"description",
"context",
"agent",
"model",
"allowed-tools",
"argument-hint",
"user-invocable",
"disable-model-invocation",
"license",
"metadata",
]);

try {
const schemaPath = path.resolve(__dirname, "..", "schemas", "skill-frontmatter.schema.json");
// nosemgrep: gitlab.eslint.detect-non-literal-fs-filename
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
if (schema && schema.properties && typeof schema.properties === "object") {
return new Set(Object.keys(schema.properties));
}
} catch {
// Fall back to hardcoded set if schema can't be read
}

return fallback;
}

const ALLOWED_PROPERTIES = loadAllowedProperties();

module.exports = {
names: ["skill-frontmatter", "SKILL002"],
Expand Down Expand Up @@ -146,5 +182,17 @@
});
}
}

// Check for non-spec frontmatter properties
const topLevelKeys = frontmatter.match(/^[a-z][\w-]*(?=:)/gm) || [];
for (const key of topLevelKeys) {
if (!ALLOWED_PROPERTIES.has(key)) {
onError({
lineNumber: 2,
detail: `Non-spec frontmatter property: "${key}" (allowed: ${[...ALLOWED_PROPERTIES].sort().join(", ")})`,
context: "Unknown property",
});
}
}
},
};
Loading
Loading