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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vouchington-tooling",
3
- "version": "0.50.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
- | ($old_main + $unmerged + $orphans)[]
53
- | "\(.id) \(tags | if length == 0 then "<untagged>" else join(",") end) \(.reason)"'
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
- deletions=$(jq -r -s --rawfile revisions "$work/main-revisions" --argjson keep "$keep_main" \
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
+ }
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "name": "agent-workflow",
6
6
  "plugin": "vouchington-workflow",
7
- "pluginVersion": "0.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.12.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.`