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, with their wording and default severity
  • Validators: Regex scripts the gate runs on every file of the pack’s languages
  • Static analysis: Level 2 adapters for the tools of your ecosystem
  • Agents: Specialized agents with domain expertise
  • Canonical examples: Iron Law templates for code generation
  • Knowledge: Guides and architectural patterns

Seven packs ship with the plugin: Symfony, React, Python, Go, Rust, Bash, and AI/ML. Go and Rust are the two minimal complete packs: a manifest, validators, a structure scanner, a Level 2 adapter, a canonical example and a test file. Neither ships agents or templates, which keeps them readable as a starting point. Symfony is the most complete, with agents, templates and static analysis wired in.

Quick Start

/craftsman:scaffold pack my-pack-name

This generates the pack directory structure. Edit pack.yml to configure it.

Pack Structure

packs/<name>/
├── pack.yml                    # Pack manifest (required)
├── hooks/
│   └── <name>-validator.sh     # Regex validator, defines pack_validate_<lang>()
├── static-analysis/
│   └── <name>-analysis.sh      # Level 2 tool integration
├── commands/
│   └── scaffold-types/         # Scaffold templates for /craftsman:scaffold
├── agents/
│   └── <name>-craftsman.md     # Pack-specific agents
├── knowledge/
│   └── canonical/              # Canonical code examples (Iron Law)
└── templates/                  # Code generation templates

pack.yml Manifest

The essentials of the shipped Go pack, packs/go/pack.yml:

name: go
version: "1.0.0"
description: "Go craftsman pack - error handling, context propagation, layer boundaries, structure"
compatibility:
  core: ">=4.9.0"
  stack: ["*"]                      # a language pack is universal: dispatch is by extension

languages:                          # REQUIRED for a language pack
  - id: go
    extensions: [go]                # no leading dot
    entry_markers: [go.mod, go.sum] # files that say "this project is Go"
    protected_configs: [".golangci.yml", ".golangci.yaml"]
    validators: ["hooks/go-validator.sh", "hooks/layer-validator.sh"]
    static_analysis: ["static-analysis/go-analysis.sh"]
    supersedes:
      - errcheck=GO004,GO006        # errcheck's verdict outranks these two regex rules
    test_commands: ["go test ./...", "gotestsum"]
    lsp: "gopls"

rules:
  owned:                            # id, wording and default severity, declared once
    - id: GO001
      group: Go
      text: "no panic outside main and outside a Must-prefixed constructor - return an error"
      default_severity: block
  builtin: ["GO001", "GO002", "GO003", "GO004", "GO005", "GO006", "WARN-GO001",
            "LAYER001", "NEST001", "LOC001", "PARAM001"]
  static_analysis: ["ERRCHECK001"]

hooks:
  validators: ["hooks/go-validator.sh", "hooks/layer-validator.sh"]

static_analysis:
  tools: ["static-analysis/go-analysis.sh"]

agents: []
knowledge: ["knowledge/"]

The languages block is not optional

The engine holds no list of languages. It compiles every loaded pack.yml into the registry the hooks and craftsman-ci.sh both read. A pack without a languages: entry declaring its extensions is loaded, sourced, and never called: that is the most common reason a new pack appears to do nothing.

The other capabilities a language entry may declare:

Key Meaning
empty_run_markers Lines (POSIX ERE) a test runner prints when it tested nothing. From 4.14.0, a passing run that printed one grants no verification evidence: an npm script that only echoes, a suite with every test skipped, PHPUnit with zero assertions. Shipped by the Python, React and Symfony packs
metrics_dialect c-like or php-like only. It gives NEST001, LOC001, GOD001 and PARAM001 from the shared extractor; a language it cannot read declares none and emits those rules itself, as Go, Rust and Python do
supersedes <tool>=<RULE>,<RULE>: a Level 2 or 3 analyser whose verdict outranks the Level 1 rule. A claimed rule is deferred, never dropped: if the tool times out, crashes or skips the rule, the regex finding is reported

Writing a Validator

A validator is a bash script that defines pack_validate_<lang>(). The pack loader sources it and calls the function for each file of the matching language:

#!/usr/bin/env bash
pack_validate_mylang() {
    local file="$1"

    # MYPACK001: Description
    if grep -q "bad_pattern" "$file" 2>/dev/null; then
        add_violation "MYPACK001" "Descriptive error message"
    fi

    # WARN-MYPACK001: Soft rule
    if grep -q "questionable_pattern" "$file" 2>/dev/null; then
        add_warning "WARN-MYPACK001" "Consider improving this pattern"
    fi
}
Helper Purpose
add_violation "RULE_ID" "message" Report a finding; its severity is resolved by the rules engine, per file
add_warning "RULE_ID" "message" Report an advisory finding
line_has_ignore "$line" "RULE_ID" Check a line for craftsman-ignore: RULE_ID

Severity is the engine's decision

A validator never decides whether a finding blocks: the default lives in rules.owned (default_severity), and the user’s .craft-config.yml and .craft-rules.yml override it. A hook script exits 0 (pass) or 2 (block), never 1. Keep validators regex-only; tools such as eslint, phpstan or clippy belong in static-analysis/.

Use a rule ID prefix of your own (GO, RUST, PY, SH, PHP, TS are taken), and the WARN- prefix for advisory rules.

Writing Agents

Pack agents follow the same structure as the core agents: Markdown with YAML frontmatter that states the model and the tool grant.

---
name: my-craftsman
description: Senior expert for my language: idioms, Clean Architecture, error handling
model: sonnet
effort: medium
memory: project
maxTurns: 30
tools:
  - Read
  - Glob
  - Grep
  - Bash
---

# My Pack Craftsman

You are a senior craftsman for this language...

Grant Write and Edit only to an agent whose job is to change code.

Testing Your Pack

Give the pack a test suite: source your validator, provide mock add_violation, add_warning and line_has_ignore, and assert one refused and one accepted fixture per rule. The shipped suites live in the plugin’s tests/packs/ (test-go.sh and test-rust.sh are the reference), which is where a contributed pack’s suite goes too.

Validate the pack against the conventions, and check its rule IDs for collisions:

bash <plugin-root>/scripts/validate-pack.sh packs/my-pack/
bash <plugin-root>/scripts/validate-pack.sh packs/my-pack/ --check-collisions <plugin-root>/packs/

Publishing Your Pack

  1. Validate: run scripts/validate-pack.sh and fix every issue
  2. Test: run the pack test suite
  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

Using External Packs

Point to a local pack in your global ~/.claude/.craft-config.yml:

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

A pack is sourced as code, so only the machine owner can declare one: a project .craft-config.yml that lists external packs is ignored. External packs are loaded by pack-loader.sh at session start.