From 7b66a91a1807f48b0b2bce0bba4819165290aa3b Mon Sep 17 00:00:00 2001 From: Andrzej Ochodek Date: Fri, 30 Oct 2026 19:41:02 +0100 Subject: [PATCH] Add a /triage skill for sorting new project items The skill collects the issues in the project's Triage column (or given issues), checks each against develop, proposes Priority, Phase, Seq, Plan, Size, labels and milestone in one table and writes the fields only after the maintainer approves. It also records how an agent triages an issue it opens at the maintainer's request. CLAUDE.md lists the skill and GITHUB-SETUP mentions it in Intake. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/triage/SKILL.md | 118 +++++++++++++++++++++++++++++++++ CLAUDE.md | 5 +- docs/GITHUB-SETUP.md | 4 +- 3 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 .claude/skills/triage/SKILL.md diff --git a/.claude/skills/triage/SKILL.md b/.claude/skills/triage/SKILL.md new file mode 100644 index 0000000..88f8adf --- /dev/null +++ b/.claude/skills/triage/SKILL.md @@ -0,0 +1,118 @@ +--- +name: triage +description: Sort new OpenUrzednik.NET project items (Status Triage) - verify each issue against develop, propose Priority, Phase, Size, Seq, Plan, labels and milestone in one table, and apply it only after the maintainer approves. Use whenever the user asks to triage, sort or groom the Triage column or new issues, decide "what to do with" incoming issues, or right after an agent opens an issue the maintainer asked for. +argument-hint: "[issue number ...]" +--- + +# Triage new project items + +New issues land in the project [OpenUrzednik.NET](https://github.com/users/milten89/projects/2) with Status `Triage` ([`docs/GITHUB-SETUP.md`](../../../docs/GITHUB-SETUP.md#intake)). This skill sorts them: it checks each one against the code, proposes the project fields for all of them in one table, and writes nothing until the maintainer approves that table. CLAUDE.md lets agents change only an issue's Status to `In progress`; the approval in step 4 is what allows the other fields here. + +Guardrails, for every step: + +- Never triage, change or start an issue labelled `maintainer-only`. +- Never close an issue (as a duplicate, as fixed or otherwise), edit an issue body or post a comment without approval. Propose the text in the table's notes instead. +- Never move an item to `Ready` unless the maintainer says so in the approval: only the maintainer decides what starts. +- PRs aren't triaged: they arrive without a Status and wait in the Review queue view. + +With issue numbers as arguments, triage only those issues (whatever their Status) and skip the list query in step 1; otherwise the whole Triage column. An issue that already has Seq and Plan keeps them unless they are wrong (say why in the notes). Repository: `milten89/OpenUrzednik.NET`; examples use bash. In Git Bash, run `git show` with `MSYS_NO_PATHCONV=1`: otherwise it rewrites `origin/develop:` as a Windows path list. + +## 1. Collect + +```bash +gh project item-list 2 --owner milten89 --format json --limit 200 --jq '.totalCount' +gh project item-list 2 --owner milten89 --format json --limit 200 \ + --jq '[.items[] | select(.content.type == "Issue" and (.status == "Triage" or .status == null) and ((.labels // []) | index("maintainer-only") | not))] | .[] | "#\(.content.number) \(.id) \(.title)"' +``` + +If `totalCount` reaches the limit, raise `--limit`. An issue without a Status was missed by the auto-add workflow; triage it too. If the list is empty, say so and stop. For an issue given as an argument, find its project item id (step 5 needs it); if it prints nothing, the issue isn't in the project, and step 5 adds it with `gh project item-add 2 --owner milten89 --url ` (a write, so only after approval): + +```bash +gh project item-list 2 --owner milten89 --format json --limit 200 --jq '.items[] | select(.content.number == ) | "\(.id) \(.status)"' +``` + +For each issue, read it in full, including the comments and the issues it links: + +```bash +gh issue view -R milten89/OpenUrzednik.NET --json state,title,body,labels,milestone,author,createdAt,comments,blockedBy \ + --jq '{state, title, author: .author.login, createdAt, labels: [.labels[].name], milestone: .milestone.title, blockedBy: [.blockedBy.nodes[] | "#\(.number) \(.state)"], body, comments: [.comments[] | "\(.author.login): \(.body)"]}' +``` + +Skip closed issues (report them: a closed issue in Triage means a workflow didn't run). + +## 2. Verify against `develop` + +Work from the latest `origin/develop` (`git fetch origin`; read files with `git show origin/develop:` when the checkout is on another branch). For each claim in the issue, check the code and docs it names: + +- **Still true?** A bug may be fixed already, or a snippet may have moved (an earlier run found a broken README snippet that had moved into the packed Core README). +- **Duplicate?** Search open issues: `gh issue list -R milten89/OpenUrzednik.NET --state open --search ""`. +- **Blocked?** Note issues it depends on; a blocked issue stays in `Backlog`. +- **Against an ADR?** An issue that contradicts an accepted ADR needs `/adr` first; say so in the notes. + +## 3. Propose one table + +Read the context the fields depend on: + +```bash +# Items in order: Seq, Plan, Priority, Phase, Status +gh project item-list 2 --owner milten89 --format json --limit 200 \ + --jq '[.items[] | select(.status != "Done" and .seq != null)] | sort_by(.seq) | .[] | "\(.seq) \(.plan // "-") \(.priority // "-") \(.phase // "-") \(.status) #\(.content.number) \(.title)"' +# The next free follow-up id; count up from it for several items +gh project item-list 2 --owner milten89 --format json --limit 200 \ + --jq '[.items[].plan // "" | capture("^F(?[0-9]+)$") | .n | tonumber] | "F\((max // 0) + 1)"' +gh label list -R milten89/OpenUrzednik.NET --limit 100 --json name --jq '.[].name' +gh api repos/milten89/OpenUrzednik.NET/milestones --jq '.[] | "\(.title) open=\(.open_issues)"' +gh issue list -R milten89/OpenUrzednik.NET --milestone --state open --json number,title --jq '.[] | "#\(.number) \(.title)"' +``` + +Rules (definitions in [`docs/GITHUB-SETUP.md`](../../../docs/GITHUB-SETUP.md#fields)): + +- **Priority:** `Urgent` (harm now or a calendar deadline), `High` (needed for the next release), `Medium` (should do; may slip), `Low` (later, or after the release). +- **Milestone:** the next release's milestone only for what that release needs, all of it Urgent or High; an Urgent or High `bug` always goes in. Nice-to-haves get none ([Milestones](../../../docs/GITHUB-SETUP.md#milestones)). +- **Phase:** the phase of the 1.0 plan the work belongs to; `Phase 6` for work after 1.0. +- **Seq:** a free number between the neighbours in the intended order (the plan uses steps of 10; follow-ups take free numbers in between). When no neighbour gives a clear place, take one more than the highest Seq of the item's Phase that is still free. +- **Plan:** the next free `F<n>`, computed from all items as above, never from a few neighbours. +- **Size:** an estimate of the maintainer's effort to review or do it, not the agent's: `XS` (< 15 min), `S` (< 30 min), `M` (about 1 h), `L` (several hours). +- **Labels:** one release label from `.github/release.yml` as a forecast (`breaking-change`, `enhancement`, `bug`, `documentation`, `dependencies`, `ci`, `skip-changelog`) plus area labels (`area:*`, `provider:*`). Only labels that exist; propose a new one in the notes instead of creating it. +- **Provider proposals:** `Backlog`, Priority `Low`, no milestone. +- **Due:** only for a calendar deadline. + +Show one table for all items, in the recommended order, then the notes: + +| Issue | Priority | Phase | Seq | Plan | Size | Labels | Milestone | Status | Reason | +|---|---|---|---|---|---|---|---|---|---| +| #n title | Low | Phase 6 | 437 | F8 | XS | `area:core`, `skip-changelog` | — | Backlog | one line | + +Notes list what step 2 found (already fixed, moved, duplicate, blocked by), with proposed comment or closing text where one would help. + +## 4. Ask + +Ask the maintainer once to approve or change the table. Write nothing before a clear answer. Apply exactly the approved values; if they change something, show the changed rows again only when the change affects other rows (Seq order, Plan ids). + +## 5. Apply and report + +Read the field and option ids, then set each approved value: + +```bash +gh project field-list 2 --owner milten89 --format json \ + --jq '.fields[] | "\(.name) \(.id) \([.options[]? | "\(.name)=\(.id)"] | join(" "))"' +gh project item-edit --project-id PVT_kwHOAJh3Rs4BnebI --id <item id> --field-id <field id> --single-select-option-id <option id> # Status, Priority, Phase, Size +gh project item-edit --project-id PVT_kwHOAJh3Rs4BnebI --id <item id> --field-id <field id> --number <seq> # Seq +gh project item-edit --project-id PVT_kwHOAJh3Rs4BnebI --id <item id> --field-id <field id> --text <plan> # Plan +gh project item-edit --project-id PVT_kwHOAJh3Rs4BnebI --id <item id> --field-id <field id> --date <YYYY-MM-DD> # Due, only for a deadline +gh issue edit <n> -R milten89/OpenUrzednik.NET --add-label <label> # --remove-label for a wrong form label +gh issue edit <n> -R milten89/OpenUrzednik.NET --milestone <title> # only when the table gives a milestone +``` + +Status becomes `Backlog` (or `Ready` where the maintainer said so). Approved comments or closings come last. Then show the result from the project, not from memory: + +```bash +gh project item-list 2 --owner milten89 --format json --limit 200 \ + --jq '.items[] | select(.content.number == <n>) | {status, priority, phase, seq, plan, size, labels, milestone: .milestone.title}' +``` + +Report every failed write. + +## Issues an agent opens for the maintainer + +When the maintainer asks an agent for work that needs a new issue (one issue per PR), the agent opens it and triages it right away with the rules of step 3, without the table and approval: the request is the approval. Status is `Ready`, or stays `In progress` if a linked PR already moved it there. The one exception is the milestone: a milestone gates the release, so set one only when the maintainer's request names the release; otherwise leave it out and say so in the report. Issues an agent opens on its own initiative (problems found in passing) stay in `Triage` for this skill. diff --git a/CLAUDE.md b/CLAUDE.md index 0c4c43c..ab1944b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -144,12 +144,12 @@ Before you say a task is done, run `dotnet build`, `dotnet test -f net10.0` and Work is tracked in this repository's issues and in the public project [OpenUrzednik.NET](https://github.com/users/milten89/projects/2); its fields, views and workflows are described in [`docs/GITHUB-SETUP.md`](docs/GITHUB-SETUP.md#project). The issue body is the specification of the work. -- Take open issues with Status `Ready` and without the `maintainer-only` label, by Priority, then Seq. Only the maintainer moves an issue to `Ready`. +- Take open issues with Status `Ready` and without the `maintainer-only` label, by Priority, then Seq. Only the maintainer decides that an issue moves to `Ready` (an issue opened for work the maintainer asked for counts as decided; see `/triage`). - Before you start, check that no open issue blocks it: `gh issue view <n> --json blockedBy --jq '[.blockedBy.nodes[] | select(.state == "OPEN") | .number]'` must print `[]`. - When you start, set the issue's Status to `In progress`. That is the only field you change: never set `Done`, Priority, Phase, Seq, Size, Due, or the issue's milestone and labels unless asked. - One issue per PR. The PR body starts with `Closes #<n>`. When you open the PR, give it at least one release label from `.github/release.yml` (`gh pr create --label <label>`), normally the one forecast on the issue or proposed by the `reviewer` agent; the `release-label` check fails a PR without one. - Never start, change or close a `maintainer-only` issue. -- You may open follow-up issues for problems outside the current issue. The project adds them with Status `Triage`, and the maintainer sorts them. +- You may open follow-up issues for problems outside the current issue. The project adds them with Status `Triage`, and they are sorted with `/triage`, after the maintainer approves. An issue opened for work the maintainer asked for is triaged at once (`/triage`, last section). - Release and hotfix PRs target `main`, where `Closes` doesn't close the issue. The maintainer moves those issues to `Done`. Project fields need the `project` scope (`gh auth refresh -s project`): @@ -168,6 +168,7 @@ gh project item-edit --project-id PVT_kwHOAJh3Rs4BnebI --id <item id> --field-id - `/adr`: create a new MADR record and update the index. - `/release-notes`: draft `docs/release-notes/v<version>.md` for a release or hotfix; `nuget-release.yml` publishes nothing without it. - `/prepare-release`: prepare a release (checks, `release/X.Y`, version, notes, package checks, the PR into `main`); `after`: verify the published release and open the back-merge. +- `/triage`: sort the project's Triage column (verify each issue against `develop`, propose the fields in one table, apply them after the maintainer approves); also how an agent triages an issue it opens at the maintainer's request. - `/miodkuj`: remove AI-sounding, bureaucratic and translated phrasing from Polish prose (vendored from [bartekpucek/miodkuj@32004e3](https://github.com/bartekpucek/miodkuj/tree/32004e3), MIT). - `/stop-slop`: remove predictable AI writing patterns from English prose (vendored from [hardikpandya/stop-slop@8da1f03](https://github.com/hardikpandya/stop-slop/tree/8da1f03), MIT). - `reviewer` agent: reviews a diff against the ADRs and the conventions above. diff --git a/docs/GITHUB-SETUP.md b/docs/GITHUB-SETUP.md index 41c26a7..18715f3 100644 --- a/docs/GITHUB-SETUP.md +++ b/docs/GITHUB-SETUP.md @@ -139,7 +139,7 @@ Reading or changing the project needs the `project` scope on the `gh` token (`gh | Priority | single select | `Urgent` (harm now or a calendar deadline), `High` (needed for the next release), `Medium` (should do; may slip), `Low` (later, or after the release). Views sort by the options' order, not by name | | Phase | single select | `Phase 0` … `Phase 6`: the stages of the 1.0 plan | | Seq | number | Execution order within the plan, in steps of 10, leaving room to insert | -| Plan | text | The plan id (e.g. `P0-0`, `M5`) | +| Plan | text | The plan id (e.g. `P0-0`, `M5`); follow-ups get the next free `F<n>` | | Size | single select | Maintainer effort to review or do it: `XS` (< 15 min), `S` (< 30 min), `M` (about 1 h), `L` (several hours) | | Due | date | Only for calendar deadlines | @@ -178,4 +178,4 @@ Set in the web UI (project → ⋯ → Workflows). The Free plan allows one auto ### Intake -Issues (from the forms, or opened by agents) arrive through auto-add with Status `Triage`. PRs, Dependabot's included, are added without a Status and show in the Review queue view. Security reports never arrive (they stay private, see `SECURITY.md`). The maintainer sets labels and Priority, puts an Urgent or High bug into the next milestone, and moves provider proposals to `Backlog` with Priority Low. Only the maintainer moves an issue to `Ready`. +Issues (from the forms, or opened by agents) arrive through auto-add with Status `Triage`. PRs, Dependabot's included, are added without a Status and show in the Review queue view. Security reports never arrive (they stay private, see `SECURITY.md`). The maintainer, or an agent with the `/triage` skill and the maintainer's approval of its proposal, sets labels and the project fields, puts an Urgent or High bug into the next milestone, and moves provider proposals to `Backlog` with Priority Low. Only the maintainer decides that an issue moves to `Ready`.