diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e69de29b..e4ef04a5 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,23 @@ +{ + "name": "arustydev", + "owner": { + "name": "aRustyDev", + "email": "developer@arusty.dev" + }, + "plugins": [ + { + "name": "homebrew-dev", + "source": "./context/plugins/homebrew-dev", + "description": "Homebrew formula development toolkit — research packages, generate formulas from JSON schema, validate with brew audit/style, and batch-create formulas for a custom tap.", + "version": "1.0.0", + "author": { + "name": "Adam Smith", + "email": "developer@gh.arusty.dev" + }, + "keywords": ["homebrew", "formula", "tap", "package-manager", "macos"], + "license": "MIT", + "homepage": "https://docs.arusty.dev/ai/plugins/homebrew-dev", + "repository": "https://github.com/aRustyDev/ai.git" + } + ] +} diff --git a/.claude/commands/create-agent.md b/.claude/commands/create-agent.md new file mode 100644 index 00000000..cdb6cbf7 --- /dev/null +++ b/.claude/commands/create-agent.md @@ -0,0 +1,284 @@ +--- +description: Create a new Claude Code agent definition with proper structure, sub-agent roles, and pipeline stages +argument-hint: [--location project|context] [--type solo|orchestrated] +allowed-tools: Read, Write, Glob, Grep, Bash(mkdir:*), Bash(ls:*), AskUserQuestion, Task +--- + +# Create Claude Code Agent + +Scaffold a new agent definition with proper structure, model assignments, and pipeline design. + +## Arguments + +- `$1` - Agent name (lowercase, hyphenated, max 48 chars). Example: `homebrew-expert` +- `--location` - Where to create the agent: + - `project` (default): `.claude/agents/.md` + - `context`: `context/agents/.md` +- `--type` - Agent architecture: + - `solo` (default): Single agent with direct instructions + - `orchestrated`: Multi-agent pipeline with sub-agents + +## Agent vs Command vs Skill + +| Use Agent When | Use Command When | Use Skill When | +|----------------|------------------|----------------| +| Complex multi-step pipeline | User-triggered one-off workflow | Auto-triggered by context | +| Needs sub-agent coordination | Single sequential workflow | Consistent behavior | +| Domain expertise required | Parameters vary per invocation | Implicit, frequent use | +| Long-running autonomous work | Quick, focused tasks | Pattern matching | + +## Workflow + +### Step 1: Parse and Validate + +1. Extract agent name from `$1` — validate: `^[a-z][a-z0-9-]{0,46}[a-z0-9]$` +2. Parse `--location` (default: `project`) and `--type` (default: `solo`) +3. Determine target path: + - `project` → `.claude/agents/.md` + - `context` → `context/agents/.md` +4. Check if file exists — if so, ask to overwrite or rename + +### Step 2: Gather Agent Information + +Use AskUserQuestion to collect: + +1. **Domain / Purpose**: What domain does this agent operate in? What problems does it solve? (2-3 sentences) +2. **Key Capabilities**: What can this agent do? (3-6 bullet points) +3. **Input / Output**: What does the agent receive and what does it produce? +4. **Tool Requirements**: Which tools does this agent need? (Read, Write, Bash, WebSearch, etc.) +5. **Model Preference**: Which model should this agent use? + - `haiku` — Fast, low-cost tasks (status updates, simple checks) + - `sonnet` — Balanced analysis and generation (default) + - `opus` — Complex reasoning, architecture decisions + +For `orchestrated` type, also ask: + +6. **Sub-Agents**: What specialized roles are needed? For each: + - Name, model, purpose, tools +7. **Pipeline Stages**: What is the execution order? Which stages can run in parallel? + +### Step 3: Create Directory + +```bash +mkdir -p "$(dirname "")" +``` + +### Step 4: Generate Agent File + +#### Solo Agent Structure + +```markdown +# + + + +## Overview + + + +## Capabilities + +- +- +- ... + +## Usage + +### Invocation + + + +### Input + + + +### Output + + + +## Workflow + +### Step 1: + + +### Step 2: + + +## Model + + + +## Tools Required + + + +## Notes + + +``` + +#### Orchestrated Agent Structure + +```markdown +# + + + +## Overview + + + +## Usage + +### Full Pipeline + + +### Individual Sub-Agents + + +## Sub-Agents + +| Agent | Model | Purpose | +|-------|-------|---------| +| `` | | | + +### + +**Model**: +**Tools**: +**Input**: +**Output**: + + + +### +... + +## Pipeline + +``` +Stage 1: → Stage 2: → Stage 3: + ↘ Stage 3b: (parallel) +``` + +## Configuration + + + +## Session Data + + + +## Cost Estimation + + +``` + +### Step 5: Validate + +1. Confirm the file has proper markdown structure +2. Verify all sections are populated (no placeholders) +3. Check model assignments are valid +4. Ensure tool lists match capabilities described + +### Step 6: Report + +``` +## Agent Created + +| Field | Value | +|-------|-------| +| Agent | `` | +| Location | `` | +| Type | solo / orchestrated | +| Model | | + +**Next steps:** +1. Review and refine the agent definition +2. Test with a sample input +3. Iterate on workflow steps based on results +``` + +## Examples + +``` +/create-agent homebrew-expert --location context +/create-agent code-reviewer --type solo +/create-agent skill-reviewer --type orchestrated --location project +``` + +## Common Patterns + +### Domain Expert (Solo) + +Single agent with deep knowledge of a specific domain. Good for: code review, formula generation, security auditing. + +```yaml +Model: sonnet +Tools: Read, Glob, Grep, Write +Workflow: Analyze → Assess → Generate → Validate +``` + +### Research Pipeline (Orchestrated) + +Multiple agents gathering and synthesizing information. Good for: package research, dependency analysis, competitive analysis. + +```yaml +Sub-agents: + - researcher (haiku) — Gather raw data from multiple sources + - analyzer (sonnet) — Synthesize findings, identify patterns + - reporter (haiku) — Format output +Pipeline: researcher (parallel, N instances) → analyzer → reporter +``` + +### Review Pipeline (Orchestrated) + +Staged quality gates with increasing depth. Good for: code review, PR review, skill review. + +```yaml +Sub-agents: + - validator (haiku) — Quick structural checks + - complexity-assessor (sonnet) — Determine analysis depth + - deep-analyzer (dynamic) — Full analysis (model based on complexity) + - fixer (sonnet) — Apply improvements +Pipeline: validator → complexity-assessor → deep-analyzer → fixer +``` + +## Model Selection Guide + +| Model | Cost/call | Use When | +|-------|-----------|----------| +| Haiku | ~$0.01 | Status updates, simple validation, formatting | +| Sonnet | ~$0.07 | Analysis, code generation, balanced tasks | +| Opus | ~$0.30 | Architecture decisions, complex reasoning, ambiguous problems | + +For orchestrated agents, assign the cheapest model that can handle each sub-agent's task. Reserve Opus for the stage that requires the most judgment. + +## Troubleshooting + +**Agent not being invoked:** +- Ensure the file is in `.claude/agents/` or `context/agents/` +- Check the file has `.md` extension +- Verify the first line is a `# Title` heading + +**Sub-agents not executing in parallel:** +- Task tool calls must be in the same message to run in parallel +- Verify sub-agents have no data dependencies between them + +**Agent producing inconsistent results:** +- Add explicit validation steps after each phase +- Include example inputs/outputs in the agent definition +- Constrain the tool list to only what's needed + +## Related Commands + +- `/create-command` — Create a user-triggered slash command (simpler than agents) +- `/create-skill` — Create a model-invoked skill (auto-triggered) + +## Notes + +- Solo agents are simpler and cheaper — prefer them unless you need parallel sub-agent work +- Orchestrated agents should have clear stage boundaries and well-defined data flow between sub-agents +- Model assignments matter for cost: a pipeline with 5 Opus sub-agents costs ~$7.50/run vs ~$0.15 with Haiku +- Agent files are documentation for Claude — write them as instructions, not descriptions +- Reference existing agents in `.claude/agents/` and `context/agents/` for patterns diff --git a/context/agents/homebrew-expert.md b/context/agents/homebrew-expert.md new file mode 100644 index 00000000..8b5e5064 --- /dev/null +++ b/context/agents/homebrew-expert.md @@ -0,0 +1,152 @@ +# Homebrew Expert + +Domain expert for creating, debugging, and maintaining Homebrew formulas and taps on macOS. + +## Overview + +Use this agent when working with Homebrew packaging — creating new formulas, fixing build failures, setting up livecheck, configuring services, or preparing formulas for submission to homebrew-core or a custom tap. This agent understands the Homebrew Ruby DSL, build system conventions, and the `pkgmgr-homebrew-formula-dev` skill's template pipeline. + +## Capabilities + +- Research a package repository and determine the correct formula structure +- Generate formulas from JSON via the template pipeline (`just template-formula`) +- Debug formula build and test failures +- Configure livecheck strategies for automatic version detection +- Set up service blocks for daemon formulas +- Run and interpret `brew audit`, `brew style`, and `brew test` output +- Handle platform-specific logic (`on_macos`, `on_linux`, `on_arm`, `on_intel`) + +## Usage + +### Invocation + +Invoke via the Task tool with `subagent_type: "general-purpose"` and reference this agent definition in the prompt. + +### Input + +One of: +- A repository URL or package name to create a formula for +- A formula file path to debug or validate +- A description of a Homebrew packaging problem + +### Output + +- Rendered `.rb` formula file(s) +- Validation results (syntax, audit, style) +- Debugging analysis with fix suggestions + +## Workflow + +### Step 1: Understand the Request + +Determine the task type: +- **New formula**: Go to Step 2 +- **Debug/fix existing**: Read the formula, read error output, diagnose, fix +- **Validate**: Run the validation pipeline (ruby -c, brew audit, brew style) +- **Update version**: Check livecheck, update URL/SHA256, bump revision if needed + +### Step 2: Research the Package + +1. Inspect the repository: language, build system, dependencies, license +2. Fetch the latest release: tag, tarball URL, SHA256 +3. Identify binary names, completions, man pages, services +4. Check for existing formulas in homebrew-core for reference patterns + +### Step 3: Generate the Formula + +Use the `pkgmgr-homebrew-formula-dev` skill's template pipeline: + +1. Build a JSON object conforming to `scripts/formula.schema.ts` +2. Validate and render: `just template-formula ''` +3. Review the rendered output for correctness + +If the template pipeline doesn't cover a special case, write the formula Ruby directly following Homebrew conventions. + +### Step 4: Validate + +Run the validation pipeline: +1. `ruby -c ` — syntax check +2. `brew audit --formula ` — Homebrew conventions +3. `brew style ` — RuboCop style + +### Step 5: Deliver + +Write the formula to the target path and report results. + +## Key Reference + +| Resource | Path | +|----------|------| +| Skill definition | `context/skills/pkgmgr-homebrew-formula-dev/SKILL.md` | +| JSON Schema | `context/skills/pkgmgr-homebrew-formula-dev/scripts/formula.schema.ts` | +| Helper functions | `context/skills/pkgmgr-homebrew-formula-dev/scripts/formula.helper.ts` | +| Main template | `context/skills/pkgmgr-homebrew-formula-dev/reference/templates/main.mustache` | +| Language partials | `context/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/*.mustache` | +| Test fixtures | `context/skills/pkgmgr-homebrew-formula-dev/test/data/*.json` | +| Go reference | `context/skills/pkgmgr-homebrew-formula-dev/reference/go.md` | +| Rust reference | `context/skills/pkgmgr-homebrew-formula-dev/reference/rust.md` | +| Python reference | `context/skills/pkgmgr-homebrew-formula-dev/reference/python.md` | + +## Homebrew DSL Quick Reference + +### Common install patterns + +```ruby +# Go +system "go", "build", *std_go_args(ldflags: "-s -w -X main.version=#{version}") + +# Rust +system "cargo", "install", *std_cargo_args + +# Python +virtualenv_install_with_resources + +# CMake +system "cmake", "-S", ".", "-B", "build", *std_cmake_args +system "cmake", "--build", "build" +system "cmake", "--install", "build" +``` + +### Completions + +```ruby +generate_completions_from_executable(bin/"tool", "completions") +# or +bash_completion.install "completions/tool.bash" => "tool" +zsh_completion.install "completions/_tool" +fish_completion.install "completions/tool.fish" +``` + +### Services + +```ruby +service do + run [opt_bin/"daemon", "--config", etc/"daemon.conf"] + keep_alive true + log_path var/"log/daemon.log" + error_log_path var/"log/daemon-error.log" +end +``` + +## Model + +Sonnet — formula generation and debugging require understanding Ruby DSL patterns and build system conventions, but not deep architectural reasoning. + +## Tools Required + +- `Read`, `Glob`, `Grep` — Inspect repos and existing formulas +- `Write` — Generate formula files +- `Bash(brew:*)` — Run brew audit, style, test, install +- `Bash(curl:*)`, `Bash(shasum:*)` — Fetch tarballs and compute SHA256 +- `Bash(ruby:*)` — Syntax checking +- `Bash(just:*)` — Run the template pipeline +- `Bash(gh:*)` — Query GitHub releases +- `WebSearch`, `WebFetch` — Research packages + +## Notes + +- Always validate generated formulas before delivering — never skip `ruby -c` +- For Python formulas, `resource` blocks for pip dependencies are often the trickiest part +- HEAD-only formulas are acceptable when a project has no tagged releases +- When submitting to homebrew-core, formulas must pass `brew audit --strict --new` +- Livecheck is required for all formulas with stable URLs diff --git a/context/commands/add-formula.md b/context/commands/add-formula.md new file mode 100644 index 00000000..f2ac128a --- /dev/null +++ b/context/commands/add-formula.md @@ -0,0 +1,110 @@ +--- +description: Research a package and generate a Homebrew formula via the template pipeline +argument-hint: [--language go|rust|python|zig|cmake|autotools|meson] [--head-only] +allowed-tools: Read, Write, Glob, Grep, Bash(curl:*), Bash(shasum:*), Bash(just:*), Bash(git:*), Bash(gh:*), Bash(brew:*), Bash(npm:*), Bash(ls:*), Bash(mkdir:*), Bash(cat:*), WebFetch, WebSearch, Task, AskUserQuestion +--- + +# Add Homebrew Formula + +Research a package repository and generate a complete Homebrew formula using the `pkgmgr-homebrew-formula-dev` skill's JSON Schema → Mustache template pipeline. + +## Arguments + +- `$1` - Repository URL or package name (required). Examples: `https://github.com/owner/repo`, `owner/repo`, `my-tool` +- `--language` - Build system language (optional, auto-detected if omitted): `go`, `rust`, `python`, `zig`, `cmake`, `autotools`, `meson` +- `--head-only` - Generate a HEAD-only formula with no stable URL/SHA256 + +## Workflow + +### Step 1: Resolve Repository + +1. If `$1` is a full URL, extract owner/repo +2. If `$1` is `owner/repo`, construct `https://github.com/owner/repo` +3. If `$1` is just a name, search GitHub: `gh search repos "$1" --limit 5` and ask the user to pick +4. Verify the repo exists: `gh repo view --json name,description,url,licenseInfo,primaryLanguage` + +### Step 2: Detect Language and Build System + +1. If `--language` was provided, use it +2. Otherwise, inspect the repo: + - `go.mod` → go + - `Cargo.toml` → rust + - `setup.py` / `pyproject.toml` → python + - `build.zig` → zig + - `CMakeLists.txt` → cmake + - `configure.ac` / `Makefile.am` → autotools + - `meson.build` → meson +3. If ambiguous, ask the user + +### Step 3: Fetch Release Info + +1. Get latest release: `gh release view --repo --json tagName,tarballUrl,name` +2. If `--head-only` or no releases exist, skip to Step 4 with head-only config +3. Compute SHA256: `curl -sL | shasum -a 256` +4. Extract version from tag (strip leading `v`) + +### Step 4: Analyze Dependencies + +1. Read the repo's build files to identify: + - Build dependencies (compilers, build tools) + - Runtime dependencies (shared libraries, interpreters) + - `uses_from_macos` candidates (zlib, curl, libxml2, etc.) +2. Check if the package produces a service (daemon) or CLI tool +3. Look for completions (bash, zsh, fish) in the source + +### Step 5: Build JSON Input + +Construct a JSON object conforming to the skill's `formula.schema.ts`: + +```json +{ + "formulas": [{ + "name": "", + "desc": "", + "homepage": "", + "url": "", + "sha256": "", + "license": "", + "language": "", + "livecheck": { "url": ":stable", "strategy": ":github_latest" }, + "dependencies": [...], + "uses_from_macos": [...], + "install": { ... }, + "test": { "command": "...", "expected_output": "..." } + }] +} +``` + +Read the schema at `context/skills/pkgmgr-homebrew-formula-dev/scripts/formula.schema.ts` to ensure all fields are valid. + +### Step 6: Render Formula + +1. Locate the skill directory: `context/skills/pkgmgr-homebrew-formula-dev` +2. Ensure dependencies are installed: `cd && npm ls mustache ajv 2>/dev/null || npm install` +3. Write the JSON to a temp file +4. Run: `cd && just template-formula "$(cat )"` +5. Capture the rendered Ruby output + +### Step 7: Write and Report + +1. Determine output path — ask the user or default to `Formula/.rb` +2. Write the rendered formula to the output path +3. Show the user the generated formula +4. Suggest next steps: + - `/validate-formula ` to run brew audit/style + - `brew install --build-from-source ` to test locally + +## Examples + +``` +/add-formula https://github.com/BurntSushi/ripgrep +/add-formula sharkdp/bat --language rust +/add-formula my-internal-tool --head-only +``` + +## Notes + +- The formula schema supports: go, rust, python, zig, cmake, autotools, meson +- For Python formulas, resources (pip dependencies) must be listed manually or extracted from `requirements.txt` +- HEAD-only formulas skip URL/SHA256 and use `head "https://github.com/..."` instead +- Always verify the generated formula with `/validate-formula` before committing diff --git a/context/commands/batch-formulas.md b/context/commands/batch-formulas.md new file mode 100644 index 00000000..24df5402 --- /dev/null +++ b/context/commands/batch-formulas.md @@ -0,0 +1,102 @@ +--- +description: Research and generate multiple Homebrew formulas in parallel from a list of packages +argument-hint: [--tap path/to/tap] [--validate] +allowed-tools: Read, Write, Glob, Grep, Bash(curl:*), Bash(shasum:*), Bash(just:*), Bash(git:*), Bash(gh:*), Bash(brew:*), Bash(npm:*), Bash(ls:*), Bash(mkdir:*), Bash(cat:*), WebFetch, WebSearch, Task, AskUserQuestion +--- + +# Batch Generate Homebrew Formulas + +Research multiple packages and generate Homebrew formulas for all of them, using parallel sub-agents for research and SHA256 computation. + +## Arguments + +- `$1` - Comma-separated list of repo URLs/names, OR path to a file with one package per line (required) +- `--tap` - Path to the tap's `Formula/` directory (optional, default: `Formula/`) +- `--validate` - Run `/validate-formula` on each generated formula after rendering (optional) + +## Workflow + +### Step 1: Parse Package List + +1. If `$1` is a file path (ends in `.txt`, `.json`, or `.yaml`), read it: + - `.txt`: one package per line (blank lines and `#` comments ignored) + - `.json`: array of strings or array of objects with `name`/`url` fields + - `.yaml`: same structure as JSON +2. If `$1` is a comma-separated string, split on commas and trim whitespace +3. Report the count: "Found N packages to process" + +### Step 2: Parallel Research + +For each package, launch a Task sub-agent to: + +1. Resolve the repository (same as `/add-formula` Step 1-2) +2. Fetch latest release tag and tarball URL +3. Compute SHA256 of the tarball: `curl -sL | shasum -a 256` +4. Detect language/build system +5. Analyze dependencies from build files +6. Return a structured JSON result + +Use parallel Task agents — up to 4 concurrent — to speed up research. + +### Step 3: Review Research Results + +Present a summary table to the user: + +``` +| # | Package | Version | Language | Deps | Status | +|---|---------|---------|----------|------|--------| +| 1 | ripgrep | 14.1.0 | rust | 2 | Ready | +| 2 | fd | 10.2.0 | rust | 1 | Ready | +| 3 | my-tool | — | go | 0 | HEAD-only | +``` + +Ask the user to confirm before rendering, or remove packages from the batch. + +### Step 4: Generate JSON and Render + +1. Build a single JSON object with all formulas in the `formulas` array +2. Write to a temp file +3. Run: `cd && just template-formula "$(cat )"` +4. Split the rendered output into individual formula files (split on `class ... < Formula`) + +### Step 5: Write Formula Files + +1. Create the output directory if needed: `mkdir -p /Formula/` +2. Write each formula to `/Formula/.rb` +3. Report files created + +### Step 6: Optional Validation + +If `--validate` was passed, run `/validate-formula` on each generated file and collect results. + +### Step 7: Final Report + +``` +## Batch Formula Generation Complete + +| Formula | File | Validation | +|---------|------|------------| +| ripgrep | Formula/ripgrep.rb | PASS | +| fd | Formula/fd.rb | PASS | +| my-tool | Formula/my-tool.rb | WARN (2) | + +Next steps: +- Review each formula for accuracy +- `brew install --build-from-source Formula/.rb` +- Commit and push to your tap +``` + +## Examples + +``` +/batch-formulas ripgrep,fd,bat,eza,delta +/batch-formulas packages.txt --tap homebrew-tap --validate +/batch-formulas tools.json --validate +``` + +## Notes + +- SHA256 computation requires network access to download tarballs +- Large batches (10+) may take a while due to tarball downloads +- Each formula is rendered independently — one failure won't block others +- The skill's template pipeline must be set up first (`cd && just deps`) diff --git a/context/commands/create-agent.md b/context/commands/create-agent.md new file mode 100644 index 00000000..cdb6cbf7 --- /dev/null +++ b/context/commands/create-agent.md @@ -0,0 +1,284 @@ +--- +description: Create a new Claude Code agent definition with proper structure, sub-agent roles, and pipeline stages +argument-hint: [--location project|context] [--type solo|orchestrated] +allowed-tools: Read, Write, Glob, Grep, Bash(mkdir:*), Bash(ls:*), AskUserQuestion, Task +--- + +# Create Claude Code Agent + +Scaffold a new agent definition with proper structure, model assignments, and pipeline design. + +## Arguments + +- `$1` - Agent name (lowercase, hyphenated, max 48 chars). Example: `homebrew-expert` +- `--location` - Where to create the agent: + - `project` (default): `.claude/agents/.md` + - `context`: `context/agents/.md` +- `--type` - Agent architecture: + - `solo` (default): Single agent with direct instructions + - `orchestrated`: Multi-agent pipeline with sub-agents + +## Agent vs Command vs Skill + +| Use Agent When | Use Command When | Use Skill When | +|----------------|------------------|----------------| +| Complex multi-step pipeline | User-triggered one-off workflow | Auto-triggered by context | +| Needs sub-agent coordination | Single sequential workflow | Consistent behavior | +| Domain expertise required | Parameters vary per invocation | Implicit, frequent use | +| Long-running autonomous work | Quick, focused tasks | Pattern matching | + +## Workflow + +### Step 1: Parse and Validate + +1. Extract agent name from `$1` — validate: `^[a-z][a-z0-9-]{0,46}[a-z0-9]$` +2. Parse `--location` (default: `project`) and `--type` (default: `solo`) +3. Determine target path: + - `project` → `.claude/agents/.md` + - `context` → `context/agents/.md` +4. Check if file exists — if so, ask to overwrite or rename + +### Step 2: Gather Agent Information + +Use AskUserQuestion to collect: + +1. **Domain / Purpose**: What domain does this agent operate in? What problems does it solve? (2-3 sentences) +2. **Key Capabilities**: What can this agent do? (3-6 bullet points) +3. **Input / Output**: What does the agent receive and what does it produce? +4. **Tool Requirements**: Which tools does this agent need? (Read, Write, Bash, WebSearch, etc.) +5. **Model Preference**: Which model should this agent use? + - `haiku` — Fast, low-cost tasks (status updates, simple checks) + - `sonnet` — Balanced analysis and generation (default) + - `opus` — Complex reasoning, architecture decisions + +For `orchestrated` type, also ask: + +6. **Sub-Agents**: What specialized roles are needed? For each: + - Name, model, purpose, tools +7. **Pipeline Stages**: What is the execution order? Which stages can run in parallel? + +### Step 3: Create Directory + +```bash +mkdir -p "$(dirname "")" +``` + +### Step 4: Generate Agent File + +#### Solo Agent Structure + +```markdown +# + + + +## Overview + + + +## Capabilities + +- +- +- ... + +## Usage + +### Invocation + + + +### Input + + + +### Output + + + +## Workflow + +### Step 1: + + +### Step 2: + + +## Model + + + +## Tools Required + + + +## Notes + + +``` + +#### Orchestrated Agent Structure + +```markdown +# + + + +## Overview + + + +## Usage + +### Full Pipeline + + +### Individual Sub-Agents + + +## Sub-Agents + +| Agent | Model | Purpose | +|-------|-------|---------| +| `` | | | + +### + +**Model**: +**Tools**: +**Input**: +**Output**: + + + +### +... + +## Pipeline + +``` +Stage 1: → Stage 2: → Stage 3: + ↘ Stage 3b: (parallel) +``` + +## Configuration + + + +## Session Data + + + +## Cost Estimation + + +``` + +### Step 5: Validate + +1. Confirm the file has proper markdown structure +2. Verify all sections are populated (no placeholders) +3. Check model assignments are valid +4. Ensure tool lists match capabilities described + +### Step 6: Report + +``` +## Agent Created + +| Field | Value | +|-------|-------| +| Agent | `` | +| Location | `` | +| Type | solo / orchestrated | +| Model | | + +**Next steps:** +1. Review and refine the agent definition +2. Test with a sample input +3. Iterate on workflow steps based on results +``` + +## Examples + +``` +/create-agent homebrew-expert --location context +/create-agent code-reviewer --type solo +/create-agent skill-reviewer --type orchestrated --location project +``` + +## Common Patterns + +### Domain Expert (Solo) + +Single agent with deep knowledge of a specific domain. Good for: code review, formula generation, security auditing. + +```yaml +Model: sonnet +Tools: Read, Glob, Grep, Write +Workflow: Analyze → Assess → Generate → Validate +``` + +### Research Pipeline (Orchestrated) + +Multiple agents gathering and synthesizing information. Good for: package research, dependency analysis, competitive analysis. + +```yaml +Sub-agents: + - researcher (haiku) — Gather raw data from multiple sources + - analyzer (sonnet) — Synthesize findings, identify patterns + - reporter (haiku) — Format output +Pipeline: researcher (parallel, N instances) → analyzer → reporter +``` + +### Review Pipeline (Orchestrated) + +Staged quality gates with increasing depth. Good for: code review, PR review, skill review. + +```yaml +Sub-agents: + - validator (haiku) — Quick structural checks + - complexity-assessor (sonnet) — Determine analysis depth + - deep-analyzer (dynamic) — Full analysis (model based on complexity) + - fixer (sonnet) — Apply improvements +Pipeline: validator → complexity-assessor → deep-analyzer → fixer +``` + +## Model Selection Guide + +| Model | Cost/call | Use When | +|-------|-----------|----------| +| Haiku | ~$0.01 | Status updates, simple validation, formatting | +| Sonnet | ~$0.07 | Analysis, code generation, balanced tasks | +| Opus | ~$0.30 | Architecture decisions, complex reasoning, ambiguous problems | + +For orchestrated agents, assign the cheapest model that can handle each sub-agent's task. Reserve Opus for the stage that requires the most judgment. + +## Troubleshooting + +**Agent not being invoked:** +- Ensure the file is in `.claude/agents/` or `context/agents/` +- Check the file has `.md` extension +- Verify the first line is a `# Title` heading + +**Sub-agents not executing in parallel:** +- Task tool calls must be in the same message to run in parallel +- Verify sub-agents have no data dependencies between them + +**Agent producing inconsistent results:** +- Add explicit validation steps after each phase +- Include example inputs/outputs in the agent definition +- Constrain the tool list to only what's needed + +## Related Commands + +- `/create-command` — Create a user-triggered slash command (simpler than agents) +- `/create-skill` — Create a model-invoked skill (auto-triggered) + +## Notes + +- Solo agents are simpler and cheaper — prefer them unless you need parallel sub-agent work +- Orchestrated agents should have clear stage boundaries and well-defined data flow between sub-agents +- Model assignments matter for cost: a pipeline with 5 Opus sub-agents costs ~$7.50/run vs ~$0.15 with Haiku +- Agent files are documentation for Claude — write them as instructions, not descriptions +- Reference existing agents in `.claude/agents/` and `context/agents/` for patterns diff --git a/context/commands/validate-formula.md b/context/commands/validate-formula.md new file mode 100644 index 00000000..aa5d0ff9 --- /dev/null +++ b/context/commands/validate-formula.md @@ -0,0 +1,96 @@ +--- +description: Validate a Homebrew formula with ruby syntax check, brew audit, and brew style +argument-hint: [--fix] [--strict] +allowed-tools: Read, Bash(ruby:*), Bash(brew:*), Bash(ls:*), Bash(cat:*), Glob, Grep +--- + +# Validate Homebrew Formula + +Run a multi-stage validation pipeline on a Homebrew formula file: Ruby syntax check, `brew audit`, and `brew style`. + +## Arguments + +- `$1` - Path to the formula `.rb` file (required) +- `--fix` - Auto-fix style issues with `brew style --fix` (optional) +- `--strict` - Run `brew audit --strict` for extra checks (optional) + +## Workflow + +### Step 1: Verify Input + +1. Confirm `$1` points to an existing `.rb` file +2. Read the file to confirm it contains a Homebrew formula (class inheriting from `Formula`) +3. Extract the formula name and class name for reporting + +### Step 2: Ruby Syntax Check + +Run `ruby -c "$1"` to verify valid Ruby syntax. + +- If it fails, read the file, identify the syntax error, and report the line number and issue +- Do not proceed to further checks if syntax is invalid + +### Step 3: Brew Audit + +Run the appropriate audit command: + +```bash +# Standard audit +brew audit --formula "$1" + +# With --strict flag +brew audit --strict --formula "$1" +``` + +Capture and categorize output: +- **Errors**: Must fix before submitting +- **Warnings**: Should fix, but not blocking +- **New formula warnings**: Expected for new formulas + +### Step 4: Brew Style + +Run style check: + +```bash +# Check only +brew style "$1" + +# With --fix flag (if user passed --fix) +brew style --fix "$1" +``` + +Report any RuboCop offenses found. + +### Step 5: Report Results + +Present a summary table: + +``` +## Validation Results: + +| Check | Status | Details | +|-------|--------|---------| +| Ruby syntax | PASS/FAIL | ... | +| brew audit | PASS/WARN/FAIL | N errors, M warnings | +| brew style | PASS/FAIL | N offenses | +``` + +If all checks pass, suggest: +- `brew install --build-from-source "$1"` to test the build +- `brew test ` to run the formula's test block + +If checks fail, list each issue with a suggested fix. + +## Examples + +``` +/validate-formula Formula/my-tool.rb +/validate-formula Formula/my-tool.rb --fix +/validate-formula Formula/my-tool.rb --strict +``` + +## Notes + +- Requires Homebrew installed locally (`brew` in PATH) +- `brew audit` needs the formula in a tap or uses `--formula` flag for standalone files +- `--fix` only applies to style (RuboCop) issues, not audit errors +- For new formulas, `--new` flag is automatically added to `brew audit` if the formula doesn't exist in any tap diff --git a/context/hooks/pre-commit.json b/context/hooks/pre-commit.json index 3e019ba2..6f59d37d 100644 --- a/context/hooks/pre-commit.json +++ b/context/hooks/pre-commit.json @@ -5,7 +5,18 @@ "hooks": [ { "type": "command", - "command": "tdd-guard" + "command": "tdd-guard", + "description": "Verify TDD is being used" + } + ] + }, + { + "matcher": "Bash(git commit:*)", + "hooks": [ + { + "type": "command", + "command": "", + "description": "Check 1password ssh-agent status, ensure it's running" } ] } diff --git a/context/hooks/signed-commits-only.json b/context/hooks/signed-commits-only.json new file mode 100644 index 00000000..094996b7 --- /dev/null +++ b/context/hooks/signed-commits-only.json @@ -0,0 +1,14 @@ +{ + "PostToolUse": [ + { + "matcher": "Bash(git commit:*)", + "hooks": [ + { + "type": "command", + "command": "", + "description": "Check that commits were actually signed w/ trusted keys" + } + ] + } + ] +} diff --git a/context/plugins/.template/.claude-plugin/plugin.sources.json b/context/plugins/.template/.claude-plugin/plugin.sources.json new file mode 100644 index 00000000..4741b5b8 --- /dev/null +++ b/context/plugins/.template/.claude-plugin/plugin.sources.json @@ -0,0 +1,10 @@ +{ + "$schema": "Plugin source mapping — maps local paths to repo source paths", + "sources": { + "commands/foo.md": "context/commands/foo.md", + "commands/bar.md": "context/commands/bar.md", + "commands/baz.md": "context/commands/baz.md", + "agents/foo.md": "context/agents/foo.md", + "skills/boo": "context/skills/boo" + } +} diff --git a/context/plugins/.template/docs/.gitignore b/context/plugins/.template/docs/.gitignore new file mode 100644 index 00000000..e9c07289 --- /dev/null +++ b/context/plugins/.template/docs/.gitignore @@ -0,0 +1 @@ +book \ No newline at end of file diff --git a/context/plugins/.template/docs/book.toml b/context/plugins/.template/docs/book.toml new file mode 100644 index 00000000..04e3fa60 --- /dev/null +++ b/context/plugins/.template/docs/book.toml @@ -0,0 +1,4 @@ +[book] +title = "CHANGEME" +authors = ["aRustyDev"] +language = "en" diff --git a/context/plugins/.template/docs/src/SUMMARY.md b/context/plugins/.template/docs/src/SUMMARY.md new file mode 100644 index 00000000..7390c828 --- /dev/null +++ b/context/plugins/.template/docs/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Chapter 1](./chapter_1.md) diff --git a/context/plugins/.template/docs/src/chapter_1.md b/context/plugins/.template/docs/src/chapter_1.md new file mode 100644 index 00000000..b743fda3 --- /dev/null +++ b/context/plugins/.template/docs/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 diff --git a/context/plugins/homebrew-dev/.claude-plugin/README.md b/context/plugins/homebrew-dev/.claude-plugin/README.md new file mode 100644 index 00000000..6431940e --- /dev/null +++ b/context/plugins/homebrew-dev/.claude-plugin/README.md @@ -0,0 +1 @@ +# Plugin Meta Readme diff --git a/context/plugins/homebrew-dev/.claude-plugin/plugin.json b/context/plugins/homebrew-dev/.claude-plugin/plugin.json new file mode 100644 index 00000000..4e1d1d39 --- /dev/null +++ b/context/plugins/homebrew-dev/.claude-plugin/plugin.json @@ -0,0 +1,28 @@ +{ + "name": "homebrew-dev", + "version": "1.0.0", + "description": "Homebrew formula development toolkit — research packages, generate formulas from JSON schema, validate with brew audit/style, and batch-create formulas for a custom tap.", + "author": { + "name": "Adam Smith", + "email": "developer@gh.arusty.dev", + "url": "https://im.arusty.dev" + }, + "homepage": "https://docs.arusty.dev/ai/plugins/homebrew-dev", + "repository": "https://github.com/aRustyDev/ai.git", + "license": "MIT", + "keywords": ["homebrew", "formula", "tap", "package-manager", "macos"], + + "commands": [ + "./commands/add-formula.md", + "./commands/validate-formula.md", + "./commands/batch-formulas.md" + ], + "agents": ["./agents/homebrew-expert.md"], + "skills": [ + "./skills/pkgmgr-homebrew-formula-dev/SKILL.md" + ], + "hooks": "./hooks/hooks.json", + "mcpServers": "./.mcp.json", + "outputStyles": "./styles/", + "lspServers": "./.lsp.json" +} diff --git a/context/plugins/homebrew-dev/.claude-plugin/plugin.sources.json b/context/plugins/homebrew-dev/.claude-plugin/plugin.sources.json new file mode 100644 index 00000000..53daa137 --- /dev/null +++ b/context/plugins/homebrew-dev/.claude-plugin/plugin.sources.json @@ -0,0 +1,10 @@ +{ + "$schema": "Plugin source mapping — maps local paths to repo source paths", + "sources": { + "commands/add-formula.md": "context/commands/add-formula.md", + "commands/validate-formula.md": "context/commands/validate-formula.md", + "commands/batch-formulas.md": "context/commands/batch-formulas.md", + "agents/homebrew-expert.md": "context/agents/homebrew-expert.md", + "skills/pkgmgr-homebrew-formula-dev": "context/skills/pkgmgr-homebrew-formula-dev" + } +} diff --git a/context/plugins/homebrew-dev/.lsp.json b/context/plugins/homebrew-dev/.lsp.json new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/.mcp.json b/context/plugins/homebrew-dev/.mcp.json new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/.templates/.gitkeep b/context/plugins/homebrew-dev/.templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/README.md b/context/plugins/homebrew-dev/README.md new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/agents/.gitkeep b/context/plugins/homebrew-dev/agents/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/agents/homebrew-expert.md b/context/plugins/homebrew-dev/agents/homebrew-expert.md new file mode 100644 index 00000000..8b5e5064 --- /dev/null +++ b/context/plugins/homebrew-dev/agents/homebrew-expert.md @@ -0,0 +1,152 @@ +# Homebrew Expert + +Domain expert for creating, debugging, and maintaining Homebrew formulas and taps on macOS. + +## Overview + +Use this agent when working with Homebrew packaging — creating new formulas, fixing build failures, setting up livecheck, configuring services, or preparing formulas for submission to homebrew-core or a custom tap. This agent understands the Homebrew Ruby DSL, build system conventions, and the `pkgmgr-homebrew-formula-dev` skill's template pipeline. + +## Capabilities + +- Research a package repository and determine the correct formula structure +- Generate formulas from JSON via the template pipeline (`just template-formula`) +- Debug formula build and test failures +- Configure livecheck strategies for automatic version detection +- Set up service blocks for daemon formulas +- Run and interpret `brew audit`, `brew style`, and `brew test` output +- Handle platform-specific logic (`on_macos`, `on_linux`, `on_arm`, `on_intel`) + +## Usage + +### Invocation + +Invoke via the Task tool with `subagent_type: "general-purpose"` and reference this agent definition in the prompt. + +### Input + +One of: +- A repository URL or package name to create a formula for +- A formula file path to debug or validate +- A description of a Homebrew packaging problem + +### Output + +- Rendered `.rb` formula file(s) +- Validation results (syntax, audit, style) +- Debugging analysis with fix suggestions + +## Workflow + +### Step 1: Understand the Request + +Determine the task type: +- **New formula**: Go to Step 2 +- **Debug/fix existing**: Read the formula, read error output, diagnose, fix +- **Validate**: Run the validation pipeline (ruby -c, brew audit, brew style) +- **Update version**: Check livecheck, update URL/SHA256, bump revision if needed + +### Step 2: Research the Package + +1. Inspect the repository: language, build system, dependencies, license +2. Fetch the latest release: tag, tarball URL, SHA256 +3. Identify binary names, completions, man pages, services +4. Check for existing formulas in homebrew-core for reference patterns + +### Step 3: Generate the Formula + +Use the `pkgmgr-homebrew-formula-dev` skill's template pipeline: + +1. Build a JSON object conforming to `scripts/formula.schema.ts` +2. Validate and render: `just template-formula ''` +3. Review the rendered output for correctness + +If the template pipeline doesn't cover a special case, write the formula Ruby directly following Homebrew conventions. + +### Step 4: Validate + +Run the validation pipeline: +1. `ruby -c ` — syntax check +2. `brew audit --formula ` — Homebrew conventions +3. `brew style ` — RuboCop style + +### Step 5: Deliver + +Write the formula to the target path and report results. + +## Key Reference + +| Resource | Path | +|----------|------| +| Skill definition | `context/skills/pkgmgr-homebrew-formula-dev/SKILL.md` | +| JSON Schema | `context/skills/pkgmgr-homebrew-formula-dev/scripts/formula.schema.ts` | +| Helper functions | `context/skills/pkgmgr-homebrew-formula-dev/scripts/formula.helper.ts` | +| Main template | `context/skills/pkgmgr-homebrew-formula-dev/reference/templates/main.mustache` | +| Language partials | `context/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/*.mustache` | +| Test fixtures | `context/skills/pkgmgr-homebrew-formula-dev/test/data/*.json` | +| Go reference | `context/skills/pkgmgr-homebrew-formula-dev/reference/go.md` | +| Rust reference | `context/skills/pkgmgr-homebrew-formula-dev/reference/rust.md` | +| Python reference | `context/skills/pkgmgr-homebrew-formula-dev/reference/python.md` | + +## Homebrew DSL Quick Reference + +### Common install patterns + +```ruby +# Go +system "go", "build", *std_go_args(ldflags: "-s -w -X main.version=#{version}") + +# Rust +system "cargo", "install", *std_cargo_args + +# Python +virtualenv_install_with_resources + +# CMake +system "cmake", "-S", ".", "-B", "build", *std_cmake_args +system "cmake", "--build", "build" +system "cmake", "--install", "build" +``` + +### Completions + +```ruby +generate_completions_from_executable(bin/"tool", "completions") +# or +bash_completion.install "completions/tool.bash" => "tool" +zsh_completion.install "completions/_tool" +fish_completion.install "completions/tool.fish" +``` + +### Services + +```ruby +service do + run [opt_bin/"daemon", "--config", etc/"daemon.conf"] + keep_alive true + log_path var/"log/daemon.log" + error_log_path var/"log/daemon-error.log" +end +``` + +## Model + +Sonnet — formula generation and debugging require understanding Ruby DSL patterns and build system conventions, but not deep architectural reasoning. + +## Tools Required + +- `Read`, `Glob`, `Grep` — Inspect repos and existing formulas +- `Write` — Generate formula files +- `Bash(brew:*)` — Run brew audit, style, test, install +- `Bash(curl:*)`, `Bash(shasum:*)` — Fetch tarballs and compute SHA256 +- `Bash(ruby:*)` — Syntax checking +- `Bash(just:*)` — Run the template pipeline +- `Bash(gh:*)` — Query GitHub releases +- `WebSearch`, `WebFetch` — Research packages + +## Notes + +- Always validate generated formulas before delivering — never skip `ruby -c` +- For Python formulas, `resource` blocks for pip dependencies are often the trickiest part +- HEAD-only formulas are acceptable when a project has no tagged releases +- When submitting to homebrew-core, formulas must pass `brew audit --strict --new` +- Livecheck is required for all formulas with stable URLs diff --git a/context/plugins/homebrew-dev/brewfile b/context/plugins/homebrew-dev/brewfile new file mode 100644 index 00000000..653ebabd --- /dev/null +++ b/context/plugins/homebrew-dev/brewfile @@ -0,0 +1 @@ +brew "just" diff --git a/context/plugins/homebrew-dev/commands/.gitkeep b/context/plugins/homebrew-dev/commands/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/commands/add-formula.md b/context/plugins/homebrew-dev/commands/add-formula.md new file mode 100644 index 00000000..f2ac128a --- /dev/null +++ b/context/plugins/homebrew-dev/commands/add-formula.md @@ -0,0 +1,110 @@ +--- +description: Research a package and generate a Homebrew formula via the template pipeline +argument-hint: [--language go|rust|python|zig|cmake|autotools|meson] [--head-only] +allowed-tools: Read, Write, Glob, Grep, Bash(curl:*), Bash(shasum:*), Bash(just:*), Bash(git:*), Bash(gh:*), Bash(brew:*), Bash(npm:*), Bash(ls:*), Bash(mkdir:*), Bash(cat:*), WebFetch, WebSearch, Task, AskUserQuestion +--- + +# Add Homebrew Formula + +Research a package repository and generate a complete Homebrew formula using the `pkgmgr-homebrew-formula-dev` skill's JSON Schema → Mustache template pipeline. + +## Arguments + +- `$1` - Repository URL or package name (required). Examples: `https://github.com/owner/repo`, `owner/repo`, `my-tool` +- `--language` - Build system language (optional, auto-detected if omitted): `go`, `rust`, `python`, `zig`, `cmake`, `autotools`, `meson` +- `--head-only` - Generate a HEAD-only formula with no stable URL/SHA256 + +## Workflow + +### Step 1: Resolve Repository + +1. If `$1` is a full URL, extract owner/repo +2. If `$1` is `owner/repo`, construct `https://github.com/owner/repo` +3. If `$1` is just a name, search GitHub: `gh search repos "$1" --limit 5` and ask the user to pick +4. Verify the repo exists: `gh repo view --json name,description,url,licenseInfo,primaryLanguage` + +### Step 2: Detect Language and Build System + +1. If `--language` was provided, use it +2. Otherwise, inspect the repo: + - `go.mod` → go + - `Cargo.toml` → rust + - `setup.py` / `pyproject.toml` → python + - `build.zig` → zig + - `CMakeLists.txt` → cmake + - `configure.ac` / `Makefile.am` → autotools + - `meson.build` → meson +3. If ambiguous, ask the user + +### Step 3: Fetch Release Info + +1. Get latest release: `gh release view --repo --json tagName,tarballUrl,name` +2. If `--head-only` or no releases exist, skip to Step 4 with head-only config +3. Compute SHA256: `curl -sL | shasum -a 256` +4. Extract version from tag (strip leading `v`) + +### Step 4: Analyze Dependencies + +1. Read the repo's build files to identify: + - Build dependencies (compilers, build tools) + - Runtime dependencies (shared libraries, interpreters) + - `uses_from_macos` candidates (zlib, curl, libxml2, etc.) +2. Check if the package produces a service (daemon) or CLI tool +3. Look for completions (bash, zsh, fish) in the source + +### Step 5: Build JSON Input + +Construct a JSON object conforming to the skill's `formula.schema.ts`: + +```json +{ + "formulas": [{ + "name": "", + "desc": "", + "homepage": "", + "url": "", + "sha256": "", + "license": "", + "language": "", + "livecheck": { "url": ":stable", "strategy": ":github_latest" }, + "dependencies": [...], + "uses_from_macos": [...], + "install": { ... }, + "test": { "command": "...", "expected_output": "..." } + }] +} +``` + +Read the schema at `context/skills/pkgmgr-homebrew-formula-dev/scripts/formula.schema.ts` to ensure all fields are valid. + +### Step 6: Render Formula + +1. Locate the skill directory: `context/skills/pkgmgr-homebrew-formula-dev` +2. Ensure dependencies are installed: `cd && npm ls mustache ajv 2>/dev/null || npm install` +3. Write the JSON to a temp file +4. Run: `cd && just template-formula "$(cat )"` +5. Capture the rendered Ruby output + +### Step 7: Write and Report + +1. Determine output path — ask the user or default to `Formula/.rb` +2. Write the rendered formula to the output path +3. Show the user the generated formula +4. Suggest next steps: + - `/validate-formula ` to run brew audit/style + - `brew install --build-from-source ` to test locally + +## Examples + +``` +/add-formula https://github.com/BurntSushi/ripgrep +/add-formula sharkdp/bat --language rust +/add-formula my-internal-tool --head-only +``` + +## Notes + +- The formula schema supports: go, rust, python, zig, cmake, autotools, meson +- For Python formulas, resources (pip dependencies) must be listed manually or extracted from `requirements.txt` +- HEAD-only formulas skip URL/SHA256 and use `head "https://github.com/..."` instead +- Always verify the generated formula with `/validate-formula` before committing diff --git a/context/plugins/homebrew-dev/commands/batch-formulas.md b/context/plugins/homebrew-dev/commands/batch-formulas.md new file mode 100644 index 00000000..24df5402 --- /dev/null +++ b/context/plugins/homebrew-dev/commands/batch-formulas.md @@ -0,0 +1,102 @@ +--- +description: Research and generate multiple Homebrew formulas in parallel from a list of packages +argument-hint: [--tap path/to/tap] [--validate] +allowed-tools: Read, Write, Glob, Grep, Bash(curl:*), Bash(shasum:*), Bash(just:*), Bash(git:*), Bash(gh:*), Bash(brew:*), Bash(npm:*), Bash(ls:*), Bash(mkdir:*), Bash(cat:*), WebFetch, WebSearch, Task, AskUserQuestion +--- + +# Batch Generate Homebrew Formulas + +Research multiple packages and generate Homebrew formulas for all of them, using parallel sub-agents for research and SHA256 computation. + +## Arguments + +- `$1` - Comma-separated list of repo URLs/names, OR path to a file with one package per line (required) +- `--tap` - Path to the tap's `Formula/` directory (optional, default: `Formula/`) +- `--validate` - Run `/validate-formula` on each generated formula after rendering (optional) + +## Workflow + +### Step 1: Parse Package List + +1. If `$1` is a file path (ends in `.txt`, `.json`, or `.yaml`), read it: + - `.txt`: one package per line (blank lines and `#` comments ignored) + - `.json`: array of strings or array of objects with `name`/`url` fields + - `.yaml`: same structure as JSON +2. If `$1` is a comma-separated string, split on commas and trim whitespace +3. Report the count: "Found N packages to process" + +### Step 2: Parallel Research + +For each package, launch a Task sub-agent to: + +1. Resolve the repository (same as `/add-formula` Step 1-2) +2. Fetch latest release tag and tarball URL +3. Compute SHA256 of the tarball: `curl -sL | shasum -a 256` +4. Detect language/build system +5. Analyze dependencies from build files +6. Return a structured JSON result + +Use parallel Task agents — up to 4 concurrent — to speed up research. + +### Step 3: Review Research Results + +Present a summary table to the user: + +``` +| # | Package | Version | Language | Deps | Status | +|---|---------|---------|----------|------|--------| +| 1 | ripgrep | 14.1.0 | rust | 2 | Ready | +| 2 | fd | 10.2.0 | rust | 1 | Ready | +| 3 | my-tool | — | go | 0 | HEAD-only | +``` + +Ask the user to confirm before rendering, or remove packages from the batch. + +### Step 4: Generate JSON and Render + +1. Build a single JSON object with all formulas in the `formulas` array +2. Write to a temp file +3. Run: `cd && just template-formula "$(cat )"` +4. Split the rendered output into individual formula files (split on `class ... < Formula`) + +### Step 5: Write Formula Files + +1. Create the output directory if needed: `mkdir -p /Formula/` +2. Write each formula to `/Formula/.rb` +3. Report files created + +### Step 6: Optional Validation + +If `--validate` was passed, run `/validate-formula` on each generated file and collect results. + +### Step 7: Final Report + +``` +## Batch Formula Generation Complete + +| Formula | File | Validation | +|---------|------|------------| +| ripgrep | Formula/ripgrep.rb | PASS | +| fd | Formula/fd.rb | PASS | +| my-tool | Formula/my-tool.rb | WARN (2) | + +Next steps: +- Review each formula for accuracy +- `brew install --build-from-source Formula/.rb` +- Commit and push to your tap +``` + +## Examples + +``` +/batch-formulas ripgrep,fd,bat,eza,delta +/batch-formulas packages.txt --tap homebrew-tap --validate +/batch-formulas tools.json --validate +``` + +## Notes + +- SHA256 computation requires network access to download tarballs +- Large batches (10+) may take a while due to tarball downloads +- Each formula is rendered independently — one failure won't block others +- The skill's template pipeline must be set up first (`cd && just deps`) diff --git a/context/plugins/homebrew-dev/commands/validate-formula.md b/context/plugins/homebrew-dev/commands/validate-formula.md new file mode 100644 index 00000000..aa5d0ff9 --- /dev/null +++ b/context/plugins/homebrew-dev/commands/validate-formula.md @@ -0,0 +1,96 @@ +--- +description: Validate a Homebrew formula with ruby syntax check, brew audit, and brew style +argument-hint: [--fix] [--strict] +allowed-tools: Read, Bash(ruby:*), Bash(brew:*), Bash(ls:*), Bash(cat:*), Glob, Grep +--- + +# Validate Homebrew Formula + +Run a multi-stage validation pipeline on a Homebrew formula file: Ruby syntax check, `brew audit`, and `brew style`. + +## Arguments + +- `$1` - Path to the formula `.rb` file (required) +- `--fix` - Auto-fix style issues with `brew style --fix` (optional) +- `--strict` - Run `brew audit --strict` for extra checks (optional) + +## Workflow + +### Step 1: Verify Input + +1. Confirm `$1` points to an existing `.rb` file +2. Read the file to confirm it contains a Homebrew formula (class inheriting from `Formula`) +3. Extract the formula name and class name for reporting + +### Step 2: Ruby Syntax Check + +Run `ruby -c "$1"` to verify valid Ruby syntax. + +- If it fails, read the file, identify the syntax error, and report the line number and issue +- Do not proceed to further checks if syntax is invalid + +### Step 3: Brew Audit + +Run the appropriate audit command: + +```bash +# Standard audit +brew audit --formula "$1" + +# With --strict flag +brew audit --strict --formula "$1" +``` + +Capture and categorize output: +- **Errors**: Must fix before submitting +- **Warnings**: Should fix, but not blocking +- **New formula warnings**: Expected for new formulas + +### Step 4: Brew Style + +Run style check: + +```bash +# Check only +brew style "$1" + +# With --fix flag (if user passed --fix) +brew style --fix "$1" +``` + +Report any RuboCop offenses found. + +### Step 5: Report Results + +Present a summary table: + +``` +## Validation Results: + +| Check | Status | Details | +|-------|--------|---------| +| Ruby syntax | PASS/FAIL | ... | +| brew audit | PASS/WARN/FAIL | N errors, M warnings | +| brew style | PASS/FAIL | N offenses | +``` + +If all checks pass, suggest: +- `brew install --build-from-source "$1"` to test the build +- `brew test ` to run the formula's test block + +If checks fail, list each issue with a suggested fix. + +## Examples + +``` +/validate-formula Formula/my-tool.rb +/validate-formula Formula/my-tool.rb --fix +/validate-formula Formula/my-tool.rb --strict +``` + +## Notes + +- Requires Homebrew installed locally (`brew` in PATH) +- `brew audit` needs the formula in a tap or uses `--formula` flag for standalone files +- `--fix` only applies to style (RuboCop) issues, not audit errors +- For new formulas, `--new` flag is automatically added to `brew audit` if the formula doesn't exist in any tap diff --git a/context/plugins/homebrew-dev/docs/.gitignore b/context/plugins/homebrew-dev/docs/.gitignore new file mode 100644 index 00000000..e9c07289 --- /dev/null +++ b/context/plugins/homebrew-dev/docs/.gitignore @@ -0,0 +1 @@ +book \ No newline at end of file diff --git a/context/plugins/homebrew-dev/docs/book.toml b/context/plugins/homebrew-dev/docs/book.toml new file mode 100644 index 00000000..72ed26e1 --- /dev/null +++ b/context/plugins/homebrew-dev/docs/book.toml @@ -0,0 +1,4 @@ +[book] +title = "Homebrew Dev" +authors = ["aRustyDev"] +language = "en" diff --git a/context/plugins/homebrew-dev/docs/src/SUMMARY.md b/context/plugins/homebrew-dev/docs/src/SUMMARY.md new file mode 100644 index 00000000..7390c828 --- /dev/null +++ b/context/plugins/homebrew-dev/docs/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Chapter 1](./chapter_1.md) diff --git a/context/plugins/homebrew-dev/docs/src/chapter_1.md b/context/plugins/homebrew-dev/docs/src/chapter_1.md new file mode 100644 index 00000000..b743fda3 --- /dev/null +++ b/context/plugins/homebrew-dev/docs/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 diff --git a/context/plugins/homebrew-dev/hooks/.gitkeep b/context/plugins/homebrew-dev/hooks/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/hooks/hooks.json b/context/plugins/homebrew-dev/hooks/hooks.json new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/justfile b/context/plugins/homebrew-dev/justfile new file mode 100644 index 00000000..a0edd70a --- /dev/null +++ b/context/plugins/homebrew-dev/justfile @@ -0,0 +1,15 @@ +# Build this plugin into dist/ +build: + just -f "{{justfile_directory()}}/../../justfile" build-plugin homebrew-dev + +# Install this plugin locally +install: + just -f "{{justfile_directory()}}/../../justfile" install-plugin homebrew-dev + +# Validate source mappings +check: + just -f "{{justfile_directory()}}/../../justfile" check-plugin-sources homebrew-dev + +# Run tests +test: check + echo "TODO: additional plugin-specific tests" diff --git a/context/plugins/homebrew-dev/scripts/.gitkeep b/context/plugins/homebrew-dev/scripts/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/skills/.gitkeep b/context/plugins/homebrew-dev/skills/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/.gitignore b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/.gitignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/README.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/README.md new file mode 100644 index 00000000..299ccf2f --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/README.md @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/SKILL.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/SKILL.md new file mode 100644 index 00000000..80a41035 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/SKILL.md @@ -0,0 +1,172 @@ +--- +name: pkgmgr-homebrew-formula-dev +description: Create, test, and maintain Homebrew formulas. Use when adding packages to a Homebrew tap, debugging formula issues, running brew audit/test, or automating version updates with livecheck. Use when creating a new Homebrew formula for a project. +--- + +# Homebrew Formula Development + +Guide for researching, creating, testing, and maintaining Homebrew formulas in a custom tap. + +## When to Use This Skill + +- Creating a new Homebrew formula for a project +- Debugging formula build or test failures +- Running local validation before CI +- Understanding Homebrew's Ruby DSL +- Setting up livecheck for automatic version detection + +## Template Pipeline + +This skill includes a **JSON Schema → Mustache template** pipeline for generating formulas from structured data. + +### Workflow + +1. Create a JSON file conforming to `scripts/formula.schema.ts` +2. Run `just template-formula ` to validate and render +3. The pipeline validates with AJV, preprocesses (PascalCase, language dispatch, license rendering), then renders via Mustache + +### Key Files + +| File | Purpose | +|------|---------| +| `scripts/formula.schema.ts` | JSON Schema (draft-2020-12) defining formula structure | +| `scripts/formula.helper.ts` | Preprocessing: PascalCase, license rendering, install flattening, partials loading | +| `reference/templates/main.mustache` | Main template — renders all shared fields, dispatches to language partials | +| `reference/templates/langs/*.mustache` | Language-specific install partials (go, rust, python, zig, cmake, autotools, meson) | +| `test/data/*.json` | Test fixtures covering each scenario | +| `test/cases/*.sh` | Test cases that validate rendered output | + +### Running + +```bash +# Render a formula from JSON +just template-formula path/to/formula.json + +# Run all tests +just test +``` + +### Adding a New Language + +1. Add `install-` definition to `scripts/formula.schema.ts` +2. Add language dispatch `allOf` entry in the `formula` definition +3. Create `reference/templates/langs/.mustache` partial +4. Add `""` to the `language` enum +5. Add a test fixture in `test/data/` and test case in `test/cases/` + +## Research Phase + +Before creating a formula, gather this information: + +| Field | How to Find | +|-------|-------------| +| Latest version | `gh api repos/owner/repo/releases/latest --jq '.tag_name'` (404 → HEAD-only) | +| License | Check LICENSE file or repo metadata (use SPDX identifier) | +| Build system | Look at Makefile, go.mod, Cargo.toml, pyproject.toml, etc. | +| Dependencies | Check build docs, CI files, or dependency manifests | +| Default branch | Check repo settings — may be `main` or `master` | +| Binary name | May differ from formula name — check Cargo.toml `[[bin]]`, Go `cmd/`, or pyproject.toml `[project.scripts]` | + +### Determine Formula Type + +| Scenario | Type | Has `url`/`sha256`? | Has `livecheck`? | +|----------|------|---------------------|------------------| +| Tagged releases | Standard | Yes | Yes | +| No releases | HEAD-only | No | No | +| Monorepo subdirectory | Standard | Yes | Yes | + +### Calculate SHA256 + +```bash +curl -sL "https://github.com/owner/repo/archive/refs/tags/vX.Y.Z.tar.gz" | shasum -a 256 +``` + +### Formula Naming + +- Formula name: **kebab-case** (`hex-patch`, `jwt-ui`) +- Class name: **PascalCase** (`HexPatch`, `JwtUi`) + +## Formula Structure + +### File Location + +Formulas are organized alphabetically: `Formula//.rb` + +### Key Elements + +| Element | Purpose | +|---------|---------| +| `desc` | Short description (~80 chars) for `brew info` | +| `homepage` | Project homepage URL | +| `url` | Source tarball URL (omit for HEAD-only) | +| `sha256` | Checksum (omit for HEAD-only) | +| `license` | SPDX identifier | +| `head` | Git URL for `--HEAD` installs | +| `livecheck` | Auto-detect new versions (omit for HEAD-only) | +| `depends_on` | Build or runtime dependencies | +| `test` | Verification block | + +### SPDX License Identifiers + +| License | SPDX | +|---------|------| +| MIT | `"MIT"` | +| Apache 2.0 | `"Apache-2.0"` | +| GPL 3.0 (only) | `"GPL-3.0-only"` | +| GPL 3.0 (or later) | `"GPL-3.0-or-later"` | +| BSD 2-Clause | `"BSD-2-Clause"` | +| BSD 3-Clause | `"BSD-3-Clause"` | + +Always specify `-only` or `-or-later` for GPL/LGPL/AGPL. + +## Language-Specific Patterns + +Each language has a reference doc with install patterns, schema fields, and common issues: + +- **Go:** `reference/langs/go.md` +- **Rust:** `reference/langs/rust.md` +- **Python:** `reference/langs/python.md` + +Additional languages supported by the template pipeline (cmake, autotools, meson, zig) — see their Mustache partials in `reference/templates/langs/`. + +## Reference Materials + +| Topic | Location | +|-------|----------| +| Local validation steps | `reference/checklists/local-validation.md` | +| Common issues & FAQ | `reference/faq/common.md` | +| Test block patterns | `reference/testing/patterns.md` | +| Generated formula examples | `reference/templates/formulas/*.rb` | +| JSON Schema definition | `scripts/formula.schema.ts` | +| Bottle attestation & provenance | `reference/security/attestation.md` | + +## Batch Formula Creation + +When creating many formulas at once: + +1. **Compute SHA256 hashes in parallel** — launch multiple `curl | shasum` calls concurrently +2. **Research build details in parallel** — check build manifests concurrently +3. **Write all formula files** — no dependencies between them +4. **Create branches/PRs sequentially** — one branch per formula, each from main +5. **Use `ruby -c *.rb`** to syntax-check all formulas before pushing + +## Checklist + +- [ ] Research complete (version, license, build system, deps, binary name, default branch) +- [ ] Formula type determined (standard vs HEAD-only) +- [ ] SHA256 calculated (if not HEAD-only) +- [ ] Formula file created at `Formula//.rb` +- [ ] `ruby -c` passes +- [ ] `brew audit --new` passes +- [ ] `brew style arustydev/tap` passes +- [ ] `brew install --build-from-source` succeeds +- [ ] `brew test` passes +- [ ] Binary executes correctly +- [ ] PR created with CI passing + +## References + +- [Homebrew Formula Cookbook](https://docs.brew.sh/Formula-Cookbook) +- [SPDX License List](https://spdx.org/licenses/) +- [Homebrew Ruby Style Guide](https://docs.brew.sh/Ruby-Style-Guide) +- [Homebrew Python Guidelines](https://docs.brew.sh/Python-for-Formula-Authors) diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/TODO.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/TODO.md new file mode 100644 index 00000000..09e4520e --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/TODO.md @@ -0,0 +1,33 @@ +- [ ] Zig support +- [ ] C/C++ support +- [ ] Java support +- [ ] Roc support +- [ ] Rust support +- [ ] Go support +- [ ] Python support +- [ ] TypeScript support +- [ ] Swift support +- [ ] Kotlin support +- [ ] Scala support +- [ ] Haskell support +- [ ] Lua support +- [ ] JavaScript support +- [ ] PHP support +- [ ] Ruby support +- [ ] Erlang support +- [ ] Elixir support +- [ ] Clojure support +- [ ] CSharp support +- [ ] Objective-C support +- [ ] FSharp support +- [ ] Fortran support +- [ ] Dart support +- [ ] Erlang support +- [ ] Gleam support +- [ ] Groovy support +- [ ] Scheme support +- [ ] OCaml support +- [ ] Perl support +- [ ] Nim support +- [ ] Julia support +- [ ] R support diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/justfile b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/justfile new file mode 100644 index 00000000..5447f725 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/justfile @@ -0,0 +1,77 @@ +export PATH := "./node_modules/.bin:" + env('PATH') +export NODE_PATH := justfile_directory() + "/node_modules" + +# ───────────────────────────────────────────────────────────────────────────── +# Setup +# ───────────────────────────────────────────────────────────────────────────── + +# Install dependencies (macOS) +[group('setup')] +[macos] +deps: + brew bundle -f "{{ justfile_directory() }}/reference/Brewfile" + npm install + +# Print kebab-case and PascalCase for a formula name +[group('setup')] +add-formula name: + echo "{{ kebabcase(name) }}" + echo "{{ uppercamelcase(name) }}" + +# ───────────────────────────────────────────────────────────────────────────── +# Template Pipeline +# ───────────────────────────────────────────────────────────────────────────── + +# Validate JSON against schema and render a Homebrew formula via Mustache +[group('pipeline')] +[no-exit-message] +template-formula json: + #!/usr/bin/env node + const Mustache = require('mustache'); + const fs = require('fs'); + const path = require('path'); + const Ajv = require('ajv/dist/2020'); + + Mustache.escape = (text) => text; + const { SCHEMA } = require('{{ justfile_directory() }}/scripts/formula.schema.ts'); + const { preprocessFormulas, loadPartials, parseInput } = require('{{ justfile_directory() }}/scripts/formula.helper.ts'); + const baseDir = '{{ justfile_directory() }}/reference/templates'; + + const jsObject = JSON.parse(parseInput(`{{json}}`)); + const ajv = new Ajv(); + const validate = ajv.compile(SCHEMA); + if (!validate(jsObject)) { + console.error(JSON.stringify(validate.errors, null, 2)); + process.exit(1); + } + + const partials = loadPartials(path.join(baseDir, 'langs')); + preprocessFormulas(jsObject.formulas); + + const main = fs.readFileSync(path.join(baseDir, 'main.mustache'), 'utf8'); + console.log(Mustache.render(main, jsObject, partials)); + +# ───────────────────────────────────────────────────────────────────────────── +# Testing +# ───────────────────────────────────────────────────────────────────────────── + +# Run all test cases in test/cases/ +[group('test')] +[no-exit-message] +test: + #!/usr/bin/env bash + set -euo pipefail + cd "{{ justfile_directory() }}" + passed=0 + failed=0 + for t in test/cases/test-*.sh; do + echo "--- Running $(basename "$t") ---" + if bash "$t"; then + passed=$((passed + 1)) + else + failed=$((failed + 1)) + fi + done + echo "" + echo "Results: $passed passed, $failed failed" + [ "$failed" -eq 0 ] diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/package-lock.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/package-lock.json new file mode 100644 index 00000000..3b1eeb6d --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/package-lock.json @@ -0,0 +1,99 @@ +{ + "name": "pkgmgr-homebrew-formula-dev", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pkgmgr-homebrew-formula-dev", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "ajv": "^8.17.1", + "mustache": "^4.2.0" + }, + "devDependencies": { + "@types/node": "^25.1.0" + } + }, + "node_modules/@types/node": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", + "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/package.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/package.json new file mode 100644 index 00000000..6503831e --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/package.json @@ -0,0 +1,20 @@ +{ + "name": "pkgmgr-homebrew-formula-dev", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "ajv": "^8.17.1", + "mustache": "^4.2.0" + }, + "devDependencies": { + "@types/node": "^25.1.0" + } +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/Brewfile b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/Brewfile new file mode 100644 index 00000000..9e215f6b --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/Brewfile @@ -0,0 +1,3 @@ +# bash +# jq +# yq diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/checklists/local-validation.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/checklists/local-validation.md new file mode 100644 index 00000000..5b5ef0c1 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/checklists/local-validation.md @@ -0,0 +1,48 @@ +## Local Validation + +### Step 1: Sync to Tap Location + +Formula files must be in the Homebrew tap location for testing: + +```bash +mkdir -p /opt/homebrew/Library/Taps/arustydev/homebrew-tap/Formula// +cp Formula//.rb /opt/homebrew/Library/Taps/arustydev/homebrew-tap/Formula// +``` + +**Note:** On Apple Silicon Macs the prefix is `/opt/homebrew`, on Intel Macs it's `/usr/local`. Check with `brew --prefix`. + +### Step 2: Run Audit + +```bash +brew audit --new --formula arustydev/tap/ +``` + +This checks for common formula issues but NOT style violations. + +### Step 3: Run CI Syntax Check (Critical) + +```bash +brew style arustydev/tap +``` + +This runs rubocop against ALL files in the tap — same as CI. This catches issues that `brew audit` misses. + +### Step 4: Test Installation + +```bash +brew install --build-from-source arustydev/tap/ +# For HEAD-only: +brew install --HEAD arustydev/tap/ +``` + +### Step 5: Run Formula Tests + +```bash +brew test arustydev/tap/ +``` + +### Step 6: Verify Binary + +```bash + --help +``` diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/faq/common.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/faq/common.md new file mode 100644 index 00000000..e1035cfa --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/faq/common.md @@ -0,0 +1,25 @@ +## Common Issues + +### CI Failures from Rubocop + +**Problem:** `brew style` fails on markdown files containing Ruby code blocks. + +**Cause:** `brew style` uses rubocop-md which lints code fenced as `ruby` in markdown files. The tap's `.rubocop.yml` exclusions do NOT apply — `brew style` uses Homebrew's central config. + +**Solution:** Use `text` instead of `ruby` for code fence language in any markdown documentation. + +### Line Length Errors + +**Problem:** Lines longer than 118 characters. + +**Solution:** Split long strings or use Homebrew's allowed patterns (URLs, sha256 lines, etc. are exempt). + +### Test Block Failures + +**Problem:** Formula installs but `brew test` fails. + +**Solution:** Check if the binary exits non-zero on `--help` and use `shell_output("...", exit_code)`. Ensure test creates necessary files and uses `testpath`. + +### Typed/Frozen String Headers + +The `# typed: strict` and `# frozen_string_literal: true` headers are **optional** for tap formulas. They are not enforced by `brew style` and many tap formulas omit them. Include them for consistency if the tap already uses them, otherwise omit. diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/langs/go.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/langs/go.md new file mode 100644 index 00000000..66587b38 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/langs/go.md @@ -0,0 +1,69 @@ +## Go Formula Patterns + +### Researching a Go Project + +| What | Where to Look | Command / File | +|------|---------------|----------------| +| Build system confirmation | Repo root | Look for `go.mod` — confirms it's a Go module | +| Binary name(s) | `cmd/` directory or repo root | `ls cmd/` — each subdirectory is typically a binary | +| Main package location | `cmd//main.go` or `main.go` at root | If root: no path arg needed; if `cmd/`: use `"./cmd/"` | +| Version injection | `main.go` or `internal/version` | `grep -r 'version' main.go` — look for `var version` to set via `-X` ldflags | +| CGO usage | Source files | `grep -r 'import "C"'` or `grep -r '#cgo'` — if found, need system library deps | +| Build tags | Source files, CI config | `grep -r '//go:build'` or check CI for `-tags` flags | +| Runtime dependencies | Import statements, docs | `grep -r 'os/exec'` for external tool calls; check README for runtime requirements | +| Completions | CLI framework used | If using cobra: look for `GenBashCompletion`; if using urfave/cli: check for `EnableBashCompletion` | +| Test command | README, `--help` output | Most Go CLIs support `--help` or `--version`; check if exit code is 0 | + +**Quick check sequence:** + +```bash +# Confirm Go project and find binaries +gh api repos/OWNER/REPO/contents/go.mod --jq '.name' 2>/dev/null && echo "Go project" +gh api repos/OWNER/REPO/contents/cmd --jq '.[].name' 2>/dev/null || echo "main.go at root" + +# Check for version variable +gh api repos/OWNER/REPO/contents/main.go --jq '.content' | base64 -d | grep -i 'version' +``` + +### Dependencies + +```text +depends_on "go" => :build +``` + +### Install Block + +```text +def install + system "go", "build", *std_go_args(ldflags: "-s -w -X main.version=#{version}"), "./cmd/binary" +end +``` + +- If `main.go` is at repo root: omit the path argument +- If `main.go` is at `./cmd//`: include `"./cmd/"` +- `-s -w` strips debug info for smaller binary +- `-X main.version=#{version}` injects version at build time + +### JSON Schema Fields (`install-go`) + +| Field | Default | Purpose | +|-------|---------|---------| +| `ldflags` | `"-s -w -X main.version=#{version}"` | Linker flags | +| `cmd_path` | `"./cmd/..."` | Go package path to build | +| `output` | formula name | Output binary name | +| `tags` | — | Go build tags (e.g. `["netgo", "osusergo"]`) | +| `env` | — | Environment variables (e.g. `{"CGO_ENABLED": "0"}`) | + +### Mustache Partial + +The `langs/go.mustache` partial renders `std_go_args(ldflags:)` with optional `cmd_path`. + +### Common Issues + +- **Binary name differs from formula name:** Check for `cmd/` subdirectories — the directory name is usually the binary name +- **CGO dependencies:** If the project uses CGO, add system library deps and ensure `CGO_ENABLED` is not set to `0` +- **Multiple binaries:** Use `cmd_path: "./cmd/..."` to build all, or specify individual paths + +### Reference + +See `reference/templates/formulas/go.rb` for a pipeline-generated example. diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/langs/python.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/langs/python.md new file mode 100644 index 00000000..91a67123 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/langs/python.md @@ -0,0 +1,97 @@ +## Python Formula Patterns + +### Researching a Python Project + +| What | Where to Look | Command / File | +|------|---------------|----------------| +| Build system | Repo root | `pyproject.toml` (modern), `setup.py`/`setup.cfg` (legacy) | +| Binary name(s) | `pyproject.toml` `[project.scripts]` or `setup.py` `entry_points` | The key is the binary name, the value is the module path | +| Python version | `pyproject.toml` `requires-python` | Map to Homebrew's `python@3.XX` — use latest compatible | +| Direct dependencies | `pyproject.toml` `[project.dependencies]` or `requirements.txt` | Each becomes a `resource` block unless already in homebrew-core | +| Resource URLs | PyPI | `https://pypi.org/project//#files` — use the `.tar.gz` sdist, not wheel | +| Completions | CLI framework | If using `click`: look for `shell_complete`; if using `argparse`: check for custom completion | +| Native extensions | `setup.py` or `pyproject.toml` build-backend | If using `setuptools` with C extensions: needs compiler deps | +| Test command | README, `--help` output | Check if tool supports `--version` or `--help` | + +**Quick check sequence:** + +```bash +# Confirm Python project, find entry points +gh api repos/OWNER/REPO/contents/pyproject.toml --jq '.content' | base64 -d | grep -A10 '\[project.scripts\]' + +# Find Python version requirement +gh api repos/OWNER/REPO/contents/pyproject.toml --jq '.content' | base64 -d | grep 'requires-python' + +# List dependencies +gh api repos/OWNER/REPO/contents/pyproject.toml --jq '.content' | base64 -d | grep -A30 '\[project\]' | grep -A20 'dependencies' +``` + +**Generating resource blocks:** After initial formula creation, use `brew update-python-resources ` to auto-generate resource blocks from PyPI. + +### Dependencies + +```text +depends_on "python@3.12" +``` + +### Install Block + +```text +include Language::Python::Virtualenv + +def install + virtualenv_install_with_resources +end +``` + +- `virtualenv_install_with_resources` creates a venv, installs resource blocks, then installs the formula +- Check `pyproject.toml`, `setup.py`, or `setup.cfg` for the project's build system + +### JSON Schema Fields (`install-python`) + +| Field | Default | Purpose | +|-------|---------|---------| +| `python_version` | `"python3"` | Python version dependency | +| `using` | `"virtualenv"` | Install method (`virtualenv`, `pip`, `setuptools`) | +| `site_packages` | `false` | Allow access to system site-packages | +| `resources` | — | Python package dependencies (defined at formula level) | + +### Mustache Partial + +The `langs/python.mustache` partial renders `virtualenv_install_with_resources`. + +### Resource Blocks + +Python formulas need `resource` blocks for pip dependencies not in homebrew-core: + +```text +resource "certifi" do + url "https://files.pythonhosted.org/packages/certifi-2024.2.2.tar.gz" + sha256 "..." +end +``` + +Generate resource blocks with: + +```bash +brew update-python-resources +``` + +### Entry Points + +Check `pyproject.toml` `[project.scripts]` for the binary names the formula will install: + +```toml +[project.scripts] +my-tool = "my_package.cli:main" +``` + +### Common Issues + +- **Missing resources:** Python dependencies must be declared as `resource` blocks — they're not auto-resolved +- **Version pinning:** Use the exact source tarball versions that match the project's requirements +- **Site packages:** Set `site_packages: true` only when the formula needs access to Homebrew-installed Python packages + +### Reference + +See `reference/templates/formulas/python.rb` for a pipeline-generated example. diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/langs/rust.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/langs/rust.md new file mode 100644 index 00000000..dd766d10 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/langs/rust.md @@ -0,0 +1,88 @@ +## Rust Formula Patterns + +### Researching a Rust Project + +| What | Where to Look | Command / File | +|------|---------------|----------------| +| Build system confirmation | Repo root | Look for `Cargo.toml` — confirms it's a Rust/Cargo project | +| Binary name(s) | `Cargo.toml` `[[bin]]` section | May differ from repo name (e.g. `jwt-ui` installs `jui`) | +| Workspace layout | `Cargo.toml` `[workspace]` | If workspace: find the CLI crate in `members` list, use its path | +| Features | `Cargo.toml` `[features]` | Check for `default`, optional features like `tls`, `jemalloc` | +| System dependencies | `build.rs`, CI config | `grep -r 'pkg-config\|system-deps\|cc::Build'` — indicates C library deps | +| OpenSSL / TLS usage | `Cargo.toml` deps | `grep -i 'openssl\|native-tls\|rustls'` — may need `openssl@3` dep | +| uses_from_macos | Linked system libs | If uses `zlib`, `libxml2`, `curl` etc. via `-sys` crates | +| Completions | CLI framework | If using `clap`: look for `clap_complete` in deps; check for `completions` subcommand | +| Minimum Rust version | `Cargo.toml` `rust-version` or `rust-toolchain.toml` | Homebrew provides latest stable — flag if MSRV is unusual | +| Test command | README, `--help` output | Most Rust CLIs support `--help` or `--version` | + +**Quick check sequence:** + +```bash +# Confirm Rust project, find binary names and features +gh api repos/OWNER/REPO/contents/Cargo.toml --jq '.content' | base64 -d | grep -A5 '\[\[bin\]\]' +gh api repos/OWNER/REPO/contents/Cargo.toml --jq '.content' | base64 -d | grep -A20 '\[features\]' + +# Check for system deps +gh api repos/OWNER/REPO/contents/build.rs --jq '.name' 2>/dev/null && echo "Has build.rs — check for C deps" +``` + +### Dependencies + +```text +depends_on "rust" => :build +``` + +### Install Block + +```text +def install + system "cargo", "install", *std_cargo_args +end +``` + +- `std_cargo_args` handles `--root`, `--path`, and `--locked` +- Check `Cargo.toml` `[[bin]]` — binary name may differ from formula name (e.g. `jwt-ui` installs `jui`) + +### JSON Schema Fields (`install-rust`) + +| Field | Default | Purpose | +|-------|---------|---------| +| `path` | `"."` | Path to Cargo.toml directory | +| `features` | — | Cargo features to enable | +| `all_features` | `false` | Enable all Cargo features | +| `no_default_features` | `false` | Disable default features | +| `bins` | — | Specific binaries to install | + +### Mustache Partial + +The `langs/rust.mustache` partial renders `std_cargo_args` with optional `path` and `features`. + +### Feature Selection + +When a crate has optional features: + +```text +system "cargo", "install", *std_cargo_args, "--features", "tls,jemalloc" +``` + +### Monorepo / Workspace Builds + +When building from a subdirectory of a workspace: + +```text +def install + cd "crates/cli" do + system "cargo", "install", *std_cargo_args + end +end +``` + +### Common Issues + +- **Lock file version mismatch:** Ensure the Rust toolchain version matches the project's `Cargo.lock` +- **Vendored dependencies:** Some projects vendor C libraries — check for `build.rs` and add system deps +- **uses_from_macos:** Rust projects using `openssl` often need `uses_from_macos "zlib"` + +### Reference + +See `reference/templates/formulas/rust.rb` for a pipeline-generated example. diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/security/attestation.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/security/attestation.md new file mode 100644 index 00000000..1a7a9327 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/security/attestation.md @@ -0,0 +1,91 @@ +## Bottle Attestation & Build Provenance + +Homebrew supports cryptographic attestation of bottle artifacts using Sigstore and GitHub's artifact attestation infrastructure. This is an **infrastructure-level** feature — it applies to the CI/CD pipeline that builds bottles, not to individual formula files. + +### How It Works + +1. Bottles are built in CI (GitHub Actions) +2. The build workflow generates a Sigstore-signed attestation linking the artifact to the source repo and workflow +3. On `brew install`, Homebrew verifies the attestation before installing the bottle +4. Verification uses `gh attestation verify` under the hood + +This achieves **SLSA Build L2** provenance — users can verify that a bottle was built from a specific commit by a specific workflow. + +### Homebrew Core Behavior + +- Homebrew 5.0+ verifies attestations by default for `homebrew-core` bottles +- Controlled by `HOMEBREW_VERIFY_ATTESTATIONS=1` environment variable +- Requires the `gh` CLI to be installed +- Falls back to a backfill repository (`trailofbits/homebrew-brew-verify`) for bottles built before attestation was enabled + +### Setting Up Attestation for a Custom Tap + +To enable attestation verification for bottles in your own tap: + +#### 1. Add attestation to your bottle-building workflow + +```yaml +# .github/workflows/bottles.yml +permissions: + id-token: write # Required for Sigstore signing + attestations: write # Required to store attestations + contents: read + +jobs: + build-bottle: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Build bottle + run: | + brew install --build-bottle myformula + brew bottle myformula + + - name: Attest bottle artifact + uses: actions/attest-build-provenance@v3 + with: + subject-path: '*.bottle.tar.gz' +``` + +#### 2. Verify attestations locally + +```bash +# Verify a bottle artifact against the tap repo +gh attestation verify myformula--1.0.0.arm64_sonoma.bottle.tar.gz \ + --repo owner/homebrew-tap +``` + +#### 3. Programmatic verification (Ruby API) + +Homebrew exposes `Homebrew::Attestation` for internal verification: + +| Method | Purpose | +|--------|---------| +| `check_attestation(bottle, signing_repo, signing_workflow)` | Verify a bottle against any repo's attestations | +| `check_core_attestation(bottle)` | Verify against `Homebrew/homebrew-core` | +| `enabled?` | Check if attestation verification is active | + +For third-party taps, `check_attestation` accepts any `signing_repo` — your tap's GitHub repo. + +### Key Requirements + +| Requirement | Details | +|-------------|---------| +| GitHub Actions | Attestations are generated via GitHub's infrastructure | +| `gh` CLI | Required on the verifying machine | +| Workflow permissions | `id-token: write` and `attestations: write` | +| Sigstore | Used for signing — no key management needed | +| Public repo | GitHub artifact attestations require public repositories (or GitHub Enterprise) | + +### What This Means for Formula Authors + +- **You don't need to do anything in the formula file** — attestation is handled by the bottle-building CI workflow +- **SHA256 in the formula** still validates the source tarball — attestation validates the pre-built bottle +- **HEAD-only formulas** don't use bottles, so attestation doesn't apply + +### References + +- [Homebrew Build Provenance (Trail of Bits)](https://blog.trailofbits.com/2024/05/14/a-peek-into-build-provenance-for-homebrew/) +- [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) +- [GitHub Artifact Attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations) diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/ISSUE_TEMPLATE.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..6d479dba --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/ISSUE_TEMPLATE.md @@ -0,0 +1,43 @@ +--- +name: New Formula Request +about: Request a Homebrew formula for a new package +labels: formula, new +--- + +## Package + +- **Name:** +- **Repository:** +- **Language:** +- **License:** + +## Release + +- **Latest version:** +- **Release URL:** +- **Source tarball:** +- **SHA256:** + +## Build Details + +- **Binary name(s):** +- **Build dependencies:** +- **Runtime dependencies:** +- **uses_from_macos:** + +## Install Notes + + + +## Test Command + +```bash +# Command to verify the install works +``` + +## Checklist + +- [ ] Repository is public +- [ ] Has tagged releases (or explicitly HEAD-only) +- [ ] License file exists in repo +- [ ] Binary runs on macOS (arm64 + x86_64) diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/PULL_REQUEST_TEMPLATE.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..12d28569 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +## Formula + +- **Name:** +- **Version:** +- **Language:** +- **Type:** + +## Changes + + + +## Validation + +- [ ] `ruby -c Formula//.rb` passes +- [ ] `brew audit --new ` passes (new formulas) +- [ ] `brew audit ` passes (existing formulas) +- [ ] `brew style ` passes +- [ ] `brew install --build-from-source ` succeeds +- [ ] `brew test ` passes +- [ ] Binary executes correctly (` --version` or `--help`) +- [ ] Livecheck returns correct version (`brew livecheck `) + +## Test Output + +``` + +``` + +## Notes + + diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/go.rb b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/go.rb new file mode 100644 index 00000000..72b72033 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/go.rb @@ -0,0 +1,34 @@ +class MyTool < Formula + desc "A fast CLI tool written in Go" + homepage "https://github.com/example/my-tool" + url "https://github.com/example/my-tool/archive/refs/tags/v1.2.3.tar.gz" + sha256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + license "MIT" + head "https://github.com/example/my-tool.git", branch: "main" + + livecheck do + url :stable + strategy :github_latest + end + + depends_on "go" => :build + + depends_on "pkg-config" => :build + + def install + system "go", "build", *std_go_args(ldflags: "-s -w -X main.version=#{version}"), "./cmd/my-tool" + + generate_completions_from_executable(bin/"my-tool", "completions", "bash", + shell_output_dir: bash_completion) + generate_completions_from_executable(bin/"my-tool", "completions", "zsh", + shell_output_dir: zsh_completion) + generate_completions_from_executable(bin/"my-tool", "completions", "fish", + shell_output_dir: fish_completion) + end + + test do + system bin/"my-tool", "--version" + assert_match "my-tool version #{version}", shell_output("#{bin}/my-tool --help") + end +end + diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/head-only.rb b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/head-only.rb new file mode 100644 index 00000000..50e84e93 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/head-only.rb @@ -0,0 +1,18 @@ +class BleedingEdge < Formula + desc "A tool that only builds from HEAD" + homepage "https://github.com/example/bleeding-edge" + license "GPL-3.0-only" + head "https://github.com/example/bleeding-edge.git", branch: "main" + + depends_on "go" => :build + + def install + system "go", "build", *std_go_args(ldflags: "-s -w") + end + + test do + system bin/"bleeding-edge", "--help" + assert_match "bleeding-edge", shell_output("#{bin}/bleeding-edge --help") + end +end + diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/python.rb b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/python.rb new file mode 100644 index 00000000..36191710 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/python.rb @@ -0,0 +1,34 @@ +class MyPythonTool < Formula + desc "A Python-based CLI utility" + homepage "https://github.com/example/my-python-tool" + url "https://github.com/example/my-python-tool/archive/refs/tags/v2.0.0.tar.gz" + sha256 "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" + license "MIT" + + livecheck do + url :stable + strategy :pypi + end + + depends_on "python@3.12" => :build + + resource "certifi" do + url "https://files.pythonhosted.org/packages/certifi-2024.2.2.tar.gz" + sha256 "1111111111111111111111111111111111111111111111111111111111111111" + end + + resource "urllib3" do + url "https://files.pythonhosted.org/packages/urllib3-2.2.1.tar.gz" + sha256 "2222222222222222222222222222222222222222222222222222222222222222" + end + + def install + virtualenv_install_with_resources + end + + test do + system bin/"my-python-tool", "--version" + assert_match "my-python-tool #{version}", shell_output("#{bin}/my-python-tool --help") + end +end + diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/rust.rb b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/rust.rb new file mode 100644 index 00000000..08af0677 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/rust.rb @@ -0,0 +1,25 @@ +class RustAnalyzer < Formula + desc "Experimental Rust compiler front-end for IDEs" + homepage "https://rust-analyzer.github.io" + url "https://github.com/rust-lang/rust-analyzer/archive/refs/tags/v0.3.1.tar.gz" + sha256 "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" + license "Apache-2.0" + + livecheck do + url :stable + strategy :github_latest + end + + depends_on "rust" => :build + uses_from_macos "zlib" + + def install + system "cargo", "install", *std_cargo_args, "--features", "lsp", "--features", "jemalloc" + end + + test do + system bin/"rust-analyzer", "--help" + assert_match "rust-analyzer", shell_output("#{bin}/rust-analyzer --help") + end +end + diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/service.rb b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/service.rb new file mode 100644 index 00000000..0c251576 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/formulas/service.rb @@ -0,0 +1,38 @@ +class MyDaemon < Formula + desc "A background service daemon" + homepage "https://github.com/example/my-daemon" + url "https://github.com/example/my-daemon/archive/refs/tags/v1.0.0.tar.gz" + sha256 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + license "MIT" + + livecheck do + url :stable + strategy :github_latest + end + + depends_on "go" => :build + + def install + system "go", "build", *std_go_args(ldflags: "-s -w -X main.version=#{version}") + end + + service do + run ["#{opt_bin}/my-daemon", "--config", "#{etc}/my-daemon.conf"] + run_type :immediate + keep_alive true + log_path "#{var}/log/my-daemon.log" + error_log_path "#{var}/log/my-daemon-error.log" + end + + test do + system bin/"my-daemon", "--version" + assert_match "my-daemon version #{version}", shell_output("#{bin}/my-daemon --help") + end + + def caveats + <<~EOS + To start my-daemon now and restart at login:\n brew services start my-daemon + EOS + end +end + diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/autotools.mustache b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/autotools.mustache new file mode 100644 index 00000000..dac3adb5 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/autotools.mustache @@ -0,0 +1,5 @@ + {{#autoreconf}} + system "autoreconf", "-fiv" + {{/autoreconf}} + system "./configure", *std_configure_args{{#configure_args}}, "{{.}}"{{/configure_args}} + system "make", "install" diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/cmake.mustache b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/cmake.mustache new file mode 100644 index 00000000..858ef53b --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/cmake.mustache @@ -0,0 +1,13 @@ + {{#source_dir}} + cd "{{source_dir}}" do + {{/source_dir}} + args = std_cmake_args + {{#cmake_args}} + args << "{{.}}" + {{/cmake_args}} + system "cmake", "-S", "{{#source_dir}}{{source_dir}}{{/source_dir}}{{^source_dir}}.{{/source_dir}}", "-B", "{{#build_dir}}{{build_dir}}{{/build_dir}}{{^build_dir}}build{{/build_dir}}", *args + system "cmake", "--build", "{{#build_dir}}{{build_dir}}{{/build_dir}}{{^build_dir}}build{{/build_dir}}" + system "cmake", "--install", "{{#build_dir}}{{build_dir}}{{/build_dir}}{{^build_dir}}build{{/build_dir}}" + {{#source_dir}} + end + {{/source_dir}} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/go.mustache b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/go.mustache new file mode 100644 index 00000000..00ec6dd7 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/go.mustache @@ -0,0 +1,4 @@ + {{#env}} + ENV["CGO_ENABLED"] = "{{CGO_ENABLED}}" + {{/env}} + system "go", "build", *std_go_args(ldflags: "{{ldflags}}"){{#cmd_path}}, "{{cmd_path}}"{{/cmd_path}} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/meson.mustache b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/meson.mustache new file mode 100644 index 00000000..0fbf1a0a --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/meson.mustache @@ -0,0 +1,3 @@ + system "meson", "setup", "{{#build_dir}}{{build_dir}}{{/build_dir}}{{^build_dir}}build{{/build_dir}}", *std_meson_args{{#meson_args}}, "{{.}}"{{/meson_args}} + system "meson", "compile", "-C", "{{#build_dir}}{{build_dir}}{{/build_dir}}{{^build_dir}}build{{/build_dir}}" + system "meson", "install", "-C", "{{#build_dir}}{{build_dir}}{{/build_dir}}{{^build_dir}}build{{/build_dir}}" diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/python.mustache b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/python.mustache new file mode 100644 index 00000000..a1a3b804 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/python.mustache @@ -0,0 +1 @@ + virtualenv_install_with_resources diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/rust.mustache b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/rust.mustache new file mode 100644 index 00000000..e04fbc98 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/rust.mustache @@ -0,0 +1 @@ + system "cargo", "install", *std_cargo_args{{#path}}(path: "{{path}}"){{/path}}{{#features}}, "--features", "{{.}}"{{/features}} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/zig.mustache b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/zig.mustache new file mode 100644 index 00000000..a063bd77 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/langs/zig.mustache @@ -0,0 +1,4 @@ + system "zig", "build"{{#build_mode}}, "-Doptimize={{build_mode}}"{{/build_mode}}{{#targets}}{{#.}}, "{{.}}"{{/.}}{{/targets}} + {{#bins}} + bin.install ".zig-out/bin/{{.}}" + {{/bins}} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/main.mustache b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/main.mustache new file mode 100644 index 00000000..581319ae --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/templates/main.mustache @@ -0,0 +1,170 @@ +{{#formulas}} +class {{class_name}} < Formula + desc "{{desc}}" + homepage "{{homepage}}" +{{#url}} + url "{{url}}" +{{/url}} +{{#sha256}} + sha256 "{{sha256}}" +{{/sha256}} +{{#version}} + version "{{version}}" +{{/version}} + license {{license_rendered}} +{{#revision}} + revision {{revision}} +{{/revision}} +{{#head}} + head "{{url}}"{{#branch}}, branch: "{{branch}}"{{/branch}} +{{/head}} +{{#bottle}} + + bottle do +{{#cellar}} + cellar {{cellar}} +{{/cellar}} +{{#sha256}} +{{#arm64_sonoma}} + sha256 arm64_sonoma: "{{arm64_sonoma}}" +{{/arm64_sonoma}} +{{#ventura}} + sha256 ventura: "{{ventura}}" +{{/ventura}} +{{/sha256}} +{{#rebuild}} + rebuild {{rebuild}} +{{/rebuild}} + end +{{/bottle}} +{{#livecheck}} + + livecheck do + url {{url}} + strategy {{strategy}} +{{#regex}} + regex {{regex}} +{{/regex}} + end +{{/livecheck}} +{{#dependencies}} + + depends_on "{{name}}"{{#type}} => {{type}}{{/type}} +{{/dependencies}} +{{#uses_from_macos}} + uses_from_macos "{{name}}"{{#since}}, since: {{since}}{{/since}} +{{/uses_from_macos}} +{{#resources}} + + resource "{{name}}" do + url "{{url}}" + sha256 "{{sha256}}" + end +{{/resources}} +{{#conflicts_with}} + conflicts_with "{{name}}"{{#because}}, because: "{{because}}"{{/because}} +{{/conflicts_with}} +{{#keg_only}} + keg_only {{keg_only}} +{{/keg_only}} +{{#skip_clean}} + skip_clean "{{.}}" +{{/skip_clean}} +{{#patches}} + + patch do +{{#url}} + url "{{url}}" + sha256 "{{sha256}}" +{{/url}} + end +{{/patches}} + + def install +{{#is_go}} +{{> langs/go}} +{{/is_go}} +{{#is_rust}} +{{> langs/rust}} +{{/is_rust}} +{{#is_python}} +{{> langs/python}} +{{/is_python}} +{{#is_zig}} +{{> langs/zig}} +{{/is_zig}} +{{#is_cmake}} +{{> langs/cmake}} +{{/is_cmake}} +{{#is_autotools}} +{{> langs/autotools}} +{{/is_autotools}} +{{#is_meson}} +{{> langs/meson}} +{{/is_meson}} +{{#completions}} + +{{#bash}} + generate_completions_from_executable(bin/"{{formula_name}}", "completions", "bash", + shell_output_dir: bash_completion) +{{/bash}} +{{#zsh}} + generate_completions_from_executable(bin/"{{formula_name}}", "completions", "zsh", + shell_output_dir: zsh_completion) +{{/zsh}} +{{#fish}} + generate_completions_from_executable(bin/"{{formula_name}}", "completions", "fish", + shell_output_dir: fish_completion) +{{/fish}} +{{/completions}} + end +{{#service}} + + service do +{{#run_is_array}} + run {{run_rendered}} +{{/run_is_array}} +{{^run_is_array}} + run [{{run_rendered}}] +{{/run_is_array}} +{{#run_type}} + run_type {{run_type}} +{{/run_type}} +{{#interval}} + interval {{interval}} +{{/interval}} +{{#keep_alive_bool}} + keep_alive {{keep_alive_rendered}} +{{/keep_alive_bool}} +{{#log_path}} + log_path "{{log_path}}" +{{/log_path}} +{{#error_log_path}} + error_log_path "{{error_log_path}}" +{{/error_log_path}} +{{#working_dir}} + working_dir "{{working_dir}}" +{{/working_dir}} + end +{{/service}} +{{#test}} + + test do +{{#command}} + {{command}} +{{/command}} +{{#expected_output}} + assert_match "{{expected_output}}", shell_output("#{bin}/{{formula_name}} --help") +{{/expected_output}} + end +{{/test}} +{{#caveats}} + + def caveats + <<~EOS + {{caveats}} + EOS + end +{{/caveats}} +end +{{/formulas}} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/testing/patterns.md b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/testing/patterns.md new file mode 100644 index 00000000..801149a2 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/reference/testing/patterns.md @@ -0,0 +1,37 @@ +## Test Block Patterns + +### Simple help check + +```text +test do + assert_match "tool-name", shell_output("#{bin}/tool --help") +end +``` + +### Non-zero exit on help + +Some tools exit non-zero when showing help. Pass the expected exit code: + +```text +test do + assert_match "tool-name", shell_output("#{bin}/tool --help", 2) +end +``` + +### Version check + +```text +test do + assert_match version.to_s, shell_output("#{bin}/tool --version") +end +``` + +### Functional test with file I/O + +```text +test do + (testpath/"input.txt").write("test content") + output = shell_output("#{bin}/tool #{testpath}/input.txt") + assert_equal "expected", output.strip +end +``` diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/scripts/formula.helper.ts b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/scripts/formula.helper.ts new file mode 100644 index 00000000..11c67fae --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/scripts/formula.helper.ts @@ -0,0 +1,82 @@ +const fs = require("fs"); +const path = require("path"); + +function pascalCase(str) { + return str.replace(/(^|[-_])([a-z])/g, (_, _sep, c) => c.toUpperCase()); +} + +function renderLicense(license) { + if (typeof license === "string") return '"' + license + '"'; + if (license.all_of) + return ( + "all_of: [" + license.all_of.map((l) => '"' + l + '"').join(", ") + "]" + ); + if (license.any_of) + return ( + "any_of: [" + license.any_of.map((l) => '"' + l + '"').join(", ") + "]" + ); + return '"MIT"'; +} + +function preprocessFormulas(formulas) { + for (const formula of formulas) { + formula.class_name = pascalCase(formula.name); + formula.formula_name = formula.name; + formula.license_rendered = renderLicense(formula.license); + + if (formula.language) { + formula["is_" + formula.language] = true; + } + + if (formula.install) { + Object.assign(formula, formula.install); + } + + if (formula.service) { + if (Array.isArray(formula.service.run)) { + formula.service.run_is_array = true; + formula.service.run_rendered = + "[" + formula.service.run.map((s) => '"' + s + '"').join(", ") + "]"; + } else { + formula.service.run_rendered = '"' + formula.service.run + '"'; + } + if (typeof formula.service.keep_alive !== "undefined") { + formula.service.keep_alive_bool = true; + formula.service.keep_alive_rendered = JSON.stringify( + formula.service.keep_alive, + ); + } + } + } +} + +function loadPartials(langsDir) { + const partials = {}; + for (const file of fs.readdirSync(langsDir)) { + if (file.endsWith(".mustache")) { + const name = "langs/" + path.basename(file, ".mustache"); + partials[name] = fs.readFileSync(path.join(langsDir, file), "utf8"); + } + } + return partials; +} + +function parseInput(input) { + try { + if ( + (input.startsWith("/") || input.startsWith(".")) && + fs.existsSync(input) + ) { + return fs.readFileSync(input, "utf8"); + } + } catch {} + return input; +} + +module.exports = { + pascalCase, + renderLicense, + preprocessFormulas, + loadPartials, + parseInput, +}; diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/scripts/formula.schema.ts b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/scripts/formula.schema.ts new file mode 100644 index 00000000..fcc55d99 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/scripts/formula.schema.ts @@ -0,0 +1,1113 @@ +module.exports.SCHEMA = { + $id: "https://schemas.arusty.dev/homebrew/formula.schema.json", + $schema: "https://json-schema.org/draft/2020-12/schema", + title: "Homebrew Formulas", + description: "Array of Homebrew formula definitions for code generation", + type: "object", + properties: { + formulas: { + type: "array", + uniqueItems: true, + items: { $ref: "#/$defs/formula" }, + }, + }, + required: ["formulas"], + + $defs: { + // ─── Primitive types ─────────────────────────────────────────────── + + "url-pattern": { + title: "URL Pattern", + description: "HTTP(S) URL", + type: "string", + pattern: "^https?://", + examples: [ + "https://github.com/owner/repo/archive/refs/tags/v1.0.0.tar.gz", + "https://downloads.example.com/tool-1.2.3.tar.xz", + ], + }, + + "git-url-pattern": { + title: "Git URL Pattern", + description: "URL ending in .git for HEAD-only or VCS sources", + type: "string", + pattern: "\\.git$", + examples: [ + "https://github.com/owner/repo.git", + "git@github.com:owner/repo.git", + ], + }, + + "sha256-hash": { + title: "SHA-256 Hash", + description: "Lowercase hex-encoded SHA-256 digest", + type: "string", + pattern: "^[a-f0-9]{64}$", + examples: [ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + ], + }, + + "version-string": { + title: "Version String", + description: "Semantic version (major.minor.patch with optional pre-release)", + type: "string", + pattern: "^\\d+\\.\\d+\\.\\d+([\\-+].+)?$", + examples: ["1.0.0", "2.3.1-rc1", "0.9.0+build.123"], + }, + + "spdx-identifier": { + title: "SPDX License Identifier", + description: "Common SPDX license identifier", + type: "string", + enum: [ + "MIT", + "Apache-2.0", + "GPL-2.0-only", + "GPL-2.0-or-later", + "GPL-3.0-only", + "GPL-3.0-or-later", + "LGPL-2.1-only", + "LGPL-2.1-or-later", + "LGPL-3.0-only", + "LGPL-3.0-or-later", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "MPL-2.0", + "AGPL-3.0-only", + "AGPL-3.0-or-later", + "Unlicense", + "Zlib", + "BSL-1.0", + "0BSD", + "CC0-1.0", + "WTFPL", + "Artistic-2.0", + "PostgreSQL", + "OpenSSL", + ], + }, + + // ─── Complex types ───────────────────────────────────────────────── + + license: { + title: "License", + description: "SPDX license — simple identifier or complex expression with all_of/any_of/with", + oneOf: [ + { $ref: "#/$defs/spdx-identifier" }, + { + type: "object", + title: "Complex License", + description: "License expression with combinators", + properties: { + all_of: { + type: "array", + description: "All licenses apply (AND)", + items: { $ref: "#/$defs/spdx-identifier" }, + examples: [["Apache-2.0", "MIT"]], + }, + any_of: { + type: "array", + description: "Any one of these licenses applies (OR)", + items: { $ref: "#/$defs/spdx-identifier" }, + examples: [["MIT", "Apache-2.0"]], + }, + with: { + type: "array", + description: "License exceptions (WITH clause)", + items: { type: "string" }, + examples: [["LLVM-exception"]], + }, + }, + additionalProperties: false, + }, + ], + }, + + "head-config": { + title: "HEAD Configuration", + description: "Configuration for building from the latest VCS revision", + type: "object", + properties: { + url: { + $ref: "#/$defs/git-url-pattern", + description: "Git repository URL", + }, + branch: { + type: "string", + description: "Branch to track", + enum: ["main", "master", "develop", "trunk"], + default: "main", + examples: ["main", "master"], + }, + revision: { + type: "string", + description: "Pin to a specific commit SHA", + examples: ["abc123def456"], + }, + }, + required: ["url"], + dependentRequired: { revision: ["url"] }, + additionalProperties: false, + }, + + "bottle-config": { + title: "Bottle Configuration", + description: "Pre-built binary bottle specification", + type: "object", + properties: { + cellar: { + title: "Cellar", + description: "Cellar path or relocatability marker", + oneOf: [ + { + type: "string", + enum: [":any", ":any_skip_relocation"], + default: ":any_skip_relocation", + }, + { type: "string", pattern: "^/" }, + ], + examples: [":any_skip_relocation", "/usr/local/Cellar"], + }, + sha256: { + type: "object", + title: "Platform SHA256 Map", + description: "Map of platform tag to SHA-256 digest", + additionalProperties: { $ref: "#/$defs/sha256-hash" }, + examples: [ + { + arm64_sonoma: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + ventura: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, + ], + }, + rebuild: { + type: "integer", + description: "Bottle rebuild number", + minimum: 0, + default: 0, + examples: [0, 1], + }, + }, + additionalProperties: false, + }, + + "livecheck-config": { + title: "Livecheck Configuration", + description: "Automated version-checking configuration", + type: "object", + properties: { + url: { + title: "Livecheck URL", + description: "URL to check or a symbol referencing the formula URL", + oneOf: [ + { type: "string", enum: [":stable", ":homepage", ":head", ":url"] }, + { $ref: "#/$defs/url-pattern" }, + ], + default: ":stable", + examples: [":stable", ":homepage", "https://github.com/owner/repo/releases"], + }, + strategy: { + type: "string", + description: "Livecheck strategy", + enum: [ + ":github_latest", + ":github_releases", + ":page_match", + ":header_match", + ":sparkle", + ":git", + ":npm", + ":pypi", + ":crate", + ], + default: ":github_latest", + examples: [":github_latest", ":page_match"], + }, + regex: { + type: "string", + description: "Ruby regex pattern for version extraction", + examples: ["/v?(\\d+(?:\\.\\d+)+)/i"], + }, + }, + allOf: [ + { + if: { properties: { strategy: { const: ":page_match" } }, required: ["strategy"] }, + then: { required: ["regex"] }, + }, + ], + additionalProperties: false, + }, + + dependency: { + title: "Dependency", + description: "A formula or cask dependency", + type: "object", + properties: { + name: { + type: "string", + description: "Dependency formula name", + examples: ["openssl@3", "pkg-config", "cmake"], + }, + type: { + type: "string", + description: "Dependency type tag", + enum: [":build", ":test", ":runtime", ":optional", ":recommended"], + default: ":build", + examples: [":build", ":test"], + }, + }, + required: ["name"], + additionalProperties: false, + }, + + "uses-from-macos": { + title: "Uses From macOS", + description: "System library provided by macOS; only added as dep on Linux", + type: "object", + properties: { + name: { + type: "string", + description: "macOS system library name", + examples: ["curl", "zlib", "libxml2", "ncurses"], + }, + since: { + type: "string", + description: "Minimum macOS version providing this library", + enum: [ + ":el_capitan", + ":sierra", + ":high_sierra", + ":mojave", + ":catalina", + ":big_sur", + ":monterey", + ":ventura", + ":sonoma", + ":sequoia", + ], + examples: [":ventura", ":sonoma"], + }, + }, + required: ["name"], + additionalProperties: false, + }, + + resource: { + title: "Resource", + description: "Additional source archive fetched during install (e.g. Python deps)", + type: "object", + properties: { + name: { + type: "string", + description: "Resource name", + examples: ["certifi", "urllib3"], + }, + url: { + $ref: "#/$defs/url-pattern", + description: "Download URL for the resource", + }, + sha256: { + $ref: "#/$defs/sha256-hash", + description: "SHA-256 of the resource archive", + }, + }, + required: ["name", "url", "sha256"], + additionalProperties: false, + }, + + "patch-config": { + title: "Patch", + description: "A patch to apply — external URL or inline diff", + oneOf: [ + { + type: "object", + title: "External Patch", + description: "Patch fetched from a URL", + properties: { + url: { $ref: "#/$defs/url-pattern", description: "Patch file URL" }, + sha256: { $ref: "#/$defs/sha256-hash", description: "Patch file SHA-256" }, + }, + required: ["url", "sha256"], + additionalProperties: false, + }, + { + type: "string", + title: "Inline Patch", + description: "Inline diff content (DATA or __END__)", + examples: ["diff --git a/file.c b/file.c\n..."], + }, + ], + }, + + "conflicts-with": { + title: "Conflicts With", + description: "Formula that conflicts with this one", + type: "object", + properties: { + name: { + type: "string", + description: "Conflicting formula name", + examples: ["ripgrep"], + }, + because: { + type: "string", + description: "Reason for the conflict", + examples: ["both install an `rg` binary"], + }, + }, + required: ["name"], + additionalProperties: false, + }, + + "keg-only": { + title: "Keg Only", + description: "Reason this formula is keg-only (not symlinked into prefix)", + oneOf: [ + { + type: "string", + enum: [ + ":versioned_formula", + ":provided_by_macos", + ":shadowed_by_macos", + ], + }, + { + type: "string", + description: "Custom keg-only reason", + examples: ["this formula conflicts with the system version"], + }, + ], + }, + + deprecation: { + title: "Deprecation", + description: "Deprecation notice with date and reason", + type: "object", + properties: { + date: { + type: "string", + description: "Deprecation date (ISO 8601)", + pattern: "^\\d{4}-\\d{2}-\\d{2}$", + examples: ["2024-01-15"], + }, + because: { + type: "string", + description: "Reason for deprecation", + examples: [":unsupported", ":repo_archived", "no longer maintained"], + }, + }, + required: ["date", "because"], + additionalProperties: false, + }, + + "fails-with": { + title: "Fails With", + description: "Compiler version known to mis-compile this formula", + type: "object", + properties: { + compiler: { + type: "string", + description: "Compiler name", + enum: [":gcc", ":clang", ":llvm_gcc"], + examples: [":gcc"], + }, + version: { + type: "string", + description: "Compiler version range", + examples: ["5", "< 11"], + }, + cause: { + type: "string", + description: "Why it fails", + examples: ["internal compiler error"], + }, + }, + required: ["compiler"], + additionalProperties: false, + }, + + "on-platform": { + title: "Platform-specific Configuration", + description: "Dependencies and env overrides for a specific OS", + type: "object", + properties: { + dependencies: { + type: "array", + uniqueItems: true, + items: { $ref: "#/$defs/dependency" }, + description: "Platform-specific dependencies", + }, + env: { + type: "object", + description: "Environment variable overrides", + additionalProperties: { type: "string" }, + examples: [{ LDFLAGS: "-lrt" }], + }, + }, + additionalProperties: false, + }, + + "service-config": { + title: "Service Configuration", + description: "launchd/systemd service definition", + type: "object", + properties: { + run: { + description: "Command to run (string or array)", + oneOf: [ + { type: "string", examples: ["#{opt_bin}/myservice"] }, + { + type: "array", + items: { type: "string" }, + examples: [["#{opt_bin}/myservice", "--config", "#{etc}/my.conf"]], + }, + ], + }, + run_type: { + type: "string", + description: "How the service is managed", + enum: [":immediate", ":interval", ":cron"], + default: ":immediate", + examples: [":immediate", ":interval"], + }, + interval: { + type: "integer", + description: "Run interval in seconds (when run_type is :interval)", + minimum: 1, + examples: [300, 3600], + }, + cron: { + type: "string", + description: "Cron schedule (when run_type is :cron)", + examples: ["0 * * * *"], + }, + keep_alive: { + title: "Keep Alive", + description: "Whether to restart the service if it exits", + oneOf: [ + { type: "boolean", default: false }, + { + type: "object", + properties: { + always: { type: "boolean" }, + successful_exit: { type: "boolean" }, + crashed: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + examples: [true, { crashed: true }], + }, + log_path: { + type: "string", + description: "Path for stdout log", + examples: ["#{var}/log/myservice.log"], + }, + error_log_path: { + type: "string", + description: "Path for stderr log", + examples: ["#{var}/log/myservice-error.log"], + }, + environment_variables: { + type: "object", + description: "Environment variables for the service", + additionalProperties: { type: "string" }, + examples: [{ LANG: "en_US.UTF-8" }], + }, + working_dir: { + type: "string", + description: "Working directory for the service", + examples: ["#{var}/lib/myservice"], + }, + }, + required: ["run"], + allOf: [ + { + if: { properties: { run_type: { const: ":interval" } }, required: ["run_type"] }, + then: { required: ["interval"] }, + }, + { + if: { properties: { run_type: { const: ":cron" } }, required: ["run_type"] }, + then: { required: ["cron"] }, + }, + ], + additionalProperties: false, + }, + + // ─── Language-specific install configs ────────────────────────────── + + "install-go": { + title: "Go Install Configuration", + description: "Install config for Go formulas", + $comment: "Ruby DSL: std_go_args(ldflags:, output:)", + type: "object", + properties: { + cmd_path: { + type: "string", + description: "Go package path to build (relative to module root)", + default: "./cmd/...", + examples: ["./cmd/mytool", "./"], + }, + ldflags: { + type: "string", + description: "Linker flags passed via -ldflags", + default: "-s -w -X main.version=#{version}", + examples: ["-s -w -X main.version=#{version}"], + }, + output: { + type: "string", + description: "Output binary name (defaults to formula name)", + examples: ["mytool"], + }, + tags: { + type: "array", + items: { type: "string" }, + description: "Go build tags", + examples: [["netgo", "osusergo"]], + }, + env: { + type: "object", + description: "Environment variables for go build", + additionalProperties: { type: "string" }, + examples: [{ CGO_ENABLED: "0" }], + }, + }, + additionalProperties: false, + }, + + "install-rust": { + title: "Rust Install Configuration", + description: "Install config for Rust/Cargo formulas", + $comment: "Ruby DSL: std_cargo_args(path:, features:)", + type: "object", + properties: { + path: { + type: "string", + description: "Path to Cargo.toml directory", + default: ".", + examples: [".", "crates/cli"], + }, + features: { + type: "array", + items: { type: "string" }, + description: "Cargo features to enable", + examples: [["tls", "jemalloc"]], + }, + all_features: { + type: "boolean", + description: "Enable all Cargo features", + default: false, + }, + no_default_features: { + type: "boolean", + description: "Disable default Cargo features", + default: false, + }, + bins: { + type: "array", + items: { type: "string" }, + description: "Specific binaries to install", + examples: [["mytool", "mytool-helper"]], + }, + }, + additionalProperties: false, + }, + + "install-python": { + title: "Python Install Configuration", + description: "Install config for Python formulas", + $comment: "Ruby DSL: virtualenv_install_with_resources", + type: "object", + properties: { + python_version: { + type: "string", + description: "Python version to use", + default: "python3", + examples: ["python3", "python@3.12"], + }, + resources: { + type: "array", + uniqueItems: true, + items: { $ref: "#/$defs/resource" }, + description: "Python package resources to install", + }, + using: { + type: "string", + description: "Install method", + enum: ["virtualenv", "pip", "setuptools"], + default: "virtualenv", + examples: ["virtualenv"], + }, + site_packages: { + type: "boolean", + description: "Allow access to system site-packages", + default: false, + }, + }, + additionalProperties: false, + }, + + "install-nodejs": { + title: "Node.js Install Configuration", + description: "Install config for Node.js/npm formulas", + $comment: "Ruby DSL: std_npm_args", + type: "object", + properties: { + node_version: { + type: "string", + description: "Node.js version dependency", + default: "node", + examples: ["node", "node@20"], + }, + npm_install: { + type: "boolean", + description: "Use npm install for building", + default: true, + }, + build_from_source: { + type: "boolean", + description: "Build native addons from source", + default: false, + }, + }, + additionalProperties: false, + }, + + "install-cmake": { + title: "CMake Install Configuration", + description: "Install config for CMake-based formulas", + $comment: "Ruby DSL: std_cmake_args", + type: "object", + properties: { + source_dir: { + type: "string", + description: "Path to CMakeLists.txt directory", + default: ".", + examples: [".", "src"], + }, + build_dir: { + type: "string", + description: "Out-of-source build directory", + default: "build", + examples: ["build"], + }, + cmake_args: { + type: "array", + items: { type: "string" }, + description: "Additional CMake arguments", + examples: [["-DBUILD_SHARED_LIBS=ON", "-DCMAKE_BUILD_TYPE=Release"]], + }, + }, + additionalProperties: false, + }, + + "install-autotools": { + title: "Autotools Install Configuration", + description: "Install config for autotools (./configure && make) formulas", + $comment: "Ruby DSL: std_configure_args", + type: "object", + properties: { + configure_args: { + type: "array", + items: { type: "string" }, + description: "Extra arguments to ./configure", + examples: [["--enable-shared", "--disable-static"]], + }, + autoreconf: { + type: "boolean", + description: "Run autoreconf -fiv before configure", + default: false, + }, + }, + additionalProperties: false, + }, + + "install-meson": { + title: "Meson Install Configuration", + description: "Install config for Meson-based formulas", + $comment: "Ruby DSL: std_meson_args", + type: "object", + properties: { + build_dir: { + type: "string", + description: "Build directory", + default: "build", + examples: ["build"], + }, + meson_args: { + type: "array", + items: { type: "string" }, + description: "Additional Meson arguments", + examples: [["-Dfeature=enabled"]], + }, + }, + additionalProperties: false, + }, + + "install-java": { + title: "Java Install Configuration", + description: "Install config for Java formulas", + $comment: "Ruby DSL: write_jar_script", + type: "object", + properties: { + java_version: { + type: "string", + description: "Java version dependency", + default: "openjdk", + examples: ["openjdk", "openjdk@17", "openjdk@21"], + }, + build_system: { + type: "string", + description: "Java build system", + enum: ["maven", "gradle", "ant", "manual"], + default: "maven", + examples: ["maven", "gradle"], + }, + jar_path: { + type: "string", + description: "Path to the output JAR file", + examples: ["target/mytool.jar"], + }, + wrapper_name: { + type: "string", + description: "Name for the shell wrapper script", + examples: ["mytool"], + }, + }, + dependentRequired: { jar_path: ["wrapper_name"] }, + additionalProperties: false, + }, + + "install-zig": { + title: "Zig Install Configuration", + description: "Install config for Zig formulas", + $comment: "Ruby DSL: zig build", + type: "object", + properties: { + build_mode: { + type: "string", + description: "Zig build optimization mode", + enum: ["ReleaseSafe", "ReleaseFast", "ReleaseSmall", "Debug"], + default: "ReleaseSafe", + examples: ["ReleaseSafe", "ReleaseFast"], + }, + targets: { + type: "array", + items: { type: "string" }, + description: "Zig build targets", + examples: [["install"]], + }, + }, + additionalProperties: false, + }, + + // ─── Main formula definition ─────────────────────────────────────── + + formula: { + title: "Homebrew Formula", + description: "A single Homebrew formula definition", + type: "object", + properties: { + // Core fields + name: { + type: "string", + description: "Formula name (kebab-case, lowercase)", + pattern: "^[a-z][a-z0-9]*(-[a-z0-9]+)*$", + examples: ["my-tool", "openssl@3"], + }, + desc: { + type: "string", + description: "Short description displayed in brew info", + maxLength: 80, + examples: ["A command-line tool for doing something useful"], + }, + homepage: { + $ref: "#/$defs/url-pattern", + description: "Project homepage URL", + examples: ["https://github.com/owner/repo"], + }, + url: { + $ref: "#/$defs/url-pattern", + description: "Source tarball URL", + }, + sha256: { + $ref: "#/$defs/sha256-hash", + description: "SHA-256 of the source tarball", + }, + version: { + $ref: "#/$defs/version-string", + description: "Explicit version override (usually inferred from URL)", + }, + revision: { + type: "integer", + description: "Formula revision number (bump when formula changes but version does not)", + minimum: 0, + default: 0, + examples: [0, 1], + }, + license: { + $ref: "#/$defs/license", + description: "SPDX license expression", + }, + head: { + $ref: "#/$defs/head-config", + description: "HEAD-only build configuration", + }, + bottle: { + $ref: "#/$defs/bottle-config", + description: "Pre-built bottle specification", + }, + livecheck: { + $ref: "#/$defs/livecheck-config", + description: "Automated version checking", + }, + language: { + type: "string", + description: "Primary build language/system (drives install config shape)", + enum: [ + "go", + "rust", + "python", + "nodejs", + "cmake", + "autotools", + "meson", + "java", + "zig", + "make", + "custom", + ], + examples: ["go", "rust", "python"], + }, + + // Arrays + dependencies: { + type: "array", + uniqueItems: true, + items: { $ref: "#/$defs/dependency" }, + description: "Formula dependencies", + }, + uses_from_macos: { + type: "array", + uniqueItems: true, + items: { $ref: "#/$defs/uses-from-macos" }, + description: "System libraries provided by macOS", + }, + resources: { + type: "array", + uniqueItems: true, + items: { $ref: "#/$defs/resource" }, + description: "Additional source archives", + }, + patches: { + type: "array", + uniqueItems: true, + items: { $ref: "#/$defs/patch-config" }, + description: "Patches to apply to the source", + }, + conflicts_with: { + type: "array", + uniqueItems: true, + items: { $ref: "#/$defs/conflicts-with" }, + description: "Formulas that conflict with this one", + }, + skip_clean: { + type: "array", + uniqueItems: true, + items: { type: "string" }, + description: "Paths to skip during post-install cleanup", + examples: [["libexec"]], + }, + link_overwrite: { + type: "array", + uniqueItems: true, + items: { type: "string" }, + description: "Files allowed to overwrite during linking", + examples: [["bin/tool"]], + }, + fails_with: { + type: "array", + uniqueItems: true, + items: { $ref: "#/$defs/fails-with" }, + description: "Known compiler incompatibilities", + }, + + // Scalars + caveats: { + type: "string", + description: "Post-install message shown to user", + examples: ["Run `brew services start myservice` to start the service."], + }, + keg_only: { + $ref: "#/$defs/keg-only", + description: "Reason the formula is keg-only", + }, + pour_bottle: { + type: "boolean", + description: "Whether to pour the bottle on install", + default: true, + }, + deprecate: { + $ref: "#/$defs/deprecation", + description: "Deprecation notice", + }, + disable: { + $ref: "#/$defs/deprecation", + description: "Disable notice (stronger than deprecation)", + }, + + // Nested objects + install: { + type: "object", + description: "Language-specific install configuration (shape determined by language field)", + }, + test: { + type: "object", + description: "Post-install test configuration", + properties: { + command: { + type: "string", + description: "Shell command to verify the install", + examples: ["system bin/\"mytool\", \"--version\""], + }, + input: { + type: "string", + description: "Stdin input for the test command", + examples: ["test input"], + }, + expected_output: { + type: "string", + description: "Expected output pattern (Ruby regex or string)", + examples: ["mytool version #{version}"], + }, + }, + additionalProperties: false, + }, + service: { + $ref: "#/$defs/service-config", + description: "launchd/systemd service definition", + }, + on_macos: { + $ref: "#/$defs/on-platform", + description: "macOS-specific overrides", + }, + on_linux: { + $ref: "#/$defs/on-platform", + description: "Linux-specific overrides", + }, + completions: { + type: "object", + description: "Shell completion file generation", + properties: { + bash: { + type: "string", + description: "Command to generate bash completions", + examples: ["generate_completions bash > bash_completion.d/mytool"], + }, + zsh: { + type: "string", + description: "Command to generate zsh completions", + examples: ["generate_completions zsh > zsh/site-functions/_mytool"], + }, + fish: { + type: "string", + description: "Command to generate fish completions", + examples: ["generate_completions fish > fish/vendor_completions.d/mytool.fish"], + }, + }, + additionalProperties: false, + }, + }, + + required: ["name", "desc", "homepage", "license"], + + // Standard vs HEAD-only + if: { + required: ["url"], + }, + then: { + required: ["sha256", "livecheck"], + }, + else: { + required: ["head"], + }, + + // Language dispatch + dependentSchemas + allOf: [ + { + if: { properties: { language: { const: "go" } }, required: ["language"] }, + then: { + properties: { install: { $ref: "#/$defs/install-go" } }, + required: ["install"], + }, + }, + { + if: { properties: { language: { const: "rust" } }, required: ["language"] }, + then: { + properties: { install: { $ref: "#/$defs/install-rust" } }, + required: ["install"], + }, + }, + { + if: { properties: { language: { const: "python" } }, required: ["language"] }, + then: { + properties: { install: { $ref: "#/$defs/install-python" } }, + required: ["install"], + }, + }, + { + if: { properties: { language: { const: "nodejs" } }, required: ["language"] }, + then: { + properties: { install: { $ref: "#/$defs/install-nodejs" } }, + required: ["install"], + }, + }, + { + if: { properties: { language: { const: "cmake" } }, required: ["language"] }, + then: { + properties: { install: { $ref: "#/$defs/install-cmake" } }, + required: ["install"], + }, + }, + { + if: { properties: { language: { const: "autotools" } }, required: ["language"] }, + then: { + properties: { install: { $ref: "#/$defs/install-autotools" } }, + required: ["install"], + }, + }, + { + if: { properties: { language: { const: "meson" } }, required: ["language"] }, + then: { + properties: { install: { $ref: "#/$defs/install-meson" } }, + required: ["install"], + }, + }, + { + if: { properties: { language: { const: "java" } }, required: ["language"] }, + then: { + properties: { install: { $ref: "#/$defs/install-java" } }, + required: ["install"], + }, + }, + { + if: { properties: { language: { const: "zig" } }, required: ["language"] }, + then: { + properties: { install: { $ref: "#/$defs/install-zig" } }, + required: ["install"], + }, + }, + ], + + dependentSchemas: { + service: { + required: ["caveats"], + }, + }, + + additionalProperties: false, + }, + }, +}; diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/settings.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/settings.json new file mode 100644 index 00000000..98072443 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/settings.json @@ -0,0 +1,24 @@ +{ + "mcp": { + "servers": { + "json-mcp-server": { + "command": "npx", + "args": [ + "json-mcp-server@latest", + "--verbose=true", + "--file-path=/path/to/file.json", + "--jq-path=/path/to/jq" + ] + }, + "jq-mcp-server": { + "command": "python", + "args": ["/absolute/path/to/jq-mcp-server/mcp_server.py"], + "env": { + "DATA_PATH": "/absolute/path/to/your/data/directory", + "JSON_FILE_PATH": "main.json", + "JSON_SCHEMA_FILE_PATH": "schema.json" + } + } + } + } +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-go-renders.sh b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-go-renders.sh new file mode 100755 index 00000000..94f51f5e --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-go-renders.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail +SKILL_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$SKILL_DIR" + +output=$(just template-formula "$SKILL_DIR/test/data/go-standard.json") + +errors=0 +for pattern in "class MyTool < Formula" "std_go_args" "depends_on \"go\"" "depends_on \"pkg-config\"" "livecheck do" "head \"https://github.com/example/my-tool.git\"" "generate_completions_from_executable" "test do"; do + if ! echo "$output" | grep -q "$pattern"; then + echo "FAIL: expected '$pattern' in output" + errors=$((errors + 1)) + fi +done + +if [ "$errors" -eq 0 ]; then + echo "PASS: test-go-renders" +else + echo "FAIL: test-go-renders ($errors assertions failed)" + echo "--- output ---" + echo "$output" + exit 1 +fi diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-head-only.sh b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-head-only.sh new file mode 100755 index 00000000..c8ac5667 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-head-only.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail +SKILL_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$SKILL_DIR" + +output=$(just template-formula "$SKILL_DIR/test/data/head-only.json") + +errors=0 + +# Should have head +if ! echo "$output" | grep -q 'head "https://github.com/example/bleeding-edge.git"'; then + echo "FAIL: expected head URL in output" + errors=$((errors + 1)) +fi + +# Should NOT have url/sha256 lines (no stable URL) +if echo "$output" | grep -q ' url "https://'; then + echo "FAIL: unexpected url line in HEAD-only formula" + errors=$((errors + 1)) +fi +if echo "$output" | grep -q ' sha256 "'; then + echo "FAIL: unexpected sha256 line in HEAD-only formula" + errors=$((errors + 1)) +fi + +# Should NOT have livecheck +if echo "$output" | grep -q 'livecheck do'; then + echo "FAIL: unexpected livecheck in HEAD-only formula" + errors=$((errors + 1)) +fi + +if [ "$errors" -eq 0 ]; then + echo "PASS: test-head-only" +else + echo "FAIL: test-head-only ($errors assertions failed)" + echo "--- output ---" + echo "$output" + exit 1 +fi diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-invalid-rejected.sh b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-invalid-rejected.sh new file mode 100755 index 00000000..6dcd2aa6 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-invalid-rejected.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail +SKILL_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$SKILL_DIR" + +errors=0 + +for file in "$SKILL_DIR"/test/data/invalid-*.json; do + if just template-formula "$file" >/dev/null 2>&1; then + echo "FAIL: expected $(basename "$file") to be rejected" + errors=$((errors + 1)) + else + echo " OK: $(basename "$file") correctly rejected" + fi +done + +if [ "$errors" -eq 0 ]; then + echo "PASS: test-invalid-rejected" +else + echo "FAIL: test-invalid-rejected ($errors assertions failed)" + exit 1 +fi diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-python-renders.sh b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-python-renders.sh new file mode 100755 index 00000000..b396ddfa --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-python-renders.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail +SKILL_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$SKILL_DIR" + +output=$(just template-formula "$SKILL_DIR/test/data/python-standard.json") + +errors=0 +for pattern in "class MyPythonTool < Formula" "virtualenv_install_with_resources" 'resource "certifi"' 'resource "urllib3"'; do + if ! echo "$output" | grep -q "$pattern"; then + echo "FAIL: expected '$pattern' in output" + errors=$((errors + 1)) + fi +done + +if [ "$errors" -eq 0 ]; then + echo "PASS: test-python-renders" +else + echo "FAIL: test-python-renders ($errors assertions failed)" + echo "--- output ---" + echo "$output" + exit 1 +fi diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-rust-renders.sh b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-rust-renders.sh new file mode 100755 index 00000000..955bd4cd --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-rust-renders.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail +SKILL_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$SKILL_DIR" + +output=$(just template-formula "$SKILL_DIR/test/data/rust-standard.json") + +errors=0 +for pattern in "class RustAnalyzer < Formula" "std_cargo_args" "depends_on \"rust\"" "uses_from_macos \"zlib\"" '"lsp"' '"jemalloc"'; do + if ! echo "$output" | grep -q "$pattern"; then + echo "FAIL: expected '$pattern' in output" + errors=$((errors + 1)) + fi +done + +if [ "$errors" -eq 0 ]; then + echo "PASS: test-rust-renders" +else + echo "FAIL: test-rust-renders ($errors assertions failed)" + echo "--- output ---" + echo "$output" + exit 1 +fi diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-service-formula.sh b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-service-formula.sh new file mode 100755 index 00000000..997d44e0 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/cases/test-service-formula.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail +SKILL_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +cd "$SKILL_DIR" + +output=$(just template-formula "$SKILL_DIR/test/data/service-formula.json") + +errors=0 +for pattern in "class MyDaemon < Formula" "service do" "run_type :immediate" "keep_alive true" "log_path" "def caveats"; do + if ! echo "$output" | grep -q "$pattern"; then + echo "FAIL: expected '$pattern' in output" + errors=$((errors + 1)) + fi +done + +if [ "$errors" -eq 0 ]; then + echo "PASS: test-service-formula" +else + echo "FAIL: test-service-formula ($errors assertions failed)" + echo "--- output ---" + echo "$output" + exit 1 +fi diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/go-standard.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/go-standard.json new file mode 100644 index 00000000..d57182ef --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/go-standard.json @@ -0,0 +1,38 @@ +{ + "formulas": [ + { + "name": "my-tool", + "desc": "A fast CLI tool written in Go", + "homepage": "https://github.com/example/my-tool", + "url": "https://github.com/example/my-tool/archive/refs/tags/v1.2.3.tar.gz", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "license": "MIT", + "language": "go", + "head": { + "url": "https://github.com/example/my-tool.git", + "branch": "main" + }, + "livecheck": { + "url": ":stable", + "strategy": ":github_latest" + }, + "dependencies": [ + { "name": "go", "type": ":build" }, + { "name": "pkg-config", "type": ":build" } + ], + "install": { + "ldflags": "-s -w -X main.version=#{version}", + "cmd_path": "./cmd/my-tool" + }, + "completions": { + "bash": "generate_completions bash > bash_completion.d/my-tool", + "zsh": "generate_completions zsh > zsh/site-functions/_my-tool", + "fish": "generate_completions fish > fish/vendor_completions.d/my-tool.fish" + }, + "test": { + "command": "system bin/\"my-tool\", \"--version\"", + "expected_output": "my-tool version #{version}" + } + } + ] +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/head-only.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/head-only.json new file mode 100644 index 00000000..5cd1b968 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/head-only.json @@ -0,0 +1,25 @@ +{ + "formulas": [ + { + "name": "bleeding-edge", + "desc": "A tool that only builds from HEAD", + "homepage": "https://github.com/example/bleeding-edge", + "license": "GPL-3.0-only", + "language": "go", + "head": { + "url": "https://github.com/example/bleeding-edge.git", + "branch": "main" + }, + "dependencies": [ + { "name": "go", "type": ":build" } + ], + "install": { + "ldflags": "-s -w" + }, + "test": { + "command": "system bin/\"bleeding-edge\", \"--help\"", + "expected_output": "bleeding-edge" + } + } + ] +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/invalid-bad-sha256.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/invalid-bad-sha256.json new file mode 100644 index 00000000..7a378a3f --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/invalid-bad-sha256.json @@ -0,0 +1,16 @@ +{ + "formulas": [ + { + "name": "bad-sha", + "desc": "Formula with bad sha256", + "homepage": "https://github.com/example/bad-sha", + "url": "https://github.com/example/bad-sha/archive/v1.0.0.tar.gz", + "sha256": "not-a-valid-sha256", + "license": "MIT", + "livecheck": { + "url": ":stable", + "strategy": ":github_latest" + } + } + ] +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/invalid-missing-required.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/invalid-missing-required.json new file mode 100644 index 00000000..76331561 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/invalid-missing-required.json @@ -0,0 +1,12 @@ +{ + "formulas": [ + { + "name": "bad-formula", + "homepage": "https://github.com/example/bad", + "license": "MIT", + "head": { + "url": "https://github.com/example/bad.git" + } + } + ] +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/python-standard.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/python-standard.json new file mode 100644 index 00000000..a22635fe --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/python-standard.json @@ -0,0 +1,40 @@ +{ + "formulas": [ + { + "name": "my-python-tool", + "desc": "A Python-based CLI utility", + "homepage": "https://github.com/example/my-python-tool", + "url": "https://github.com/example/my-python-tool/archive/refs/tags/v2.0.0.tar.gz", + "sha256": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "license": "MIT", + "language": "python", + "livecheck": { + "url": ":stable", + "strategy": ":pypi" + }, + "dependencies": [ + { "name": "python@3.12", "type": ":build" } + ], + "resources": [ + { + "name": "certifi", + "url": "https://files.pythonhosted.org/packages/certifi-2024.2.2.tar.gz", + "sha256": "1111111111111111111111111111111111111111111111111111111111111111" + }, + { + "name": "urllib3", + "url": "https://files.pythonhosted.org/packages/urllib3-2.2.1.tar.gz", + "sha256": "2222222222222222222222222222222222222222222222222222222222222222" + } + ], + "install": { + "python_version": "python@3.12", + "using": "virtualenv" + }, + "test": { + "command": "system bin/\"my-python-tool\", \"--version\"", + "expected_output": "my-python-tool #{version}" + } + } + ] +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/rust-standard.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/rust-standard.json new file mode 100644 index 00000000..fa316f43 --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/rust-standard.json @@ -0,0 +1,31 @@ +{ + "formulas": [ + { + "name": "rust-analyzer", + "desc": "Experimental Rust compiler front-end for IDEs", + "homepage": "https://rust-analyzer.github.io", + "url": "https://github.com/rust-lang/rust-analyzer/archive/refs/tags/v0.3.1.tar.gz", + "sha256": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", + "license": "Apache-2.0", + "language": "rust", + "livecheck": { + "url": ":stable", + "strategy": ":github_latest" + }, + "dependencies": [ + { "name": "rust", "type": ":build" } + ], + "uses_from_macos": [ + { "name": "zlib" } + ], + "install": { + "features": ["lsp", "jemalloc"], + "bins": ["rust-analyzer"] + }, + "test": { + "command": "system bin/\"rust-analyzer\", \"--help\"", + "expected_output": "rust-analyzer" + } + } + ] +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/service-formula.json b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/service-formula.json new file mode 100644 index 00000000..be3cb89d --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/data/service-formula.json @@ -0,0 +1,35 @@ +{ + "formulas": [ + { + "name": "my-daemon", + "desc": "A background service daemon", + "homepage": "https://github.com/example/my-daemon", + "url": "https://github.com/example/my-daemon/archive/refs/tags/v1.0.0.tar.gz", + "sha256": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "license": "MIT", + "language": "go", + "livecheck": { + "url": ":stable", + "strategy": ":github_latest" + }, + "dependencies": [ + { "name": "go", "type": ":build" } + ], + "install": { + "ldflags": "-s -w -X main.version=#{version}" + }, + "service": { + "run": ["#{opt_bin}/my-daemon", "--config", "#{etc}/my-daemon.conf"], + "run_type": ":immediate", + "keep_alive": true, + "log_path": "#{var}/log/my-daemon.log", + "error_log_path": "#{var}/log/my-daemon-error.log" + }, + "caveats": "To start my-daemon now and restart at login:\\n brew services start my-daemon", + "test": { + "command": "system bin/\"my-daemon\", \"--version\"", + "expected_output": "my-daemon version #{version}" + } + } + ] +} diff --git a/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/run-all.sh b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/run-all.sh new file mode 100755 index 00000000..2e1bb8ca --- /dev/null +++ b/context/plugins/homebrew-dev/skills/pkgmgr-homebrew-formula-dev/test/run-all.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail +SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$SKILL_DIR" +just test diff --git a/context/plugins/homebrew-dev/styles/.gitkeep b/context/plugins/homebrew-dev/styles/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/context/skills/pkgmgr-homebrew-formula-dev/reference/templates/ISSUE_TEMPLATE.md b/context/skills/pkgmgr-homebrew-formula-dev/reference/templates/ISSUE_TEMPLATE.md index e69de29b..6d479dba 100644 --- a/context/skills/pkgmgr-homebrew-formula-dev/reference/templates/ISSUE_TEMPLATE.md +++ b/context/skills/pkgmgr-homebrew-formula-dev/reference/templates/ISSUE_TEMPLATE.md @@ -0,0 +1,43 @@ +--- +name: New Formula Request +about: Request a Homebrew formula for a new package +labels: formula, new +--- + +## Package + +- **Name:** +- **Repository:** +- **Language:** +- **License:** + +## Release + +- **Latest version:** +- **Release URL:** +- **Source tarball:** +- **SHA256:** + +## Build Details + +- **Binary name(s):** +- **Build dependencies:** +- **Runtime dependencies:** +- **uses_from_macos:** + +## Install Notes + + + +## Test Command + +```bash +# Command to verify the install works +``` + +## Checklist + +- [ ] Repository is public +- [ ] Has tagged releases (or explicitly HEAD-only) +- [ ] License file exists in repo +- [ ] Binary runs on macOS (arm64 + x86_64) diff --git a/context/skills/pkgmgr-homebrew-formula-dev/reference/templates/PULL_REQUEST_TEMPLATE.md b/context/skills/pkgmgr-homebrew-formula-dev/reference/templates/PULL_REQUEST_TEMPLATE.md index e69de29b..12d28569 100644 --- a/context/skills/pkgmgr-homebrew-formula-dev/reference/templates/PULL_REQUEST_TEMPLATE.md +++ b/context/skills/pkgmgr-homebrew-formula-dev/reference/templates/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +## Formula + +- **Name:** +- **Version:** +- **Language:** +- **Type:** + +## Changes + + + +## Validation + +- [ ] `ruby -c Formula//.rb` passes +- [ ] `brew audit --new ` passes (new formulas) +- [ ] `brew audit ` passes (existing formulas) +- [ ] `brew style ` passes +- [ ] `brew install --build-from-source ` succeeds +- [ ] `brew test ` passes +- [ ] Binary executes correctly (` --version` or `--help`) +- [ ] Livecheck returns correct version (`brew livecheck `) + +## Test Output + +``` + +``` + +## Notes + + diff --git a/justfile b/justfile index 5ae84538..73fa87b7 100644 --- a/justfile +++ b/justfile @@ -689,6 +689,100 @@ import-and-normalize repo skill target: # Validate the result just validate-skill "{{ justfile_directory() }}/components/skills/{{ target }}" +# Plugin management + +# Install a plugin locally via symlinks +[group('plugins')] +install-plugin name: + #!/usr/bin/env bash + set -euo pipefail + PLUGIN_DIR="context/plugins/{{ name }}" + SOURCES="$PLUGIN_DIR/.claude-plugin/plugin.sources.json" + if [ ! -f "$SOURCES" ]; then + echo "Error: $SOURCES not found"; exit 1 + fi + RECEIPT="{{ CLAUDE_DIR }}/plugins/{{ name }}.installed" + mkdir -p "{{ CLAUDE_DIR }}/plugins" + # Read each source mapping and create symlinks + jq -r '.sources | to_entries[] | "\(.key)\t\(.value)"' "$SOURCES" | while IFS=$'\t' read -r local_path source_path; do + target="{{ CLAUDE_DIR }}/$local_path" + mkdir -p "$(dirname "$target")" + ln -sfn "$(pwd)/$source_path" "$target" + echo " → $local_path" + done + # Write receipt + jq -r '.sources | keys[]' "$SOURCES" > "$RECEIPT" + echo "✓ Plugin {{ name }} installed" + +# Build a plugin — copy source components into the plugin directory +[group('plugins')] +build-plugin name: + #!/usr/bin/env bash + set -euo pipefail + PLUGIN_DIR="context/plugins/{{ name }}" + SOURCES="$PLUGIN_DIR/.claude-plugin/plugin.sources.json" + if [ ! -f "$SOURCES" ]; then + echo "Error: $SOURCES not found"; exit 1 + fi + # Copy each source component into the plugin directory + jq -r '.sources | to_entries[] | "\(.key)\t\(.value)"' "$SOURCES" | while IFS=$'\t' read -r local_path source_path; do + target="$PLUGIN_DIR/$local_path" + mkdir -p "$(dirname "$target")" + if [ -d "$source_path" ]; then + rm -rf "$target" + cp -r "$source_path" "$target" + else + cp "$source_path" "$target" + fi + echo " → $local_path" + done + echo "✓ Plugin {{ name }} built" + +# Uninstall a plugin +[group('plugins')] +uninstall-plugin name: + #!/usr/bin/env bash + set -euo pipefail + RECEIPT="{{ CLAUDE_DIR }}/plugins/{{ name }}.installed" + if [ ! -f "$RECEIPT" ]; then + echo "Plugin {{ name }} is not installed"; exit 1 + fi + while read -r local_path; do + rm -f "{{ CLAUDE_DIR }}/$local_path" + echo " ✕ $local_path" + done < "$RECEIPT" + rm -f "$RECEIPT" + echo "✓ Plugin {{ name }} uninstalled" + +# Validate plugin source mappings +[group('plugins')] +check-plugin-sources name: + #!/usr/bin/env bash + set -euo pipefail + PLUGIN_DIR="context/plugins/{{ name }}" + SOURCES="$PLUGIN_DIR/.claude-plugin/plugin.sources.json" + errors=0 + jq -r '.sources | to_entries[] | "\(.key)\t\(.value)"' "$SOURCES" | while IFS=$'\t' read -r local_path source_path; do + if [ ! -e "$source_path" ]; then + echo "MISSING: $source_path (for $local_path)" + errors=$((errors + 1)) + fi + done + [ "$errors" -eq 0 ] && echo "✓ All sources exist" || exit 1 + +# List available and installed plugins +[group('plugins')] +list-plugins: + #!/usr/bin/env bash + echo "Available plugins:" + for d in context/plugins/*/; do + name=$(basename "$d") + [ "$name" = "TODO.md" ] && continue + installed="" + [ -f "{{ CLAUDE_DIR }}/plugins/$name.installed" ] && installed=" [installed]" + echo " $name$installed" + done + opencode: echo "TODO: Setup repo for OpenCode integration, using .ai/* contents"