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", "description": "Reusable AI skills for Claude Code and Antigravity CLI",
"plugins": [ "plugins": [
{
"name": "core-essentials",
"source": "./dist/claude-code/core-essentials"
},
{ {
"name": "date-time", "name": "date-time",
"description": "Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context.", "source": "./dist/claude-code/date-time"
"source": "./",
"skills": [
"./skills/date-time"
]
}, },
{ {
"name": "docs-sync-audit", "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": "./dist/claude-code/docs-sync-audit"
"source": "./",
"skills": [
"./skills/docs-sync-audit"
]
}, },
{ {
"name": "git-publish-workflow", "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": "./dist/claude-code/git-publish-workflow"
"source": "./",
"skills": [
"./skills/git-publish-workflow"
]
}, },
{ {
"name": "readme-sync-audit", "name": "readme-sync-audit",
"description": "Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state.", "source": "./dist/claude-code/readme-sync-audit"
"source": "./",
"skills": [
"./skills/readme-sync-audit"
]
}, },
{ {
"name": "ship-it", "name": "ship-it",
"description": "Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR.", "source": "./dist/claude-code/ship-it"
"source": "./",
"skills": [
"./skills/ship-it"
]
}, },
{ {
"name": "systematic-enumeration", "name": "systematic-enumeration",
"description": "Forces element-by-element verification for finite sets to prevent counting errors.", "source": "./dist/claude-code/systematic-enumeration"
"source": "./",
"skills": [
"./skills/systematic-enumeration"
]
}, },
{ {
"name": "technical-devlog-scribe", "name": "technical-devlog-scribe",
"description": "Generates a highly structured, objective technical summary of a development session.", "source": "./dist/claude-code/technical-devlog-scribe"
"source": "./",
"skills": [
"./skills/technical-devlog-scribe"
]
}, },
{ {
"name": "rootiest-ai-all", "name": "rootiest-ai-all",
"description": "Reusable AI skills for Claude Code and Antigravity CLI (all skills)", "source": "./dist/claude-code/rootiest-ai-all"
"source": "./",
"skills": [
"./skills/"
]
} }
] ]
} }
+5 -4
View File
@@ -3,15 +3,16 @@ name: Generate plugin manifests
on: on:
pull_request: pull_request:
paths: paths:
- "skills/**" - "plugins/**"
- "manifest.yaml" - "manifest.yaml"
- "scripts/**" - "scripts/**"
push: push:
branches: [main] branches: [main]
paths: paths:
- "skills/**" - "plugins/**"
- "manifest.yaml" - "manifest.yaml"
- "scripts/**" - "scripts/**"
workflow_dispatch: {}
jobs: jobs:
validate: validate:
@@ -26,7 +27,7 @@ jobs:
- run: python3 scripts/generate_plugins.py --check - run: python3 scripts/generate_plugins.py --check
generate: generate:
if: github.event_name == 'push' if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -39,7 +40,7 @@ jobs:
run: | run: |
git config user.name "gitea-actions" git config user.name "gitea-actions"
git config user.email "actions@git.rootiest.dev" 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 if git diff --cached --quiet; then
echo "No generated output changes." echo "No generated output changes."
exit 0 exit 0
+9
View File
@@ -116,3 +116,12 @@ AGENTS/
docs/plans docs/plans
docs/devlogs 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 # 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. A shareable extension library for **Claude Code** and **Antigravity CLI**
(`agy`). The unit of distribution is a **plugin** — a bundle that can carry
Skills are plain Markdown files. The included `install.sh` script handles discovery and installation in a single command. 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 ## Table of Contents
- [Skills](#skills) - [Plugins](#plugins)
- [systematic-enumeration](#systematic-enumeration) - [systematic-enumeration](#systematic-enumeration)
- [git-publish-workflow](#git-publish-workflow) - [git-publish-workflow](#git-publish-workflow)
- [readme-sync-audit](#readme-sync-audit) - [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) - [date-time](#date-time)
- [technical-devlog-scribe](#technical-devlog-scribe) - [technical-devlog-scribe](#technical-devlog-scribe)
- [ship-it](#ship-it) - [ship-it](#ship-it)
- [core-essentials](#core-essentials)
- [Installation](#installation) - [Installation](#installation)
- [Claude Code Plugin Marketplace](#claude-code-plugin-marketplace) - [Claude Code](#claude-code)
- [Quick Install (curl)](#quick-install-curl) - [Antigravity CLI (agy)](#antigravity-cli-agy)
- [Flags & Options](#flags--options)
- [Environment Variables](#environment-variables)
- [Examples](#examples)
- [Manual Install](#manual-install)
- [Repository Structure](#repository-structure) - [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) - [License](#license)
--- ---
## Skills ## Plugins
### `systematic-enumeration` ### `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 ## 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 ### Claude Code
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).
``` ```
/plugin marketplace add https://git.rootiest.dev/rootiest/rootiest-ai.git /plugin marketplace add https://git.rootiest.dev/rootiest/rootiest-ai.git
/plugin install git-publish-workflow@rootiest-ai /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 /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. `agy` discovers plugins from a `plugins/` folder pointed at by an entry in
`plugins.json` — either globally (`~/.gemini/config/plugins.json`) or per
> [!TIP] project (`.agents/plugins.json`). Clone this repo, then add an entry
> The short-url `https://url.rootiest.dev/ai-install` can be substituted for the full URL in the examples below. pointing at the generated `dist/agy` directory:
> e.g. `curl -sL https://url.rootiest.dev/ai-install | bash -s -- --all`
**Install all skills for both Claude Code and Antigravity CLI:**
```bash ```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:** ```json
// ~/.gemini/config/plugins.json
```bash {
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --all --claude "entries": [
{ "path": "~/rootiest-ai/dist/agy" }
]
}
``` ```
**Install all skills for Antigravity CLI only:** `git pull` in the clone to pick up updates; `agy plugin list` / `agy plugin
validate <path>` to inspect what's loaded.
```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
```
--- ---
## Repository Structure ## Repository Structure
Skill content lives entirely in `skills/<name>/SKILL.md` — that's the single ### Anatomy of a Plugin
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.
CI (`.gitea/workflows/plugins.yml`) runs `scripts/generate_plugins.py` on Every plugin lives under `plugins/<name>/` and is the single source of
every push to `main` that touches `skills/`, `manifest.yaml`, or the truth for that name — nothing under `plugins/` is ever written by the
generator itself, and commits the regenerated output: 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 | | Path | Generated for |
|---|---| |---|---|
| `.claude-plugin/marketplace.json` | Claude Code plugin marketplace | | `.claude-plugin/marketplace.json` | Claude Code plugin marketplace |
| `dist/agy/**` | Antigravity CLI (`agy`) plugins | | `dist/claude-code/**` | Claude Code plugin directories (linked from the marketplace) |
| `descriptions.json` | `install.sh` skill listing | | `dist/agy/**` | Antigravity CLI (`agy`) plugin directories |
Don't hand-edit any of the paths above — edit the source skill or CI (`.gitea/workflows/plugins.yml`) runs the generator on every push to
`manifest.yaml` and let CI regenerate them. Pull requests run the same `main` that touches `plugins/`, `manifest.yaml`, or the generator itself,
generator in `--check` mode to catch missing/invalid frontmatter before merge. 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 ## 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 # 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", "name": "date-time"
"description": "Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context."
} }
+1 -2
View File
@@ -1,4 +1,3 @@
{ {
"name": "docs-sync-audit", "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."
} }
+1 -2
View File
@@ -1,4 +1,3 @@
{ {
"name": "git-publish-workflow", "name": "git-publish-workflow"
"description": "Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs."
} }
+1 -2
View File
@@ -1,4 +1,3 @@
{ {
"name": "readme-sync-audit", "name": "readme-sync-audit"
"description": "Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state."
} }
+1 -2
View File
@@ -1,4 +1,3 @@
{ {
"name": "rootiest-ai-all", "name": "rootiest-ai-all"
"description": "Reusable AI skills for Claude Code and Antigravity CLI (all skills)"
} }
+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", "name": "ship-it"
"description": "Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR."
} }
+1 -2
View File
@@ -1,4 +1,3 @@
{ {
"name": "systematic-enumeration", "name": "systematic-enumeration"
"description": "Forces element-by-element verification for finite sets to prevent counting errors."
} }
+1 -2
View File
@@ -1,4 +1,3 @@
{ {
"name": "technical-devlog-scribe", "name": "technical-devlog-scribe"
"description": "Generates a highly structured, objective technical summary of a development session."
} }
@@ -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`) # 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`. 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 #!/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 A "plugin" is a directory under `plugins/<name>/` that may bundle any mix of:
skills/<name>/SKILL.md (per-skill frontmatter), then regenerates:
- 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) - .claude-plugin/marketplace.json (Claude Code target)
- dist/agy/** (Antigravity CLI target) - dist/agy/** (Antigravity CLI target)
Run with --check to only validate the SSoT (frontmatter, manifest.yaml) and Run with --check to only validate the SSoT (plugin.json/SKILL.md frontmatter,
skip writing any output — used as the pull-request gate. manifest.yaml) and skip writing any output — used as the pull-request gate.
""" """
from __future__ import annotations from __future__ import annotations
import argparse
import json import json
import os
import shutil import shutil
import stat
import subprocess
import sys import sys
import tempfile
from pathlib import Path from pathlib import Path
import yaml import yaml
ROOT = Path(__file__).resolve().parent.parent ROOT = Path(__file__).resolve().parent.parent
SKILLS_DIR = ROOT / "skills"
MANIFEST_PATH = ROOT / "manifest.yaml" 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): class ValidationError(Exception):
pass pass
# ── Manifest / Frontmatter Loading ───────────────────────────────────────────
def load_manifest() -> dict: def load_manifest() -> dict:
with MANIFEST_PATH.open("r", encoding="utf-8") as f: with MANIFEST_PATH.open("r", encoding="utf-8") as f:
manifest = yaml.safe_load(f) manifest = yaml.safe_load(f)
@@ -40,121 +66,323 @@ def load_manifest() -> dict:
return manifest 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") text = skill_md.read_text(encoding="utf-8")
if not text.startswith("---\n"): if not text.startswith("---\n"):
raise ValidationError(f"{skill_md}: missing YAML frontmatter delimiter") raise ValidationError(f"{skill_md}: missing YAML frontmatter delimiter")
end = text.find("\n---", 4) end = text.find("\n---", 4)
if end == -1: if end == -1:
raise ValidationError(f"{skill_md}: unterminated YAML frontmatter") raise ValidationError(f"{skill_md}: unterminated YAML frontmatter")
raw = text[4:end] data = yaml.safe_load(text[4:end]) or {}
data = yaml.safe_load(raw) or {} for field in REQUIRED_SKILL_FIELDS:
for field in REQUIRED_FRONTMATTER_FIELDS:
if not data.get(field): if not data.get(field):
raise ValidationError(f"{skill_md}: frontmatter missing required field '{field}'") raise ValidationError(f"{skill_md}: frontmatter missing required field '{field}'")
return data return data
def discover_skills() -> list[dict]: # ── Source Discovery & Layering ──────────────────────────────────────────────
skills = []
seen_names = set()
for skill_dir in sorted(SKILLS_DIR.iterdir()): def collect_layers(source_roots: list[Path]) -> "dict[str, list[Path]]":
if not skill_dir.is_dir(): """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 continue
skill_md = skill_dir / "SKILL.md" for plugin_dir in sorted(plugins_dir.iterdir()):
if not skill_md.exists(): if not plugin_dir.is_dir():
raise ValidationError(f"{skill_dir}: missing SKILL.md") continue
frontmatter = parse_frontmatter(skill_md) layers.setdefault(plugin_dir.name, []).append(plugin_dir)
name = frontmatter["name"] if not layers:
if name != skill_dir.name: raise ValidationError(
raise ValidationError( f"no plugins found under plugins/ in any source root: {[str(r) for r in source_roots]}"
f"{skill_md}: frontmatter name '{name}' does not match directory name '{skill_dir.name}'" )
) return layers
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
def gen_descriptions_json(skills: list[dict]) -> None: def merge_layers(layers: "dict[str, list[Path]]", workdir: Path) -> Path:
descriptions = {s["frontmatter"]["name"]: s["frontmatter"]["description"] for s in skills} """Flatten each plugin's layers into a single merged directory (later wins)."""
out = ROOT / "descriptions.json" merged_root = workdir / "merged-plugins"
out.write_text(json.dumps(descriptions, indent=2) + "\n", encoding="utf-8") 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: def discover_plugins(merged_root: Path) -> list[dict]:
plugin_dir = ROOT / ".claude-plugin"
plugin_dir.mkdir(exist_ok=True)
plugins = [] plugins = []
for s in skills: for plugin_dir in sorted(merged_root.iterdir()):
fm = s["frontmatter"] 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( plugins.append(
{ {
"name": fm["name"], "dir": plugin_dir,
"description": fm["description"], "meta": meta,
"source": "./", "skills": skills,
"skills": [f"./skills/{fm['name']}"], "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( seen = set()
{ for p in plugins:
"name": manifest["bundle"]["id"], name = p["meta"]["name"]
"description": f"{manifest['marketplace']['description']} (all skills)", if name in seen:
"source": "./", raise ValidationError(f"duplicate plugin name '{name}'")
"skills": ["./skills/"], 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 = { marketplace = {
"name": manifest["marketplace"]["name"], "name": manifest["marketplace"]["name"],
"owner": manifest["marketplace"]["owner"], "owner": manifest["marketplace"]["owner"],
"description": manifest["marketplace"]["description"], "description": manifest["marketplace"]["description"],
"plugins": plugins, "plugins": marketplace_entries,
} }
plugin_dir = out_dir / ".claude-plugin"
out = plugin_dir / "marketplace.json" plugin_dir.mkdir(exist_ok=True)
out.write_text(json.dumps(marketplace, indent=2) + "\n", encoding="utf-8") (plugin_dir / "marketplace.json").write_text(json.dumps(marketplace, indent=2) + "\n", encoding="utf-8")
def gen_agy(manifest: dict, skills: list[dict]) -> None: # ── Antigravity CLI (agy) Target ─────────────────────────────────────────────
agy_dir = ROOT / "dist" / "agy"
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(): if agy_dir.exists():
shutil.rmtree(agy_dir) shutil.rmtree(agy_dir)
agy_dir.mkdir(parents=True) agy_dir.mkdir(parents=True)
def write_plugin(plugin_name: str, description: str, members: list[dict]) -> None: for p in plugins:
plugin_root = agy_dir / plugin_name write_agy_plugin(p, agy_dir / p["meta"]["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 s in skills: bundle_id = manifest["bundle"]["id"]
fm = s["frontmatter"] bundle_dest = agy_dir / bundle_id
write_plugin(fm["name"], fm["description"], [s]) 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( dist_readme = out_dir / "dist" / "README.md"
manifest["bundle"]["id"],
f"{manifest['marketplace']['description']} (all skills)",
skills,
)
dist_readme = ROOT / "dist" / "README.md"
dist_readme.write_text( dist_readme.write_text(
"# Generated\n\n" "# Generated\n\n"
"This directory is generated by `scripts/generate_plugins.py` from " "This directory is generated by `scripts/generate_plugins.py` from "
"`manifest.yaml` and `skills/`. Do not edit files here directly — " "`manifest.yaml` and `plugins/`. Do not edit files here directly — "
"edit the source skill instead and regenerate.\n", "edit the source plugin instead and regenerate.\n",
encoding="utf-8", 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: def main() -> int:
check_only = "--check" in sys.argv[1:] args = parse_args(sys.argv[1:])
try: try:
manifest = load_manifest() manifest = load_manifest()
skills = discover_skills()
for target in manifest["targets"]: for target in manifest["targets"]:
if target not in GENERATORS: if target not in GENERATORS:
raise ValidationError(f"manifest.yaml: unknown target '{target}'") raise ValidationError(f"manifest.yaml: unknown target '{target}'")
if check_only:
print(f"OK: {len(skills)} skill(s), {len(manifest['targets'])} target(s) validated") source_roots = [Path(s).expanduser().resolve() for s in (args.source or [str(ROOT)])]
return 0 has_overlay = bool(args.private_repo) or len(source_roots) > 1
gen_descriptions_json(skills)
for target in manifest["targets"]: with tempfile.TemporaryDirectory(prefix="rootiest-ai-build-") as tmp:
GENERATORS[target](manifest, skills) workdir = Path(tmp)
print(f"Generated plugins for {len(skills)} skill(s), targets: {', '.join(manifest['targets'])}") 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 return 0
except ValidationError as e: except ValidationError as e:
print(f"error: {e}", file=sys.stderr) print(f"error: {e}", file=sys.stderr)