Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ version in [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json);

## [0.1.0] - 2026-10-15

- Site lists `guard-git` as the third hook (page from `agents/README.md`, in the unattended-backlog workflow; `--skill guard-git` copies `agents/hooks/`), and the home list tags third-party entries with their author, like the README.
- Docs pass before launch: plainer, shorter READMEs and site copy; "Needs:" lines name the `guard-git` hook that any agent install brings; internal decision ids dropped; stale facts fixed (`switch` pre-switch notes, `score-loop` example total, `lanes` decrease, broken `repo-scrub` and `kanban` links).
- `install-skills --skill <name>` / `-Skill <name>` (#233) installs one skill, agent or hook plus what it needs; `--list-skills` / `-ListSkills` lists the names. Combines with `--workflow`. Optional companions are no longer pulled in (e.g. `find-me-a-skill` no longer brings `meta-orchestrator`; `pr-watch` fixes inline without `pair`). Each README says "Works on its own." or what it needs, each site page shows its one-line install, and a test installs every entry alone and checks it links to nothing missing.
- `/switch setup [name]` (#234) runs first-time setup from inside Claude: it shows the profile and settings diff (`setup --dry-run`, new), asks in chat, then writes with `--yes` and says to open a new terminal. `name` names the config dir you're on (`$CLAUDE_CONFIG_DIR`, else `~/.claude`; `main` by default) and is refused when that dir or name is already registered differently. Falls back to `python` when `python3` is the Windows Store stub. The README's first-time steps lead with it; the shell command stays as the fallback.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Run one task, or a whole backlog, with agents.
| [`meta-orchestrator`](meta-orchestrator/README.md) | Runs a repo's backlog unattended. It loops `planner` → `manager` → `verifier` per issue (`shape: flat` uses `implementer` instead of `manager`; `shape: swarm` uses a swarm of haiku implementers; the implementer model follows issue size). It holds the merge gate itself: no merge until tests and CI are green. It reads `skilleddocs/` for the design record and appends every event to `skilleddocs/orchestrator/ledger.md` (committed in the repo, unless `docs_commit: uncommitted`). After a budget of merges it writes `orchestrator-handoff.md`, so `/meta-orchestrator <repo> resume` continues in a fresh session. | `gh`; the four agents installed; a kanban board, `status:*` labels, or a roadmap doc (whatever `next` finds) |
| [`lanes`](lanes/README.md) | Sets how many issues a `meta-orchestrator` run works on at once: predicts each queued issue's files, groups overlapping issues into the same lane, proposes a count with a token-cost line, and on your yes writes named lanes to `scope.md`. `/lanes <n> \| increase \| decrease`; changes apply at the orchestrator's next loop. | a `meta-orchestrator` run; `gh` |
| [`score-loop`](score-loop/README.md) | Pushes one rerunnable numeric score up or down, unattended. It builds a local harness that mirrors the grader (correctness check, median of N runs, noise margin from baseline runs). It then commits one hypothesis at a time on `<prefix>score-loop/<slug>`, keeps a commit only when it beats the best by more than the margin, and `git revert`s the rest. Every experiment is logged to `skilleddocs/experiments.md`. It stops on budget, deadline freeze or plateau. Official submissions stay inside a quota you approve; paid ones go through `spend-gate`. | a score you can compute locally |
| [`ponytail`](ponytail/README.md) *(DietrichGebert)* | Lazy-senior-developer mode for the whole session: the smallest change that fully solves the task, plus a closing line on what was skipped and any risk. Levels `lite`, `full` (default), `ultra`; stays on until "stop ponytail". | none |
| [`ponytail`](ponytail/README.md) *(Dietrich Gebert)* | Lazy-senior-developer mode for the whole session: the smallest change that fully solves the task, plus a closing line on what was skipped and any risk. Levels `lite`, `full` (default), `ultra`; stays on until "stop ponytail". | none |

### Sessions and accounts

Expand Down Expand Up @@ -136,7 +136,7 @@ Full explainer (inputs, outputs, procedure, guard hook): [`agents/README.md`](ag

### [`hooks/`](hooks/) — optional per-repo hooks

Not auto-installed. [`smartzone.py`](hooks/smartzone.py) is a `UserPromptSubmit` hook that warns in-conversation when the transcript suggests context has left the smart zone (~75k tokens, `SMARTZONE_TOKENS`), so a session wraps up instead of degrading. [`spend-guard.py`](hooks/spend-guard.py) is a `PreToolUse` hook that blocks known billable commands/MCP calls (GPU pod creation, paid deploys, ...) unless an approval token from the [`spend-gate`](spend-gate/README.md) skill already exists. Copy + settings snippets in [`hooks/README.md`](hooks/README.md).
Not auto-installed. The third hook, [`guard-git.py`](agents/hooks/guard-git.py), lives under `agents/hooks/` and comes with the agents: it stops the `implementer` and `manager` from pushing to `main`, force pushing or merging, so only the orchestrator merges ([details](agents/README.md#hooksguard-gitpy)). [`smartzone.py`](hooks/smartzone.py) is a `UserPromptSubmit` hook that warns in-conversation when the transcript suggests context has left the smart zone (~75k tokens, `SMARTZONE_TOKENS`), so a session wraps up instead of degrading. [`spend-guard.py`](hooks/spend-guard.py) is a `PreToolUse` hook that blocks known billable commands/MCP calls (GPU pod creation, paid deploys, ...) unless an approval token from the [`spend-gate`](spend-gate/README.md) skill already exists. Copy + settings snippets in [`hooks/README.md`](hooks/README.md).

### [`site/`](site/) — static skills site

Expand Down Expand Up @@ -388,4 +388,4 @@ Pass `--prefix <p>` (sh) / `-Prefix <p>` (ps1) to fill in the branch-prefix plac

## License

MIT (see [`LICENSE`](LICENSE)) for everything original in this repo. Matt Pocock's skills and DietrichGebert's `ponytail` carry their own MIT notices; see [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md).
MIT (see [`LICENSE`](LICENSE)) for everything original in this repo. Matt Pocock's skills and Dietrich Gebert's `ponytail` carry their own MIT notices; see [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md).
12 changes: 11 additions & 1 deletion agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Needs: `consult-plan`, `next`, `update-progress`, the `guard-git` hook (installe

## hooks/guard-git.py

`PreToolUse` hook on `Bash|PowerShell`, wired in the `implementer` and `manager` frontmatter. Exits 2 (blocks, reason on stderr) or 0 (allows).
`PreToolUse` hook that stops the `implementer` and `manager` agents from pushing to protected branches, force pushing, merging PRs or writing other remote state. It runs on `Bash|PowerShell`, is wired in their frontmatter, and exits 2 (blocks, reason on stderr) or 0 (allows).

**As a plugin.** Claude Code ignores `hooks` frontmatter in plugin agents, so the plugin wires the same command once, plugin-wide, in [`.claude-plugin/plugin.json`](../.claude-plugin/plugin.json), with `--plugin`.

Expand Down Expand Up @@ -352,6 +352,16 @@ Protected branches: `GUARD_PROTECTED_BRANCHES` (comma list), default `main,maste
- A block is working as intended, not something for the agent to route around.
- Self-test (every case above, current branch injected, plus stdin end-to-end runs and the `--plugin` agent filter): `python agents/hooks/guard-git.py --self-test`.

Example output when a guarded agent tries to merge its own PR, then push to main:
the hook exits 2 and prints one line on stderr for each.

```transcript
⏺ Bash(gh pr merge 12 --squash)
⎿ Blocked: gh pr merge is a write; guarded agents get read-only gh + pr create; card moves, comments and labels belong to the caller.
⏺ Bash(git push origin main)
⎿ Blocked: push to protected branch 'main' is not allowed.
```

## Example

One issue through planner, manager and verifier, as an orchestrator (or you)
Expand Down
14 changes: 8 additions & 6 deletions find-me-a-skill/CATALOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Install section.
| manager | agent | opus | high | `Agent(subagent_type="manager")` | Owns one task inside a worktree — briefs an implementer subagent, reviews its diff and reruns tests itself, restarts a fresh implementer from skilleddocs/HANDOFF.md when one stalls (≤3 rounds). | unattended-backlog, pair-to-pr, orchestration-shapes |
| planner | agent | opus | medium | `Agent(subagent_type="planner")` | Picks the next issue from a repo's board, applies the run's scope exclusions, decides whether to divide, rates its size, recommends a shape and model, and returns a ≤40-line kickoff brief. | unattended-backlog, orchestration-shapes |
| verifier | agent | opus | high | `Agent(subagent_type="verifier")` | Verifies an implementation session: checks its handoff's claims against git, tests and CI, reviews the diff, updates docs/board/issues, reconciles deviations via consult-plan, and ends with the next task. | unattended-backlog, orchestration-shapes |
| guard-git | hook | haiku | low | install: comes with the agents | PreToolUse hook that stops the implementer and manager agents from pushing to protected branches, force pushing, merging PRs or writing other remote state. | unattended-backlog |
| smartzone | hook | haiku | low | install: hooks/README.md | "Your context has left the smart zone" warning. | wrap-up-long-session |
| spend-guard | hook | haiku | low | install: hooks/README.md | Block billable commands without an approval. | paid-tests |

Expand Down Expand Up @@ -113,12 +114,13 @@ human-in-the-loop points: nothing continues without you.
5. planner (agent, opus · medium): Runs next, picks exactly one issue and returns a kickoff brief of at most 40 lines.
6. manager (agent, opus · high): Briefs an implementer, reviews every round inline and reruns the tests itself, for up to 3 rounds.
7. implementer (agent, sonnet · medium): Implements on its branch, commits small, watches the tests pass and ends with session-handoff.
8. verifier (agent, opus · high): Runs update-progress end to end, answers or parks deviations, then runs next.
9. you: When the statusline warns near the limit (90%), type /switch between merges; it is user-only, so Claude can't start it.
10. /switch (skill, haiku · low): The session resumes on the other account; running subagents pause (resume each with SendMessage to its id) and background commands end, hence between merges.
11. /remind (skill, haiku · low): /remind when <first account>'s session limit resets pops a toast at the reset time and says it once at your next prompt.
12. /progress-report (skill, sonnet · low): Every N merges, writes a two-minute report of what happened since the last one, never overlapping.
13. you: Read the report and do the manual steps it lists.
8. guard-git (hook, haiku · low): Runs on every manager and implementer shell command: a feature-branch push and gh pr create get through, while pushes to main, force pushes, merges and other remote writes are blocked, so only the orchestrator merges.
9. verifier (agent, opus · high): Runs update-progress end to end, answers or parks deviations, then runs next.
10. you: When the statusline warns near the limit (90%), type /switch between merges; it is user-only, so Claude can't start it.
11. /switch (skill, haiku · low): The session resumes on the other account; running subagents pause (resume each with SendMessage to its id) and background commands end, hence between merges.
12. /remind (skill, haiku · low): /remind when <first account>'s session limit resets pops a toast at the reset time and says it once at your next prompt.
13. /progress-report (skill, sonnet · low): Every N merges, writes a two-minute report of what happened since the last one, never overlapping.
14. you: Read the report and do the manual steps it lists.

### pair-to-pr: One issue to a reviewed PR

Expand Down
48 changes: 29 additions & 19 deletions scripts/build_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,34 +98,39 @@ def collect(root):


def hook_files(root):
"""hooks/*.py, sorted. Agent hooks (agents/hooks/) are not listed."""
d = os.path.join(root, "hooks")
if not os.path.isdir(d):
return []
return [os.path.join(d, fn) for fn in sorted(os.listdir(d)) if fn.endswith(".py")]
"""(path, readme) per hook, sorted by name: hooks/*.py, documented in
hooks/README.md, and agents/hooks/*.py (guard-git), documented in
agents/README.md."""
out = []
for d, readme in (("hooks", "hooks/README.md"), ("agents/hooks", "agents/README.md")):
full = os.path.join(root, *d.split("/"))
if os.path.isdir(full):
out += [(os.path.join(full, fn), readme) for fn in os.listdir(full) if fn.endswith(".py")]
return sorted(out, key=lambda t: os.path.basename(t[0]))


def collect_hooks(root):
"""One entry per hooks/<stem>.py. Its page text is the "## `<stem>.py`"
section of hooks/README.md. Its summary is the heading tagline (text after
the em dash), capitalized and ending in a period like skill summaries; its
"""One entry per hook file. Its page text is the "## `<stem>.py`" section
of hooks/README.md (agents/README.md: "## hooks/<stem>.py"). Its summary
is the heading tagline (text after the em dash), capitalized and ending in
a period like skill summaries, else the section's first sentence; its
description is that summary plus the section's first paragraph."""
files = hook_files(root)
if not files:
return []
readme = read_text(os.path.join(root, "hooks", "README.md"))
out = []
for path in files:
readmes = {}
for path, readme_rel in hook_files(root):
fn = os.path.basename(path)
heading, body = site_detail.hook_section(readme, fn)
if readme_rel not in readmes:
readmes[readme_rel] = read_text(os.path.join(root, *readme_rel.split("/")))
heading, body = site_detail.hook_section(readmes[readme_rel], fn)
if not body:
raise SystemExit("hooks/README.md: no '## `%s`' section" % fn)
raise SystemExit("%s: no '## `%s`' section" % (readme_rel, fn))
e = _entry({}, os.path.splitext(fn)[0], "hooks", posix_rel(path, root), body + "\n", "")
lead = site_detail.first_paragraph_text(body)
e["summary"] = hook_tagline(heading) or first_sentence(lead)
e["description"] = e["summary"] + " " + lead if lead else e["summary"]
e["_x"]["lead"] = lead
e["_x"]["heading"] = heading
e["_x"]["hook_readme"] = readme_rel
out.append(e)
return out

Expand Down Expand Up @@ -189,7 +194,10 @@ def normalize_base(base):
return "/" + base if base else ""


def render_list(entries, base=""):
def render_list(entries, base="", meta=None):
"""Home list by category; third-party entries show their author
(meta.json authors) before the summary, like the README's tags."""
authors = (meta or {}).get("authors", {})
out = []
for cat in dict.fromkeys(e["category"] for e in entries): # entries are in category order
items = [e for e in entries if e["category"] == cat]
Expand All @@ -198,10 +206,12 @@ def render_list(entries, base=""):
% (esc(cat), esc(site_map.group_title(cat)), len(items)))
out.append('<ul class="skill-list">')
for e in items:
by = authors.get(e["name"])
by = '<span class="by">(%s)</span> ' % esc(by) if by else ""
out.append(
'<li><a href="%s/%s"><span class="nm">/%s</span>'
'<span class="ds">%s</span></a></li>'
% (esc(base), esc(e["name"]), esc(e["name"]), esc(e["summary"])))
'<span class="ds">%s%s</span></a></li>'
% (esc(base), esc(e["name"]), esc(e["name"]), by, esc(e["summary"])))
out.append("</ul>")
out.append("</section>")
return "\n".join(out)
Expand Down Expand Up @@ -255,7 +265,7 @@ def nav_data(entries, meta=None):
def render_index(template, entries, base="", meta=None):
return (template.replace("{{BASE}}", esc(base))
.replace("{{COUNT}}", esc(count_line(entries)))
.replace("{{LIST}}", render_list(entries, base))
.replace("{{LIST}}", render_list(entries, base, meta))
.replace("{{DATA}}", nav_data(entries, meta)))


Expand Down
2 changes: 1 addition & 1 deletion scripts/site_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def invoke(e):
if kind == "agent":
return '`Agent(subagent_type="%s")`' % e["name"]
if kind == "hook":
return "install: hooks/README.md"
return "install: %s" % ("comes with the agents" if e["source"].startswith("agents/") else "hooks/README.md")
return "`/%s`" % e["name"]


Expand Down
Loading
Loading