feat: multi-extension plugin framework for Claude Code and agy #1

Merged
rootiest merged 1 commits from feat-plugin-marketplace-framework into main 2026-08-24 23:22:50 +00:00
60 changed files with 1300 additions and 688 deletions
+12 -40
View File
@@ -6,69 +6,41 @@
},
"description": "Reusable AI skills for Claude Code and Antigravity CLI",
"plugins": [
{
"name": "core-essentials",
"source": "./dist/claude-code/core-essentials"
},
{
"name": "date-time",
"description": "Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context.",
"source": "./",
"skills": [
"./skills/date-time"
]
"source": "./dist/claude-code/date-time"
},
{
"name": "docs-sync-audit",
"description": "Analyzes repository delta since the last documentation update and synchronizes the Single Source of Truth (docs/, wiki, or README) with the current codebase state.",
"source": "./",
"skills": [
"./skills/docs-sync-audit"
]
"source": "./dist/claude-code/docs-sync-audit"
},
{
"name": "git-publish-workflow",
"description": "Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs.",
"source": "./",
"skills": [
"./skills/git-publish-workflow"
]
"source": "./dist/claude-code/git-publish-workflow"
},
{
"name": "readme-sync-audit",
"description": "Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state.",
"source": "./",
"skills": [
"./skills/readme-sync-audit"
]
"source": "./dist/claude-code/readme-sync-audit"
},
{
"name": "ship-it",
"description": "Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR.",
"source": "./",
"skills": [
"./skills/ship-it"
]
"source": "./dist/claude-code/ship-it"
},
{
"name": "systematic-enumeration",
"description": "Forces element-by-element verification for finite sets to prevent counting errors.",
"source": "./",
"skills": [
"./skills/systematic-enumeration"
]
"source": "./dist/claude-code/systematic-enumeration"
},
{
"name": "technical-devlog-scribe",
"description": "Generates a highly structured, objective technical summary of a development session.",
"source": "./",
"skills": [
"./skills/technical-devlog-scribe"
]
"source": "./dist/claude-code/technical-devlog-scribe"
},
{
"name": "rootiest-ai-all",
"description": "Reusable AI skills for Claude Code and Antigravity CLI (all skills)",
"source": "./",
"skills": [
"./skills/"
]
"source": "./dist/claude-code/rootiest-ai-all"
}
]
}
+5 -4
View File
@@ -3,15 +3,16 @@ name: Generate plugin manifests
on:
pull_request:
paths:
- "skills/**"
- "plugins/**"
- "manifest.yaml"
- "scripts/**"
push:
branches: [main]
paths:
- "skills/**"
- "plugins/**"
- "manifest.yaml"
- "scripts/**"
workflow_dispatch: {}
jobs:
validate:
@@ -26,7 +27,7 @@ jobs:
- run: python3 scripts/generate_plugins.py --check
generate:
if: github.event_name == 'push'
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -39,7 +40,7 @@ jobs:
run: |
git config user.name "gitea-actions"
git config user.email "actions@git.rootiest.dev"
git add .claude-plugin descriptions.json dist
git add .claude-plugin dist
if git diff --cached --quiet; then
echo "No generated output changes."
exit 0
+9
View File
@@ -116,3 +116,12 @@ AGENTS/
docs/plans
docs/devlogs
# ────────────────────────────────────────────────────────
# ─────────────────── Private Overlay Builds ─────────────────
# Output of `generate_plugins.py` when layering a private/PII-bearing
# source on top of the public plugins/ tree. Never commit this.
dist-private/
# ─────────────────────────── Python ──────────────────────────
__pycache__/
*.pyc
+128 -149
View File
@@ -1,14 +1,16 @@
# Rootiest AI Repository
A collection of reusable AI skills (structured prompt protocols) for **Claude Code** and **Antigravity CLI**. Each skill defines a precise execution protocol that guides the AI through complex, multi-step tasks — replacing ad-hoc prompting with consistent, auditable workflows.
Skills are plain Markdown files. The included `install.sh` script handles discovery and installation in a single command.
A shareable extension library for **Claude Code** and **Antigravity CLI**
(`agy`). The unit of distribution is a **plugin** — a bundle that can carry
any mix of skills (structured prompt protocols), MCP servers, lifecycle
hooks, and tool-specific extras (slash commands, subagents, rules) — built
from one source tree and published as a native marketplace for both tools.
---
## Table of Contents
- [Skills](#skills)
- [Plugins](#plugins)
- [systematic-enumeration](#systematic-enumeration)
- [git-publish-workflow](#git-publish-workflow)
- [readme-sync-audit](#readme-sync-audit)
@@ -16,19 +18,19 @@ Skills are plain Markdown files. The included `install.sh` script handles discov
- [date-time](#date-time)
- [technical-devlog-scribe](#technical-devlog-scribe)
- [ship-it](#ship-it)
- [core-essentials](#core-essentials)
- [Installation](#installation)
- [Claude Code Plugin Marketplace](#claude-code-plugin-marketplace)
- [Quick Install (curl)](#quick-install-curl)
- [Flags & Options](#flags--options)
- [Environment Variables](#environment-variables)
- [Examples](#examples)
- [Manual Install](#manual-install)
- [Claude Code](#claude-code)
- [Antigravity CLI (agy)](#antigravity-cli-agy)
- [Repository Structure](#repository-structure)
- [Anatomy of a Plugin](#anatomy-of-a-plugin)
- [How Generation Works](#how-generation-works)
- [Private Overlay Builds](#private-overlay-builds)
- [License](#license)
---
## Skills
## Plugins
### `systematic-enumeration`
@@ -121,177 +123,154 @@ Two sequential phases — Phase 2 is blocked until Phase 1 succeeds:
---
### `core-essentials`
**Purpose:** Cross-tool utilities that don't belong to a single workflow.
Currently bundles `delegate-agy`, which hands a subtask off to the
Antigravity CLI (`agy`) in headless mode — useful for a second opinion,
external grounded research, or a large multi-file audit (>500 lines) that
would otherwise bloat the current context.
---
## Installation
### Claude Code Plugin Marketplace
This repository is a native plugin marketplace for both tools — there is no
install script. Each plugin can be installed individually, or as one bundle
(`rootiest-ai-all`).
This repository is a Claude Code plugin marketplace. Each skill is installable
individually, or install everything as one bundle. This is the preferred
install path for Claude Code — `install.sh` remains available as a
cross-tool fallback (and is the only supported path for Antigravity CLI today).
### Claude Code
```
/plugin marketplace add https://git.rootiest.dev/rootiest/rootiest-ai.git
/plugin install git-publish-workflow@rootiest-ai
```
Install every skill at once with the `rootiest-ai-all` bundle:
Install everything at once:
```
/plugin install rootiest-ai-all@rootiest-ai
```
Run `/plugin marketplace update` to pick up newly published skills.
Run `/plugin marketplace update` to pick up newly published plugins.
### Quick Install (curl)
### Antigravity CLI (`agy`)
The intended usage is a single `curl | bash` command. The installer fetches and runs `install.sh` directly — no clone required.
> [!TIP]
> The short-url `https://url.rootiest.dev/ai-install` can be substituted for the full URL in the examples below.
> e.g. `curl -sL https://url.rootiest.dev/ai-install | bash -s -- --all`
**Install all skills for both Claude Code and Antigravity CLI:**
`agy` discovers plugins from a `plugins/` folder pointed at by an entry in
`plugins.json` — either globally (`~/.gemini/config/plugins.json`) or per
project (`.agents/plugins.json`). Clone this repo, then add an entry
pointing at the generated `dist/agy` directory:
```bash
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --all
git clone https://git.rootiest.dev/rootiest/rootiest-ai.git ~/rootiest-ai
```
**Install all skills for Claude Code only:**
```bash
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --all --claude
```json
// ~/.gemini/config/plugins.json
{
"entries": [
{ "path": "~/rootiest-ai/dist/agy" }
]
}
```
**Install all skills for Antigravity CLI only:**
```bash
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --all --antigravity
```
**Install a single skill for both tools:**
```bash
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- systematic-enumeration
```
**Install specific skills for Claude Code only:**
```bash
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --claude git-publish-workflow readme-sync-audit
```
**Install specific skills for Antigravity CLI only:**
```bash
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --antigravity systematic-enumeration git-publish-workflow
```
> **Note:** Providing explicit skill names alongside `--all` causes the named skills to take precedence — only those skills are installed.
---
### Flags & Options
| Flag | Description |
|---|---|
| `-c`, `--claude` | Install into Claude Code (`~/.claude/skills/`) |
| `-g`, `--antigravity` | Install into Antigravity CLI (`~/.gemini/antigravity-cli/skills/`) |
| `-a`, `--all` | Install every skill available in the repository |
| `SKILL...` | Install one or more named skills (positional arguments) |
| `-l`, `--list` | List all available skills with descriptions and exit |
| `-h`, `--help` | Show the help page |
**Tool targeting:** if neither `--claude` nor `--antigravity` is specified, the installer targets **both** tools by default.
**Skill selection precedence:** explicit skill names always override `--all`. Passing `--all skill-name` installs only `skill-name`, not every skill.
**Dependencies:** `curl` and `git` must be present on `PATH`. The installer checks for both and exits with a clear error if either is missing.
---
### Environment Variables
| Variable | Default | Description |
|---|---|---|
| `CLAUDE_SKILLS_DIR` | `~/.claude/skills` | Override the Claude Code install directory |
| `ANTIGRAVITY_SKILLS_DIR` | `~/.gemini/antigravity-cli/skills` | Override the Antigravity CLI install directory. Falls back to `GEMINI_SKILLS_DIR` if set (legacy support). |
Example — installing to a project-local skills directory:
```bash
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh \
| CLAUDE_SKILLS_DIR=./.claude/skills bash -s -- --claude systematic-enumeration
```
---
### Examples
```bash
# Install everything, both tools (simplest possible invocation)
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --all
# Install one skill, Claude only
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --claude git-publish-workflow
# Install two skills, Antigravity only
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --antigravity systematic-enumeration readme-sync-audit
# Override install directory, Claude only
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh \
| CLAUDE_SKILLS_DIR=~/my-skills bash -s -- --claude --all
```
---
### Manual Install
If you prefer to inspect the script before running it:
```bash
# Download
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh -o install.sh
# Review
less install.sh
# Run
bash install.sh --all
```
Or clone the repository and run locally:
```bash
git clone https://git.rootiest.dev/rootiest/rootiest-ai.git
cd rootiest-ai
bash install.sh --all
```
`git pull` in the clone to pick up updates; `agy plugin list` / `agy plugin
validate <path>` to inspect what's loaded.
---
## Repository Structure
Skill content lives entirely in `skills/<name>/SKILL.md` — that's the single
source of truth for a skill's metadata (YAML frontmatter: `name`,
`description`, `version`, `author`, `user-invocable`) and instructions.
`manifest.yaml` holds only marketplace-level metadata (owner, marketplace
name, which agent targets to generate for) — it does not list skills
individually; adding a new `skills/<name>/` folder is picked up automatically.
### Anatomy of a Plugin
CI (`.gitea/workflows/plugins.yml`) runs `scripts/generate_plugins.py` on
every push to `main` that touches `skills/`, `manifest.yaml`, or the
generator itself, and commits the regenerated output:
Every plugin lives under `plugins/<name>/` and is the single source of
truth for that name — nothing under `plugins/` is ever written by the
generator. A plugin can bundle any subset of:
```
plugins/<name>/
├── plugin.json # required: name, description, version, author
├── skills/<skill>/SKILL.md # 0+ skills (YAML frontmatter: name, description, version, author)
├── hooks.json # optional: lifecycle hooks, Claude-shaped event → matcher groups
├── mcp.json # optional: {"mcpServers": {...}}
├── rules/AGENTS.md # optional: agy-only, always-on project rules
├── commands/*.md # optional: Claude Code-only slash commands
└── agents/*.md # optional: Claude Code-only subagents
```
`hooks.json` and `mcp.json` are translated per target rather than copied
verbatim where the two tools' schemas diverge:
- MCP: the shared `mcpServers` shape passes straight through to Claude's
`.mcp.json`; a `url`/`serverUrl` remote entry becomes agy's
`serverUrl` field in `mcp_config.json`.
- Hooks: Claude Code has far more event types than agy documents. Events
agy doesn't support (`SessionStart`, `TaskCreated`, etc.) simply stay
Claude-only — the agy output only carries `PreToolUse`/`PostToolUse`
(kept grouped with their `matcher`) and `PreInvocation`/`PostInvocation`/
`Stop` (flattened to agy's handler-list shape, since agy doesn't group
those by matcher).
- `rules/` has no Claude Code plugin equivalent and is skipped for that
target; `commands/`/`agents/` have no agy equivalent and are skipped for
that target.
### How Generation Works
`scripts/generate_plugins.py` reads `manifest.yaml` (marketplace metadata,
bundle id, target list) and every `plugins/<name>/`, then regenerates:
| Path | Generated for |
|---|---|
| `.claude-plugin/marketplace.json` | Claude Code plugin marketplace |
| `dist/agy/**` | Antigravity CLI (`agy`) plugins |
| `descriptions.json` | `install.sh` skill listing |
| `dist/claude-code/**` | Claude Code plugin directories (linked from the marketplace) |
| `dist/agy/**` | Antigravity CLI (`agy`) plugin directories |
Don't hand-edit any of the paths above — edit the source skill or
`manifest.yaml` and let CI regenerate them. Pull requests run the same
generator in `--check` mode to catch missing/invalid frontmatter before merge.
CI (`.gitea/workflows/plugins.yml`) runs the generator on every push to
`main` that touches `plugins/`, `manifest.yaml`, or the generator itself,
and commits the regenerated output; it can also be re-run on demand from
the Actions tab (`workflow_dispatch`). Pull requests run the same generator
in `--check` mode to catch missing/invalid `plugin.json`/`SKILL.md`
frontmatter before merge.
Don't hand-edit anything under `.claude-plugin/` or `dist/` — edit the
source plugin or `manifest.yaml` and let the generator regenerate them.
---
## Private Overlay Builds
The generator supports layering an additional, non-public source of
plugins on top of this repo — for keeping PII, tokens, API keys, or
personal-only plugins out of a public repo's git history entirely, while
still reusing the same plugin format and generator.
```bash
# Layer a second, already-cloned repo on top of this one
python3 scripts/generate_plugins.py --source . --source ~/rootiest-ai-private
# Or have the generator clone it (token read from an env var, never a CLI argument)
export PRIVATE_TOKEN=...
python3 scripts/generate_plugins.py \
--private-repo https://git.example.com/you/rootiest-ai-private.git \
--private-ref main \
--private-token-env PRIVATE_TOKEN
```
A later `--source` overlays the earlier ones per plugin: a plugin name that
only exists in the private source is added; a plugin name that exists in
both is merged file-by-file, with the private copy winning on conflicts
(e.g. supplying a real `mcp.json` where the public plugin ships a
placeholder).
Output never lands in this repo's tracked paths for an overlaid build —
`--out` defaults to `dist-private/` (gitignored) whenever more than one
source is in play. Add `--install-local` to also drop the result straight
into `~/.claude/plugins/marketplaces/<name>-private/` and print the
`~/.gemini/config/plugins.json` entry for agy, so a personal build is
usable immediately without committing anything anywhere.
---
## License
-9
View File
@@ -1,9 +0,0 @@
{
"date-time": "Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context.",
"docs-sync-audit": "Analyzes repository delta since the last documentation update and synchronizes the Single Source of Truth (docs/, wiki, or README) with the current codebase state.",
"git-publish-workflow": "Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs.",
"readme-sync-audit": "Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state.",
"ship-it": "Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR.",
"systematic-enumeration": "Forces element-by-element verification for finite sets to prevent counting errors.",
"technical-devlog-scribe": "Generates a highly structured, objective technical summary of a development session."
}
+1 -1
View File
@@ -1,3 +1,3 @@
# Generated
This directory is generated by `scripts/generate_plugins.py` from `manifest.yaml` and `skills/`. Do not edit files here directly — edit the source skill instead and regenerate.
This directory is generated by `scripts/generate_plugins.py` from `manifest.yaml` and `plugins/`. Do not edit files here directly — edit the source plugin instead and regenerate.
+3
View File
@@ -0,0 +1,3 @@
{
"name": "core-essentials"
}
+18
View File
@@ -0,0 +1,18 @@
---
name: delegate-agy
description: Delegates a subtask to the Antigravity CLI (agy) when the user wants a second opinion, external grounded research, or needs a large multi-file audit (>500 lines) processed without bloating the current context.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# Antigravity Subagent Delegation (`delegate-agy`)
When the user asks for a second opinion, external grounded research, or when a task requires processing large multi-file audits (>500 lines) that would bloat context, delegate the subtask to `agy`.
## Execution Syntax
Run `agy` in headless, non-interactive mode using the bash tool:
```bash
agy --dangerously-skip-permissions -p "<detailed_task_prompt>"
```
+1 -2
View File
@@ -1,4 +1,3 @@
{
"name": "date-time",
"description": "Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context."
"name": "date-time"
}
+1 -2
View File
@@ -1,4 +1,3 @@
{
"name": "docs-sync-audit",
"description": "Analyzes repository delta since the last documentation update and synchronizes the Single Source of Truth (docs/, wiki, or README) with the current codebase state."
"name": "docs-sync-audit"
}
+1 -2
View File
@@ -1,4 +1,3 @@
{
"name": "git-publish-workflow",
"description": "Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs."
"name": "git-publish-workflow"
}
+1 -2
View File
@@ -1,4 +1,3 @@
{
"name": "readme-sync-audit",
"description": "Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state."
"name": "readme-sync-audit"
}
+1 -2
View File
@@ -1,4 +1,3 @@
{
"name": "rootiest-ai-all",
"description": "Reusable AI skills for Claude Code and Antigravity CLI (all skills)"
"name": "rootiest-ai-all"
}
+18
View File
@@ -0,0 +1,18 @@
---
name: delegate-agy
description: Delegates a subtask to the Antigravity CLI (agy) when the user wants a second opinion, external grounded research, or needs a large multi-file audit (>500 lines) processed without bloating the current context.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# Antigravity Subagent Delegation (`delegate-agy`)
When the user asks for a second opinion, external grounded research, or when a task requires processing large multi-file audits (>500 lines) that would bloat context, delegate the subtask to `agy`.
## Execution Syntax
Run `agy` in headless, non-interactive mode using the bash tool:
```bash
agy --dangerously-skip-permissions -p "<detailed_task_prompt>"
```
+1 -2
View File
@@ -1,4 +1,3 @@
{
"name": "ship-it",
"description": "Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR."
"name": "ship-it"
}
+1 -2
View File
@@ -1,4 +1,3 @@
{
"name": "systematic-enumeration",
"description": "Forces element-by-element verification for finite sets to prevent counting errors."
"name": "systematic-enumeration"
}
+1 -2
View File
@@ -1,4 +1,3 @@
{
"name": "technical-devlog-scribe",
"description": "Generates a highly structured, objective technical summary of a development session."
"name": "technical-devlog-scribe"
}
@@ -0,0 +1,6 @@
{
"name": "core-essentials",
"description": "Core cross-tool utilities, including delegation to the Antigravity CLI (agy) for grounded research and large multi-file audits.",
"version": "1.0.0",
"author": "Rootiest"
}
@@ -0,0 +1,18 @@
---
name: delegate-agy
description: Delegates a subtask to the Antigravity CLI (agy) when the user wants a second opinion, external grounded research, or needs a large multi-file audit (>500 lines) processed without bloating the current context.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# Antigravity Subagent Delegation (`delegate-agy`)
When the user asks for a second opinion, external grounded research, or when a task requires processing large multi-file audits (>500 lines) that would bloat context, delegate the subtask to `agy`.
## Execution Syntax
Run `agy` in headless, non-interactive mode using the bash tool:
```bash
agy --dangerously-skip-permissions -p "<detailed_task_prompt>"
```
+6
View File
@@ -0,0 +1,6 @@
{
"name": "date-time",
"description": "Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context.",
"version": "1.0.0",
"author": "Rootiest"
}
@@ -0,0 +1,6 @@
{
"name": "docs-sync-audit",
"description": "Analyzes repository delta since the last documentation update and synchronizes the Single Source of Truth (docs/, wiki, or README) with the current codebase state.",
"version": "1.3.0",
"author": "Rootiest"
}
@@ -0,0 +1,6 @@
{
"name": "git-publish-workflow",
"description": "Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs.",
"version": "1.2.0",
"author": "Rootiest"
}
@@ -0,0 +1,6 @@
{
"name": "readme-sync-audit",
"description": "Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state.",
"version": "1.2.0",
"author": "Rootiest"
}
@@ -0,0 +1,4 @@
{
"name": "rootiest-ai-all",
"description": "Reusable AI skills for Claude Code and Antigravity CLI (all skills)"
}
@@ -0,0 +1,43 @@
---
name: date-time
description: Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# Current Date and Time Retrieval
## Purpose
Enables the assistant to retrieve the exact, real-time current date and time when addressing time-sensitive queries, scheduling tasks, calculating durations, or validating chronological context.
## Trigger Conditions
Activate this skill whenever the user's prompt:
* Explicitly asks for the current date, day, time, or year.
* References relative time expressions (e.g., "today", "yesterday", "next week", "recently").
* Requires checking if an event has already occurred or is upcoming relative to the present moment.
* Needs to calculate an age, duration, or countdown from the present day.
## Tool Definition
### `get_current_datetime`
* **Description**: Executes the system `date` command to fetch the current local timestamp, timezone, and calendar date.
* **Parameters**: None required.
## Execution Workflow
1. **Detect**: Recognize a time-sensitive trigger in the user's input.
2. **Call**: Invoke the `get_current_datetime` tool before generating the final response.
3. **Process**: Use the returned timestamp to anchor your temporal reasoning.
4. **Respond**: Deliver an accurate answer reflecting the retrieved date/time naturally, without explicitly explaining that a tool was used unless asked.
## Examples
### Example 1
* **User**: "What day of the week is it today?"
* **Assistant Action**: Invoke `get_current_datetime`.
* **Response**: "Today is [Day of Week], [Date]."
### Example 2
* **User**: "Is the 2026 World Cup happening this month?"
* **Assistant Action**: Invoke `get_current_datetime`.
* **Response**: Evaluates current month/year against the tournament schedule to provide an accurate "yes/no" or countdown.
@@ -0,0 +1,18 @@
---
name: delegate-agy
description: Delegates a subtask to the Antigravity CLI (agy) when the user wants a second opinion, external grounded research, or needs a large multi-file audit (>500 lines) processed without bloating the current context.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# Antigravity Subagent Delegation (`delegate-agy`)
When the user asks for a second opinion, external grounded research, or when a task requires processing large multi-file audits (>500 lines) that would bloat context, delegate the subtask to `agy`.
## Execution Syntax
Run `agy` in headless, non-interactive mode using the bash tool:
```bash
agy --dangerously-skip-permissions -p "<detailed_task_prompt>"
```
@@ -0,0 +1,48 @@
---
name: docs-sync-audit
description: Analyzes repository delta since the last documentation update and synchronizes the Single Source of Truth (docs/, wiki, or README) with the current codebase state.
version: 1.3.0
user-invocable: true
author: Rootiest
---
# Documentation Synchronization & Audit Skill
## **Objective**
To ensure project documentation accurately reflects the current state of the codebase by identifying the "Single Source of Truth" (SSoT) (e.g., a `docs/` directory or wiki) and prioritizing updates there. The `README.md` is updated concurrently only for high-level changes or if it serves as the SSoT.
## **Execution Protocol**
### **Phase 0: SSoT Discovery**
1. **Locate Documentation Root**: Scan the repository structure for dedicated documentation directories (e.g., `docs/`, `wiki/`, `website/docs/`) or configuration files (e.g., `mkdocs.yml`, `docusaurus.config.js`).
2. **Establish SSoT**: If a dedicated documentation structure exists, designate it as the SSoT. If absent, fall back to `README.md` as the primary SSoT.
### **Phase 1: Delta Analysis**
1. **Time-Travel Check**: Locate the last commit where the SSoT files were modified.
2. **Feature Diff**: Analyze all code changes (files added, functions modified, dependencies updated) from that commit to the present `HEAD`.
3. **Extraction**: Identify new environment variables, CLI flags, installation steps, or logic changes that are not yet documented.
### **Phase 2: The Pruning & Update Audit**
Perform a targeted comparison of the SSoT against the current code:
* **Route Updates**: Direct detailed API, configuration, and architectural updates to their respective files within the SSoT (`docs/` or wiki).
* **Prune**: Remove any setup steps, dependencies, or "Coming Soon" features from the SSoT that no longer exist or have been replaced.
* **Correct**: Update version numbers, file paths, and command-line examples to match the current implementation.
* **Synthesize**: Add concise documentation for new features identified in Phase 1.
* **README Alignment**: If `docs/` is the SSoT, update the `README.md` *only* to reflect critical, high-level changes (e.g., Quick Start, Installation) or to ensure it properly links to the newly updated sections in the SSoT.
### **Phase 3: Structural Integrity Check**
Ensure the updated SSoT (and README, if applicable) includes or updates these critical sections:
1. **Quick Start**: Are the commands (e.g., `cargo run`, `npm start`) still the primary entry points?
2. **Configuration**: Are all current `.env` or config keys listed?
3. **Usage Examples**: Do the provided code snippets actually compile/run with the current API?
## **Constraints & Rules**
* **Hierarchy Enforcement**: Never duplicate deep technical documentation in the README if a `docs/` folder exists. Use the README as a high-level landing page that points to the SSoT.
* **Minimalism**: Maintain the existing tone of the documentation. Do not add "fluff" or marketing language unless the original document uses it.
* **No Hallucinations**: If a feature's purpose is unclear from the code diff, add a `TODO` comment or ask the user for clarification rather than guessing.
* **Markdown Standards**: Use standard GFM (GitHub Flavored Markdown) or MDX if applicable to the SSoT. Ensure all code blocks have the correct language identifier for syntax highlighting.
## **Trigger Scenarios**
* **Direct Command:** The user invokes `/docs-sync-audit`, `/readme-sync-audit`, or `/update-docs`.
* **Natural Language:** User says "Update the docs", "Sync the wiki", or "Sync the README with my recent changes."
* **Contextual Suggestion:** Trigger automatically if the model detects significant changes to public APIs, CLI arguments, or environment variables without a corresponding documentation update.
@@ -0,0 +1,49 @@
---
name: git-publish-workflow
description: Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs.
version: 1.2.0
user-invocable: true
author: Rootiest
---
# Git Publish & PR Workflow
## **Objective**
To provide a hands-off, end-to-end automation for moving local changes into a formal Pull Request, ensuring code quality through automated and manual verification steps, while intelligently routing Stacked PRs.
## **Execution Protocol**
### **Phase 1: Scope & Base Determination**
Before execution, check the local git state:
1. **Scope Check**: Determine the work boundary:
* **Case A (Partial):** If staged changes exist, operate **ONLY** on staged changes.
* **Case B (Full):** If no changes are staged, operate on **ALL** modified/untracked files.
2. **Base Branch Detection**: Identify the currently checked-out branch.
* **Independent PR**: If the current branch is `main` (or `master`), the new branch will be based on `main`. The PR target will be `main`.
* **Stacked PR**: If the current branch is a feature branch (e.g., `feat-a`), assume the new changes are dependent. The new branch will be created from the current branch. The PR target will be the current feature branch (NOT `main`).
* *Safety Check*: If creating a Stacked PR, output a brief terminal message stating: "Detected active feature branch. Stacking new PR on top of `[current-branch-name]`."
### **Phase 2: The "Safe-Commit" Sequence**
1. **Branching**: Generate a `kebab-case` branch name (e.g., `feat-auth-logic` or `fix-header-css`) based off the branch determined in Phase 1.
2. **Naming**: Use **Conventional Commits** for the message (e.g., `feat(ui): add logout button`).
3. **Verification**:
* Identify the project type (e.g., Rust/Cargo, Python/Poetry, Node/NPM).
* Run the primary `test`, `lint`, or `build` command.
* **Abort Policy**: If verification fails, stop the sequence and report the error. Do not push.
### **Phase 3: Remote Integration**
1. **Push**: Upload the new branch to `origin`.
2. **PR Creation**: Open a Pull Request targeting the base branch determined in Phase 1 (either `main` or the parent feature branch).
3. **Documentation**: Populate the PR description with:
* **Summary**: A high-level overview of "Why" and "What."
* **Manual Verification Checklist**: Provide a Markdown list (`- [ ]`) of 3-5 tactical steps for a human to verify the change in a live environment.
## **Constraints & Rules**
* **Atomic Commits**: If multiple distinct features are found in the scope, suggest splitting the work instead of one giant commit.
* **No Force Push**: Never use `--force` unless explicitly requested in the follow-up prompt.
* **Clean State**: Ensure the workflow ends with the user on the new branch, not the original branch.
## **Trigger Scenarios**
* User says: "Ship this."
* User says: "Make a PR for my current changes."
* Invoked via `/git-publish-workflow`.
@@ -0,0 +1,41 @@
---
name: readme-sync-audit
description: Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state.
version: 1.2.0
user-invocable: true
author: Rootiest
---
# README Synchronization & Audit Skill
## **Objective**
To ensure the `README.md` serves as a "Single Source of Truth" by programmatically aligning documentation with the actual state of the codebase. This skill prioritizes accuracy and the removal of obsolete instructions.
## **Execution Protocol**
### **Phase 1: Delta Analysis**
1. **Time-Travel Check**: Locate the last commit where `README.md` was modified.
2. **Feature Diff**: Analyze all code changes (files added, functions modified, dependencies updated) from that commit to the present `HEAD`.
3. **Extraction**: Identify new environment variables, CLI flags, installation steps, or logic changes that are not yet documented.
### **Phase 2: The Pruning & Update Audit**
Perform a line-by-line comparison of the existing README against the current code:
* **Prune**: Remove any setup steps, dependencies, or "Coming Soon" features that no longer exist or have been replaced.
* **Correct**: Update version numbers, file paths, and command-line examples to match the current implementation.
* **Synthesize**: Add concise documentation for new features identified in Phase 1.
### **Phase 3: Structural Integrity Check**
Ensure the updated README includes (or updates) these critical sections:
1. **Quick Start**: Are the commands (e.g., `cargo run`, `npm start`) still the primary entry points?
2. **Configuration**: Are all current `.env` or config keys listed?
3. **Usage Examples**: Do the provided code snippets actually compile/run with the current API?
## **Constraints & Rules**
* **Minimalism**: Maintain the existing tone of the README. Do not add "fluff" or marketing language unless the original document uses it.
* **No Hallucinations**: If a feature's purpose is unclear from the code diff, add a `TODO` comment or ask the user for clarification rather than guessing.
* **Markdown Standards**: Use standard GFM (GitHub Flavored Markdown). Ensure all code blocks have the correct language identifier for syntax highlighting.
## **Trigger Scenarios**
* **Direct Command:** The user invokes `/readme-sync-audit` or `/update-docs`.
* **Natural Language:** User says "Update the docs" or "Sync the README with my recent changes."
* **Contextual Suggestion:** Trigger automatically if the model detects significant changes to public APIs, CLI arguments, or environment variables without a corresponding documentation update.
+6
View File
@@ -0,0 +1,6 @@
{
"name": "ship-it",
"description": "Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR.",
"version": "1.0.0",
"author": "Rootiest"
}
+25
View File
@@ -0,0 +1,25 @@
---
name: ship-it
description: Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# /ship-it
Instructions:
Execute the following two phases sequentially. Do not proceed to Phase 2 unless Phase 1 completes successfully.
1. Phase 1: Documentation Sync & Code Audit
- Act as the `/docs-sync-audit` skill.
- Scan all file changes since the last README edit and update the README to ensure it accurately reflects the current state of the codebase.
- Audit all code files for any syntax errors, regressions, or issues.
- CRITICAL: If any code errors or breaking issues are discovered during the audit, HALT the workflow immediately and report them to the user. Do not proceed to publishing.
2. Phase 2: Git Publish Workflow
- Act as the `/git-publish-workflow` skill.
- Create a new, descriptively named git branch.
- Stage and commit all pending changes (including the newly updated README from Phase 1).
- Push the branch to the remote repository.
- Generate a Pull Request (PR) from the new branch into 'main'.
@@ -0,0 +1,6 @@
{
"name": "systematic-enumeration",
"description": "Forces element-by-element verification for finite sets to prevent counting errors.",
"version": "1.0.0",
"author": "Rootiest"
}
@@ -0,0 +1,40 @@
---
name: systematic-enumeration
description: Forces element-by-element verification for finite sets to prevent counting errors.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# Systematic Enumeration & Verification Skill
## **Objective**
To eliminate heuristic errors and "hallucinated patterns" when analyzing finite sets. This protocol overrides the model's tendency toward "holistic recognition" in favor of systematic, element-by-element verification.
## **Execution Protocol**
When this skill is triggered, you MUST NOT provide a direct answer immediately. Follow these three phases to ensure accuracy:
### **Phase 1: Set Definition**
Explicitly define the boundaries and members of the finite set being analyzed.
* **Requirement:** List the members before performing any tests.
* *Example:* "The set consists of the files in the `/src` directory: [main.rs, utils.rs, types.rs]."
### **Phase 2: Atomic Element Testing (O(n))**
Iterate through every item in the set. For each item, perform a literal check against the target property.
* **Format:** Use a list or table to force token-level focus on each element.
* **Structure:** `[Item] -> [Logic/Observation] -> [Boolean Result]`
* *Note:* For character-based tests, split the string into individual characters to bypass tokenization bias.
### **Phase 3: Reduction & Summation**
Aggregate the `True` results from Phase 2 to derive the final answer.
* **Self-Correction:** Verify that the count of items tested in Phase 2 exactly matches the count of the set defined in Phase 1. If there is a mismatch, restart Phase 2.
## **Constraints & Anti-Patterns**
* **STRICT BAN on Heuristics:** Do not use phrases like "typically," "usually," or "it appears that."
* **NO Pattern Matching:** Do not extrapolate a rule (e.g., "every other item") as a substitute for testing every item.
* **Computational Justification:** Treat the process as an $O(n)$ operation where $n$ is small enough that accuracy is the only priority.
## **Trigger Scenarios**
* Counting specific characters or substrings within a string.
* Verifying property adherence across a list of variables, files, or objects.
* Membership testing in sets where false negatives are high-risk.
@@ -0,0 +1,6 @@
{
"name": "technical-devlog-scribe",
"description": "Generates a highly structured, objective technical summary of a development session.",
"version": "1.0.0",
"author": "Rootiest"
}
@@ -0,0 +1,32 @@
---
name: technical-devlog-scribe
description: Generates a highly structured, objective technical summary of a development session.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# SKILL: Technical Devlog Scribe
## Description
Generates a comprehensive, highly structured technical summary of a development session. This skill acts as an objective technical scribe, producing a reliable historical record optimized for future context loading and maintaining a single source of truth for project evolution.
## System Directives
* **Tone & Style:** Maintain an objective, dense, and highly technical tone. Avoid conversational filler or fluff.
* **Accuracy:** Rely strictly on the actions, code snippets, and decisions discussed within the current session. Do not hallucinate external constraints.
* **File Routing:** The output must be saved directly to `AGENTS/devlogs/<kebab-case-short-description>.md`. Ensure the filename is concise but descriptive (e.g., `AGENTS/devlogs/oauth2-token-refresh-fix.md`).
## Required Output Structure
The generated markdown file must adhere strictly to the following format:
---
**[START OF FILE FORMAT]**
```yaml
---
date: YYYY-MM-DD
title: <Clear, concise title>
tags: [<relevant>, <tech>, <stack>, <tags>]
status: <Complete | In-Progress | Blocked>
---
-377
View File
@@ -1,377 +0,0 @@
#!/usr/bin/env bash
# install.sh — AI Skill Installer
# Install skills from rootiest/rootiest-ai into Claude Code and/or Antigravity CLI
#
# Usage: bash <(curl -sL <url>) [OPTIONS] [SKILL...]
set -euo pipefail
# ── Constants ──────────────────────────────────────────────────────────────────
readonly BASE_URL="https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main"
readonly REPO_URL="https://git.rootiest.dev/rootiest/rootiest-ai.git"
readonly API_URL="https://git.rootiest.dev/api/v1/repos/rootiest/rootiest-ai/contents/skills"
# Overridable via environment
CLAUDE_SKILLS_DIR="${CLAUDE_SKILLS_DIR:-${HOME}/.claude/skills}"
# ANTIGRAVITY_SKILLS_DIR takes precedence; fall back to GEMINI_SKILLS_DIR for legacy support
ANTIGRAVITY_SKILLS_DIR="${ANTIGRAVITY_SKILLS_DIR:-${GEMINI_SKILLS_DIR:-${HOME}/.gemini/antigravity-cli/skills}}"
# ── ANSI Colors (only when writing to a terminal) ─────────────────────────────
if [[ -t 1 ]]; then
BOLD=$'\033[1m'
BOLD_CYAN=$'\033[1;36m'
BOLD_GREEN=$'\033[1;32m'
GREEN=$'\033[0;32m'
YELLOW=$'\033[0;33m'
RED=$'\033[0;31m'
DIM=$'\033[2m'
RESET=$'\033[0m'
else
BOLD='' BOLD_CYAN='' BOLD_GREEN='' GREEN='' YELLOW='' RED='' DIM='' RESET=''
fi
# ── State ─────────────────────────────────────────────────────────────────────
INSTALL_CLAUDE=false
INSTALL_ANTIGRAVITY=false
INSTALL_ALL=false
LIST_SKILLS=false
declare -a SKILLS=()
DESCRIPTIONS_JSON=""
TMP_DIR=""
# ── Cleanup ───────────────────────────────────────────────────────────────────
cleanup() {
if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT
# ── Output Helpers ────────────────────────────────────────────────────────────
info() { printf " ${BOLD_CYAN}${RESET} %b\n" "$*"; }
ok() { printf " ${BOLD_GREEN}${RESET} %b\n" "$*"; }
warn() { printf " ${YELLOW}${RESET} %b\n" "$*" >&2; }
die() { printf " ${RED}✗ ERROR:${RESET} %b\n" "$*" >&2; exit 1; }
sep() { printf "${BOLD_CYAN}%s${RESET}\n" "──────────────────────────────────────────"; }
# ── Help ──────────────────────────────────────────────────────────────────────
show_help() {
printf "\n"
printf "${BOLD_CYAN} AI Skill Installer${RESET}\n"
printf "${DIM} Install skills from rootiest/rootiest-ai into Claude Code and Antigravity CLI${RESET}\n"
printf "\n"
printf "${BOLD} USAGE${RESET}\n"
printf " install.sh [OPTIONS] [SKILL...]\n"
printf " bash <(curl -sL <url>/install.sh) [OPTIONS] [SKILL...]\n"
printf "\n"
printf "${BOLD} TOOL TARGETS${RESET}\n"
printf " ${GREEN}-c, --claude${RESET} Install into Claude Code (\$CLAUDE_SKILLS_DIR)\n"
printf " ${GREEN}-g, --antigravity${RESET} Install into Antigravity CLI (\$ANTIGRAVITY_SKILLS_DIR)\n"
printf " ${DIM} (default: install for both tools)${RESET}\n"
printf "\n"
printf "${BOLD} SKILL SELECTION${RESET}\n"
printf " ${GREEN}-a, --all${RESET} Install every skill in the repository\n"
printf " ${GREEN}SKILL...${RESET} One or more skill names (folder names under skills/)\n"
printf " ${DIM} Specific names always override --all${RESET}\n"
printf "\n"
printf "${BOLD} OTHER${RESET}\n"
printf " ${GREEN}-l, --list${RESET} List all available skills and exit\n"
printf " ${GREEN}-h, --help${RESET} Show this help page\n"
printf "\n"
printf "${BOLD} EXAMPLES${RESET}\n"
printf " ${DIM}# Install all skills for both tools${RESET}\n"
printf " install.sh --all\n"
printf "\n"
printf " ${DIM}# Install one skill, Claude Code only${RESET}\n"
printf " install.sh --claude my-skill\n"
printf "\n"
printf " ${DIM}# Install two skills, Antigravity CLI only${RESET}\n"
printf " install.sh --antigravity skill-one skill-two\n"
printf "\n"
printf " ${DIM}# Named skills override --all (only those listed are installed)${RESET}\n"
printf " install.sh --all --claude skill-name\n"
printf "\n"
printf "${BOLD} ENVIRONMENT${RESET}\n"
printf " ${GREEN}CLAUDE_SKILLS_DIR${RESET} Claude skills directory (default: ~/.claude/skills)\n"
printf " ${GREEN}ANTIGRAVITY_SKILLS_DIR${RESET} Antigravity skills directory (default: ~/.gemini/antigravity-cli/skills)\n"
printf " ${DIM} Falls back to GEMINI_SKILLS_DIR if set (legacy support)${RESET}\n"
printf "\n"
printf "${BOLD} REPOSITORY${RESET}\n"
printf " ${DIM}%s${RESET}\n" "$REPO_URL"
printf "\n"
}
# ── Dependency Check ──────────────────────────────────────────────────────────
check_deps() {
local -a missing=()
command -v curl &>/dev/null || missing+=(curl)
command -v git &>/dev/null || missing+=(git)
if [[ ${#missing[@]} -gt 0 ]]; then
die "Missing required tools: ${missing[*]}"
fi
}
# ── Validate Skill Name ───────────────────────────────────────────────────────
validate_skill_name() {
local name="$1"
if [[ -z "$name" ]]; then die "Skill name cannot be empty."; fi
if [[ "$name" == *".."* ]]; then die "Invalid skill name (path traversal): '${name}'"; fi
if [[ "$name" == *"/"* ]]; then die "Invalid skill name (contains slash): '${name}'"; fi
}
# ── Parse Directory Names from Gitea API JSON ─────────────────────────────────
# Supports jq, python3, or no-dep grep/awk fallback
parse_dir_names() {
local json="$1"
if command -v jq &>/dev/null; then
printf '%s' "$json" | jq -r '.[] | select(.type == "dir") | .name'
return
fi
if command -v python3 &>/dev/null; then
printf '%s' "$json" | python3 -c \
"import sys,json; [print(e['name']) for e in json.load(sys.stdin) if e.get('type')=='dir']"
return
fi
# Awk fallback: track name/type fields within each JSON object token stream.
# Reliable for single-line (minified) Gitea API responses.
printf '%s' "$json" | tr ',' '\n' | awk -F'"' '
/^[[:space:]]*"name"/ { name = $4 }
/^[[:space:]]*"type".*"dir"/ { if (name != "") print name; name = "" }
'
}
# ── Discover Available Skills ─────────────────────────────────────────────────
discover_skills() {
info "Discovering available skills from repository..."
# Attempt 1: Gitea contents API
local api_response
api_response=$(curl -sf --max-time 15 "$API_URL" 2>/dev/null) || api_response=""
if [[ -n "$api_response" ]]; then
mapfile -t SKILLS < <(parse_dir_names "$api_response" 2>/dev/null | grep -v '^$' || true)
fi
# Attempt 2: Sparse/no-checkout git clone — no JSON parsing required
if [[ ${#SKILLS[@]} -eq 0 ]]; then
warn "API unavailable or returned no results — falling back to git ls-tree..."
TMP_DIR=$(mktemp -d)
if ! git clone \
--quiet \
--depth=1 \
--filter=blob:none \
--no-checkout \
"$REPO_URL" \
"${TMP_DIR}/repo" 2>/dev/null; then
die "Cannot reach repository.\n URL: ${REPO_URL}\n Check your network connection."
fi
mapfile -t SKILLS < <(
git -C "${TMP_DIR}/repo" ls-tree --name-only HEAD "skills/" 2>/dev/null \
| grep -v '^\.' \
| grep -v '^$' \
|| true
)
fi
if [[ ${#SKILLS[@]} -eq 0 ]]; then
die "No skills found in repository. Verify the skills/ directory exists:\n ${REPO_URL}"
fi
ok "Found ${#SKILLS[@]} skill(s): ${DIM}${SKILLS[*]}${RESET}"
}
# ── Fetch & Query Skill Descriptions ─────────────────────────────────────────
fetch_descriptions() {
# Prefer a local descriptions.json when running from a clone
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)" || script_dir=""
if [[ -n "$script_dir" && -f "${script_dir}/descriptions.json" ]]; then
DESCRIPTIONS_JSON=$(<"${script_dir}/descriptions.json")
return
fi
local url="${BASE_URL}/descriptions.json"
DESCRIPTIONS_JSON=$(curl -sf --max-time 10 "$url" 2>/dev/null) || DESCRIPTIONS_JSON=""
}
get_description() {
local skill_name="$1"
if [[ -z "$DESCRIPTIONS_JSON" ]]; then return; fi
if command -v jq &>/dev/null; then
printf '%s' "$DESCRIPTIONS_JSON" | jq -r --arg n "$skill_name" '.[$n] // empty'
return
fi
if command -v python3 &>/dev/null; then
printf '%s' "$DESCRIPTIONS_JSON" | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(d.get('${skill_name}',''))"
return
fi
# awk fallback: relies on pretty-printed JSON with one key per line
printf '%s' "$DESCRIPTIONS_JSON" | awk -F'"' -v key="$skill_name" '$2 == key { print $4 }'
}
# ── List Available Skills ─────────────────────────────────────────────────────
list_skills() {
check_deps
discover_skills
fetch_descriptions
printf "\n"
sep
printf " ${BOLD}Available skills${RESET}\n"
sep
printf "\n"
for skill in "${SKILLS[@]}"; do
local desc
desc=$(get_description "$skill")
if [[ -n "$desc" ]]; then
printf " ${GREEN}${RESET} ${BOLD}%s${RESET}\n ${DIM}%s${RESET}\n\n" "$skill" "$desc"
else
printf " ${GREEN}${RESET} %s\n" "$skill"
fi
done
printf "\n"
}
# ── Fetch a Single Skill's SKILL.md ──────────────────────────────────────────
fetch_skill() {
local skill_name="$1"
local url="${BASE_URL}/skills/${skill_name}/SKILL.md"
if ! curl -sf --max-time 30 "$url" 2>/dev/null; then
warn "Download failed: ${url}"
return 1
fi
}
# ── Install to Claude Code ────────────────────────────────────────────────────
install_to_claude() {
local skill_name="$1"
local dest="${CLAUDE_SKILLS_DIR}/${skill_name}/SKILL.md"
info "[Claude] Downloading ${BOLD}${skill_name}${RESET}..."
local content
if ! content=$(fetch_skill "$skill_name"); then
warn "[Claude] Skipping '${skill_name}' — download failed."
return 1
fi
mkdir -p "${CLAUDE_SKILLS_DIR}/${skill_name}"
printf '%s\n' "$content" > "$dest"
ok "[Claude] ${BOLD}${skill_name}${RESET}${DIM}${dest}${RESET}"
}
# ── Install to Antigravity CLI ────────────────────────────────────────────────
install_to_antigravity() {
local skill_name="$1"
local dest="${ANTIGRAVITY_SKILLS_DIR}/${skill_name}/SKILL.md"
info "[Antigravity] Downloading ${BOLD}${skill_name}${RESET}..."
local content
if ! content=$(fetch_skill "$skill_name"); then
warn "[Antigravity] Skipping '${skill_name}' — download failed."
return 1
fi
mkdir -p "${ANTIGRAVITY_SKILLS_DIR}/${skill_name}"
printf '%s\n' "$content" > "$dest"
ok "[Antigravity] ${BOLD}${skill_name}${RESET}${DIM}${dest}${RESET}"
}
# ── Main ──────────────────────────────────────────────────────────────────────
main() {
if [[ $# -eq 0 ]]; then show_help; exit 0; fi
local -a positional=()
# ── Parse Arguments ────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) show_help; exit 0 ;;
-l|--list) LIST_SKILLS=true ;;
-c|--claude) INSTALL_CLAUDE=true ;;
-g|--antigravity|--gemini) INSTALL_ANTIGRAVITY=true ;;
-a|--all) INSTALL_ALL=true ;;
-*) die "Unknown option: '${1}'. Run with -h for help." ;;
*) positional+=("$1") ;;
esac
shift
done
if $LIST_SKILLS; then list_skills; exit 0; fi
# Default: install for both tools when neither is specified
if ! $INSTALL_CLAUDE && ! $INSTALL_ANTIGRAVITY; then
INSTALL_CLAUDE=true
INSTALL_ANTIGRAVITY=true
fi
check_deps
# ── Skill Resolution ───────────────────────────────────────────────────────
# Named positional arguments always take precedence over --all
if [[ ${#positional[@]} -gt 0 ]]; then
SKILLS=("${positional[@]}")
for skill in "${SKILLS[@]}"; do
validate_skill_name "$skill"
done
elif $INSTALL_ALL; then
discover_skills
else
warn "No skills specified. Provide skill name(s) or use --all."
printf "\n"
show_help
exit 1
fi
# ── Target Label for Display ──────────────────────────────────────────────
local target_label
if $INSTALL_CLAUDE && $INSTALL_ANTIGRAVITY; then
target_label="Claude Code + Antigravity CLI"
elif $INSTALL_CLAUDE; then
target_label="Claude Code"
else
target_label="Antigravity CLI"
fi
# ── Header ────────────────────────────────────────────────────────────────
printf "\n"
sep
printf " ${BOLD}%d skill(s)${RESET} → %s\n" "${#SKILLS[@]}" "$target_label"
sep
printf "\n"
# ── Install Loop ──────────────────────────────────────────────────────────
local errors=0
for skill in "${SKILLS[@]}"; do
printf " ${BOLD}%s${RESET}\n" "$skill"
if $INSTALL_CLAUDE; then
install_to_claude "$skill" || errors=$((errors + 1))
fi
if $INSTALL_ANTIGRAVITY; then
install_to_antigravity "$skill" || errors=$((errors + 1))
fi
printf "\n"
done
# ── Summary ───────────────────────────────────────────────────────────────
sep
if [[ $errors -eq 0 ]]; then
printf " ${BOLD_GREEN}✓ All skills installed successfully.${RESET}\n"
else
printf " ${YELLOW}⚠ %d installation(s) failed.${RESET} Review warnings above.\n" "$errors"
fi
sep
printf "\n"
if [[ $errors -gt 0 ]]; then exit 1; fi
return 0
}
main "$@"
+6
View File
@@ -0,0 +1,6 @@
{
"name": "core-essentials",
"description": "Core cross-tool utilities, including delegation to the Antigravity CLI (agy) for grounded research and large multi-file audits.",
"version": "1.0.0",
"author": "Rootiest"
}
@@ -1,3 +1,11 @@
---
name: delegate-agy
description: Delegates a subtask to the Antigravity CLI (agy) when the user wants a second opinion, external grounded research, or needs a large multi-file audit (>500 lines) processed without bloating the current context.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# Antigravity Subagent Delegation (`delegate-agy`)
When the user asks for a second opinion, external grounded research, or when a task requires processing large multi-file audits (>500 lines) that would bloat context, delegate the subtask to `agy`.
+6
View File
@@ -0,0 +1,6 @@
{
"name": "date-time",
"description": "Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context.",
"version": "1.0.0",
"author": "Rootiest"
}
@@ -0,0 +1,43 @@
---
name: date-time
description: Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# Current Date and Time Retrieval
## Purpose
Enables the assistant to retrieve the exact, real-time current date and time when addressing time-sensitive queries, scheduling tasks, calculating durations, or validating chronological context.
## Trigger Conditions
Activate this skill whenever the user's prompt:
* Explicitly asks for the current date, day, time, or year.
* References relative time expressions (e.g., "today", "yesterday", "next week", "recently").
* Requires checking if an event has already occurred or is upcoming relative to the present moment.
* Needs to calculate an age, duration, or countdown from the present day.
## Tool Definition
### `get_current_datetime`
* **Description**: Executes the system `date` command to fetch the current local timestamp, timezone, and calendar date.
* **Parameters**: None required.
## Execution Workflow
1. **Detect**: Recognize a time-sensitive trigger in the user's input.
2. **Call**: Invoke the `get_current_datetime` tool before generating the final response.
3. **Process**: Use the returned timestamp to anchor your temporal reasoning.
4. **Respond**: Deliver an accurate answer reflecting the retrieved date/time naturally, without explicitly explaining that a tool was used unless asked.
## Examples
### Example 1
* **User**: "What day of the week is it today?"
* **Assistant Action**: Invoke `get_current_datetime`.
* **Response**: "Today is [Day of Week], [Date]."
### Example 2
* **User**: "Is the 2026 World Cup happening this month?"
* **Assistant Action**: Invoke `get_current_datetime`.
* **Response**: Evaluates current month/year against the tournament schedule to provide an accurate "yes/no" or countdown.
+6
View File
@@ -0,0 +1,6 @@
{
"name": "docs-sync-audit",
"description": "Analyzes repository delta since the last documentation update and synchronizes the Single Source of Truth (docs/, wiki, or README) with the current codebase state.",
"version": "1.3.0",
"author": "Rootiest"
}
@@ -0,0 +1,48 @@
---
name: docs-sync-audit
description: Analyzes repository delta since the last documentation update and synchronizes the Single Source of Truth (docs/, wiki, or README) with the current codebase state.
version: 1.3.0
user-invocable: true
author: Rootiest
---
# Documentation Synchronization & Audit Skill
## **Objective**
To ensure project documentation accurately reflects the current state of the codebase by identifying the "Single Source of Truth" (SSoT) (e.g., a `docs/` directory or wiki) and prioritizing updates there. The `README.md` is updated concurrently only for high-level changes or if it serves as the SSoT.
## **Execution Protocol**
### **Phase 0: SSoT Discovery**
1. **Locate Documentation Root**: Scan the repository structure for dedicated documentation directories (e.g., `docs/`, `wiki/`, `website/docs/`) or configuration files (e.g., `mkdocs.yml`, `docusaurus.config.js`).
2. **Establish SSoT**: If a dedicated documentation structure exists, designate it as the SSoT. If absent, fall back to `README.md` as the primary SSoT.
### **Phase 1: Delta Analysis**
1. **Time-Travel Check**: Locate the last commit where the SSoT files were modified.
2. **Feature Diff**: Analyze all code changes (files added, functions modified, dependencies updated) from that commit to the present `HEAD`.
3. **Extraction**: Identify new environment variables, CLI flags, installation steps, or logic changes that are not yet documented.
### **Phase 2: The Pruning & Update Audit**
Perform a targeted comparison of the SSoT against the current code:
* **Route Updates**: Direct detailed API, configuration, and architectural updates to their respective files within the SSoT (`docs/` or wiki).
* **Prune**: Remove any setup steps, dependencies, or "Coming Soon" features from the SSoT that no longer exist or have been replaced.
* **Correct**: Update version numbers, file paths, and command-line examples to match the current implementation.
* **Synthesize**: Add concise documentation for new features identified in Phase 1.
* **README Alignment**: If `docs/` is the SSoT, update the `README.md` *only* to reflect critical, high-level changes (e.g., Quick Start, Installation) or to ensure it properly links to the newly updated sections in the SSoT.
### **Phase 3: Structural Integrity Check**
Ensure the updated SSoT (and README, if applicable) includes or updates these critical sections:
1. **Quick Start**: Are the commands (e.g., `cargo run`, `npm start`) still the primary entry points?
2. **Configuration**: Are all current `.env` or config keys listed?
3. **Usage Examples**: Do the provided code snippets actually compile/run with the current API?
## **Constraints & Rules**
* **Hierarchy Enforcement**: Never duplicate deep technical documentation in the README if a `docs/` folder exists. Use the README as a high-level landing page that points to the SSoT.
* **Minimalism**: Maintain the existing tone of the documentation. Do not add "fluff" or marketing language unless the original document uses it.
* **No Hallucinations**: If a feature's purpose is unclear from the code diff, add a `TODO` comment or ask the user for clarification rather than guessing.
* **Markdown Standards**: Use standard GFM (GitHub Flavored Markdown) or MDX if applicable to the SSoT. Ensure all code blocks have the correct language identifier for syntax highlighting.
## **Trigger Scenarios**
* **Direct Command:** The user invokes `/docs-sync-audit`, `/readme-sync-audit`, or `/update-docs`.
* **Natural Language:** User says "Update the docs", "Sync the wiki", or "Sync the README with my recent changes."
* **Contextual Suggestion:** Trigger automatically if the model detects significant changes to public APIs, CLI arguments, or environment variables without a corresponding documentation update.
+6
View File
@@ -0,0 +1,6 @@
{
"name": "git-publish-workflow",
"description": "Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs.",
"version": "1.2.0",
"author": "Rootiest"
}
@@ -0,0 +1,49 @@
---
name: git-publish-workflow
description: Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs.
version: 1.2.0
user-invocable: true
author: Rootiest
---
# Git Publish & PR Workflow
## **Objective**
To provide a hands-off, end-to-end automation for moving local changes into a formal Pull Request, ensuring code quality through automated and manual verification steps, while intelligently routing Stacked PRs.
## **Execution Protocol**
### **Phase 1: Scope & Base Determination**
Before execution, check the local git state:
1. **Scope Check**: Determine the work boundary:
* **Case A (Partial):** If staged changes exist, operate **ONLY** on staged changes.
* **Case B (Full):** If no changes are staged, operate on **ALL** modified/untracked files.
2. **Base Branch Detection**: Identify the currently checked-out branch.
* **Independent PR**: If the current branch is `main` (or `master`), the new branch will be based on `main`. The PR target will be `main`.
* **Stacked PR**: If the current branch is a feature branch (e.g., `feat-a`), assume the new changes are dependent. The new branch will be created from the current branch. The PR target will be the current feature branch (NOT `main`).
* *Safety Check*: If creating a Stacked PR, output a brief terminal message stating: "Detected active feature branch. Stacking new PR on top of `[current-branch-name]`."
### **Phase 2: The "Safe-Commit" Sequence**
1. **Branching**: Generate a `kebab-case` branch name (e.g., `feat-auth-logic` or `fix-header-css`) based off the branch determined in Phase 1.
2. **Naming**: Use **Conventional Commits** for the message (e.g., `feat(ui): add logout button`).
3. **Verification**:
* Identify the project type (e.g., Rust/Cargo, Python/Poetry, Node/NPM).
* Run the primary `test`, `lint`, or `build` command.
* **Abort Policy**: If verification fails, stop the sequence and report the error. Do not push.
### **Phase 3: Remote Integration**
1. **Push**: Upload the new branch to `origin`.
2. **PR Creation**: Open a Pull Request targeting the base branch determined in Phase 1 (either `main` or the parent feature branch).
3. **Documentation**: Populate the PR description with:
* **Summary**: A high-level overview of "Why" and "What."
* **Manual Verification Checklist**: Provide a Markdown list (`- [ ]`) of 3-5 tactical steps for a human to verify the change in a live environment.
## **Constraints & Rules**
* **Atomic Commits**: If multiple distinct features are found in the scope, suggest splitting the work instead of one giant commit.
* **No Force Push**: Never use `--force` unless explicitly requested in the follow-up prompt.
* **Clean State**: Ensure the workflow ends with the user on the new branch, not the original branch.
## **Trigger Scenarios**
* User says: "Ship this."
* User says: "Make a PR for my current changes."
* Invoked via `/git-publish-workflow`.
+6
View File
@@ -0,0 +1,6 @@
{
"name": "readme-sync-audit",
"description": "Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state.",
"version": "1.2.0",
"author": "Rootiest"
}
@@ -0,0 +1,41 @@
---
name: readme-sync-audit
description: Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state.
version: 1.2.0
user-invocable: true
author: Rootiest
---
# README Synchronization & Audit Skill
## **Objective**
To ensure the `README.md` serves as a "Single Source of Truth" by programmatically aligning documentation with the actual state of the codebase. This skill prioritizes accuracy and the removal of obsolete instructions.
## **Execution Protocol**
### **Phase 1: Delta Analysis**
1. **Time-Travel Check**: Locate the last commit where `README.md` was modified.
2. **Feature Diff**: Analyze all code changes (files added, functions modified, dependencies updated) from that commit to the present `HEAD`.
3. **Extraction**: Identify new environment variables, CLI flags, installation steps, or logic changes that are not yet documented.
### **Phase 2: The Pruning & Update Audit**
Perform a line-by-line comparison of the existing README against the current code:
* **Prune**: Remove any setup steps, dependencies, or "Coming Soon" features that no longer exist or have been replaced.
* **Correct**: Update version numbers, file paths, and command-line examples to match the current implementation.
* **Synthesize**: Add concise documentation for new features identified in Phase 1.
### **Phase 3: Structural Integrity Check**
Ensure the updated README includes (or updates) these critical sections:
1. **Quick Start**: Are the commands (e.g., `cargo run`, `npm start`) still the primary entry points?
2. **Configuration**: Are all current `.env` or config keys listed?
3. **Usage Examples**: Do the provided code snippets actually compile/run with the current API?
## **Constraints & Rules**
* **Minimalism**: Maintain the existing tone of the README. Do not add "fluff" or marketing language unless the original document uses it.
* **No Hallucinations**: If a feature's purpose is unclear from the code diff, add a `TODO` comment or ask the user for clarification rather than guessing.
* **Markdown Standards**: Use standard GFM (GitHub Flavored Markdown). Ensure all code blocks have the correct language identifier for syntax highlighting.
## **Trigger Scenarios**
* **Direct Command:** The user invokes `/readme-sync-audit` or `/update-docs`.
* **Natural Language:** User says "Update the docs" or "Sync the README with my recent changes."
* **Contextual Suggestion:** Trigger automatically if the model detects significant changes to public APIs, CLI arguments, or environment variables without a corresponding documentation update.
+6
View File
@@ -0,0 +1,6 @@
{
"name": "ship-it",
"description": "Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR.",
"version": "1.0.0",
"author": "Rootiest"
}
+25
View File
@@ -0,0 +1,25 @@
---
name: ship-it
description: Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# /ship-it
Instructions:
Execute the following two phases sequentially. Do not proceed to Phase 2 unless Phase 1 completes successfully.
1. Phase 1: Documentation Sync & Code Audit
- Act as the `/docs-sync-audit` skill.
- Scan all file changes since the last README edit and update the README to ensure it accurately reflects the current state of the codebase.
- Audit all code files for any syntax errors, regressions, or issues.
- CRITICAL: If any code errors or breaking issues are discovered during the audit, HALT the workflow immediately and report them to the user. Do not proceed to publishing.
2. Phase 2: Git Publish Workflow
- Act as the `/git-publish-workflow` skill.
- Create a new, descriptively named git branch.
- Stage and commit all pending changes (including the newly updated README from Phase 1).
- Push the branch to the remote repository.
- Generate a Pull Request (PR) from the new branch into 'main'.
@@ -0,0 +1,6 @@
{
"name": "systematic-enumeration",
"description": "Forces element-by-element verification for finite sets to prevent counting errors.",
"version": "1.0.0",
"author": "Rootiest"
}
@@ -0,0 +1,40 @@
---
name: systematic-enumeration
description: Forces element-by-element verification for finite sets to prevent counting errors.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# Systematic Enumeration & Verification Skill
## **Objective**
To eliminate heuristic errors and "hallucinated patterns" when analyzing finite sets. This protocol overrides the model's tendency toward "holistic recognition" in favor of systematic, element-by-element verification.
## **Execution Protocol**
When this skill is triggered, you MUST NOT provide a direct answer immediately. Follow these three phases to ensure accuracy:
### **Phase 1: Set Definition**
Explicitly define the boundaries and members of the finite set being analyzed.
* **Requirement:** List the members before performing any tests.
* *Example:* "The set consists of the files in the `/src` directory: [main.rs, utils.rs, types.rs]."
### **Phase 2: Atomic Element Testing (O(n))**
Iterate through every item in the set. For each item, perform a literal check against the target property.
* **Format:** Use a list or table to force token-level focus on each element.
* **Structure:** `[Item] -> [Logic/Observation] -> [Boolean Result]`
* *Note:* For character-based tests, split the string into individual characters to bypass tokenization bias.
### **Phase 3: Reduction & Summation**
Aggregate the `True` results from Phase 2 to derive the final answer.
* **Self-Correction:** Verify that the count of items tested in Phase 2 exactly matches the count of the set defined in Phase 1. If there is a mismatch, restart Phase 2.
## **Constraints & Anti-Patterns**
* **STRICT BAN on Heuristics:** Do not use phrases like "typically," "usually," or "it appears that."
* **NO Pattern Matching:** Do not extrapolate a rule (e.g., "every other item") as a substitute for testing every item.
* **Computational Justification:** Treat the process as an $O(n)$ operation where $n$ is small enough that accuracy is the only priority.
## **Trigger Scenarios**
* Counting specific characters or substrings within a string.
* Verifying property adherence across a list of variables, files, or objects.
* Membership testing in sets where false negatives are high-risk.
@@ -0,0 +1,6 @@
{
"name": "technical-devlog-scribe",
"description": "Generates a highly structured, objective technical summary of a development session.",
"version": "1.0.0",
"author": "Rootiest"
}
@@ -0,0 +1,32 @@
---
name: technical-devlog-scribe
description: Generates a highly structured, objective technical summary of a development session.
version: 1.0.0
user-invocable: true
author: Rootiest
---
# SKILL: Technical Devlog Scribe
## Description
Generates a comprehensive, highly structured technical summary of a development session. This skill acts as an objective technical scribe, producing a reliable historical record optimized for future context loading and maintaining a single source of truth for project evolution.
## System Directives
* **Tone & Style:** Maintain an objective, dense, and highly technical tone. Avoid conversational filler or fluff.
* **Accuracy:** Rely strictly on the actions, code snippets, and decisions discussed within the current session. Do not hallucinate external constraints.
* **File Routing:** The output must be saved directly to `AGENTS/devlogs/<kebab-case-short-description>.md`. Ensure the filename is concise but descriptive (e.g., `AGENTS/devlogs/oauth2-token-refresh-fix.md`).
## Required Output Structure
The generated markdown file must adhere strictly to the following format:
---
**[START OF FILE FORMAT]**
```yaml
---
date: YYYY-MM-DD
title: <Clear, concise title>
tags: [<relevant>, <tech>, <stack>, <tags>]
status: <Complete | In-Progress | Blocked>
---
+398 -92
View File
@@ -1,36 +1,62 @@
#!/usr/bin/env python3
"""Generate per-agent plugin manifests from the skills/ SSoT.
"""Generate per-agent plugin/marketplace trees from the plugins/ SSoT.
Reads manifest.yaml (marketplace/org metadata + target list) and every
skills/<name>/SKILL.md (per-skill frontmatter), then regenerates:
A "plugin" is a directory under `plugins/<name>/` that may bundle any mix of:
- plugin.json (required marker + metadata: name, description, version, author)
- skills/<name>/SKILL.md (0+ skills)
- hooks.json (canonical, Claude-shaped: {"<EventName>": [<matcher-group>, ...]})
- mcp.json ({"mcpServers": {...}}, shared shape across targets)
- rules/AGENTS.md (agy-only; ignored by the Claude Code target)
- commands/*.md (Claude Code-only slash commands)
- agents/*.md (Claude Code-only subagents)
One or more source roots (each containing its own `plugins/` directory) are
layered together a later source overlays/overrides an earlier one on a
per-plugin, per-file basis. This is how a private repo (PII/tokens/local-only
plugins) can extend or override the public plugin set without either repo
knowing about the other's internals.
Regenerates, under --out (default: repo root, i.e. today's committed paths):
- descriptions.json (from SKILL.md frontmatter)
- .claude-plugin/marketplace.json (Claude Code target)
- dist/agy/** (Antigravity CLI target)
Run with --check to only validate the SSoT (frontmatter, manifest.yaml) and
skip writing any output used as the pull-request gate.
Run with --check to only validate the SSoT (plugin.json/SKILL.md frontmatter,
manifest.yaml) and skip writing any output used as the pull-request gate.
"""
from __future__ import annotations
import argparse
import json
import os
import shutil
import stat
import subprocess
import sys
import tempfile
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent.parent
SKILLS_DIR = ROOT / "skills"
MANIFEST_PATH = ROOT / "manifest.yaml"
REQUIRED_FRONTMATTER_FIELDS = ("name", "description", "version", "author")
REQUIRED_PLUGIN_FIELDS = ("name", "description")
REQUIRED_SKILL_FIELDS = ("name", "description")
# agy only documents these five hook events; everything else is Claude-only.
AGY_GROUPED_EVENTS = ("PreToolUse", "PostToolUse")
AGY_FLAT_EVENTS = ("PreInvocation", "PostInvocation", "Stop")
class ValidationError(Exception):
pass
# ── Manifest / Frontmatter Loading ───────────────────────────────────────────
def load_manifest() -> dict:
with MANIFEST_PATH.open("r", encoding="utf-8") as f:
manifest = yaml.safe_load(f)
@@ -40,121 +66,323 @@ def load_manifest() -> dict:
return manifest
def parse_frontmatter(skill_md: Path) -> dict:
def load_json(path: Path) -> dict:
try:
return json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as e:
raise ValidationError(f"{path}: invalid JSON ({e})")
def parse_skill_frontmatter(skill_md: Path) -> dict:
text = skill_md.read_text(encoding="utf-8")
if not text.startswith("---\n"):
raise ValidationError(f"{skill_md}: missing YAML frontmatter delimiter")
end = text.find("\n---", 4)
if end == -1:
raise ValidationError(f"{skill_md}: unterminated YAML frontmatter")
raw = text[4:end]
data = yaml.safe_load(raw) or {}
for field in REQUIRED_FRONTMATTER_FIELDS:
data = yaml.safe_load(text[4:end]) or {}
for field in REQUIRED_SKILL_FIELDS:
if not data.get(field):
raise ValidationError(f"{skill_md}: frontmatter missing required field '{field}'")
return data
def discover_skills() -> list[dict]:
skills = []
seen_names = set()
for skill_dir in sorted(SKILLS_DIR.iterdir()):
if not skill_dir.is_dir():
# ── Source Discovery & Layering ──────────────────────────────────────────────
def collect_layers(source_roots: list[Path]) -> "dict[str, list[Path]]":
"""Map plugin name -> ordered list of source dirs (base first, overlays after)."""
layers: dict[str, list[Path]] = {}
for root in source_roots:
plugins_dir = root / "plugins"
if not plugins_dir.is_dir():
continue
skill_md = skill_dir / "SKILL.md"
if not skill_md.exists():
raise ValidationError(f"{skill_dir}: missing SKILL.md")
frontmatter = parse_frontmatter(skill_md)
name = frontmatter["name"]
if name != skill_dir.name:
raise ValidationError(
f"{skill_md}: frontmatter name '{name}' does not match directory name '{skill_dir.name}'"
)
if name in seen_names:
raise ValidationError(f"duplicate skill name '{name}'")
seen_names.add(name)
skills.append({"dir": skill_dir, "frontmatter": frontmatter})
if not skills:
raise ValidationError("no skills found under skills/")
return skills
for plugin_dir in sorted(plugins_dir.iterdir()):
if not plugin_dir.is_dir():
continue
layers.setdefault(plugin_dir.name, []).append(plugin_dir)
if not layers:
raise ValidationError(
f"no plugins found under plugins/ in any source root: {[str(r) for r in source_roots]}"
)
return layers
def gen_descriptions_json(skills: list[dict]) -> None:
descriptions = {s["frontmatter"]["name"]: s["frontmatter"]["description"] for s in skills}
out = ROOT / "descriptions.json"
out.write_text(json.dumps(descriptions, indent=2) + "\n", encoding="utf-8")
def merge_layers(layers: "dict[str, list[Path]]", workdir: Path) -> Path:
"""Flatten each plugin's layers into a single merged directory (later wins)."""
merged_root = workdir / "merged-plugins"
merged_root.mkdir(parents=True, exist_ok=True)
for name, dirs in layers.items():
dest = merged_root / name
dest.mkdir(parents=True, exist_ok=True)
for d in dirs:
shutil.copytree(d, dest, dirs_exist_ok=True)
return merged_root
def gen_claude_code(manifest: dict, skills: list[dict]) -> None:
plugin_dir = ROOT / ".claude-plugin"
plugin_dir.mkdir(exist_ok=True)
def discover_plugins(merged_root: Path) -> list[dict]:
plugins = []
for s in skills:
fm = s["frontmatter"]
for plugin_dir in sorted(merged_root.iterdir()):
if not plugin_dir.is_dir():
continue
manifest_path = plugin_dir / "plugin.json"
if not manifest_path.exists():
raise ValidationError(f"{plugin_dir}: missing plugin.json")
meta = load_json(manifest_path)
if meta.get("name") != plugin_dir.name:
raise ValidationError(
f"{manifest_path}: name '{meta.get('name')}' does not match directory name '{plugin_dir.name}'"
)
for field in REQUIRED_PLUGIN_FIELDS:
if not meta.get(field):
raise ValidationError(f"{manifest_path}: missing required field '{field}'")
skills = []
skills_dir = plugin_dir / "skills"
if skills_dir.is_dir():
for skill_dir in sorted(skills_dir.iterdir()):
if not skill_dir.is_dir():
continue
skill_md = skill_dir / "SKILL.md"
if not skill_md.exists():
raise ValidationError(f"{skill_dir}: missing SKILL.md")
fm = parse_skill_frontmatter(skill_md)
if fm["name"] != skill_dir.name:
raise ValidationError(
f"{skill_md}: frontmatter name '{fm['name']}' does not match directory name '{skill_dir.name}'"
)
skills.append({"dir": skill_dir, "frontmatter": fm})
hooks_path = plugin_dir / "hooks.json"
hooks = load_json(hooks_path) if hooks_path.exists() else None
mcp_path = plugin_dir / "mcp.json"
mcp = load_json(mcp_path) if mcp_path.exists() else None
plugins.append(
{
"name": fm["name"],
"description": fm["description"],
"source": "./",
"skills": [f"./skills/{fm['name']}"],
"dir": plugin_dir,
"meta": meta,
"skills": skills,
"hooks": hooks,
"mcp": mcp,
"rules_dir": plugin_dir / "rules" if (plugin_dir / "rules").is_dir() else None,
"commands_dir": plugin_dir / "commands" if (plugin_dir / "commands").is_dir() else None,
"agents_dir": plugin_dir / "agents" if (plugin_dir / "agents").is_dir() else None,
}
)
plugins.append(
{
"name": manifest["bundle"]["id"],
"description": f"{manifest['marketplace']['description']} (all skills)",
"source": "./",
"skills": ["./skills/"],
}
seen = set()
for p in plugins:
name = p["meta"]["name"]
if name in seen:
raise ValidationError(f"duplicate plugin name '{name}'")
seen.add(name)
return plugins
# ── Private Repo Cloning ─────────────────────────────────────────────────────
_ASKPASS_SCRIPT = """#!/bin/sh
case "$1" in
*sername*) echo "x-access-token" ;;
*) echo "$GIT_ASKPASS_TOKEN" ;;
esac
"""
def clone_private_repo(url: str, ref: str, token_env: str | None, workdir: Path) -> Path:
clone_dir = workdir / "private-repo"
env = os.environ.copy()
env["GIT_TERMINAL_PROMPT"] = "0"
if token_env:
token = os.environ.get(token_env)
if not token:
raise ValidationError(f"--private-token-env={token_env} is not set in the environment")
askpass_path = workdir / "git-askpass.sh"
askpass_path.write_text(_ASKPASS_SCRIPT, encoding="utf-8")
askpass_path.chmod(askpass_path.stat().st_mode | stat.S_IEXEC)
env["GIT_ASKPASS"] = str(askpass_path)
env["GIT_ASKPASS_TOKEN"] = token
result = subprocess.run(
["git", "clone", "--quiet", "--depth", "1", "--branch", ref, url, str(clone_dir)],
env=env,
capture_output=True,
text=True,
)
if result.returncode != 0:
raise ValidationError(f"failed to clone private repo '{url}' (ref {ref}): {result.stderr.strip()}")
return clone_dir
# ── Hook / MCP Translation ───────────────────────────────────────────────────
def translate_hooks_for_agy(hooks: dict, plugin_name: str) -> "dict | None":
agy_events: dict = {}
for event in AGY_GROUPED_EVENTS:
if event in hooks:
agy_events[event] = hooks[event]
for event in AGY_FLAT_EVENTS:
if event in hooks:
flat = []
for group in hooks[event]:
flat.extend(group.get("hooks", []))
agy_events[event] = flat
if not agy_events:
return None
return {plugin_name: agy_events}
def translate_mcp_for_agy(mcp: dict) -> dict:
servers = {}
for name, cfg in mcp.get("mcpServers", {}).items():
if "command" in cfg:
out = {"command": cfg["command"]}
if "args" in cfg:
out["args"] = cfg["args"]
if "env" in cfg:
out["env"] = cfg["env"]
servers[name] = out
else:
url = cfg.get("serverUrl") or cfg.get("url")
if url:
servers[name] = {"serverUrl": url}
return {"mcpServers": servers}
# ── Claude Code Target ───────────────────────────────────────────────────────
def write_claude_plugin(plugin: dict, dest: Path) -> None:
meta = plugin["meta"]
dest.mkdir(parents=True, exist_ok=True)
claude_plugin_dir = dest / ".claude-plugin"
claude_plugin_dir.mkdir(exist_ok=True)
manifest = {"name": meta["name"]}
for field in ("description", "version", "author"):
if meta.get(field):
manifest[field] = meta[field]
(claude_plugin_dir / "plugin.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
if plugin["skills"]:
skills_out = dest / "skills"
for s in plugin["skills"]:
shutil.copytree(s["dir"], skills_out / s["frontmatter"]["name"])
for optional_dir in ("commands_dir", "agents_dir"):
src = plugin[optional_dir]
if src is not None:
shutil.copytree(src, dest / src.name)
if plugin["hooks"] is not None:
hooks_out = dest / "hooks"
hooks_out.mkdir(exist_ok=True)
(hooks_out / "hooks.json").write_text(
json.dumps({"hooks": plugin["hooks"]}, indent=2) + "\n", encoding="utf-8"
)
if plugin["mcp"] is not None:
(dest / ".mcp.json").write_text(json.dumps(plugin["mcp"], indent=2) + "\n", encoding="utf-8")
def gen_claude_code(manifest: dict, plugins: list[dict], out_dir: Path) -> None:
# Generated output lives under dist/claude-code/ — never inside plugins/,
# which is the SSoT and must stay untouched by generation.
plugins_out = out_dir / "dist" / "claude-code"
if plugins_out.exists():
shutil.rmtree(plugins_out)
plugins_out.mkdir(parents=True)
marketplace_entries = []
for p in plugins:
name = p["meta"]["name"]
write_claude_plugin(p, plugins_out / name)
marketplace_entries.append({"name": name, "source": f"./dist/claude-code/{name}"})
bundle_id = manifest["bundle"]["id"]
bundle_dest = plugins_out / bundle_id
(bundle_dest / ".claude-plugin").mkdir(parents=True)
(bundle_dest / ".claude-plugin" / "plugin.json").write_text(
json.dumps(
{"name": bundle_id, "description": f"{manifest['marketplace']['description']} (all skills)"},
indent=2,
)
+ "\n",
encoding="utf-8",
)
bundle_skills = bundle_dest / "skills"
for p in plugins:
for s in p["skills"]:
shutil.copytree(s["dir"], bundle_skills / s["frontmatter"]["name"])
marketplace_entries.append({"name": bundle_id, "source": f"./dist/claude-code/{bundle_id}"})
marketplace = {
"name": manifest["marketplace"]["name"],
"owner": manifest["marketplace"]["owner"],
"description": manifest["marketplace"]["description"],
"plugins": plugins,
"plugins": marketplace_entries,
}
out = plugin_dir / "marketplace.json"
out.write_text(json.dumps(marketplace, indent=2) + "\n", encoding="utf-8")
plugin_dir = out_dir / ".claude-plugin"
plugin_dir.mkdir(exist_ok=True)
(plugin_dir / "marketplace.json").write_text(json.dumps(marketplace, indent=2) + "\n", encoding="utf-8")
def gen_agy(manifest: dict, skills: list[dict]) -> None:
agy_dir = ROOT / "dist" / "agy"
# ── Antigravity CLI (agy) Target ─────────────────────────────────────────────
def write_agy_plugin(plugin: dict, dest: Path) -> None:
meta = plugin["meta"]
name = meta["name"]
dest.mkdir(parents=True, exist_ok=True)
(dest / "plugin.json").write_text(json.dumps({"name": name}, indent=2) + "\n", encoding="utf-8")
if plugin["skills"]:
skills_out = dest / "skills"
for s in plugin["skills"]:
shutil.copytree(s["dir"], skills_out / s["frontmatter"]["name"])
if plugin["rules_dir"] is not None:
shutil.copytree(plugin["rules_dir"], dest / "rules")
if plugin["hooks"] is not None:
translated = translate_hooks_for_agy(plugin["hooks"], name)
if translated is not None:
(dest / "hooks.json").write_text(json.dumps(translated, indent=2) + "\n", encoding="utf-8")
if plugin["mcp"] is not None:
(dest / "mcp_config.json").write_text(
json.dumps(translate_mcp_for_agy(plugin["mcp"]), indent=2) + "\n", encoding="utf-8"
)
def gen_agy(manifest: dict, plugins: list[dict], out_dir: Path) -> None:
agy_dir = out_dir / "dist" / "agy"
if agy_dir.exists():
shutil.rmtree(agy_dir)
agy_dir.mkdir(parents=True)
def write_plugin(plugin_name: str, description: str, members: list[dict]) -> None:
plugin_root = agy_dir / plugin_name
skills_out = plugin_root / "skills"
skills_out.mkdir(parents=True)
for s in members:
dest = skills_out / s["frontmatter"]["name"]
shutil.copytree(s["dir"], dest)
plugin_json = {"name": plugin_name, "description": description}
(plugin_root / "plugin.json").write_text(
json.dumps(plugin_json, indent=2) + "\n", encoding="utf-8"
)
for p in plugins:
write_agy_plugin(p, agy_dir / p["meta"]["name"])
for s in skills:
fm = s["frontmatter"]
write_plugin(fm["name"], fm["description"], [s])
bundle_id = manifest["bundle"]["id"]
bundle_dest = agy_dir / bundle_id
bundle_dest.mkdir(parents=True)
(bundle_dest / "plugin.json").write_text(json.dumps({"name": bundle_id}, indent=2) + "\n", encoding="utf-8")
bundle_skills = bundle_dest / "skills"
for p in plugins:
for s in p["skills"]:
shutil.copytree(s["dir"], bundle_skills / s["frontmatter"]["name"])
write_plugin(
manifest["bundle"]["id"],
f"{manifest['marketplace']['description']} (all skills)",
skills,
)
dist_readme = ROOT / "dist" / "README.md"
dist_readme = out_dir / "dist" / "README.md"
dist_readme.write_text(
"# Generated\n\n"
"This directory is generated by `scripts/generate_plugins.py` from "
"`manifest.yaml` and `skills/`. Do not edit files here directly — "
"edit the source skill instead and regenerate.\n",
"`manifest.yaml` and `plugins/`. Do not edit files here directly — "
"edit the source plugin instead and regenerate.\n",
encoding="utf-8",
)
@@ -165,21 +393,99 @@ GENERATORS = {
}
# ── Local Install Helper ─────────────────────────────────────────────────────
def install_local(manifest: dict, out_dir: Path) -> None:
home = Path.home()
mp_name = f"{manifest['marketplace']['name']}-private"
claude_src = out_dir / ".claude-plugin"
if claude_src.exists():
claude_dest = home / ".claude" / "plugins" / "marketplaces" / mp_name
if claude_dest.exists():
shutil.rmtree(claude_dest)
claude_dest.mkdir(parents=True)
shutil.copytree(claude_src, claude_dest / ".claude-plugin")
shutil.copytree(out_dir / "dist" / "claude-code", claude_dest / "dist" / "claude-code")
print(f"Installed Claude Code marketplace locally: {claude_dest}")
print(f" /plugin marketplace add {claude_dest}")
agy_src = out_dir / "dist" / "agy"
if agy_src.exists():
print("For agy, add this entry to ~/.gemini/config/plugins.json:")
print(json.dumps({"entries": [{"path": str(agy_src)}]}, indent=2))
# ── CLI ───────────────────────────────────────────────────────────────────────
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--check", action="store_true", help="validate only, write nothing")
parser.add_argument(
"--source",
action="append",
default=None,
help="a directory containing its own plugins/ folder; repeatable, layered in order (default: repo root)",
)
parser.add_argument("--private-repo", help="git URL of a private overlay repo to clone and layer on top")
parser.add_argument("--private-ref", default="main", help="branch/tag to clone from --private-repo (default: main)")
parser.add_argument(
"--private-token-env",
help="name of an environment variable holding a token for --private-repo (read at run time, never taken as a literal value)",
)
parser.add_argument(
"--out",
help="output directory (default: '.' for a pure public build, 'dist-private' when overlaying a private source)",
)
parser.add_argument(
"--install-local",
action="store_true",
help="after generating, install the result into local Claude Code / agy config",
)
return parser.parse_args(argv)
def main() -> int:
check_only = "--check" in sys.argv[1:]
args = parse_args(sys.argv[1:])
try:
manifest = load_manifest()
skills = discover_skills()
for target in manifest["targets"]:
if target not in GENERATORS:
raise ValidationError(f"manifest.yaml: unknown target '{target}'")
if check_only:
print(f"OK: {len(skills)} skill(s), {len(manifest['targets'])} target(s) validated")
return 0
gen_descriptions_json(skills)
for target in manifest["targets"]:
GENERATORS[target](manifest, skills)
print(f"Generated plugins for {len(skills)} skill(s), targets: {', '.join(manifest['targets'])}")
source_roots = [Path(s).expanduser().resolve() for s in (args.source or [str(ROOT)])]
has_overlay = bool(args.private_repo) or len(source_roots) > 1
with tempfile.TemporaryDirectory(prefix="rootiest-ai-build-") as tmp:
workdir = Path(tmp)
if args.private_repo:
private_dir = clone_private_repo(args.private_repo, args.private_ref, args.private_token_env, workdir)
source_roots.append(private_dir)
layers = collect_layers(source_roots)
merged_root = merge_layers(layers, workdir)
plugins = discover_plugins(merged_root)
if args.check:
print(f"OK: {len(plugins)} plugin(s), {len(manifest['targets'])} target(s) validated")
return 0
out_dir = Path(args.out).expanduser().resolve() if args.out else (
(ROOT / "dist-private") if has_overlay else ROOT
)
out_dir.mkdir(parents=True, exist_ok=True)
for target in manifest["targets"]:
GENERATORS[target](manifest, plugins, out_dir)
print(f"Generated plugins for {len(plugins)} plugin(s) -> {out_dir}, targets: {', '.join(manifest['targets'])}")
if args.install_local:
install_local(manifest, out_dir)
return 0
except ValidationError as e:
print(f"error: {e}", file=sys.stderr)