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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ cursor-agent --plugin-dir "/path/to/skillsets/pstack" --plugin-dir "/path/to/ski
## Rules

- Do not edit the files in a provider folder. `fetch.sh` deletes and downloads them again. It also makes `<provider>/.claude-plugin/plugin.json`, `<provider>/.codex-plugin/plugin.json` and `.claude-plugin/marketplace.json`, so do not edit those files.
- To add a provider, add a `fetch_skills <source> <folder> [skill...]` line to `fetch.sh` before `write_marketplace`, run `./fetch.sh`, and update the table in `README.md`. The folder name must be new and use only lowercase letters, numbers and `-`. `fetch.sh` refuses other names, and refuses to delete a folder that it did not make. Do not remove this check.
- To add a provider, add a `fetch_skills <source> <folder> [skill...] [-skill...]` line (`-skill` leaves a skill out) to `fetch.sh` before `write_marketplace`, run `./fetch.sh`, and update the table in `README.md`. The folder name must be new and use only lowercase letters, numbers and `-`. `fetch.sh` refuses other names, and refuses to delete a folder that it did not make. Do not remove this check.
- The `skills` CLI stages its files in git. Run `git reset` after `./fetch.sh` and stage only the files you want to commit.

## Verify a change
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,10 @@ The `Fetch skills` GitHub Actions workflow runs `fetch.sh` every day. If the ski
Add one line to `fetch.sh`, before `write_marketplace`:

```bash
fetch_skills <owner>/<repo>[/<path>] <folder> [skill...]
fetch_skills <owner>/<repo>[/<path>] <folder> [skill...] [-skill...]
```

Without skill names, the script downloads all skills of the provider.
Without skill names, the script downloads all skills of the provider. Put `-` before a skill name to leave that skill out, for example `fetch_skills cursor/plugins/pstack pstack -statechart`. The script stops with an error if the provider has no skill with that name.

Use a new folder name with only lowercase letters, numbers and `-`. `fetch.sh` deletes and downloads each provider folder again, so it stops with an error if the name is not valid, or if the folder exists and `fetch.sh` did not make it (for example `lib` or `test`).

Expand Down
21 changes: 18 additions & 3 deletions fetch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ check_dest() {
fi
}

