vouchington-tooling 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/gha/ghcr-main-history.sh +28 -5
- package/scripts/gha/ghcr-package-retention.sh +4 -0
- package/scripts/gha/ghcr-referenced-digests.sh +65 -0
- package/skills/manifest.json +32 -19
- package/skills/triage-prs/SKILL.md +179 -0
- package/skills/triage-prs/references/close.md +83 -0
- package/skills/triage-prs/references/feedback.md +58 -0
- package/skills/triage-prs/references/rubric.md +61 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
4
4
|
"description": "Vouchington CLI and extractable tooling libraries.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
# Sourced by ghcr-package-retention.sh for --main-history. Expects package_root, packages, apply,
|
|
3
3
|
# keep_main and min_age_days from the caller.
|
|
4
4
|
|
|
5
|
+
# shellcheck source=/dev/null
|
|
6
|
+
. "$(dirname "${BASH_SOURCE[0]}")/ghcr-referenced-digests.sh"
|
|
7
|
+
|
|
5
8
|
prune_main_history() {
|
|
9
|
+
# The registry namespace of the packages: GHCR_OWNER, else the workflow's repository owner.
|
|
10
|
+
local owner="${GHCR_OWNER:-${GITHUB_REPOSITORY_OWNER:-}}"
|
|
11
|
+
if [ -z "$owner" ]; then
|
|
12
|
+
echo '::error::ghcr-package-retention: GHCR_OWNER is required with --main-history' >&2
|
|
13
|
+
exit 2
|
|
14
|
+
fi
|
|
15
|
+
|
|
6
16
|
# A zero keep count would delete every main image in one run.
|
|
7
17
|
case "$keep_main" in
|
|
8
18
|
'' | *[!0-9]*) keep_main=0 ;;
|
|
@@ -49,10 +59,13 @@ prune_main_history() {
|
|
|
49
59
|
| min) as $oldest_image
|
|
50
60
|
| ($versions | map(select(attachment and idle and $oldest_image != null
|
|
51
61
|
and created < $oldest_image - 86400) | . + {reason: "orphan"})) as $orphans
|
|
52
|
-
| ($
|
|
53
|
-
|
|
62
|
+
| {roots: ($versions | map(select((attachment | not) and (.id | IN($doomed[]) | not)) | .name)),
|
|
63
|
+
candidates: ($old_main + $unmerged + $orphans | map({id, name, reason,
|
|
64
|
+
tags: (tags | if length == 0 then "<untagged>" else join(",") end)}))}'
|
|
65
|
+
local filter='.candidates | map(select(.name | IN($referenced[]) | not))[]
|
|
66
|
+
| "\(.id) \(.tags) \(.reason)"'
|
|
54
67
|
|
|
55
|
-
local deleted_total=0 package path error deletions id tags reason
|
|
68
|
+
local deleted_total=0 package path error deletions id tags reason referenced image resolved
|
|
56
69
|
for package in "${packages[@]}"; do
|
|
57
70
|
path="$package_root/$package/versions"
|
|
58
71
|
if ! error=$(gh api --paginate "$path?per_page=100" 2>&1 > "$work/pages"); then
|
|
@@ -67,8 +80,18 @@ prune_main_history() {
|
|
|
67
80
|
exit 1
|
|
68
81
|
fi
|
|
69
82
|
|
|
70
|
-
|
|
71
|
-
--argjson cutoff "$cutoff" "$plan" "$work/pages"
|
|
83
|
+
jq -s --rawfile revisions "$work/main-revisions" --argjson keep "$keep_main" \
|
|
84
|
+
--argjson cutoff "$cutoff" "$plan" "$work/pages" > "$work/plan"
|
|
85
|
+
# Never delete a digest that a kept image or index still references (platform manifests,
|
|
86
|
+
# attestations, subjects). An unreadable kept manifest aborts rather than risk a delete.
|
|
87
|
+
referenced='[]'
|
|
88
|
+
if [ "$(jq '.candidates | length' "$work/plan")" -gt 0 ]; then
|
|
89
|
+
jq -r '.roots[]' "$work/plan" > "$work/roots"
|
|
90
|
+
image="$owner/$(printf '%b' "${package//%/\\x}")"
|
|
91
|
+
resolved=$(resolve_referenced "$image" "$owner" "$work" "$work/roots") || exit 1
|
|
92
|
+
referenced=$(printf '%s\n' "$resolved" | jq -R . | jq -s .)
|
|
93
|
+
fi
|
|
94
|
+
deletions=$(jq -r --argjson referenced "$referenced" "$filter" "$work/plan")
|
|
72
95
|
echo "· $package: $(jq -s 'add // [] | length' "$work/pages") version(s)"
|
|
73
96
|
while read -r id tags reason; do
|
|
74
97
|
[ -n "$id" ] || continue
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
# - An attachment: untagged or tagged only sha256-*. Attestations and their referrer indexes are
|
|
13
13
|
# pushed after their image, and platform manifests moments before it, so an attachment created
|
|
14
14
|
# over a day before the oldest surviving image, and idle for the same age, belongs to none.
|
|
15
|
+
# Before deleting, the manifests of every surviving image are read from the registry (GH_TOKEN or
|
|
16
|
+
# GITHUB_TOKEN, else gh auth token) and anything they reference through manifests[] or subject is
|
|
17
|
+
# kept; if one cannot be read, the package is left untouched. The registry namespace is GHCR_OWNER,
|
|
18
|
+
# else GITHUB_REPOSITORY_OWNER (required for user-owned packages too).
|
|
15
19
|
# Packages that do not exist (HTTP 404) are skipped. Defaults to a dry run; pass --apply to delete.
|
|
16
20
|
#
|
|
17
21
|
# Packages come from the arguments, or from GHCR_PACKAGES (space separated) when none are given.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Sourced by ghcr-main-history.sh. Walks OCI manifests through manifests[] and subject.
|
|
3
|
+
|
|
4
|
+
# registry_token <image> <owner> <work-dir>: exchanges the GitHub token for a registry pull token
|
|
5
|
+
# (GHCR's documented /token endpoint, Basic auth) and prints it. Credentials go to curl on stdin.
|
|
6
|
+
registry_token() {
|
|
7
|
+
local image="$1" owner="$2" dir="$3" token registry
|
|
8
|
+
registry="${GHCR_REGISTRY_URL:-https://ghcr.io}"
|
|
9
|
+
token="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
|
|
10
|
+
if [ -z "$token" ]; then
|
|
11
|
+
token=$(gh auth token) || {
|
|
12
|
+
echo '::error::No GH_TOKEN or GITHUB_TOKEN, and gh auth token failed' >&2
|
|
13
|
+
return 1
|
|
14
|
+
}
|
|
15
|
+
fi
|
|
16
|
+
if ! printf 'user = "%s:%s"\n' "$owner" "$token" |
|
|
17
|
+
curl -fsS -K - "$registry/token?scope=repository:$image:pull" \
|
|
18
|
+
> "$dir/token.json" 2> "$dir/curl-error"; then
|
|
19
|
+
printf '::error::Could not get a registry token for %s: %s\n' "$image" \
|
|
20
|
+
"$(cat "$dir/curl-error")" >&2
|
|
21
|
+
return 1
|
|
22
|
+
fi
|
|
23
|
+
jq -er '.token | select(type == "string" and . != "")' "$dir/token.json" || {
|
|
24
|
+
printf '::error::Registry token response for %s had no token\n' "$image" >&2
|
|
25
|
+
return 1
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# resolve_referenced <image> <owner> <work-dir> <roots-file>: prints every digest reachable from
|
|
30
|
+
# the root digests (one per line in the file), roots included. Returns non-zero on any failure, so
|
|
31
|
+
# the caller deletes nothing.
|
|
32
|
+
resolve_referenced() {
|
|
33
|
+
local image="$1" owner="$2" dir="$3" digest registry_bearer registry accept
|
|
34
|
+
registry="${GHCR_REGISTRY_URL:-https://ghcr.io}"
|
|
35
|
+
registry_bearer=$(registry_token "$image" "$owner" "$dir") || return 1
|
|
36
|
+
accept='application/vnd.oci.image.index.v1+json'
|
|
37
|
+
accept="$accept,application/vnd.oci.image.manifest.v1+json"
|
|
38
|
+
accept="$accept,application/vnd.docker.distribution.manifest.list.v2+json"
|
|
39
|
+
accept="$accept,application/vnd.docker.distribution.manifest.v2+json"
|
|
40
|
+
|
|
41
|
+
: > "$dir/seen"
|
|
42
|
+
cp "$4" "$dir/queue" || return 1
|
|
43
|
+
while [ -s "$dir/queue" ]; do
|
|
44
|
+
digest=$(head -n 1 "$dir/queue")
|
|
45
|
+
sed -i.bak 1d "$dir/queue" || return 1
|
|
46
|
+
rm -f "$dir/queue.bak"
|
|
47
|
+
[ -n "$digest" ] || continue
|
|
48
|
+
! grep -qxF "$digest" "$dir/seen" || continue
|
|
49
|
+
echo "$digest" >> "$dir/seen"
|
|
50
|
+
if ! printf 'header = "Authorization: Bearer %s"\n' "$registry_bearer" |
|
|
51
|
+
curl -fsS -K - -H "Accept: $accept" "$registry/v2/$image/manifests/$digest" \
|
|
52
|
+
> "$dir/manifest" 2> "$dir/curl-error"; then
|
|
53
|
+
printf '::error::Could not read manifest %s of %s: %s\n' "$digest" "$image" \
|
|
54
|
+
"$(cat "$dir/curl-error")" >&2
|
|
55
|
+
return 1
|
|
56
|
+
fi
|
|
57
|
+
if ! jq -r '[(.manifests // [])[].digest, (.subject.digest // empty)] | .[]' \
|
|
58
|
+
"$dir/manifest" >> "$dir/queue" 2> "$dir/jq-error"; then
|
|
59
|
+
printf '::error::Could not parse manifest %s of %s: %s\n' "$digest" "$image" \
|
|
60
|
+
"$(cat "$dir/jq-error")" >&2
|
|
61
|
+
return 1
|
|
62
|
+
fi
|
|
63
|
+
done
|
|
64
|
+
cat "$dir/seen"
|
|
65
|
+
}
|
package/skills/manifest.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
{
|
|
5
5
|
"name": "agent-workflow",
|
|
6
6
|
"plugin": "vouchington-workflow",
|
|
7
|
-
"pluginVersion": "0.
|
|
7
|
+
"pluginVersion": "0.13.0",
|
|
8
8
|
"path": "agent-workflow/SKILL.md",
|
|
9
9
|
"prerequisites": [
|
|
10
10
|
"blackboard",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
{
|
|
25
25
|
"name": "blackboard",
|
|
26
26
|
"plugin": "vouchington-workflow",
|
|
27
|
-
"pluginVersion": "0.
|
|
27
|
+
"pluginVersion": "0.13.0",
|
|
28
28
|
"path": "blackboard/SKILL.md"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
{
|
|
37
37
|
"name": "dependabot",
|
|
38
38
|
"plugin": "vouchington-workflow",
|
|
39
|
-
"pluginVersion": "0.
|
|
39
|
+
"pluginVersion": "0.13.0",
|
|
40
40
|
"path": "dependabot/SKILL.md",
|
|
41
41
|
"prerequisites": ["github-actions-checklist"]
|
|
42
42
|
},
|
|
@@ -50,26 +50,26 @@
|
|
|
50
50
|
{
|
|
51
51
|
"name": "git-commit-checklist",
|
|
52
52
|
"plugin": "vouchington-workflow",
|
|
53
|
-
"pluginVersion": "0.
|
|
53
|
+
"pluginVersion": "0.13.0",
|
|
54
54
|
"path": "git-commit-checklist/SKILL.md"
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"name": "github-actions-authoring",
|
|
58
58
|
"plugin": "vouchington-workflow",
|
|
59
|
-
"pluginVersion": "0.
|
|
59
|
+
"pluginVersion": "0.13.0",
|
|
60
60
|
"path": "github-actions-authoring/SKILL.md",
|
|
61
61
|
"prerequisites": ["github-actions-checklist"]
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
"name": "github-actions-checklist",
|
|
65
65
|
"plugin": "vouchington-workflow",
|
|
66
|
-
"pluginVersion": "0.
|
|
66
|
+
"pluginVersion": "0.13.0",
|
|
67
67
|
"path": "github-actions-checklist/SKILL.md"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"name": "github-issue",
|
|
71
71
|
"plugin": "vouchington-workflow",
|
|
72
|
-
"pluginVersion": "0.
|
|
72
|
+
"pluginVersion": "0.13.0",
|
|
73
73
|
"path": "github-issue/SKILL.md"
|
|
74
74
|
},
|
|
75
75
|
{
|
|
@@ -82,26 +82,26 @@
|
|
|
82
82
|
{
|
|
83
83
|
"name": "npm-publishing",
|
|
84
84
|
"plugin": "vouchington-workflow",
|
|
85
|
-
"pluginVersion": "0.
|
|
85
|
+
"pluginVersion": "0.13.0",
|
|
86
86
|
"path": "npm-publishing/SKILL.md"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
"name": "organize-github-issues",
|
|
90
90
|
"plugin": "vouchington-workflow",
|
|
91
|
-
"pluginVersion": "0.
|
|
91
|
+
"pluginVersion": "0.13.0",
|
|
92
92
|
"path": "organize-github-issues/SKILL.md",
|
|
93
93
|
"prerequisites": ["github-issue"]
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
"name": "package-json-checklist",
|
|
97
97
|
"plugin": "vouchington-workflow",
|
|
98
|
-
"pluginVersion": "0.
|
|
98
|
+
"pluginVersion": "0.13.0",
|
|
99
99
|
"path": "package-json-checklist/SKILL.md"
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
"name": "planning",
|
|
103
103
|
"plugin": "vouchington-workflow",
|
|
104
|
-
"pluginVersion": "0.
|
|
104
|
+
"pluginVersion": "0.13.0",
|
|
105
105
|
"path": "planning/SKILL.md",
|
|
106
106
|
"prerequisites": ["github-issue"]
|
|
107
107
|
},
|
|
@@ -133,53 +133,53 @@
|
|
|
133
133
|
{
|
|
134
134
|
"name": "pr-description",
|
|
135
135
|
"plugin": "vouchington-workflow",
|
|
136
|
-
"pluginVersion": "0.
|
|
136
|
+
"pluginVersion": "0.13.0",
|
|
137
137
|
"path": "pr-description/SKILL.md"
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"name": "retrospective",
|
|
141
141
|
"plugin": "vouchington-workflow",
|
|
142
|
-
"pluginVersion": "0.
|
|
142
|
+
"pluginVersion": "0.13.0",
|
|
143
143
|
"path": "retrospective/SKILL.md"
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
"name": "retrospective-distill",
|
|
147
147
|
"plugin": "vouchington-workflow",
|
|
148
|
-
"pluginVersion": "0.
|
|
148
|
+
"pluginVersion": "0.13.0",
|
|
149
149
|
"path": "retrospective-distill/SKILL.md",
|
|
150
150
|
"prerequisites": ["github-issue"]
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
"name": "review-ci-logs",
|
|
154
154
|
"plugin": "vouchington-workflow",
|
|
155
|
-
"pluginVersion": "0.
|
|
155
|
+
"pluginVersion": "0.13.0",
|
|
156
156
|
"path": "review-ci-logs/SKILL.md",
|
|
157
157
|
"prerequisites": ["github-actions-checklist", "pr-description"]
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
"name": "review-github-issue-taxonomy",
|
|
161
161
|
"plugin": "vouchington-workflow",
|
|
162
|
-
"pluginVersion": "0.
|
|
162
|
+
"pluginVersion": "0.13.0",
|
|
163
163
|
"path": "review-github-issue-taxonomy/SKILL.md",
|
|
164
164
|
"prerequisites": ["github-issue"]
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
"name": "revisit-followups",
|
|
168
168
|
"plugin": "vouchington-workflow",
|
|
169
|
-
"pluginVersion": "0.
|
|
169
|
+
"pluginVersion": "0.13.0",
|
|
170
170
|
"path": "revisit-followups/SKILL.md",
|
|
171
171
|
"prerequisites": ["github-issue"]
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
"name": "stacked-prs",
|
|
175
175
|
"plugin": "vouchington-workflow",
|
|
176
|
-
"pluginVersion": "0.
|
|
176
|
+
"pluginVersion": "0.13.0",
|
|
177
177
|
"path": "stacked-prs/SKILL.md"
|
|
178
178
|
},
|
|
179
179
|
{
|
|
180
180
|
"name": "static-analysis-checklist",
|
|
181
181
|
"plugin": "vouchington-workflow",
|
|
182
|
-
"pluginVersion": "0.
|
|
182
|
+
"pluginVersion": "0.13.0",
|
|
183
183
|
"path": "static-analysis-checklist/SKILL.md",
|
|
184
184
|
"prerequisites": ["github-actions-checklist"]
|
|
185
185
|
},
|
|
@@ -203,6 +203,19 @@
|
|
|
203
203
|
"pluginVersion": "0.2.0",
|
|
204
204
|
"path": "test-authoring/SKILL.md"
|
|
205
205
|
},
|
|
206
|
+
{
|
|
207
|
+
"name": "triage-prs",
|
|
208
|
+
"plugin": "vouchington-workflow",
|
|
209
|
+
"pluginVersion": "0.13.0",
|
|
210
|
+
"path": "triage-prs/SKILL.md",
|
|
211
|
+
"prerequisites": [
|
|
212
|
+
"pr-description",
|
|
213
|
+
"retrospective",
|
|
214
|
+
"review-ci-logs",
|
|
215
|
+
"github-issue",
|
|
216
|
+
"stacked-prs"
|
|
217
|
+
]
|
|
218
|
+
},
|
|
206
219
|
{
|
|
207
220
|
"name": "vitest-test-authoring",
|
|
208
221
|
"plugin": "vouchington-testing",
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: triage-prs
|
|
3
|
+
description: Review the PR queue and merge or close PRs under the user's triage authorization.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Triage pull requests
|
|
7
|
+
|
|
8
|
+
Read applicable `AGENTS.md` and their declared adapter alias, otherwise
|
|
9
|
+
`.agents/skills/triage-prs/SKILL.md` from the repository root if present.
|
|
10
|
+
Local policy overrides these defaults; do not reload an already-read adapter or canonical skill.
|
|
11
|
+
|
|
12
|
+
Review open pull requests, merge the good ones, and close the rest. The argument is an optional
|
|
13
|
+
scope: PR numbers, an author, a label, `all`, a title substring, or blank for the default scope.
|
|
14
|
+
|
|
15
|
+
## Adapter parameters
|
|
16
|
+
|
|
17
|
+
The repository adapter (a consumer wrapper skill or `AGENTS.md`) owns every repository-specific
|
|
18
|
+
value. Resolve these before Step 1 and stop
|
|
19
|
+
to ask when one this run needs is missing; never guess a value.
|
|
20
|
+
|
|
21
|
+
- **Automation label:** the label that marks machine-created PRs. It defines the default scope.
|
|
22
|
+
- **Orphan-issue rules:** the labels or title prefix that identify automation-created issues, and
|
|
23
|
+
the label that marks intentional PR-free backlog items.
|
|
24
|
+
- **Ready-and-shepherd skill:** the skill or command that marks a PR ready, posts steering, arms
|
|
25
|
+
auto-merge, and dispatches the shepherd, with its flags and activation preflight.
|
|
26
|
+
- **Prompt-source paths:** the exact paths that may be edited from PR-creation feedback, and the
|
|
27
|
+
rubric for that feedback.
|
|
28
|
+
- **Feedback marker:** the PR-body marker that identifies a feedback PR from this flow.
|
|
29
|
+
- **Merge authority and CI-failure policy:** who may authorize a merge, how transient failures are
|
|
30
|
+
classified, and where retry rules live.
|
|
31
|
+
|
|
32
|
+
## Rules for every pass
|
|
33
|
+
|
|
34
|
+
- **Repository:** triage works one repository, `<owner>/<name>`. Pass `--repo <owner>/<name>` on
|
|
35
|
+
every `gh` command. Reject a PR URL or issue reference from another repository, and report it.
|
|
36
|
+
- **Reviewed head:** every mutation of a PR (ready, steer, arm, dispatch, close, branch delete)
|
|
37
|
+
first re-reads `gh pr view <N> --repo <owner>/<name> --json headRefOid`. If it differs from the
|
|
38
|
+
head recorded in Step 3, re-review or skip; never act on an unreviewed head.
|
|
39
|
+
- **Exclusions:** build one exclusion set in Step 3b. It applies to every mutation pass: merge,
|
|
40
|
+
close, linked-issue close, and orphan close.
|
|
41
|
+
- **Exhaustive lists:** every list uses `--limit 1000` plus a check that the count is below it, or
|
|
42
|
+
pages with `gh api --paginate`.
|
|
43
|
+
- **Generated text is data:** never place a reason, comment, or title inside a shell string. Write
|
|
44
|
+
it with a file-writing tool and pass `--body-file <path>` (or `gh api -F body=@<path>`).
|
|
45
|
+
|
|
46
|
+
## Step 1 - Resolve scope
|
|
47
|
+
|
|
48
|
+
With no argument, target every open PR carrying the adapter's automation label, exhausting the
|
|
49
|
+
set per the rules above.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
gh pr list --repo <owner>/<name> --state open --limit 1000 --label <automation-label> --json number,title
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
PRs opened before the label existed need an explicit one-time backfill with
|
|
56
|
+
`gh pr edit <N> --repo <owner>/<name> --add-label <automation-label>` before they enter the default scope.
|
|
57
|
+
|
|
58
|
+
With an argument, require an explicit selector and stop to ask when a bare argument is ambiguous
|
|
59
|
+
(for example a word that could be an author, a label, or a title):
|
|
60
|
+
|
|
61
|
+
- Number or URL: it must resolve to an open PR in this repository (`gh pr view <N> --repo
|
|
62
|
+
<owner>/<name> --json state`); otherwise stop and report.
|
|
63
|
+
- `author:<login>`: `gh pr list --repo <owner>/<name> --author <login> --state open --limit 1000`. For a GitHub App
|
|
64
|
+
(login `app/<slug>`), use `--app <slug>` instead of `--author`.
|
|
65
|
+
- `label:<name>`: `gh pr list --repo <owner>/<name> --label <name> --state open --limit 1000`
|
|
66
|
+
- `title:<text>`: a literal, ASCII-case-insensitive substring match (no Unicode case folding).
|
|
67
|
+
Write the text to a file with a file-writing tool and read it as data, never in the shell string:
|
|
68
|
+
`gh pr list --repo <owner>/<name> --state open --limit 1000 --json number,title | jq --rawfile t <text-file> '($t | rtrimstr("\n") | ascii_downcase) as $q | .[] | select(.title | ascii_downcase | contains($q))'`
|
|
69
|
+
- `all`: every open PR regardless of author.
|
|
70
|
+
|
|
71
|
+
**Safety: a broad or non-default scope needs confirmation.** The default automation-label query is
|
|
72
|
+
a reliable machine-set signal, so proceed on it directly. For any other scope (`all`, an author, a
|
|
73
|
+
title match, or another label) the set can include human-authored PRs, so always confirm the close
|
|
74
|
+
list with the user before executing closes or branch deletions.
|
|
75
|
+
|
|
76
|
+
## Step 2 - Gather status
|
|
77
|
+
|
|
78
|
+
Fetch CI rollup and diff sizes for the in-scope PR numbers only. Do not re-fetch all open PRs:
|
|
79
|
+
a broader list pulls out-of-scope PRs into the review and close queues.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
gh pr view <N> --repo <owner>/<name> --json number,title,isDraft,additions,deletions,changedFiles,statusCheckRollup
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Run one call per PR, or filter one `gh pr list --json ...` result with
|
|
86
|
+
`jq '.[] | select(.number | IN(<N1>,<N2>,...))'` when the set is large.
|
|
87
|
+
|
|
88
|
+
## Step 3 - Review and steering assessment
|
|
89
|
+
|
|
90
|
+
Batch the in-scope PRs across up to three parallel read-only agents. Each reads the PR
|
|
91
|
+
description and full diff, loads [review-ci-logs](../review-ci-logs/SKILL.md) for failing required
|
|
92
|
+
checks, and returns per PR:
|
|
93
|
+
|
|
94
|
+
- **Head:** the reviewed `headRefOid`, bound to the diff: read `gh pr view <N> --repo <owner>/<name>
|
|
95
|
+
--json headRefOid` before and after fetching the diff, and accept the review only when both
|
|
96
|
+
values match; otherwise repeat it
|
|
97
|
+
- **Decision:** `MERGE` or `CLOSE`
|
|
98
|
+
- **Reason:** a one-line summary
|
|
99
|
+
- **Steering:** (MERGE only) whether the shepherd needs direction, and the exact PR comment body
|
|
100
|
+
- **PR creation feedback:** for verified agent-authored PRs, apply the adapter's rubric and return
|
|
101
|
+
evidence, provenance, the exact source target, a recommendation, and a recurrence tag;
|
|
102
|
+
otherwise `none`
|
|
103
|
+
|
|
104
|
+
Each agent reads with `gh pr view <N>`, `gh pr diff <N>` (both with `--repo`), and the repository's CI check listing.
|
|
105
|
+
|
|
106
|
+
See [the default rubric](references/rubric.md) for MERGE versus CLOSE, generalizing a narrow
|
|
107
|
+
band-aid before closing it, and the shape of steering comments. The user's session steering
|
|
108
|
+
overrides the rubric, including flipping a green PR to CLOSE; present it as context.
|
|
109
|
+
|
|
110
|
+
## Step 3b - Build the exclusion set
|
|
111
|
+
|
|
112
|
+
Before any mutation, resolve [feedback duplicates](references/feedback.md#duplicate-resolution).
|
|
113
|
+
Every feedback PR this run generates or reuses, and its source issue, joins the exclusion set. A
|
|
114
|
+
member is never readied, shepherded, armed, merged, closed, or branch-deleted, and its issue is
|
|
115
|
+
never closed, in this run, whatever its Step 3 decision.
|
|
116
|
+
|
|
117
|
+
## Step 4 - Execute MERGE
|
|
118
|
+
|
|
119
|
+
Skip excluded PRs and re-check the reviewed head per the rules. Carry each root-cause follow-up
|
|
120
|
+
from the [rubric](references/rubric.md) (an issue to file, link, or re-scope) into this pass and
|
|
121
|
+
the Step 6 report so it is not dropped. Process MERGE decisions in
|
|
122
|
+
ascending PR-number order through the adapter's ready-and-shepherd
|
|
123
|
+
skill, passing the PR number, the auto-merge flag, and any steering file. That skill light-reviews
|
|
124
|
+
the diff, posts steering before the trigger, readies the draft, verifies merge settings, and
|
|
125
|
+
dispatches the shepherd. Run its activation preflight before every mutation. If activation is
|
|
126
|
+
disabled, unset, or unreadable, record `activation disabled - unchanged` and do not describe the PR
|
|
127
|
+
as ready, armed, or dispatched.
|
|
128
|
+
|
|
129
|
+
Classify the outcome:
|
|
130
|
+
|
|
131
|
+
- **Unsteered and arming succeeds:** already armed. Auto-merge may complete once requirements pass
|
|
132
|
+
while the asynchronous shepherd independently checks the PR.
|
|
133
|
+
- **Steered, or arming fails on a conflict:** leave the PR ready and unarmed after dispatch.
|
|
134
|
+
Dispatch is fire-and-forget; a `running` checkpoint proves session acceptance, not success. A
|
|
135
|
+
human inspects the eventual state and decides whether to arm auto-merge. Do not wait for or infer
|
|
136
|
+
a `complete` checkpoint.
|
|
137
|
+
- **Repository auto-merge disabled:** dispatch the shepherd, report that a manual merge is
|
|
138
|
+
required, and leave auto-merge unchanged.
|
|
139
|
+
- **Native GitHub stack:** shepherd the owned layers per
|
|
140
|
+
[stacked-prs](../stacked-prs/SKILL.md#ownership-and-workers) and merge the ready prefix per
|
|
141
|
+
[Merge the ready prefix](../stacked-prs/SKILL.md#merge-the-ready-prefix), within explicit human
|
|
142
|
+
approval and only as far as this run's decisions cover. Resolve the full stack against the Step 1
|
|
143
|
+
scope and Step 3 decisions first. End the ready prefix below the first layer that is out of scope
|
|
144
|
+
or not `MERGE`; never shepherd or merge a layer this run did not review. GitHub has no
|
|
145
|
+
auto-merge for stacks, and an armed auto-merge does not authorize the drain on its own.
|
|
146
|
+
|
|
147
|
+
Do not add a second pass that polls the dispatch workflow. It ends once the session is accepted or
|
|
148
|
+
rejected and cannot attest that the remote agent consumed steering or left the branch mergeable.
|
|
149
|
+
|
|
150
|
+
## Step 5 - Execute CLOSE
|
|
151
|
+
|
|
152
|
+
Skip excluded PRs and re-check the reviewed head per the rules. Write the one-line reason to a file
|
|
153
|
+
with a file-writing tool, then comment from the file and close:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
gh pr comment <N> --repo <owner>/<name> --body-file <reason-file>
|
|
157
|
+
gh pr close <N> --repo <owner>/<name>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Report every close that kept its branch, with the reason. Branch deletion, linked-issue closes, and orphan automation issues (default scope only) follow the
|
|
161
|
+
[close procedure](references/close.md).
|
|
162
|
+
|
|
163
|
+
## Step 6 - Verify and report
|
|
164
|
+
|
|
165
|
+
Check that merge and close actions landed:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
gh pr view <N> --repo <owner>/<name> --json isDraft,autoMergeRequest,state
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Emit a summary table with the columns PR, Decision, Action, and Steering. Actions are
|
|
172
|
+
`ready + shepherd + auto-merge armed`, `ready + shepherd, manual verification required`,
|
|
173
|
+
`ready + shepherd, manual merge required`, `activation disabled - unchanged`, and
|
|
174
|
+
`closed + branch deleted`.
|
|
175
|
+
|
|
176
|
+
After Steps 4 and 5, amend the Step 3 creation feedback with action-time steering and CI evidence.
|
|
177
|
+
Do not wait for the newly dispatched shepherd; record only evidence available during this run.
|
|
178
|
+
Then publish [preauthorized prompt feedback](references/feedback.md), group the recommendations by
|
|
179
|
+
root cause, and invoke [retrospective](../retrospective/SKILL.md) before the final report.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Close procedure
|
|
2
|
+
|
|
3
|
+
Applies the rules in `SKILL.md`: explicit `--repo`, reviewed head re-check, the exclusion set,
|
|
4
|
+
exhaustive lists, and generated text passed only from files.
|
|
5
|
+
|
|
6
|
+
## Delete the branch
|
|
7
|
+
|
|
8
|
+
`gh pr close` does not delete the branch unless asked, and merge-time auto-deletion does not apply
|
|
9
|
+
to closes. Read the head branch name and head repository as data (`gh pr view <N> --repo <owner>/<name>
|
|
10
|
+
--json headRefName,headRepository,headRepositoryOwner`) and pass them to `jq --arg`, never into a
|
|
11
|
+
shell string. Before deleting, find every open PR that depends on the branch:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
gh pr list --repo <owner>/<name> --state open --limit 1000 \
|
|
15
|
+
--json number,title,baseRefName,headRefName,headRepository,headRepositoryOwner \
|
|
16
|
+
| jq --arg b "$BRANCH" --arg r "$HEAD_REPO" '.[] | select(.baseRefName == $b or (.headRefName == $b and .headRepository.name == $r))'
|
|
17
|
+
gh search prs --state open --limit 1000 --owner <owner> --head "$BRANCH" --json url,repository
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The first query includes PRs from forks that target this branch (they appear under `--repo`), and
|
|
21
|
+
PRs sharing the head. The second finds PRs in other repositories that use this branch as their
|
|
22
|
+
head. Ignore the PR being closed. If any other PR remains, or cross-repository reuse cannot be ruled
|
|
23
|
+
out, do not delete the branch: deleting a base closes its dependents, including unreviewed layers,
|
|
24
|
+
and deleting a shared head closes the others. Close without deleting and report the PR and why the
|
|
25
|
+
branch was kept. Otherwise close with `gh pr close <N> --repo <owner>/<name> --delete-branch` in
|
|
26
|
+
place of the plain close in Step 5.
|
|
27
|
+
|
|
28
|
+
## Linked issues
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
gh pr view <N> --repo <owner>/<name> --json closingIssuesReferences \
|
|
32
|
+
--jq '.closingIssuesReferences[] | {number, title, url, repository}'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Keep each reference's repository and URL. Close only issues in this repository; skip and report
|
|
36
|
+
cross-repository targets. Close an issue only if it appears automation-created under the adapter's
|
|
37
|
+
orphan-issue rules. Skip anything that looks human-authored: automation sometimes reuses a
|
|
38
|
+
human-tracked issue by adding `Closes #<N>` to a PR body. Skip excluded issues. Before closing,
|
|
39
|
+
re-read the issue (`gh issue view <ISSUE_N> --repo <owner>/<name> --json state,labels,title`) and
|
|
40
|
+
confirm it is still open and still classified as automation-created; then
|
|
41
|
+
apply the [no-open-PR checks](#no-open-pr-checks), excluding the PR just closed.
|
|
42
|
+
|
|
43
|
+
Write the comment (`Closing alongside PR #<N>: <reason>`) to a file, then:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
gh issue comment <ISSUE_N> --repo <owner>/<name> --body-file <comment-file>
|
|
47
|
+
gh issue close <ISSUE_N> --repo <owner>/<name>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Orphan automation issues
|
|
51
|
+
|
|
52
|
+
Default scope only. Skip this for narrow-scope runs: scanning the full issue queue would close
|
|
53
|
+
issues unrelated to the PRs actually triaged.
|
|
54
|
+
|
|
55
|
+
Find open automation issues with no open linked PR: issues whose PR was never opened, was merged or
|
|
56
|
+
closed separately, or was just closed. Identify them with the adapter's orphan-issue rules, exclude
|
|
57
|
+
the adapter's PR-free backlog label (intentional human-escalation items), and exclude the exclusion
|
|
58
|
+
set. Exhaust the list:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gh issue list --repo <owner>/<name> --state open --limit 1000 --json number,title,labels
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Verify the count is below the limit, otherwise page with `gh api --paginate`.
|
|
65
|
+
|
|
66
|
+
### No-open-PR checks
|
|
67
|
+
|
|
68
|
+
`closedByPullRequestsReferences` sees only PRs using `Closes` or `Fixes`; it is blind to `Refs #N`
|
|
69
|
+
and `Part of #N`. Keep each PR's URL and repository, and query by them rather than by bare number.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
gh issue view <ISSUE_N> --repo <owner>/<name> --json closedByPullRequestsReferences \
|
|
73
|
+
--jq '.closedByPullRequestsReferences[] | {url, repository}'
|
|
74
|
+
gh pr view <PR_URL> --json state --jq .state # the reference's own state field is null
|
|
75
|
+
gh search prs --repo <owner>/<name> --state open --limit 1000 "#<ISSUE_N>" --json url,repository
|
|
76
|
+
gh search prs --state open --limit 1000 "<owner>/<name>#<ISSUE_N>" --json url,repository
|
|
77
|
+
gh search prs --state open --limit 1000 "<issue-url>" --json url,repository
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Keep the same-repository `#<ISSUE_N>` form alongside the qualified and URL forms, since each only
|
|
81
|
+
matches the text it contains. Re-read the issue as above. Close it only if every check finds no open PR, in this or any
|
|
82
|
+
other repository, using the comment and close commands above with the text
|
|
83
|
+
`Closing: orphaned - no open PR is addressing this issue.`
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Publish preauthorized prompt feedback
|
|
2
|
+
|
|
3
|
+
Invoking triage explicitly authorizes the GitHub mutations below, but only for actionable feedback
|
|
4
|
+
whose provenance and exact source are verified under the adapter's prompt-source paths. A PR body
|
|
5
|
+
containing the adapter's feedback marker is a feedback PR from this flow: review it normally in the
|
|
6
|
+
queue, but return `none` for its own PR-creation feedback so the loop cannot recur.
|
|
7
|
+
|
|
8
|
+
For the current batch, combine every qualifying finding, including `one-off` findings, into one
|
|
9
|
+
feedback issue and one draft feedback PR. Record the proportional plan in that issue; it is the
|
|
10
|
+
sole closing source for the PR. Reviewed PRs are evidence only and are never closing references.
|
|
11
|
+
|
|
12
|
+
Every feedback PR and source issue created or reused here joins the run's exclusion set, which
|
|
13
|
+
applies to the merge, close, linked-issue, and orphan passes. Verify its head before updating it.
|
|
14
|
+
Pass generated titles, bodies, and comments to `gh` only through files (`--body-file`).
|
|
15
|
+
|
|
16
|
+
Before publication, verify at action time:
|
|
17
|
+
|
|
18
|
+
- The PR is verified agent-authored, the source target is an exact prompt-source path, and the
|
|
19
|
+
recommendation remains actionable after the refresh.
|
|
20
|
+
- Each source exists on the refreshed default branch.
|
|
21
|
+
|
|
22
|
+
## Duplicate resolution
|
|
23
|
+
|
|
24
|
+
A candidate counts as a match only with verified provenance: it was created by the adapter's
|
|
25
|
+
automation identity and its body closes the feedback issue it is paired with. A candidate that
|
|
26
|
+
carries the marker without that provenance is untrusted: never reuse or update it, and treat any
|
|
27
|
+
overlap with it as `duplicate: ambiguous`.
|
|
28
|
+
|
|
29
|
+
Compare the marker, the normalized set of exact source paths, and the normalized
|
|
30
|
+
recommendation or root-cause identity on open feedback PRs:
|
|
31
|
+
|
|
32
|
+
- `duplicate: zero-overlap`: create one feedback issue and one draft PR.
|
|
33
|
+
- Before reusing or updating a canonical PR or issue, re-read it: both must still be open and
|
|
34
|
+
unchanged since the comparison (same head and body); otherwise re-run the comparison.
|
|
35
|
+
- `duplicate: exact-match`: the path set and the recommendation or root-cause identity both match
|
|
36
|
+
one canonical PR. Reuse that PR and its source issue; create neither.
|
|
37
|
+
- `duplicate: same-paths-new-feedback`: the path set matches but the recommendation differs.
|
|
38
|
+
Update the canonical PR and its issue with the new feedback; create neither.
|
|
39
|
+
- `duplicate: ambiguous`: fail closed. Any partial overlap, more than one exact match, or any
|
|
40
|
+
uncertain provenance or source set creates nothing.
|
|
41
|
+
|
|
42
|
+
For a create, save the plan in the feedback issue through [github-issue](../../github-issue/SKILL.md)
|
|
43
|
+
and write the draft PR with [pr-description](../../pr-description/SKILL.md). The PR body includes
|
|
44
|
+
the marker, carries the automation label, and links the feedback issue with a closing keyword. Do
|
|
45
|
+
not ready, shepherd, auto-merge, or merge a generated or reused feedback PR in the same run.
|
|
46
|
+
|
|
47
|
+
## Retrospective disposition
|
|
48
|
+
|
|
49
|
+
- `disposition: published`: record `Disposition: preauthorized-pr #N` after a successful create,
|
|
50
|
+
exact-match reuse, or `same-paths-new-feedback` update of the canonical PR and issue.
|
|
51
|
+
- `disposition: deferred`: record `deferred` with the concrete blocker after a failed, partial, or
|
|
52
|
+
ambiguous publication. Do not ask the user or claim the feedback was handled.
|
|
53
|
+
- `disposition: user-choice`: keep non-prompt feedback in the normal human follow-up flow.
|
|
54
|
+
|
|
55
|
+
Group the Step 3 PR-creation recommendations by root cause and invoke
|
|
56
|
+
[retrospective](../../retrospective/SKILL.md). It records findings for merged and closed PRs alike.
|
|
57
|
+
Preserve `none` when no actionable generator improvement was found, and complete this flow before
|
|
58
|
+
the final triage report.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Default triage rubric
|
|
2
|
+
|
|
3
|
+
This rubric is a default, not a law. Session steering from the user overrides it.
|
|
4
|
+
|
|
5
|
+
- **MERGE:** a real bugfix, a general root-cause fix, or a safe chore. A CI failure caused by the
|
|
6
|
+
PR's own change is fine; the shepherd drives it green. A merge conflict (`CONFLICTING` or
|
|
7
|
+
`DIRTY`) does not by itself warrant a close. If the fix is good on the merits, salvage it through
|
|
8
|
+
the MERGE path: ready it, arm auto-merge, and let the shepherd rebase, resolve, and drive CI
|
|
9
|
+
green. Salvage by default without asking the user.
|
|
10
|
+
- **CLOSE:** a narrow per-fingerprint band-aid (prefer general solutions), stale, superseded, or
|
|
11
|
+
scope already landed on the default branch. Close a conflicting branch only when it is also one
|
|
12
|
+
of those; never close a good fix solely because it conflicts.
|
|
13
|
+
|
|
14
|
+
## Before closing a narrow band-aid, ask whether it generalizes
|
|
15
|
+
|
|
16
|
+
A per-fingerprint band-aid often points at a root cause worth a general fix. Diagnose the failure
|
|
17
|
+
it papers over first. Is it:
|
|
18
|
+
|
|
19
|
+
- **repository-owned:** the repository's own code, config, or infrastructure, such as a step
|
|
20
|
+
timeout, a permissions policy, a build toolchain, or a lock or port collision in its scripts;
|
|
21
|
+
- **deterministic:** it hits a fixed budget or limit. Litmus test: rerun the identical job
|
|
22
|
+
unchanged; if it hits the same wall every time, raise the limit, remove the bottleneck, or
|
|
23
|
+
restructure the work; or
|
|
24
|
+
- **transient:** the same input sometimes passes, such as an intermittent race, a propagation
|
|
25
|
+
delay, or a genuinely external 5xx or rate limit?
|
|
26
|
+
|
|
27
|
+
Only the transient case justifies broadening a retry rule. Repository-owned and deterministic
|
|
28
|
+
failures make a retry rule invalid; the fix is the root cause.
|
|
29
|
+
|
|
30
|
+
- **Anti-fork:** never hardcode a concrete resource address (an ARN, a bus name, a batch id) into a
|
|
31
|
+
fingerprint. Broaden the existing consumer-by-root-cause rule instead of forking one per action,
|
|
32
|
+
status, or URL, following the adapter's retry-rule policy. A rule that hardcodes one resource
|
|
33
|
+
re-fails the moment a different resource hits the same race.
|
|
34
|
+
- **Hard cases:** when a failure cannot be classified (no rerun-success evidence either way),
|
|
35
|
+
never add or bump a retry rule as a substitute for classification. Fix the best-supported cause
|
|
36
|
+
and capture the evidence the next occurrence needs, per the adapter's CI-failure policy.
|
|
37
|
+
- **Treadmill signature:** a rule whose attempt limit is bumped again on a later PR for the same
|
|
38
|
+
consumer and root-cause pair, without the cause changing. That pair is repository-owned and was
|
|
39
|
+
mis-elevated the first time; bumping it again repeats the mistake.
|
|
40
|
+
- **Elevate, do not hide:** a repository-owned cause must file or link a root-cause issue as part
|
|
41
|
+
of the disposition, reusing an open issue for the same pair rather than duplicating it. A PR
|
|
42
|
+
whose follow-ups say `None` while the cause is repository-owned is a signal to push back. For
|
|
43
|
+
deterministic and repository-owned failures, close the band-aid and file a follow-up issue for
|
|
44
|
+
the general fix under [github-issue](../../github-issue/SKILL.md), or re-scope an existing one.
|
|
45
|
+
Generalize-or-elevate beats a plain close.
|
|
46
|
+
|
|
47
|
+
## What steering looks like
|
|
48
|
+
|
|
49
|
+
When a MERGE PR needs shepherd direction, write the exact reviewed comment body to a temporary file
|
|
50
|
+
with a file-writing tool, then read it back in full. Do not build the file with shell
|
|
51
|
+
interpolation, a heredoc, `echo`, or `printf`: file-based input keeps shell metacharacters and
|
|
52
|
+
backticks in reviewed steering from being expanded. Pass the file to the ready-and-shepherd skill
|
|
53
|
+
so it posts a separate ordinary PR comment before the shepherd trigger.
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
|
|
57
|
+
- `Prefer the general timeout-bound solution over a transient-retry rule.`
|
|
58
|
+
- `The lint error is self-inflicted. Fix it first, then continue.`
|
|
59
|
+
- `Do not add a new catalogue rule. Fix the underlying race condition instead.`
|
|
60
|
+
- `This deadlock is transient. Broaden the matcher rather than closing the rule.`
|
|
61
|
+
- `This timeout is deterministic, not transient. Close the narrow rule and open a root-cause issue.`
|