Caution
First-time contributors: open a GitHub issue first. Do not open a pull request.
PRs from first-time contributors will be closed without review. Open an issue describing the bug, idea, or proposed change. Wait for a maintainer to acknowledge and agree on scope before writing code. Once you have at least one merged PR, this restriction no longer applies.
Why: this repo encodes opinionated, hand-tuned prompts. Drive-by PRs that tweak wording, add abstractions, or refactor "for cleanliness" cost more to review than they save. Issues are cheap. Aligned PRs ship.
- Ground Rules
- Setup
- Repository Layout
- Development Workflow
- Adding a Plugin
- Adding a Skill, Agent, Hook, or Command
- Vendor-Neutral Body Checklist
- Pi Install
- Make Targets
- Pull Request Checklist
- Commit and PR Style
- Reporting Bugs and Security Issues
- One source of truth:
src/. Hand-edit only files undersrc/skills/,src/agents/,src/hooks/,src/plugins/, and the top-levelscripts/,tests/,docs/, andMakefile. Everything underdist/and the generated root-level files (.claude-plugin/marketplace.json,.agents/plugins/marketplace.json,gemini-extension.json,AGENTS.md, rootskills/agents/hookssymlinks) is regenerated by the compiler — do not edit by hand.dist/is markedlinguist-generated=trueso GitHub hides it in diffs. - Bases are vendor-neutral.
src/skills/<skill>/SKILL.mdandsrc/agents/<agent>/AGENT.mddescribe intent in generic terms — no$ARGUMENTS, noTask(...), nomcp__*, no${CLAUDE_*}variables, no Claude-specific preprocessor backticks. Put Claude-only behavior insrc/skills/<skill>/claude/body.md(full replacement) or via mirror overlay. See Vendor-Neutral Body Checklist. - Run
make buildafter changes. Generated artifacts must be in sync. CI runsmake check(build +git diff --exit-code) and will fail on drift. - Surgical changes. Match existing style. Don't rename, restructure, or reformat unrelated files in the same PR.
- No new dependencies without discussion. Open an issue first.
make setup # Activate repo-tracked git hooks + install dev deps (uv-managed)make setup sets git config core.hooksPath scripts/git-hooks so the
versioned hooks in scripts/git-hooks/ run on every commit/push. The split:
pre-commit— fast path (~3 s): ruff + shellcheck + shfmt + markdownlint- plugin frontmatter validation + gitleaks on staged changes.
pre-push— heavy path (~30 s): regenerate all derived artifacts, fail on drift, run the full pytest suite. Mirrors what GitHub Actions does.
Skip a hook only when truly necessary: git commit --no-verify /
git push --no-verify. CI will still enforce.
Required tools on PATH:
uv(Python toolchain)shellcheckandshfmt(shell linting)markdownlint-cli2(optional but recommended)bunornodeif you plan to runmake skill-evals*
cc-thingz/
├── src/ # ALL hand-edited sources
│ ├── skills/<skill>/
│ │ ├── SKILL.md # Vendor-neutral base (agentskills.io)
│ │ ├── scripts/, references/, assets/ # Base support files (optional)
│ │ └── <target>/ # Per-target overlay (optional)
│ │ ├── body.md # Body overlay — full or mirror mode
│ │ ├── frontmatter.yaml # Frontmatter overlay (mergedeep)
│ │ └── scripts/, … # Support-file overlay
│ ├── agents/<agent>/
│ │ ├── AGENT.md # Vendor-neutral base (name + description + body)
│ │ └── <target>/ # body.md / frontmatter.yaml overlays
│ ├── hooks/<hook>/
│ │ ├── HOOK.sh # or HOOK.py
│ │ └── meta.yaml # event, timeout, optional status_message
│ ├── pi-extensions/ # TypeScript Pi extension sources
│ │ └── <extension>/ # Compiled into dist/pi/extensions/
│ └── plugins/<plugin>/plugin.yaml # Plugin composition (skills/agents/hooks lists)
├── dist/ # ALL generated — DO NOT EDIT
│ ├── claude/plugins/<plugin>/{skills,agents,hooks,commands}/
│ ├── codex/{agents/*.toml,plugins/<plugin>/{skills,hooks}/}
│ ├── gemini/{skills,agents,hooks/hooks.json}/
│ └── pi/{skills,agents,hooks,extensions}/ # Flat — no plugin grouping
├── scripts/
│ ├── build/ # Compiler entry points
│ │ ├── compile.py # Main entry — `make build`
│ │ ├── overlay.py # Frontmatter + body + support-file overlay engine
│ │ ├── compile_skill.py
│ │ ├── compile_agent.py
│ │ ├── compile_hook.py
│ │ ├── compile_pi_extension.py # Verbatim tree-copy for Pi extensions
│ │ ├── codex_toml.py # Agent → Codex TOML emitter
│ │ ├── plugin_index.py # plugin.yaml → output path resolver
│ │ └── manifests.py # Marketplace manifest generator
│ ├── validate/
│ │ ├── validate-config.py
│ │ ├── validate_genericity.py # Forbidden-token scan for vendor-neutral bases
│ │ └── lint-instructions.py
│ ├── evals/ # Paid OpenAI skill-eval workflow
│ ├── release/ # Mirroring + tagging
│ └── git-hooks/ # Activated by `make setup`
├── tests/ # pytest + bats
├── docs/
│ └── instruction-lint-rules.md # Format rules for skill/agent instruction files
├── .claude-plugin/marketplace.json # Generated — sources at ./dist/claude/plugins/*
├── .agents/plugins/marketplace.json # Generated — sources at ./dist/codex/plugins/*
├── gemini-extension.json # Generated — references ${extensionPath}/dist/gemini/
├── AGENTS.md # Generated — AGENTS.md catalog
├── skills -> dist/gemini/skills # Generated symlink — Gemini scans extension root by name
├── agents -> dist/gemini/agents # Generated symlink — Gemini scans extension root by name
├── hooks -> dist/gemini/hooks # Generated symlink — Gemini scans extension root by name
└── Makefile
# 1. Edit sources under src/skills/, src/agents/, src/hooks/, src/plugins/.
# 2. Recompile dist/ and root-level generated files
make build
# 3. Verify everything passes locally
make ci # lint + validate + check (drift) + test
# 4. Commit src/ AND dist/ AND root-generated files in the same commit
git add -A
git commitmake check runs make build and then git diff --exit-code. If it fails,
either a source change wasn't rebuilt or a generated file was hand-edited.
Run make build and commit the dist/ diff. Never use --no-verify to
bypass hooks.
-
Create
src/plugins/<your-plugin>/plugin.yamllisting what the plugin ships:name: your-plugin version: 2.2.0 description: What it does author: Your Name skills: - some-skill - another-skill agents: - some-agent hooks: - some-hook
-
Add a
src/plugins/<your-plugin>/README.mddescribing the plugin (this is copied into each per-target plugin directory by the compiler). -
Run
make build. The compiler will:- emit
dist/claude/plugins/<your-plugin>/with manifests and artifacts - emit
dist/codex/plugins/<your-plugin>/with.tomlagents - emit
dist/gemini/{skills,agents}/entries for each item - copy skills/agents into
dist/pi/flat - regenerate root marketplace manifests and
gemini-extension.json
- emit
-
Update the plugin table in the top-level
README.md.
Plugin-level metadata that previously lived in scattered .claude-plugin/,
.codex-plugin/, and gemini-extension.json files is now derived from
plugin.yaml plus src/plugins/marketplace.yaml. There is no manual JSON
to maintain.
Use the existing migrated entries as templates. The structure is the same
for skills and agents — both follow the base + per-target overlay pattern.
- Skill: create
src/skills/<skill>/SKILL.mdwithname:anddescription:frontmatter and a vendor-neutral body. If a target needs divergence, addsrc/skills/<skill>/<target>/withbody.mdand/orfrontmatter.yaml. Reference the skill from one or moresrc/plugins/<p>/plugin.yamlfiles (or leave it unreferenced to ship only on Pi, which uses a flat layout). - Agent: create
src/agents/<agent>/AGENT.mdwith the system prompt. Same overlay model as skills. The compiler emits TOML for Codex automatically. - Hook: create
src/hooks/<hook>/HOOK.sh(orHOOK.py) and ameta.yamldeclaringevent,timeout, and optionalstatus_message. Prefer Python (stdlib only) for anything beyond a thin shell wrapper — startup is well under the 5 s timeout. Add the hook to one or moreplugin.yamlfiles. Per-target hook manifests (hooks.jsonfor Claude and Gemini,codex.hooks.jsonfor Codex) are generated. - Commands (Claude only): commands now live as skills under
src/skills/<command>/and the Claude target picks them up fromclaude/frontmatter.yaml(which may carryallowed-tools,argument-hint, etc.). Use an existing skill with aclaude/frontmatter.yamloverlay as the reference example.
After any of the above, run make build && make ci.
Each src/skills/<skill>/SKILL.md and src/agents/<agent>/AGENT.md base
must be runnable across Claude Code, Codex CLI, Gemini CLI, and Pi. The
validator (scripts/validate/validate_genericity.py) blocks the build
when the base contains:
$ARGUMENTS,$1,$2, … positional argument substitutions!`cmd`or```!preprocessor dynamic-context blocks${CLAUDE_SKILL_DIR},${CLAUDE_SESSION_ID},${CLAUDE_EFFORT}and other${CLAUDE_*}variablesTask(,AskUserQuestion,TodoWrite,TaskCreate— Claude-only toolsmcp__*tool references — bare names leak into other targets
Opt-out: a base with targets: [claude] in its frontmatter is allowed to
use the above. Use this sparingly — only when the skill genuinely has no
vendor-neutral version (it's pure Claude orchestration).
Also avoid:
- Markdown tables in skill/agent bodies — they render inconsistently across CLIs. Prefer bullet lists or numbered steps.
- ASCII boxes / diagrams in bodies — they wrap badly in narrow terminals and confuse the model. Describe layout in prose; use code blocks for literal output examples.
- Inline platform-specific tool names in prose (e.g. "use the Bash tool", "spawn a subagent via Task"). Talk in capabilities ("run a shell command", "delegate to a sub-task").
To divert a Claude-only chunk: add a mirror overlay in
src/skills/<skill>/claude/body.md. The compiler auto-detects mode:
- Full replacement — overlay has no headers matching the base; the base body is dropped entirely and the overlay is used as-is.
- Mirror mode — overlay shares at least one header with the base, or uses
(_+)(append) /(+_)(prepend) suffixes on a header title. Each overlay header is an anchor matched by full header path; unmatched headers are added as new sections.
- Compiler modules live under
scripts/build/. Public entry point:scripts/build/compile.py. Helpers are organized by concern (overlay.py,compile_skill.py,compile_agent.py,compile_hook.py,codex_toml.py,plugin_index.py,manifests.py). New build-side code should slot into one of these modules rather than introduce a new top-level script. - Python tests use
pytest(make test). Fixture tests undertests/test_compile_*.pycompile real skills/agents fromsrc/and compare output against goldens — modify a golden only when you intend the output change. Hook tests live undertests/hooks/. - Pi extension TypeScript tests use Bun (
make test-ts). Test files live alongside the sources they test (src/pi-extensions/**/*.test.ts) and are excluded fromdist/by the build compiler. Run both suites viamake ci.
Pi consumes the flat tree under dist/pi/. The root package.json declares
pi.extensions and pi.skills pointing at dist/pi/extensions and
dist/pi/skills, so pi install handles both automatically.
Extensions and skills — install directly from GitHub (no local clone needed):
pi install git:github.com/alexei-led/cc-thingzFor local development (after make build):
pi install "$(pwd)"Re-run pi install after make build to pick up changes. Existing extensions
from other packages are preserved — Pi's loader merges packages.
Agents — pi install does not register agents; the @tintinweb/pi-subagents
loader reads them from ~/.pi/agent/agents/. After pi install, the repo is
already cloned at ~/.pi/agent/git/github.com/alexei-led/cc-thingz — symlink
the agent tree from there:
ln -snf \
~/.pi/agent/git/github.com/alexei-led/cc-thingz/dist/pi/agents \
~/.pi/agent/agentsExtensions are required, not optional. Several hooks depend on bundled extensions:
hook-runner.tsbridges all Pi lifecycle events to CC-compatible hook scripts (file-protector, git-guardrails, skill-enforcer, session-start, smart-lint, test-runner) and ccgramnotify.tsfiresterminal-notifieron agent completion (requiresterminal-notifier—brew install terminal-notifier)ask-user-question.tsprovides theask_user_questiontool used bysmart-lint.sh
Override the target with PI_CODING_AGENT_DIR=<dir> instead of
~/.pi/agent if you run Pi from a non-default location. Restart Pi or
run /reload after installing.
Rules for Pi-compatible content:
- Pi-allowed tools:
read,bash,edit,write,ask_user_question,structured_output,todo,Agent,get_subagent_result,steer_subagent,web_search,web_answer,web_research. - For Context7 docs lookup in Pi, use
ctx7ornpx ctx7@latest— never the Context7 MCP server. - A skill that genuinely can't run on Pi should add a
pi/body.mdoverlay describing an equivalent Pi workflow, or settargets:in its base frontmatter to excludepi.
make help # List all targets
make ci # Full local CI (lint + validate + check + test + test-ts)
make lint # Python (ruff) + shell (shellcheck/shfmt) + markdown + TypeScript (tsc)
make lint-typescript # Type-check Pi extension TypeScript (tsc --noEmit)
make test # pytest (Python tests)
make test-ts # Bun (TypeScript tests for Pi extensions)
make validate # Frontmatter, executable bits, plugin layout
make build # Regenerate every derived artifact
make check # build + git diff --exit-code (drift detection — CI gate)
make fmt # Auto-format Python and shell
make skill-evals-fast # Fast paid eval loop (no baseline, no HTML, advisory)Before opening a PR, confirm all of the following:
- You have at least one merged PR, or an existing issue acknowledged by a maintainer.
- Linked issue number is in the PR description (
Closes #123). -
make cipasses locally. - Plugin
version:bumped insrc/plugins/<plugin>/plugin.yamlif the plugin's surface changed. -
CHANGELOG.mdentry added. - Top-level
README.mdplugin table updated (skill/agent counts) if counts changed. - Plugin
README.mdupdated if you added skills, agents, hooks, or commands. -
make buildwas run;dist/and root-level generated files are committed in the same PR. - Base
SKILL.md/AGENT.mdis vendor-neutral (see Vendor-Neutral Body Checklist). - PR is focused on one logical change. No drive-by reformatting.
- Imperative mood, lowercase type prefix:
feat: add foo skill,fix(programming): correct lint hook,docs: …,refactor: …,chore: …. - Body explains why, not what — the diff already shows what.
- One logical change per commit. Squash noise locally before pushing.
- PR title matches the merge commit you'd want in
git log.
- Bugs and feature ideas: open a GitHub issue with a minimal reproduction.
- Security vulnerabilities: do not open a public issue. Email alexei.led@gmail.com with details and proof of concept. Acknowledgement within 72 hours.