# Contributing

> [!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](https://github.com/alexei-led/cc-thingz/issues/new/choose) 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.

## Table of Contents

- [Ground Rules](#ground-rules)
- [Setup](#setup)
- [Repository Layout](#repository-layout)
- [Development Workflow](#development-workflow)
- [Adding a Plugin](#adding-a-plugin)
- [Adding a Skill, Agent, Hook, or Command](#adding-a-skill-agent-hook-or-command)
- [Vendor-Neutral Body Checklist](#vendor-neutral-body-checklist)
- [Pi Install](#pi-install)
- [Make Targets](#make-targets)
- [Pull Request Checklist](#pull-request-checklist)
- [Commit and PR Style](#commit-and-pr-style)
- [Reporting Bugs and Security Issues](#reporting-bugs-and-security-issues)

## Ground Rules

- **One source of truth: `src/`.** Hand-edit only files under `src/skills/`,
  `src/agents/`, `src/hooks/`, `src/plugins/`, and the top-level `scripts/`,
  `tests/`, `docs/`, and `Makefile`. Everything under `dist/` and the
  generated root-level files (`.claude-plugin/marketplace.json`,
  `.agents/plugins/marketplace.json`, `gemini-extension.json`, `AGENTS.md`,
  root `skills`/`agents`/`hooks` symlinks) is regenerated by the
  compiler — do not edit by hand. `dist/` is marked
  `linguist-generated=true` so GitHub hides it in diffs.
- **Bases are vendor-neutral.** `src/skills/<skill>/SKILL.md` and
  `src/agents/<agent>/AGENT.md` describe intent in generic terms — no
  `$ARGUMENTS`, no `Task(...)`, no `mcp__*`, no `${CLAUDE_*}` variables, no
  Claude-specific preprocessor backticks. Put Claude-only behavior in
  `src/skills/<skill>/claude/body.md` (full replacement) or via mirror
  overlay. See [Vendor-Neutral Body Checklist](#vendor-neutral-body-checklist).
- **Run `make build` after changes.** Generated artifacts must be in sync.
  CI runs `make 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.

## Setup

```bash
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)
- `shellcheck` and `shfmt` (shell linting)
- `markdownlint-cli2` (optional but recommended)
- `bun` or `node` if you plan to run `make skill-evals*`

## Repository Layout

```text
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
```

## Development Workflow

```bash
# 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 commit
```

`make 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.

## Adding a Plugin

1. Create `src/plugins/<your-plugin>/plugin.yaml` listing what the plugin
   ships:

   ```yaml
   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
   ```

2. Add a `src/plugins/<your-plugin>/README.md` describing the plugin (this
   is copied into each per-target plugin directory by the compiler).

3. Run `make build`. The compiler will:
   - emit `dist/claude/plugins/<your-plugin>/` with manifests and artifacts
   - emit `dist/codex/plugins/<your-plugin>/` with `.toml` agents
   - emit `dist/gemini/{skills,agents}/` entries for each item
   - copy skills/agents into `dist/pi/` flat
   - regenerate root marketplace manifests and `gemini-extension.json`

4. 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.

## Adding a Skill, Agent, Hook, or Command

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.md` with `name:` and
  `description:` frontmatter and a vendor-neutral body. If a target needs
  divergence, add `src/skills/<skill>/<target>/` with `body.md` and/or
  `frontmatter.yaml`. Reference the skill from one or more
  `src/plugins/<p>/plugin.yaml` files (or leave it unreferenced to ship only
  on Pi, which uses a flat layout).
- **Agent:** create `src/agents/<agent>/AGENT.md` with the system prompt.
  Same overlay model as skills. The compiler emits TOML for Codex
  automatically.
- **Hook:** create `src/hooks/<hook>/HOOK.sh` (or `HOOK.py`) and a
  `meta.yaml` declaring `event`, `timeout`, and optional `status_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 more
  `plugin.yaml` files. Per-target hook manifests (`hooks.json` for Claude and
  Gemini, `codex.hooks.json` for Codex) are generated.
- **Commands (Claude only):** commands now live as skills under
  `src/skills/<command>/` and the Claude target picks them up from
  `claude/frontmatter.yaml` (which may carry `allowed-tools`,
  `argument-hint`, etc.). The `watch-team` skill is the reference example.

After any of the above, run `make build && make ci`.

## Vendor-Neutral Body Checklist

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_*}` variables
- `Task(`, `AskUserQuestion`, `TodoWrite`, `TaskCreate` — Claude-only tools
- `mcp__*` 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.

## Scripts and Tests

- 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 under `tests/test_compile_*.py`
  compile real skills/agents from `src/` and compare output against
  goldens — modify a golden only when you intend the output change.
  Hook tests live under `tests/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 from `dist/` by the build compiler. Run both suites via `make ci`.

## Pi Install

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):

```bash
pi install git:github.com/alexei-led/cc-thingz
```

For local development (after `make build`):

```bash
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:

```bash
ln -snf \
  ~/.pi/agent/git/github.com/alexei-led/cc-thingz/dist/pi/agents \
  ~/.pi/agent/agents
```

Extensions are required, not optional. Several hooks depend on bundled
extensions:

- `hook-runner.ts` bridges all Pi lifecycle events to CC-compatible hook scripts (file-protector, git-guardrails, skill-enforcer, session-start, smart-lint, test-runner) and ccgram
- `notify.ts` fires `terminal-notifier` on agent completion (requires `terminal-notifier` — `brew install terminal-notifier`)
- `ask-user-question.ts` provides the `ask_user_question` tool used by `smart-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 `ctx7` or `npx ctx7@latest` — never
  the Context7 MCP server.
- A skill that genuinely can't run on Pi should add a `pi/body.md`
  overlay describing an equivalent Pi workflow, or set `targets:` in its
  base frontmatter to exclude `pi`.

## Make Targets

```bash
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)
```

## Pull Request Checklist

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 ci` passes locally.
- [ ] Plugin `version:` bumped in `src/plugins/<plugin>/plugin.yaml` if the
      plugin's surface changed.
- [ ] `CHANGELOG.md` entry added.
- [ ] Top-level `README.md` plugin table updated (skill/agent counts) if
      counts changed.
- [ ] Plugin `README.md` updated if you added skills, agents, hooks, or
      commands.
- [ ] `make build` was run; `dist/` and root-level generated files are
      committed in the same PR.
- [ ] Base `SKILL.md` / `AGENT.md` is vendor-neutral (see
      [Vendor-Neutral Body Checklist](#vendor-neutral-body-checklist)).
- [ ] PR is focused on one logical change. No drive-by reformatting.

## Commit and PR Style

- Imperative mood, lowercase type prefix:
  `feat: add foo skill`, `fix(go-dev): 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`.

## Reporting Bugs and Security Issues

- **Bugs and feature ideas:** open a GitHub
  [issue](https://github.com/alexei-led/cc-thingz/issues/new/choose) 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.
