Skip to content
AI CraftsmanSUPERPOWERS

Creating Packs

Complete guide to creating community packs: pack.yml manifest, validators, agents, knowledge, and publishing.

Last updated: Edit on GitHub

What is a Pack?

A pack is a modular extension that adds language-specific or framework-specific capabilities to AI Craftsman Superpowers. Packs can include:

  • Rules: Validation patterns for your language/framework
  • Agents: Specialized agents with domain expertise
  • Commands: Custom workflow commands
  • Canonical examples: Iron Law templates for code generation
  • Knowledge: Guides and architectural patterns

Community packs are available for Go, Rust, Python, and more.

Pack Structure

packs/my-pack/
├── pack.yml                    # Pack manifest (required)
├── agents/                     # Pack-specific agents
│   └── my-agent.md
├── commands/                   # Pack-specific commands
│   └── my-command.md
├── knowledge/
│   ├── canonical/              # Iron Law template examples
│   │   └── my-example.ext
│   └── guides/                 # Knowledge documents
│       └── patterns.md
├── hooks/                      # Validation scripts
│   └── validators/
│       └── validate-my-rules.sh
├── templates/                  # Code scaffolding templates
│   └── entity.tpl
├── static-analysis/            # Analysis rule configs
│   └── ruleset.xml
└── tests/                      # Pack validation tests
    └── validate-pack.test.sh

pack.yml Manifest

The pack manifest is the entry point. All fields are required unless marked optional.

name: "go-craftsman"           # Unique pack identifier (kebab-case)
version: "1.0.0"               # Semantic version
description: "Go language pack with idiomatic patterns and Clean Architecture"
author: "your-github-handle"
license: "Apache-2.0"

# Minimum plugin version required
requires_plugin: "2.4.0"

# Language/framework support
languages:
  - go

# Agents this pack provides
agents:
  - name: "go-craftsman"
    file: "agents/go-craftsman.md"
    description: "Senior Go expert: idiomatic patterns, Clean Architecture"

# Commands this pack provides
commands:
  - name: "scaffold-handler"
    file: "commands/scaffold-handler.md"
    description: "Scaffold HTTP handler with validation and error handling"

# Rules this pack adds (loaded into rules engine)
rules:
  go:
    error_wrapping: true         # Always wrap errors with context
    no_panic: "warn"             # Avoid panic in non-main packages
    interface_segregation: true  # Small, focused interfaces

# Canonical examples for Iron Law pattern
canonical_examples:
  - name: "go-entity"
    file: "knowledge/canonical/go-entity.go"
    description: "Domain entity with value objects"
  - name: "go-repository"
    file: "knowledge/canonical/go-repository.go"
    description: "Repository interface + in-memory implementation"

# Knowledge documents loaded into agent context
knowledge:
  - file: "knowledge/guides/go-patterns.md"
    tags: ["patterns", "idioms", "clean-architecture"]

# Hooks to register
hooks:
  pre_write:
    - file: "hooks/validators/validate-go.sh"
      languages: ["go"]
  post_write:
    - file: "hooks/validators/static-analysis.sh"
      languages: ["go"]

Writing Validators

Pack validators are shell scripts that receive the file path and content via stdin:

#!/usr/bin/env bash
# hooks/validators/validate-go.sh
# Called by hooks with: validate-go.sh <file_path>

set -euo pipefail

FILE="$1"

# Load rules engine
source "$(dirname "$0")/../../lib/rules-engine.sh"
rules_init

# Check error wrapping rule
SEVERITY=$(rules_severity_for_file "go.error_wrapping" "$FILE")

if [[ "$SEVERITY" == "block" || "$SEVERITY" == "warn" ]]; then
  # Check for unhandled errors (simplified)
  if grep -qP 'err\s*:?=.*\n\s*if err' "$FILE" 2>/dev/null; then
    if [[ "$SEVERITY" == "block" ]]; then
      echo "✗ GO001: Always wrap errors with context (fmt.Errorf(\"...: %w\", err))"
      exit 2  # Block
    else
      echo "⚠ GO001: Consider wrapping error with context"
      exit 0  # Warn but allow
    fi
  fi
fi

echo "✓ Go validation passed"
exit 0

Exit codes

Use exit 2 to block the write (Level 1 violation). Use exit 0 to pass (or warn). The rules engine interprets exit codes: 0 = pass, 1 = warn, 2 = block.

