feat: multi-extension plugin framework for Claude Code and agy #1
@@ -6,69 +6,41 @@
|
||||
},
|
||||
"description": "Reusable AI skills for Claude Code and Antigravity CLI",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "core-essentials",
|
||||
"source": "./dist/claude-code/core-essentials"
|
||||
},
|
||||
{
|
||||
"name": "date-time",
|
||||
"description": "Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context.",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/date-time"
|
||||
]
|
||||
"source": "./dist/claude-code/date-time"
|
||||
},
|
||||
{
|
||||
"name": "docs-sync-audit",
|
||||
"description": "Analyzes repository delta since the last documentation update and synchronizes the Single Source of Truth (docs/, wiki, or README) with the current codebase state.",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/docs-sync-audit"
|
||||
]
|
||||
"source": "./dist/claude-code/docs-sync-audit"
|
||||
},
|
||||
{
|
||||
"name": "git-publish-workflow",
|
||||
"description": "Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs.",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/git-publish-workflow"
|
||||
]
|
||||
"source": "./dist/claude-code/git-publish-workflow"
|
||||
},
|
||||
{
|
||||
"name": "readme-sync-audit",
|
||||
"description": "Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state.",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/readme-sync-audit"
|
||||
]
|
||||
"source": "./dist/claude-code/readme-sync-audit"
|
||||
},
|
||||
{
|
||||
"name": "ship-it",
|
||||
"description": "Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR.",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/ship-it"
|
||||
]
|
||||
"source": "./dist/claude-code/ship-it"
|
||||
},
|
||||
{
|
||||
"name": "systematic-enumeration",
|
||||
"description": "Forces element-by-element verification for finite sets to prevent counting errors.",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/systematic-enumeration"
|
||||
]
|
||||
"source": "./dist/claude-code/systematic-enumeration"
|
||||
},
|
||||
{
|
||||
"name": "technical-devlog-scribe",
|
||||
"description": "Generates a highly structured, objective technical summary of a development session.",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/technical-devlog-scribe"
|
||||
]
|
||||
"source": "./dist/claude-code/technical-devlog-scribe"
|
||||
},
|
||||
{
|
||||
"name": "rootiest-ai-all",
|
||||
"description": "Reusable AI skills for Claude Code and Antigravity CLI (all skills)",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/"
|
||||
]
|
||||
"source": "./dist/claude-code/rootiest-ai-all"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@ name: Generate plugin manifests
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "skills/**"
|
||||
- "plugins/**"
|
||||
- "manifest.yaml"
|
||||
- "scripts/**"
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "skills/**"
|
||||
- "plugins/**"
|
||||
- "manifest.yaml"
|
||||
- "scripts/**"
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
@@ -26,7 +27,7 @@ jobs:
|
||||
- run: python3 scripts/generate_plugins.py --check
|
||||
|
||||
generate:
|
||||
if: github.event_name == 'push'
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -39,7 +40,7 @@ jobs:
|
||||
run: |
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "actions@git.rootiest.dev"
|
||||
git add .claude-plugin descriptions.json dist
|
||||
git add .claude-plugin dist
|
||||
if git diff --cached --quiet; then
|
||||
echo "No generated output changes."
|
||||
exit 0
|
||||
|
||||
@@ -116,3 +116,12 @@ AGENTS/
|
||||
docs/plans
|
||||
docs/devlogs
|
||||
# ────────────────────────────────────────────────────────
|
||||
|
||||
# ─────────────────── Private Overlay Builds ─────────────────
|
||||
# Output of `generate_plugins.py` when layering a private/PII-bearing
|
||||
# source on top of the public plugins/ tree. Never commit this.
|
||||
dist-private/
|
||||
|
||||
# ─────────────────────────── Python ──────────────────────────
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# Rootiest AI Repository
|
||||
|
||||
A collection of reusable AI skills (structured prompt protocols) for **Claude Code** and **Antigravity CLI**. Each skill defines a precise execution protocol that guides the AI through complex, multi-step tasks — replacing ad-hoc prompting with consistent, auditable workflows.
|
||||
|
||||
Skills are plain Markdown files. The included `install.sh` script handles discovery and installation in a single command.
|
||||
A shareable extension library for **Claude Code** and **Antigravity CLI**
|
||||
(`agy`). The unit of distribution is a **plugin** — a bundle that can carry
|
||||
any mix of skills (structured prompt protocols), MCP servers, lifecycle
|
||||
hooks, and tool-specific extras (slash commands, subagents, rules) — built
|
||||
from one source tree and published as a native marketplace for both tools.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Skills](#skills)
|
||||
- [Plugins](#plugins)
|
||||
- [systematic-enumeration](#systematic-enumeration)
|
||||
- [git-publish-workflow](#git-publish-workflow)
|
||||
- [readme-sync-audit](#readme-sync-audit)
|
||||
@@ -16,19 +18,19 @@ Skills are plain Markdown files. The included `install.sh` script handles discov
|
||||
- [date-time](#date-time)
|
||||
- [technical-devlog-scribe](#technical-devlog-scribe)
|
||||
- [ship-it](#ship-it)
|
||||
- [core-essentials](#core-essentials)
|
||||
- [Installation](#installation)
|
||||
- [Claude Code Plugin Marketplace](#claude-code-plugin-marketplace)
|
||||
- [Quick Install (curl)](#quick-install-curl)
|
||||
- [Flags & Options](#flags--options)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Examples](#examples)
|
||||
- [Manual Install](#manual-install)
|
||||
- [Claude Code](#claude-code)
|
||||
- [Antigravity CLI (agy)](#antigravity-cli-agy)
|
||||
- [Repository Structure](#repository-structure)
|
||||
- [Anatomy of a Plugin](#anatomy-of-a-plugin)
|
||||
- [How Generation Works](#how-generation-works)
|
||||
- [Private Overlay Builds](#private-overlay-builds)
|
||||
- [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## Skills
|
||||
## Plugins
|
||||
|
||||
### `systematic-enumeration`
|
||||
|
||||
@@ -121,177 +123,154 @@ Two sequential phases — Phase 2 is blocked until Phase 1 succeeds:
|
||||
|
||||
---
|
||||
|
||||
### `core-essentials`
|
||||
|
||||
**Purpose:** Cross-tool utilities that don't belong to a single workflow.
|
||||
|
||||
Currently bundles `delegate-agy`, which hands a subtask off to the
|
||||
Antigravity CLI (`agy`) in headless mode — useful for a second opinion,
|
||||
external grounded research, or a large multi-file audit (>500 lines) that
|
||||
would otherwise bloat the current context.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Claude Code Plugin Marketplace
|
||||
This repository is a native plugin marketplace for both tools — there is no
|
||||
install script. Each plugin can be installed individually, or as one bundle
|
||||
(`rootiest-ai-all`).
|
||||
|
||||
This repository is a Claude Code plugin marketplace. Each skill is installable
|
||||
individually, or install everything as one bundle. This is the preferred
|
||||
install path for Claude Code — `install.sh` remains available as a
|
||||
cross-tool fallback (and is the only supported path for Antigravity CLI today).
|
||||
### Claude Code
|
||||
|
||||
```
|
||||
/plugin marketplace add https://git.rootiest.dev/rootiest/rootiest-ai.git
|
||||
/plugin install git-publish-workflow@rootiest-ai
|
||||
```
|
||||
|
||||
Install every skill at once with the `rootiest-ai-all` bundle:
|
||||
Install everything at once:
|
||||
|
||||
```
|
||||
/plugin install rootiest-ai-all@rootiest-ai
|
||||
```
|
||||
|
||||
Run `/plugin marketplace update` to pick up newly published skills.
|
||||
Run `/plugin marketplace update` to pick up newly published plugins.
|
||||
|
||||
### Quick Install (curl)
|
||||
### Antigravity CLI (`agy`)
|
||||
|
||||
The intended usage is a single `curl | bash` command. The installer fetches and runs `install.sh` directly — no clone required.
|
||||
|
||||
> [!TIP]
|
||||
> The short-url `https://url.rootiest.dev/ai-install` can be substituted for the full URL in the examples below.
|
||||
> e.g. `curl -sL https://url.rootiest.dev/ai-install | bash -s -- --all`
|
||||
|
||||
**Install all skills for both Claude Code and Antigravity CLI:**
|
||||
`agy` discovers plugins from a `plugins/` folder pointed at by an entry in
|
||||
`plugins.json` — either globally (`~/.gemini/config/plugins.json`) or per
|
||||
project (`.agents/plugins.json`). Clone this repo, then add an entry
|
||||
pointing at the generated `dist/agy` directory:
|
||||
|
||||
```bash
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --all
|
||||
git clone https://git.rootiest.dev/rootiest/rootiest-ai.git ~/rootiest-ai
|
||||
```
|
||||
|
||||
**Install all skills for Claude Code only:**
|
||||
|
||||
```bash
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --all --claude
|
||||
```json
|
||||
// ~/.gemini/config/plugins.json
|
||||
{
|
||||
"entries": [
|
||||
{ "path": "~/rootiest-ai/dist/agy" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Install all skills for Antigravity CLI only:**
|
||||
|
||||
```bash
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --all --antigravity
|
||||
```
|
||||
|
||||
**Install a single skill for both tools:**
|
||||
|
||||
```bash
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- systematic-enumeration
|
||||
```
|
||||
|
||||
**Install specific skills for Claude Code only:**
|
||||
|
||||
```bash
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --claude git-publish-workflow readme-sync-audit
|
||||
```
|
||||
|
||||
**Install specific skills for Antigravity CLI only:**
|
||||
|
||||
```bash
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --antigravity systematic-enumeration git-publish-workflow
|
||||
```
|
||||
|
||||
> **Note:** Providing explicit skill names alongside `--all` causes the named skills to take precedence — only those skills are installed.
|
||||
|
||||
---
|
||||
|
||||
### Flags & Options
|
||||
|
||||
| Flag | Description |
|
||||
|---|---|
|
||||
| `-c`, `--claude` | Install into Claude Code (`~/.claude/skills/`) |
|
||||
| `-g`, `--antigravity` | Install into Antigravity CLI (`~/.gemini/antigravity-cli/skills/`) |
|
||||
| `-a`, `--all` | Install every skill available in the repository |
|
||||
| `SKILL...` | Install one or more named skills (positional arguments) |
|
||||
| `-l`, `--list` | List all available skills with descriptions and exit |
|
||||
| `-h`, `--help` | Show the help page |
|
||||
|
||||
**Tool targeting:** if neither `--claude` nor `--antigravity` is specified, the installer targets **both** tools by default.
|
||||
|
||||
**Skill selection precedence:** explicit skill names always override `--all`. Passing `--all skill-name` installs only `skill-name`, not every skill.
|
||||
|
||||
**Dependencies:** `curl` and `git` must be present on `PATH`. The installer checks for both and exits with a clear error if either is missing.
|
||||
|
||||
---
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `CLAUDE_SKILLS_DIR` | `~/.claude/skills` | Override the Claude Code install directory |
|
||||
| `ANTIGRAVITY_SKILLS_DIR` | `~/.gemini/antigravity-cli/skills` | Override the Antigravity CLI install directory. Falls back to `GEMINI_SKILLS_DIR` if set (legacy support). |
|
||||
|
||||
Example — installing to a project-local skills directory:
|
||||
|
||||
```bash
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh \
|
||||
| CLAUDE_SKILLS_DIR=./.claude/skills bash -s -- --claude systematic-enumeration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Install everything, both tools (simplest possible invocation)
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --all
|
||||
|
||||
# Install one skill, Claude only
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --claude git-publish-workflow
|
||||
|
||||
# Install two skills, Antigravity only
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh | bash -s -- --antigravity systematic-enumeration readme-sync-audit
|
||||
|
||||
# Override install directory, Claude only
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh \
|
||||
| CLAUDE_SKILLS_DIR=~/my-skills bash -s -- --claude --all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Manual Install
|
||||
|
||||
If you prefer to inspect the script before running it:
|
||||
|
||||
```bash
|
||||
# Download
|
||||
curl -sL https://git.rootiest.dev/rootiest/rootiest-ai/raw/branch/main/install.sh -o install.sh
|
||||
|
||||
# Review
|
||||
less install.sh
|
||||
|
||||
# Run
|
||||
bash install.sh --all
|
||||
```
|
||||
|
||||
Or clone the repository and run locally:
|
||||
|
||||
```bash
|
||||
git clone https://git.rootiest.dev/rootiest/rootiest-ai.git
|
||||
cd rootiest-ai
|
||||
bash install.sh --all
|
||||
```
|
||||
`git pull` in the clone to pick up updates; `agy plugin list` / `agy plugin
|
||||
validate <path>` to inspect what's loaded.
|
||||
|
||||
---
|
||||
|
||||
## Repository Structure
|
||||
|
||||
Skill content lives entirely in `skills/<name>/SKILL.md` — that's the single
|
||||
source of truth for a skill's metadata (YAML frontmatter: `name`,
|
||||
`description`, `version`, `author`, `user-invocable`) and instructions.
|
||||
`manifest.yaml` holds only marketplace-level metadata (owner, marketplace
|
||||
name, which agent targets to generate for) — it does not list skills
|
||||
individually; adding a new `skills/<name>/` folder is picked up automatically.
|
||||
### Anatomy of a Plugin
|
||||
|
||||
CI (`.gitea/workflows/plugins.yml`) runs `scripts/generate_plugins.py` on
|
||||
every push to `main` that touches `skills/`, `manifest.yaml`, or the
|
||||
generator itself, and commits the regenerated output:
|
||||
Every plugin lives under `plugins/<name>/` and is the single source of
|
||||
truth for that name — nothing under `plugins/` is ever written by the
|
||||
generator. A plugin can bundle any subset of:
|
||||
|
||||
```
|
||||
plugins/<name>/
|
||||
├── plugin.json # required: name, description, version, author
|
||||
├── skills/<skill>/SKILL.md # 0+ skills (YAML frontmatter: name, description, version, author)
|
||||
├── hooks.json # optional: lifecycle hooks, Claude-shaped event → matcher groups
|
||||
├── mcp.json # optional: {"mcpServers": {...}}
|
||||
├── rules/AGENTS.md # optional: agy-only, always-on project rules
|
||||
├── commands/*.md # optional: Claude Code-only slash commands
|
||||
└── agents/*.md # optional: Claude Code-only subagents
|
||||
```
|
||||
|
||||
`hooks.json` and `mcp.json` are translated per target rather than copied
|
||||
verbatim where the two tools' schemas diverge:
|
||||
|
||||
- MCP: the shared `mcpServers` shape passes straight through to Claude's
|
||||
`.mcp.json`; a `url`/`serverUrl` remote entry becomes agy's
|
||||
`serverUrl` field in `mcp_config.json`.
|
||||
- Hooks: Claude Code has far more event types than agy documents. Events
|
||||
agy doesn't support (`SessionStart`, `TaskCreated`, etc.) simply stay
|
||||
Claude-only — the agy output only carries `PreToolUse`/`PostToolUse`
|
||||
(kept grouped with their `matcher`) and `PreInvocation`/`PostInvocation`/
|
||||
`Stop` (flattened to agy's handler-list shape, since agy doesn't group
|
||||
those by matcher).
|
||||
- `rules/` has no Claude Code plugin equivalent and is skipped for that
|
||||
target; `commands/`/`agents/` have no agy equivalent and are skipped for
|
||||
that target.
|
||||
|
||||
### How Generation Works
|
||||
|
||||
`scripts/generate_plugins.py` reads `manifest.yaml` (marketplace metadata,
|
||||
bundle id, target list) and every `plugins/<name>/`, then regenerates:
|
||||
|
||||
| Path | Generated for |
|
||||
|---|---|
|
||||
| `.claude-plugin/marketplace.json` | Claude Code plugin marketplace |
|
||||
| `dist/agy/**` | Antigravity CLI (`agy`) plugins |
|
||||
| `descriptions.json` | `install.sh` skill listing |
|
||||
| `dist/claude-code/**` | Claude Code plugin directories (linked from the marketplace) |
|
||||
| `dist/agy/**` | Antigravity CLI (`agy`) plugin directories |
|
||||
|
||||
Don't hand-edit any of the paths above — edit the source skill or
|
||||
`manifest.yaml` and let CI regenerate them. Pull requests run the same
|
||||
generator in `--check` mode to catch missing/invalid frontmatter before merge.
|
||||
CI (`.gitea/workflows/plugins.yml`) runs the generator on every push to
|
||||
`main` that touches `plugins/`, `manifest.yaml`, or the generator itself,
|
||||
and commits the regenerated output; it can also be re-run on demand from
|
||||
the Actions tab (`workflow_dispatch`). Pull requests run the same generator
|
||||
in `--check` mode to catch missing/invalid `plugin.json`/`SKILL.md`
|
||||
frontmatter before merge.
|
||||
|
||||
Don't hand-edit anything under `.claude-plugin/` or `dist/` — edit the
|
||||
source plugin or `manifest.yaml` and let the generator regenerate them.
|
||||
|
||||
---
|
||||
|
||||
## Private Overlay Builds
|
||||
|
||||
The generator supports layering an additional, non-public source of
|
||||
plugins on top of this repo — for keeping PII, tokens, API keys, or
|
||||
personal-only plugins out of a public repo's git history entirely, while
|
||||
still reusing the same plugin format and generator.
|
||||
|
||||
```bash
|
||||
# Layer a second, already-cloned repo on top of this one
|
||||
python3 scripts/generate_plugins.py --source . --source ~/rootiest-ai-private
|
||||
|
||||
# Or have the generator clone it (token read from an env var, never a CLI argument)
|
||||
export PRIVATE_TOKEN=...
|
||||
python3 scripts/generate_plugins.py \
|
||||
--private-repo https://git.example.com/you/rootiest-ai-private.git \
|
||||
--private-ref main \
|
||||
--private-token-env PRIVATE_TOKEN
|
||||
```
|
||||
|
||||
A later `--source` overlays the earlier ones per plugin: a plugin name that
|
||||
only exists in the private source is added; a plugin name that exists in
|
||||
both is merged file-by-file, with the private copy winning on conflicts
|
||||
(e.g. supplying a real `mcp.json` where the public plugin ships a
|
||||
placeholder).
|
||||
|
||||
Output never lands in this repo's tracked paths for an overlaid build —
|
||||
`--out` defaults to `dist-private/` (gitignored) whenever more than one
|
||||
source is in play. Add `--install-local` to also drop the result straight
|
||||
into `~/.claude/plugins/marketplaces/<name>-private/` and print the
|
||||
`~/.gemini/config/plugins.json` entry for agy, so a personal build is
|
||||
usable immediately without committing anything anywhere.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
# Generated
|
||||
|
||||
This directory is generated by `scripts/generate_plugins.py` from `manifest.yaml` and `skills/`. Do not edit files here directly — edit the source skill instead and regenerate.
|
||||
This directory is generated by `scripts/generate_plugins.py` from `manifest.yaml` and `plugins/`. Do not edit files here directly — edit the source plugin instead and regenerate.
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "core-essentials"
|
||||
}
|
||||
@@ -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>"
|
||||
```
|
||||
Vendored
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"name": "date-time",
|
||||
"description": "Retrieves the exact current date and time for time-sensitive queries, scheduling, duration calculations, and validating chronological context."
|
||||
"name": "date-time"
|
||||
}
|
||||
|
||||
Vendored
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"name": "docs-sync-audit",
|
||||
"description": "Analyzes repository delta since the last documentation update and synchronizes the Single Source of Truth (docs/, wiki, or README) with the current codebase state."
|
||||
"name": "docs-sync-audit"
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"name": "git-publish-workflow",
|
||||
"description": "Automates branching, conventional commits, testing, and PR creation for uncommitted or staged work. Handles both independent and stacked PRs."
|
||||
"name": "git-publish-workflow"
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"name": "readme-sync-audit",
|
||||
"description": "Analyzes repository delta since the last documentation update and synchronizes the README.md with the current codebase state."
|
||||
"name": "readme-sync-audit"
|
||||
}
|
||||
|
||||
Vendored
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"name": "rootiest-ai-all",
|
||||
"description": "Reusable AI skills for Claude Code and Antigravity CLI (all skills)"
|
||||
"name": "rootiest-ai-all"
|
||||
}
|
||||
|
||||
@@ -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>"
|
||||
```
|
||||
Vendored
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"name": "ship-it",
|
||||
"description": "Runs a comprehensive pre-flight audit, syncs the README, and publishes the changes to a new PR."
|
||||
"name": "ship-it"
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"name": "systematic-enumeration",
|
||||
"description": "Forces element-by-element verification for finite sets to prevent counting errors."
|
||||
"name": "systematic-enumeration"
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"name": "technical-devlog-scribe",
|
||||
"description": "Generates a highly structured, objective technical summary of a development session."
|
||||
"name": "technical-devlog-scribe"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "core-essentials",
|
||||
"description": "Core cross-tool utilities, including delegation to the Antigravity CLI (agy) for grounded research and large multi-file audits.",
|
||||
"version": "1.0.0",
|
||||
"author": "Rootiest"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: delegate-agy
|
||||
description: Delegates a subtask to the Antigravity CLI (agy) when the user wants a second opinion, external grounded research, or needs a large multi-file audit (>500 lines) processed without bloating the current context.
|
||||
version: 1.0.0
|
||||
user-invocable: true
|
||||
author: Rootiest
|
||||
---
|
||||
|
||||
# Antigravity Subagent Delegation (`delegate-agy`)
|
||||
|
||||
When the user asks for a second opinion, external grounded research, or when a task requires processing large multi-file audits (>500 lines) that would bloat context, delegate the subtask to `agy`.
|
||||
|
||||
## Execution Syntax
|
||||
Run `agy` in headless, non-interactive mode using the bash tool:
|
||||
|
||||
```bash
|
||||
agy --dangerously-skip-permissions -p "<detailed_task_prompt>"
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
Vendored
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
+40
@@ -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"
|
||||
}
|
||||
+32
@@ -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
@@ -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 "$@"
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "core-essentials",
|
||||
"description": "Core cross-tool utilities, including delegation to the Antigravity CLI (agy) for grounded research and large multi-file audits.",
|
||||
"version": "1.0.0",
|
||||
"author": "Rootiest"
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
name: delegate-agy
|
||||
description: Delegates a subtask to the Antigravity CLI (agy) when the user wants a second opinion, external grounded research, or needs a large multi-file audit (>500 lines) processed without bloating the current context.
|
||||
version: 1.0.0
|
||||
user-invocable: true
|
||||
author: Rootiest
|
||||
---
|
||||
|
||||
# Antigravity Subagent Delegation (`delegate-agy`)
|
||||
|
||||
When the user asks for a second opinion, external grounded research, or when a task requires processing large multi-file audits (>500 lines) that would bloat context, delegate the subtask to `agy`.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
@@ -1,36 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate per-agent plugin manifests from the skills/ SSoT.
|
||||
"""Generate per-agent plugin/marketplace trees from the plugins/ SSoT.
|
||||
|
||||
Reads manifest.yaml (marketplace/org metadata + target list) and every
|
||||
skills/<name>/SKILL.md (per-skill frontmatter), then regenerates:
|
||||
A "plugin" is a directory under `plugins/<name>/` that may bundle any mix of:
|
||||
|
||||
- plugin.json (required marker + metadata: name, description, version, author)
|
||||
- skills/<name>/SKILL.md (0+ skills)
|
||||
- hooks.json (canonical, Claude-shaped: {"<EventName>": [<matcher-group>, ...]})
|
||||
- mcp.json ({"mcpServers": {...}}, shared shape across targets)
|
||||
- rules/AGENTS.md (agy-only; ignored by the Claude Code target)
|
||||
- commands/*.md (Claude Code-only slash commands)
|
||||
- agents/*.md (Claude Code-only subagents)
|
||||
|
||||
One or more source roots (each containing its own `plugins/` directory) are
|
||||
layered together — a later source overlays/overrides an earlier one on a
|
||||
per-plugin, per-file basis. This is how a private repo (PII/tokens/local-only
|
||||
plugins) can extend or override the public plugin set without either repo
|
||||
knowing about the other's internals.
|
||||
|
||||
Regenerates, under --out (default: repo root, i.e. today's committed paths):
|
||||
|
||||
- descriptions.json (from SKILL.md frontmatter)
|
||||
- .claude-plugin/marketplace.json (Claude Code target)
|
||||
- dist/agy/** (Antigravity CLI target)
|
||||
|
||||
Run with --check to only validate the SSoT (frontmatter, manifest.yaml) and
|
||||
skip writing any output — used as the pull-request gate.
|
||||
Run with --check to only validate the SSoT (plugin.json/SKILL.md frontmatter,
|
||||
manifest.yaml) and skip writing any output — used as the pull-request gate.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SKILLS_DIR = ROOT / "skills"
|
||||
MANIFEST_PATH = ROOT / "manifest.yaml"
|
||||
|
||||
REQUIRED_FRONTMATTER_FIELDS = ("name", "description", "version", "author")
|
||||
REQUIRED_PLUGIN_FIELDS = ("name", "description")
|
||||
REQUIRED_SKILL_FIELDS = ("name", "description")
|
||||
|
||||
# agy only documents these five hook events; everything else is Claude-only.
|
||||
AGY_GROUPED_EVENTS = ("PreToolUse", "PostToolUse")
|
||||
AGY_FLAT_EVENTS = ("PreInvocation", "PostInvocation", "Stop")
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# ── Manifest / Frontmatter Loading ───────────────────────────────────────────
|
||||
|
||||
|
||||
def load_manifest() -> dict:
|
||||
with MANIFEST_PATH.open("r", encoding="utf-8") as f:
|
||||
manifest = yaml.safe_load(f)
|
||||
@@ -40,121 +66,323 @@ def load_manifest() -> dict:
|
||||
return manifest
|
||||
|
||||
|
||||
def parse_frontmatter(skill_md: Path) -> dict:
|
||||
def load_json(path: Path) -> dict:
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValidationError(f"{path}: invalid JSON ({e})")
|
||||
|
||||
|
||||
def parse_skill_frontmatter(skill_md: Path) -> dict:
|
||||
text = skill_md.read_text(encoding="utf-8")
|
||||
if not text.startswith("---\n"):
|
||||
raise ValidationError(f"{skill_md}: missing YAML frontmatter delimiter")
|
||||
end = text.find("\n---", 4)
|
||||
if end == -1:
|
||||
raise ValidationError(f"{skill_md}: unterminated YAML frontmatter")
|
||||
raw = text[4:end]
|
||||
data = yaml.safe_load(raw) or {}
|
||||
for field in REQUIRED_FRONTMATTER_FIELDS:
|
||||
data = yaml.safe_load(text[4:end]) or {}
|
||||
for field in REQUIRED_SKILL_FIELDS:
|
||||
if not data.get(field):
|
||||
raise ValidationError(f"{skill_md}: frontmatter missing required field '{field}'")
|
||||
return data
|
||||
|
||||
|
||||
def discover_skills() -> list[dict]:
|
||||
skills = []
|
||||
seen_names = set()
|
||||
for skill_dir in sorted(SKILLS_DIR.iterdir()):
|
||||
if not skill_dir.is_dir():
|
||||
# ── Source Discovery & Layering ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def collect_layers(source_roots: list[Path]) -> "dict[str, list[Path]]":
|
||||
"""Map plugin name -> ordered list of source dirs (base first, overlays after)."""
|
||||
layers: dict[str, list[Path]] = {}
|
||||
for root in source_roots:
|
||||
plugins_dir = root / "plugins"
|
||||
if not plugins_dir.is_dir():
|
||||
continue
|
||||
skill_md = skill_dir / "SKILL.md"
|
||||
if not skill_md.exists():
|
||||
raise ValidationError(f"{skill_dir}: missing SKILL.md")
|
||||
frontmatter = parse_frontmatter(skill_md)
|
||||
name = frontmatter["name"]
|
||||
if name != skill_dir.name:
|
||||
raise ValidationError(
|
||||
f"{skill_md}: frontmatter name '{name}' does not match directory name '{skill_dir.name}'"
|
||||
)
|
||||
if name in seen_names:
|
||||
raise ValidationError(f"duplicate skill name '{name}'")
|
||||
seen_names.add(name)
|
||||
skills.append({"dir": skill_dir, "frontmatter": frontmatter})
|
||||
if not skills:
|
||||
raise ValidationError("no skills found under skills/")
|
||||
return skills
|
||||
for plugin_dir in sorted(plugins_dir.iterdir()):
|
||||
if not plugin_dir.is_dir():
|
||||
continue
|
||||
layers.setdefault(plugin_dir.name, []).append(plugin_dir)
|
||||
if not layers:
|
||||
raise ValidationError(
|
||||
f"no plugins found under plugins/ in any source root: {[str(r) for r in source_roots]}"
|
||||
)
|
||||
return layers
|
||||
|
||||
|
||||
def gen_descriptions_json(skills: list[dict]) -> None:
|
||||
descriptions = {s["frontmatter"]["name"]: s["frontmatter"]["description"] for s in skills}
|
||||
out = ROOT / "descriptions.json"
|
||||
out.write_text(json.dumps(descriptions, indent=2) + "\n", encoding="utf-8")
|
||||
def merge_layers(layers: "dict[str, list[Path]]", workdir: Path) -> Path:
|
||||
"""Flatten each plugin's layers into a single merged directory (later wins)."""
|
||||
merged_root = workdir / "merged-plugins"
|
||||
merged_root.mkdir(parents=True, exist_ok=True)
|
||||
for name, dirs in layers.items():
|
||||
dest = merged_root / name
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
for d in dirs:
|
||||
shutil.copytree(d, dest, dirs_exist_ok=True)
|
||||
return merged_root
|
||||
|
||||
|
||||
def gen_claude_code(manifest: dict, skills: list[dict]) -> None:
|
||||
plugin_dir = ROOT / ".claude-plugin"
|
||||
plugin_dir.mkdir(exist_ok=True)
|
||||
|
||||
def discover_plugins(merged_root: Path) -> list[dict]:
|
||||
plugins = []
|
||||
for s in skills:
|
||||
fm = s["frontmatter"]
|
||||
for plugin_dir in sorted(merged_root.iterdir()):
|
||||
if not plugin_dir.is_dir():
|
||||
continue
|
||||
manifest_path = plugin_dir / "plugin.json"
|
||||
if not manifest_path.exists():
|
||||
raise ValidationError(f"{plugin_dir}: missing plugin.json")
|
||||
meta = load_json(manifest_path)
|
||||
if meta.get("name") != plugin_dir.name:
|
||||
raise ValidationError(
|
||||
f"{manifest_path}: name '{meta.get('name')}' does not match directory name '{plugin_dir.name}'"
|
||||
)
|
||||
for field in REQUIRED_PLUGIN_FIELDS:
|
||||
if not meta.get(field):
|
||||
raise ValidationError(f"{manifest_path}: missing required field '{field}'")
|
||||
|
||||
skills = []
|
||||
skills_dir = plugin_dir / "skills"
|
||||
if skills_dir.is_dir():
|
||||
for skill_dir in sorted(skills_dir.iterdir()):
|
||||
if not skill_dir.is_dir():
|
||||
continue
|
||||
skill_md = skill_dir / "SKILL.md"
|
||||
if not skill_md.exists():
|
||||
raise ValidationError(f"{skill_dir}: missing SKILL.md")
|
||||
fm = parse_skill_frontmatter(skill_md)
|
||||
if fm["name"] != skill_dir.name:
|
||||
raise ValidationError(
|
||||
f"{skill_md}: frontmatter name '{fm['name']}' does not match directory name '{skill_dir.name}'"
|
||||
)
|
||||
skills.append({"dir": skill_dir, "frontmatter": fm})
|
||||
|
||||
hooks_path = plugin_dir / "hooks.json"
|
||||
hooks = load_json(hooks_path) if hooks_path.exists() else None
|
||||
|
||||
mcp_path = plugin_dir / "mcp.json"
|
||||
mcp = load_json(mcp_path) if mcp_path.exists() else None
|
||||
|
||||
plugins.append(
|
||||
{
|
||||
"name": fm["name"],
|
||||
"description": fm["description"],
|
||||
"source": "./",
|
||||
"skills": [f"./skills/{fm['name']}"],
|
||||
"dir": plugin_dir,
|
||||
"meta": meta,
|
||||
"skills": skills,
|
||||
"hooks": hooks,
|
||||
"mcp": mcp,
|
||||
"rules_dir": plugin_dir / "rules" if (plugin_dir / "rules").is_dir() else None,
|
||||
"commands_dir": plugin_dir / "commands" if (plugin_dir / "commands").is_dir() else None,
|
||||
"agents_dir": plugin_dir / "agents" if (plugin_dir / "agents").is_dir() else None,
|
||||
}
|
||||
)
|
||||
|
||||
plugins.append(
|
||||
{
|
||||
"name": manifest["bundle"]["id"],
|
||||
"description": f"{manifest['marketplace']['description']} (all skills)",
|
||||
"source": "./",
|
||||
"skills": ["./skills/"],
|
||||
}
|
||||
seen = set()
|
||||
for p in plugins:
|
||||
name = p["meta"]["name"]
|
||||
if name in seen:
|
||||
raise ValidationError(f"duplicate plugin name '{name}'")
|
||||
seen.add(name)
|
||||
return plugins
|
||||
|
||||
|
||||
# ── Private Repo Cloning ─────────────────────────────────────────────────────
|
||||
|
||||
_ASKPASS_SCRIPT = """#!/bin/sh
|
||||
case "$1" in
|
||||
*sername*) echo "x-access-token" ;;
|
||||
*) echo "$GIT_ASKPASS_TOKEN" ;;
|
||||
esac
|
||||
"""
|
||||
|
||||
|
||||
def clone_private_repo(url: str, ref: str, token_env: str | None, workdir: Path) -> Path:
|
||||
clone_dir = workdir / "private-repo"
|
||||
env = os.environ.copy()
|
||||
env["GIT_TERMINAL_PROMPT"] = "0"
|
||||
|
||||
if token_env:
|
||||
token = os.environ.get(token_env)
|
||||
if not token:
|
||||
raise ValidationError(f"--private-token-env={token_env} is not set in the environment")
|
||||
askpass_path = workdir / "git-askpass.sh"
|
||||
askpass_path.write_text(_ASKPASS_SCRIPT, encoding="utf-8")
|
||||
askpass_path.chmod(askpass_path.stat().st_mode | stat.S_IEXEC)
|
||||
env["GIT_ASKPASS"] = str(askpass_path)
|
||||
env["GIT_ASKPASS_TOKEN"] = token
|
||||
|
||||
result = subprocess.run(
|
||||
["git", "clone", "--quiet", "--depth", "1", "--branch", ref, url, str(clone_dir)],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise ValidationError(f"failed to clone private repo '{url}' (ref {ref}): {result.stderr.strip()}")
|
||||
return clone_dir
|
||||
|
||||
|
||||
# ── Hook / MCP Translation ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
def translate_hooks_for_agy(hooks: dict, plugin_name: str) -> "dict | None":
|
||||
agy_events: dict = {}
|
||||
for event in AGY_GROUPED_EVENTS:
|
||||
if event in hooks:
|
||||
agy_events[event] = hooks[event]
|
||||
for event in AGY_FLAT_EVENTS:
|
||||
if event in hooks:
|
||||
flat = []
|
||||
for group in hooks[event]:
|
||||
flat.extend(group.get("hooks", []))
|
||||
agy_events[event] = flat
|
||||
if not agy_events:
|
||||
return None
|
||||
return {plugin_name: agy_events}
|
||||
|
||||
|
||||
def translate_mcp_for_agy(mcp: dict) -> dict:
|
||||
servers = {}
|
||||
for name, cfg in mcp.get("mcpServers", {}).items():
|
||||
if "command" in cfg:
|
||||
out = {"command": cfg["command"]}
|
||||
if "args" in cfg:
|
||||
out["args"] = cfg["args"]
|
||||
if "env" in cfg:
|
||||
out["env"] = cfg["env"]
|
||||
servers[name] = out
|
||||
else:
|
||||
url = cfg.get("serverUrl") or cfg.get("url")
|
||||
if url:
|
||||
servers[name] = {"serverUrl": url}
|
||||
return {"mcpServers": servers}
|
||||
|
||||
|
||||
# ── Claude Code Target ───────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def write_claude_plugin(plugin: dict, dest: Path) -> None:
|
||||
meta = plugin["meta"]
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
claude_plugin_dir = dest / ".claude-plugin"
|
||||
claude_plugin_dir.mkdir(exist_ok=True)
|
||||
manifest = {"name": meta["name"]}
|
||||
for field in ("description", "version", "author"):
|
||||
if meta.get(field):
|
||||
manifest[field] = meta[field]
|
||||
(claude_plugin_dir / "plugin.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
if plugin["skills"]:
|
||||
skills_out = dest / "skills"
|
||||
for s in plugin["skills"]:
|
||||
shutil.copytree(s["dir"], skills_out / s["frontmatter"]["name"])
|
||||
|
||||
for optional_dir in ("commands_dir", "agents_dir"):
|
||||
src = plugin[optional_dir]
|
||||
if src is not None:
|
||||
shutil.copytree(src, dest / src.name)
|
||||
|
||||
if plugin["hooks"] is not None:
|
||||
hooks_out = dest / "hooks"
|
||||
hooks_out.mkdir(exist_ok=True)
|
||||
(hooks_out / "hooks.json").write_text(
|
||||
json.dumps({"hooks": plugin["hooks"]}, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
if plugin["mcp"] is not None:
|
||||
(dest / ".mcp.json").write_text(json.dumps(plugin["mcp"], indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def gen_claude_code(manifest: dict, plugins: list[dict], out_dir: Path) -> None:
|
||||
# Generated output lives under dist/claude-code/ — never inside plugins/,
|
||||
# which is the SSoT and must stay untouched by generation.
|
||||
plugins_out = out_dir / "dist" / "claude-code"
|
||||
if plugins_out.exists():
|
||||
shutil.rmtree(plugins_out)
|
||||
plugins_out.mkdir(parents=True)
|
||||
|
||||
marketplace_entries = []
|
||||
for p in plugins:
|
||||
name = p["meta"]["name"]
|
||||
write_claude_plugin(p, plugins_out / name)
|
||||
marketplace_entries.append({"name": name, "source": f"./dist/claude-code/{name}"})
|
||||
|
||||
bundle_id = manifest["bundle"]["id"]
|
||||
bundle_dest = plugins_out / bundle_id
|
||||
(bundle_dest / ".claude-plugin").mkdir(parents=True)
|
||||
(bundle_dest / ".claude-plugin" / "plugin.json").write_text(
|
||||
json.dumps(
|
||||
{"name": bundle_id, "description": f"{manifest['marketplace']['description']} (all skills)"},
|
||||
indent=2,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
bundle_skills = bundle_dest / "skills"
|
||||
for p in plugins:
|
||||
for s in p["skills"]:
|
||||
shutil.copytree(s["dir"], bundle_skills / s["frontmatter"]["name"])
|
||||
marketplace_entries.append({"name": bundle_id, "source": f"./dist/claude-code/{bundle_id}"})
|
||||
|
||||
marketplace = {
|
||||
"name": manifest["marketplace"]["name"],
|
||||
"owner": manifest["marketplace"]["owner"],
|
||||
"description": manifest["marketplace"]["description"],
|
||||
"plugins": plugins,
|
||||
"plugins": marketplace_entries,
|
||||
}
|
||||
|
||||
out = plugin_dir / "marketplace.json"
|
||||
out.write_text(json.dumps(marketplace, indent=2) + "\n", encoding="utf-8")
|
||||
plugin_dir = out_dir / ".claude-plugin"
|
||||
plugin_dir.mkdir(exist_ok=True)
|
||||
(plugin_dir / "marketplace.json").write_text(json.dumps(marketplace, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def gen_agy(manifest: dict, skills: list[dict]) -> None:
|
||||
agy_dir = ROOT / "dist" / "agy"
|
||||
# ── Antigravity CLI (agy) Target ─────────────────────────────────────────────
|
||||
|
||||
|
||||
def write_agy_plugin(plugin: dict, dest: Path) -> None:
|
||||
meta = plugin["meta"]
|
||||
name = meta["name"]
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
(dest / "plugin.json").write_text(json.dumps({"name": name}, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
if plugin["skills"]:
|
||||
skills_out = dest / "skills"
|
||||
for s in plugin["skills"]:
|
||||
shutil.copytree(s["dir"], skills_out / s["frontmatter"]["name"])
|
||||
|
||||
if plugin["rules_dir"] is not None:
|
||||
shutil.copytree(plugin["rules_dir"], dest / "rules")
|
||||
|
||||
if plugin["hooks"] is not None:
|
||||
translated = translate_hooks_for_agy(plugin["hooks"], name)
|
||||
if translated is not None:
|
||||
(dest / "hooks.json").write_text(json.dumps(translated, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
if plugin["mcp"] is not None:
|
||||
(dest / "mcp_config.json").write_text(
|
||||
json.dumps(translate_mcp_for_agy(plugin["mcp"]), indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
def gen_agy(manifest: dict, plugins: list[dict], out_dir: Path) -> None:
|
||||
agy_dir = out_dir / "dist" / "agy"
|
||||
if agy_dir.exists():
|
||||
shutil.rmtree(agy_dir)
|
||||
agy_dir.mkdir(parents=True)
|
||||
|
||||
def write_plugin(plugin_name: str, description: str, members: list[dict]) -> None:
|
||||
plugin_root = agy_dir / plugin_name
|
||||
skills_out = plugin_root / "skills"
|
||||
skills_out.mkdir(parents=True)
|
||||
for s in members:
|
||||
dest = skills_out / s["frontmatter"]["name"]
|
||||
shutil.copytree(s["dir"], dest)
|
||||
plugin_json = {"name": plugin_name, "description": description}
|
||||
(plugin_root / "plugin.json").write_text(
|
||||
json.dumps(plugin_json, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
for p in plugins:
|
||||
write_agy_plugin(p, agy_dir / p["meta"]["name"])
|
||||
|
||||
for s in skills:
|
||||
fm = s["frontmatter"]
|
||||
write_plugin(fm["name"], fm["description"], [s])
|
||||
bundle_id = manifest["bundle"]["id"]
|
||||
bundle_dest = agy_dir / bundle_id
|
||||
bundle_dest.mkdir(parents=True)
|
||||
(bundle_dest / "plugin.json").write_text(json.dumps({"name": bundle_id}, indent=2) + "\n", encoding="utf-8")
|
||||
bundle_skills = bundle_dest / "skills"
|
||||
for p in plugins:
|
||||
for s in p["skills"]:
|
||||
shutil.copytree(s["dir"], bundle_skills / s["frontmatter"]["name"])
|
||||
|
||||
write_plugin(
|
||||
manifest["bundle"]["id"],
|
||||
f"{manifest['marketplace']['description']} (all skills)",
|
||||
skills,
|
||||
)
|
||||
|
||||
dist_readme = ROOT / "dist" / "README.md"
|
||||
dist_readme = out_dir / "dist" / "README.md"
|
||||
dist_readme.write_text(
|
||||
"# Generated\n\n"
|
||||
"This directory is generated by `scripts/generate_plugins.py` from "
|
||||
"`manifest.yaml` and `skills/`. Do not edit files here directly — "
|
||||
"edit the source skill instead and regenerate.\n",
|
||||
"`manifest.yaml` and `plugins/`. Do not edit files here directly — "
|
||||
"edit the source plugin instead and regenerate.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
@@ -165,21 +393,99 @@ GENERATORS = {
|
||||
}
|
||||
|
||||
|
||||
# ── Local Install Helper ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def install_local(manifest: dict, out_dir: Path) -> None:
|
||||
home = Path.home()
|
||||
mp_name = f"{manifest['marketplace']['name']}-private"
|
||||
|
||||
claude_src = out_dir / ".claude-plugin"
|
||||
if claude_src.exists():
|
||||
claude_dest = home / ".claude" / "plugins" / "marketplaces" / mp_name
|
||||
if claude_dest.exists():
|
||||
shutil.rmtree(claude_dest)
|
||||
claude_dest.mkdir(parents=True)
|
||||
shutil.copytree(claude_src, claude_dest / ".claude-plugin")
|
||||
shutil.copytree(out_dir / "dist" / "claude-code", claude_dest / "dist" / "claude-code")
|
||||
print(f"Installed Claude Code marketplace locally: {claude_dest}")
|
||||
print(f" /plugin marketplace add {claude_dest}")
|
||||
|
||||
agy_src = out_dir / "dist" / "agy"
|
||||
if agy_src.exists():
|
||||
print("For agy, add this entry to ~/.gemini/config/plugins.json:")
|
||||
print(json.dumps({"entries": [{"path": str(agy_src)}]}, indent=2))
|
||||
|
||||
|
||||
# ── CLI ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true", help="validate only, write nothing")
|
||||
parser.add_argument(
|
||||
"--source",
|
||||
action="append",
|
||||
default=None,
|
||||
help="a directory containing its own plugins/ folder; repeatable, layered in order (default: repo root)",
|
||||
)
|
||||
parser.add_argument("--private-repo", help="git URL of a private overlay repo to clone and layer on top")
|
||||
parser.add_argument("--private-ref", default="main", help="branch/tag to clone from --private-repo (default: main)")
|
||||
parser.add_argument(
|
||||
"--private-token-env",
|
||||
help="name of an environment variable holding a token for --private-repo (read at run time, never taken as a literal value)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
help="output directory (default: '.' for a pure public build, 'dist-private' when overlaying a private source)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--install-local",
|
||||
action="store_true",
|
||||
help="after generating, install the result into local Claude Code / agy config",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
check_only = "--check" in sys.argv[1:]
|
||||
args = parse_args(sys.argv[1:])
|
||||
|
||||
try:
|
||||
manifest = load_manifest()
|
||||
skills = discover_skills()
|
||||
for target in manifest["targets"]:
|
||||
if target not in GENERATORS:
|
||||
raise ValidationError(f"manifest.yaml: unknown target '{target}'")
|
||||
if check_only:
|
||||
print(f"OK: {len(skills)} skill(s), {len(manifest['targets'])} target(s) validated")
|
||||
return 0
|
||||
gen_descriptions_json(skills)
|
||||
for target in manifest["targets"]:
|
||||
GENERATORS[target](manifest, skills)
|
||||
print(f"Generated plugins for {len(skills)} skill(s), targets: {', '.join(manifest['targets'])}")
|
||||
|
||||
source_roots = [Path(s).expanduser().resolve() for s in (args.source or [str(ROOT)])]
|
||||
has_overlay = bool(args.private_repo) or len(source_roots) > 1
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="rootiest-ai-build-") as tmp:
|
||||
workdir = Path(tmp)
|
||||
if args.private_repo:
|
||||
private_dir = clone_private_repo(args.private_repo, args.private_ref, args.private_token_env, workdir)
|
||||
source_roots.append(private_dir)
|
||||
|
||||
layers = collect_layers(source_roots)
|
||||
merged_root = merge_layers(layers, workdir)
|
||||
plugins = discover_plugins(merged_root)
|
||||
|
||||
if args.check:
|
||||
print(f"OK: {len(plugins)} plugin(s), {len(manifest['targets'])} target(s) validated")
|
||||
return 0
|
||||
|
||||
out_dir = Path(args.out).expanduser().resolve() if args.out else (
|
||||
(ROOT / "dist-private") if has_overlay else ROOT
|
||||
)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for target in manifest["targets"]:
|
||||
GENERATORS[target](manifest, plugins, out_dir)
|
||||
|
||||
print(f"Generated plugins for {len(plugins)} plugin(s) -> {out_dir}, targets: {', '.join(manifest['targets'])}")
|
||||
|
||||
if args.install_local:
|
||||
install_local(manifest, out_dir)
|
||||
|
||||
return 0
|
||||
except ValidationError as e:
|
||||
print(f"error: {e}", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user