# fetch_skills <source> <dest> [skill...] (all skills when none are given)
# fetch_skills <source> <dest> [skill...] [-skill...]
# All skills when no skill is given. A skill that starts with - is left out.
fetch_skills() {
local source="$1" dest="${2-}"
local skills=("${@:3}")
local skills=() excludes=() arg name
for arg in "${@:3}"; do
if [[ "$arg" == -* ]]; then excludes+=("${arg#-}"); else skills+=("$arg"); fi
done
[ ${#skills[@]} -eq 0 ] && skills=("*")
check_dest "$dest"

Expand All @@ -35,6 +39,17 @@ fetch_skills() {
mv .agents/skills/* .claude/skills/
rm -rf .agents

for name in ${excludes[@]+"${excludes[@]}"}; do
if [ ! -d ".claude/skills/$name" ]; then
echo "fetch.sh: $source has no skill '$name' to leave out" >&2
exit 1
fi
rm -rf ".claude/skills/$name"
node -e 'const fs = require("fs"), [f, n] = process.argv.slice(1);
const lock = JSON.parse(fs.readFileSync(f, "utf8")); delete lock.skills[n];
fs.writeFileSync(f, JSON.stringify(lock, null, 2) + "\n")' skills-lock.json "$name"
done

# Plugin manifests, so Claude Code (CLAUDE_CODE_PLUGIN_DIRS) and Codex (codex-install.sh)
# can load the same skills
mkdir -p .codex-plugin .claude-plugin
Expand All @@ -60,7 +75,7 @@ write_marketplace() {
}

fetch_skills mattpocock/skills mattpocock
fetch_skills cursor/plugins/pstack pstack
fetch_skills cursor/plugins/pstack pstack -statechart
fetch_skills thananon/9arm-skills 9arm debug-mantra post-mortem scrutinize
fetch_skills obra/superpowers superpowers
fetch_skills humanlayer/skills humanlayer
Expand Down
4 changes: 3 additions & 1 deletion mattpocock/.claude/skills/setup-matt-pocock-skills/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Look at the current repo to understand its starting state. Read whatever exists;

- `git remote -v` and `.git/config`: is this a GitHub repo? Which one?
- `AGENTS.md` and `CLAUDE.md` at the repo root: does either exist? Is there already an `## Agent skills` section in either?
- `GLOSSARY.md` and `GLOSSARY-MAP.md` at the repo root
- `GLOSSARY.md` and `GLOSSARY-MAP.md` at the repo root, and the legacy `CONTEXT.md` / `CONTEXT-MAP.md`. If a legacy file exists, offer to `git mv` it to its `GLOSSARY` name and rewrite those names in `docs/agents/domain.md`.
- `docs/adr/` and any `src/*/docs/adr/` directories
- `docs/agents/`: does this skill's prior output already exist?
- `.scratch/`: a sign that a local-markdown issue tracker convention is already in use
Expand All @@ -48,6 +48,8 @@ Default posture: these skills were designed for GitHub. If a `git remote` points

Record the choice in `docs/agents/issue-tracker.md`. The GitHub and GitLab templates carry a "PRs as a request surface" flag, defaulted **off**. Leave it off and don't raise it: a user who wants external PRs in the triage queue can flip the flag in the file later.

A GitHub or GitLab tracker needs a reachable repo. When the user picks one and no `git remote` points at that host, get the repo URL from them and wire it up (`git init` if needed, `git remote add origin <url>`) before going further. Done when `gh repo view` / `glab repo view` prints the project from inside this directory; if it can't, stop and tell the user what's missing.

**Section B: Triage label vocabulary.** Skip this section entirely if the `triage` skill isn't installed (exploration told you), since an uninstalled skill needs no labels.

If it is installed, ask exactly one question:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@ Issues and specs for this repo live as GitHub issues. Use the `gh` CLI for all o
- **Read an issue**: `gh issue view <number> --json number,title,body,labels,comments`.
- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters.
- **Make an issue a sub-issue of a parent**: `gh issue create --parent <parent> ...`, or `gh issue edit <parent> --add-sub-issue <child>` afterwards (`gh` 2.94+). Older `gh`: `gh api --method POST repos/<owner>/<repo>/issues/<parent>/sub_issues -F sub_issue_id=<child-db-id>` (database id, as in **Blocking** below). Without sub-issues, put `Part of #<parent>` at the top of the child body.
- **Blocking**: GitHub's **native issue dependencies**, the canonical, UI-visible representation. Add an edge with `gh issue create --blocked-by <n>,<n> ...` or `gh issue edit <child> --add-blocked-by <blocker>` (`gh` 2.94+). Older `gh`: `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
- **Count open blockers**: `gh api repos/<owner>/<repo>/issues/<n> --jq .issue_dependencies_summary.blocked_by` (open blockers only; 0 means startable). `gh issue view <n> --json blockedBy` also lists closed blockers, so filter it: `--jq '[.blockedBy.nodes[] | select(.state == "OPEN")] | length'`.
- **Comment on an issue**: `gh issue comment <number> --body "..."`
- **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
- **Close**: `gh issue close <number> --comment "..."`
- **Close**: post the explanation with `gh issue comment <number> --body "..."`, then `gh issue close <number>`. On an already-closed issue, `gh issue close --comment` drops the comment and still exits 0.

Infer the repo from `git remote -v`; `gh` does this automatically when run inside a clone.

File issues only into the user's own repo. Never file into `mattpocock/skills` (the repo these skills come from) unless the user owns it. In a fork, `gh` can default to the upstream parent, so before the first `gh issue create` of a session, check the target with `gh repo view --json nameWithOwner` and confirm it with the user if it is not their repo.

## Pull requests as a triage surface

**PRs as a request surface: no.** _(Set to `yes` if this repo treats external PRs as feature requests; `/triage` reads this flag.)_

When set to `yes`, PRs run through the same labels and states as issues, using the `gh pr` equivalents:

- **Read a PR**: `gh pr view <number> --comments` and `gh pr diff <number>` for the diff.
- **Read a PR**: `gh pr view <number>`, then `gh pr view <number> --comments` (off a TTY, `--comments` prints only the comments, not the body), and `gh pr diff <number>` for the diff.
- **List external PRs for triage**: `gh api --paginate 'repos/{owner}/{repo}/pulls?state=open' --jq '.[] | select(.author_association | IN("OWNER","MEMBER","COLLABORATOR") | not) | {number, title, author: .user.login, author_association, labels: [.labels[].name]}'`.
- **Comment / label / close**: `gh pr comment`, `gh pr edit --add-label`/`--remove-label`, `gh pr close`.

Expand All @@ -40,7 +44,7 @@ Used by `/wayfinder`. The **map** is a single issue with **child** issues as tic

- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`.
- **Child ticket**: an issue linked to the map as a GitHub sub-issue (see **Make an issue a sub-issue of a parent**). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
- **Blocking**: GitHub's **native issue dependencies**, the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only, the live gate). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
- **Blocking**: see **Blocking** under Conventions.
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (see **Count open blockers**, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
- **Claim**: `gh issue edit <n> --add-assignee @me`, the session's first write.
- **Resolve**: `gh issue comment <n> --body "<answer>"`, then `gh issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Infer the repo from `git remote -v`; `glab` does this automatically when run ins
When set to `yes`, MRs run through the same labels and states as issues, using the `glab mr` equivalents:

- **Read an MR**: `glab mr view <number> --comments` and `glab mr diff <number>` for the diff.
- **List external MRs for triage**: `glab mr list -F json`, then keep only MRs whose author is not a project member/owner (a contributor's MR, not a maintainer's in-flight work).
- **List external MRs for triage**: `glab mr list -O json`, then keep only MRs whose author is not a project member/owner (a contributor's MR, not a maintainer's in-flight work).
- **Comment / label / close**: `glab mr note`, `glab mr update --label`/`--unlabel`, `glab mr close`.

Unlike GitHub, GitLab numbers issues and MRs separately, so `#42` is unambiguous once you know which surface the maintainer means.
Expand All @@ -39,8 +39,8 @@ Run `glab issue view <number> --comments`.
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.

- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `glab issue create --label wayfinder:map`. (On GitLab tiers with native epics, an epic may hold the map instead; a labelled issue works everywhere.)
- **Child ticket**: an issue carrying `Part of #<map>` at the top of its description and labels `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
- **Child ticket**: an issue carrying `Part of #<map>` at the top of its description, linked to the map (`glab issue create --linked-issues <map> --link-type relates_to`), and labels `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
- **Blocking**: GitLab's **native blocking link**, the canonical, UI-visible representation. Add it with the `/blocked_by #<n>` quick action, posted as a note (`glab issue note <child> --message "/blocked_by #<blocker>"`). Native blocking links are a Premium/Ultimate feature; on the free tier (or where unavailable) fall back to a `Blocked by: #<n>, #<n>` line at the top of the description. A ticket is unblocked when every blocker is closed.
- **Frontier query**: `glab issue list -O json` scoped to the map's children, drop any with an open blocker: a native `blocked_by` link to an open issue (`glab api projects/:id/issues/<child-iid>/links`), or an open issue in the `Blocked by` line, or an assignee; first in map order wins.
- **Frontier query**: list the map's children with `glab api projects/:id/issues/<map>/links`, keeping open issues that carry a `wayfinder:<type>` label; drop any with an open blocker: a native `blocked_by` link to an open issue (`glab api projects/:id/issues/<child-iid>/links`), or an open issue in the `Blocked by` line, or an assignee; first in map order wins.
- **Claim**: `glab issue update <n> --assignee @me`, the session's first write.
- **Resolve**: `glab issue note <n> --message "<answer>"`, then `glab issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
2 changes: 1 addition & 1 deletion mattpocock/skills-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
"source": "mattpocock/skills",
"sourceType": "github",
"skillPath": "skills/engineering/setup-matt-pocock-skills/SKILL.md",
"computedHash": "1d35d0afa3ded88c9324d38db08b2cff91cd03e0e11d64786bee41385cd7b9fb"
"computedHash": "50d6c45174f56a4323b24978f36fcc9169fae0b3abb026587bf636e468c4954c"
},
"setup-pre-commit": {
"source": "mattpocock/skills",
Expand Down
Loading
Loading