Writing Agents

Pack agents follow the same structure as core agents:

---
name: go-craftsman
description: Senior Go expert: idiomatic patterns, Clean Architecture, error handling
model: claude-sonnet-4-6
effort: medium
memory: project
maxTurns: 20
allowedTools:
  - Read
  - Glob
  - Grep
  - Bash
  - Write
  - Edit
---

# Go Craftsman Agent

You are a Senior Go Software Craftsman with 10+ years of experience in idiomatic Go, Clean Architecture, and production systems.

## Your Standards

- **Error handling**: Always wrap errors with context (`fmt.Errorf("operation: %w", err)`)
- **Interfaces**: Define interfaces at the point of use (consumer-side)
- **Packages**: One responsibility per package, avoid circular dependencies
- **Testing**: Table-driven tests, testify for assertions, httptest for HTTP handlers

## When Reviewing Code

Check for:
1. Error handling completeness (no ignored errors)
2. Interface segregation (small, focused interfaces)
3. Context propagation (ctx as first param)
4. Goroutine lifecycle management
5. Proper use of defer for cleanup

Writing Canonical Examples

Canonical examples are the templates Claude loads before generating code (Iron Law pattern). They must represent production-quality, idiomatic code:

// knowledge/canonical/go-entity.go
// CANONICAL: Go Domain Entity with Value Objects
// This is the reference implementation. Load this before generating any Go entity.

package domain

import (
    "errors"
    "time"
)

// UserID is a branded type for user identifiers
type UserID string

func NewUserID(id string) (UserID, error) {
    if id == "" {
        return "", errors.New("user ID cannot be empty")
    }
    return UserID(id), nil
}

// Email is a value object with validation
type Email struct {
    value string
}

func NewEmail(email string) (Email, error) {
    // validation logic
    return Email{value: email}, nil
}

func (e Email) String() string { return e.value }

// User is a domain entity: immutable after creation
type User struct {
    id        UserID
    email     Email
    createdAt time.Time
}

// Create is the factory function (no public constructor)
func Create(id UserID, email Email) (*User, error) {
    return &User{
        id:        id,
        email:     email,
        createdAt: time.Now(),
    }, nil
}

// Behavioral methods: no setters
func (u *User) ChangeEmail(email Email) {
    u.email = email
}

Testing Your Pack

Create a test script to validate your pack:

#!/usr/bin/env bash
# tests/validate-pack.test.sh

set -euo pipefail

PACK_DIR="$(dirname "$0")/.."
PASS=0
FAIL=0

run_test() {
  local name="$1"
  local expected="$2"
  local actual="$3"

  if [[ "$actual" == "$expected" ]]; then
    echo "  ✓ $name"
    ((PASS++))
  else
    echo "  ✗ $name (expected: $expected, got: $actual)"
    ((FAIL++))
  fi
}

echo "Testing go-craftsman pack..."

# Test pack.yml is valid YAML
python3 -c "import yaml; yaml.safe_load(open('$PACK_DIR/pack.yml'))" 2>/dev/null
run_test "pack.yml is valid YAML" "0" "$?"

# Test validator script exists and is executable
test -x "$PACK_DIR/hooks/validators/validate-go.sh"
run_test "validator is executable" "0" "$?"

# Test canonical example exists
test -f "$PACK_DIR/knowledge/canonical/go-entity.go"
run_test "canonical example exists" "0" "$?"

echo ""
echo "Results: $PASS passed, $FAIL failed"
[[ $FAIL -eq 0 ]] && exit 0 || exit 1

Run validation with:

claude plugin validate ./packs/my-pack

Publishing Your Pack

  1. Validate: Run claude plugin validate ./packs/my-pack and fix all issues
  2. Test: Run the pack test suite: bash tests/validate-pack.test.sh
  3. Fork: Fork the ai-craftsman-superpowers repository
  4. Add: Place your pack in packs/my-pack/
  5. PR: Open a pull request with a description of what your pack adds

Pack skeletons available

Run /craftsman:scaffold pack to generate a pack skeleton with all required files pre-populated. The scaffold uses the Go pack skeleton as a reference template.

Using External Packs

Point to a local pack path in your .craft-config.yml:

packs:
  core: true
  external:
    - path: "~/my-custom-pack"             # Local path
    - path: "/usr/local/share/go-pack"     # System path

External packs are loaded by the pack-loader.sh script at session start.