@windyroad/itil 1.0.0 → 1.1.0-preview.1067
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/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/catchup-scan.sh +16 -3
- package/scripts/check-upstream-responses.sh +90 -19
- package/skills/check-upstream-responses/SKILL.md +8 -8
- package/skills/manage-problem/SKILL.md +1 -1
- package/skills/report-upstream/SKILL.md +206 -34
- package/skills/update-upstream/SKILL.md +13 -5
package/package.json
CHANGED
package/scripts/catchup-scan.sh
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
# Structured stdout (one per actionable upstream entry; <= 150 bytes per
|
|
46
46
|
# line per ADR-038). ASCII `->` for the transition arrow per the P334
|
|
47
47
|
# awk/script portability lesson (no Unicode in machine-read output):
|
|
48
|
-
# CATCHUP P<NNN> <url> state=<state> transition=<KE->Verifying|Verifying->Closed>
|
|
48
|
+
# CATCHUP P<NNN> <url> state=<state> transition=<KE->Verifying|Verifying->Closed> disclosure=<issue|pull-request>
|
|
49
49
|
# CATCHUP P<NNN> inbound-<ref> state=<state> transition=<…> direction=inbound
|
|
50
50
|
# SKIP P<NNN> <url> reason=already-logged
|
|
51
51
|
# SKIP P<NNN> inbound-<ref> reason=already-logged
|
|
@@ -64,6 +64,10 @@
|
|
|
64
64
|
# @adr ADR-049 (invoked via wr-itil-catchup-scan bin shim, never repo-relative path)
|
|
65
65
|
# @adr ADR-032 (foreground synchronous skill)
|
|
66
66
|
# @adr ADR-076 (reads the `**Origin**: inbound-reported (#NN)` field for the inbound leg)
|
|
67
|
+
# @adr ADR-117 (prefer an upstream pull request over an issue — the CATCHUP
|
|
68
|
+
# row carries a `disclosure=` discriminator so the consuming skill knows
|
|
69
|
+
# whether to comment on a pull request or an issue. A `pull request` path
|
|
70
|
+
# is actionable, so it is NOT skipped alongside out-of-band / mailbox.)
|
|
67
71
|
# @problem P376 — catchup scanner misses the inbound direction (cross-direction parity)
|
|
68
72
|
# @rfc RFC-028 (consume the Origin field for inbound-reported verdict — extended to the catchup surface)
|
|
69
73
|
# @jtbd JTBD-301 (reporter feedback loop — the catchup's primary job)
|
|
@@ -264,8 +268,17 @@ for ticket_file in "${TICKET_FILES[@]}"; do
|
|
|
264
268
|
printf "SKIP %s %s reason=already-logged\n" "$ticket_id" "$upstream_url"
|
|
265
269
|
SKIP_LOGGED=$((SKIP_LOGGED + 1))
|
|
266
270
|
else
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
# Carry the artefact kind to the consumer. Without it the value is
|
|
272
|
+
# recorded on the ticket and never acted on, and `/wr-itil:update-upstream`
|
|
273
|
+
# runs `gh issue comment` against a pull request. Hyphenated on the wire
|
|
274
|
+
# so the row stays whitespace-tokenisable; the ticket body keeps the
|
|
275
|
+
# spaced `pull request` spelling.
|
|
276
|
+
case "$disclosure" in
|
|
277
|
+
*pull*request*) disclosure_field="pull-request" ;;
|
|
278
|
+
*) disclosure_field="issue" ;;
|
|
279
|
+
esac
|
|
280
|
+
printf "CATCHUP %s %s state=%s transition=%s disclosure=%s\n" \
|
|
281
|
+
"$ticket_id" "$upstream_url" "$state" "$transition" "$disclosure_field"
|
|
269
282
|
CATCHUP_COUNT=$((CATCHUP_COUNT + 1))
|
|
270
283
|
fi
|
|
271
284
|
fi
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
# discovery pipeline. Scans local problem tickets for `## Reported
|
|
6
6
|
# Upstream` back-link sections (written by `/wr-itil:report-upstream`
|
|
7
7
|
# Step 7), polls each upstream issue via `gh issue view`, diffs against
|
|
8
|
-
# cache, and surfaces new
|
|
8
|
+
# cache, and surfaces new responses / state changes / label changes since
|
|
9
9
|
# last check.
|
|
10
10
|
#
|
|
11
|
-
# Read-only externally:
|
|
11
|
+
# Read-only externally: `gh issue view`, or `gh pr view` plus the pull
|
|
12
|
+
# request's inline review comments via `gh api` — no
|
|
12
13
|
# `gh issue comment` / `gh issue create`. Does NOT trip ADR-028
|
|
13
14
|
# external-comms gate. AFK-safe.
|
|
14
15
|
#
|
|
@@ -28,13 +29,13 @@
|
|
|
28
29
|
# written to cache + audit-log
|
|
29
30
|
#
|
|
30
31
|
# Structured stdout (one per ticket; ≤ 150 bytes per line per ADR-038):
|
|
31
|
-
# NEW P<NNN> <url> state=<state> new-
|
|
32
|
+
# NEW P<NNN> <url> state=<state> new-responses=<N>
|
|
32
33
|
# STATE P<NNN> <url> state=<old>→<new>
|
|
33
34
|
# LABEL P<NNN> <url> labels-added=<csv> labels-removed=<csv>
|
|
34
35
|
# NONE P<NNN> <url> no-change-since=<last-checked>
|
|
35
36
|
# FAIL P<NNN> <url> reason=<gh-error-short>
|
|
36
37
|
#
|
|
37
|
-
# Precedence when multiple change classes apply: STATE > NEW (
|
|
38
|
+
# Precedence when multiple change classes apply: STATE > NEW (responses) > LABEL > NONE.
|
|
38
39
|
#
|
|
39
40
|
# @problem P249 — no process for issue reporters to check for responses (Phase 1)
|
|
40
41
|
# @adr ADR-014 (governance skills commit their own work)
|
|
@@ -44,6 +45,14 @@
|
|
|
44
45
|
# @adr ADR-038 (progressive disclosure — per-row byte budget)
|
|
45
46
|
# @adr ADR-049 (invoked via wr-itil-check-upstream-responses bin shim)
|
|
46
47
|
# @adr ADR-062 (inbound discovery — symmetric counterpart)
|
|
48
|
+
# @adr ADR-117 (prefer an upstream pull request over an issue — the
|
|
49
|
+
# `## Reported Upstream` disclosure path selects `gh pr view` over
|
|
50
|
+
# `gh issue view`. An ABSENT path means a ticket written before that
|
|
51
|
+
# decision, which the pre-ADR-117 skill could only ever have filed as
|
|
52
|
+
# a public issue — so absent means issue, and no probe is needed. This
|
|
53
|
+
# is the explicit legacy-path decision ADR-117 requires; an
|
|
54
|
+
# issue-then-pr probe would double the call count for every legacy
|
|
55
|
+
# ticket on every cycle against a `gh` rate budget nothing governs.)
|
|
47
56
|
# @jtbd JTBD-004 (cross-repo coordination — primary anchor)
|
|
48
57
|
# @jtbd JTBD-006 (AFK-safe)
|
|
49
58
|
# @jtbd JTBD-001 (governance without slowing down)
|
|
@@ -146,6 +155,30 @@ extract_upstream_url() {
|
|
|
146
155
|
' "$1"
|
|
147
156
|
}
|
|
148
157
|
|
|
158
|
+
# Extract the `## Reported Upstream` disclosure-path line (lower-cased).
|
|
159
|
+
# Same awk shape as catchup-scan.sh's helper of the same name.
|
|
160
|
+
extract_disclosure_path() {
|
|
161
|
+
awk '
|
|
162
|
+
/^## Reported Upstream/ { in_section = 1; next }
|
|
163
|
+
/^## / && in_section { in_section = 0 }
|
|
164
|
+
in_section && /^- \*\*Disclosure path\*\*:/ {
|
|
165
|
+
sub(/^- \*\*Disclosure path\*\*: */, "")
|
|
166
|
+
print
|
|
167
|
+
exit
|
|
168
|
+
}
|
|
169
|
+
' "$1" | tr "[:upper:]" "[:lower:]"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Which `gh` read subcommand polls this ticket's upstream artefact?
|
|
173
|
+
# `pull request` (or `pull-request`) → `gh pr view`; anything else,
|
|
174
|
+
# INCLUDING an absent disclosure path, → `gh issue view` (ADR-117).
|
|
175
|
+
upstream_view_noun() {
|
|
176
|
+
case "$1" in
|
|
177
|
+
*pull*request*) echo "pr" ;;
|
|
178
|
+
*) echo "issue" ;;
|
|
179
|
+
esac
|
|
180
|
+
}
|
|
181
|
+
|
|
149
182
|
# Extract numeric ID prefix from a ticket file basename.
|
|
150
183
|
extract_ticket_id() {
|
|
151
184
|
local base
|
|
@@ -189,8 +222,16 @@ for ticket_file in "${TICKET_FILES[@]}"; do
|
|
|
189
222
|
|
|
190
223
|
POLL_COUNT=$((POLL_COUNT + 1))
|
|
191
224
|
|
|
192
|
-
# Poll the upstream.
|
|
193
|
-
|
|
225
|
+
# Poll the upstream. Pull requests need one additional read because
|
|
226
|
+
# `gh pr view` omits inline review-thread comments. `state` carries MERGED
|
|
227
|
+
# for a merged pull request, distinct from CLOSED.
|
|
228
|
+
view_noun="$(upstream_view_noun "$(extract_disclosure_path "$ticket_file")")"
|
|
229
|
+
if [ "$view_noun" = "pr" ]; then
|
|
230
|
+
view_fields="comments,reviews,state,labels,updatedAt"
|
|
231
|
+
else
|
|
232
|
+
view_fields="comments,state,labels,updatedAt"
|
|
233
|
+
fi
|
|
234
|
+
if ! gh_output="$("$GH_BIN" "$view_noun" view "$upstream_url" --json "$view_fields" 2>&1)"; then
|
|
194
235
|
short_reason="$(echo "$gh_output" | head -1 | cut -c1-80)"
|
|
195
236
|
printf "FAIL %s %s reason=%s\n" "$ticket_id" "$upstream_url" "$short_reason"
|
|
196
237
|
PARTIAL_FAILURE=1
|
|
@@ -198,39 +239,67 @@ for ticket_file in "${TICKET_FILES[@]}"; do
|
|
|
198
239
|
continue
|
|
199
240
|
fi
|
|
200
241
|
|
|
242
|
+
if [ "$view_noun" = "pr" ]; then
|
|
243
|
+
pr_path="${upstream_url#https://github.com/}"
|
|
244
|
+
pr_path="${pr_path%/}"
|
|
245
|
+
review_endpoint="repos/${pr_path/\/pull\//\/pulls\/}/comments"
|
|
246
|
+
if ! review_pages="$("$GH_BIN" api "$review_endpoint" --paginate --slurp 2>&1)"; then
|
|
247
|
+
short_reason="$(echo "$review_pages" | head -1 | cut -c1-80)"
|
|
248
|
+
printf "FAIL %s %s reason=%s\n" "$ticket_id" "$upstream_url" "$short_reason"
|
|
249
|
+
PARTIAL_FAILURE=1
|
|
250
|
+
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
251
|
+
continue
|
|
252
|
+
fi
|
|
253
|
+
if ! review_comments="$(echo "$review_pages" | jq -c 'add // []' 2>/dev/null)"; then
|
|
254
|
+
printf "FAIL %s %s reason=malformed-inline-review-response\n" "$ticket_id" "$upstream_url"
|
|
255
|
+
PARTIAL_FAILURE=1
|
|
256
|
+
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
257
|
+
continue
|
|
258
|
+
fi
|
|
259
|
+
gh_output="$(jq -cn --argjson base "$gh_output" --argjson inline "$review_comments" '$base + {reviewComments: $inline}')"
|
|
260
|
+
else
|
|
261
|
+
gh_output="$(jq -cn --argjson base "$gh_output" '$base + {reviewComments: []}')"
|
|
262
|
+
fi
|
|
263
|
+
|
|
201
264
|
# Parse upstream state.
|
|
202
265
|
current_state="$(echo "$gh_output" | jq -r '.state // "UNKNOWN"')"
|
|
203
|
-
|
|
266
|
+
current_response_count="$(echo "$gh_output" | jq -r '((.comments // []) | length) + ((.reviews // []) | length) + ((.reviewComments // []) | length)')"
|
|
204
267
|
current_labels_csv="$(echo "$gh_output" | jq -r '[.labels[].name] | sort | join(",")')"
|
|
205
268
|
current_updated_at="$(echo "$gh_output" | jq -r '.updatedAt // ""')"
|
|
269
|
+
current_response_updated_at="$(echo "$gh_output" | jq -r '[
|
|
270
|
+
(.comments[]? | .updatedAt // .createdAt),
|
|
271
|
+
(.reviews[]? | .updatedAt // .submittedAt // .createdAt),
|
|
272
|
+
(.reviewComments[]? | .updatedAt // .createdAt)
|
|
273
|
+
] | map(select(. != null and . != "")) | max // ""')"
|
|
206
274
|
|
|
207
275
|
# Look up cache entry.
|
|
208
276
|
cache_state="$(echo "$UPDATED_CACHE" | jq -r ".tickets[\"$ticket_id\"].last_seen_state // \"\"")"
|
|
209
|
-
|
|
277
|
+
cache_response_count="$(echo "$UPDATED_CACHE" | jq -r ".tickets[\"$ticket_id\"].last_seen_response_count // .tickets[\"$ticket_id\"].last_seen_comment_count // -1")"
|
|
210
278
|
cache_labels_csv="$(echo "$UPDATED_CACHE" | jq -r ".tickets[\"$ticket_id\"].last_seen_labels // [] | sort | join(\",\")")"
|
|
211
279
|
cache_last_checked="$(echo "$UPDATED_CACHE" | jq -r ".tickets[\"$ticket_id\"].last_checked_at // \"\"")"
|
|
280
|
+
cache_response_updated_at="$(echo "$UPDATED_CACHE" | jq -r ".tickets[\"$ticket_id\"].last_seen_response_updated_at // \"\"")"
|
|
212
281
|
|
|
213
|
-
# Decide the change class (precedence: STATE > NEW (
|
|
282
|
+
# Decide the change class (precedence: STATE > NEW (responses/activity) > LABEL > NONE).
|
|
214
283
|
no_cache_entry=0
|
|
215
|
-
if [ -z "$cache_state" ] || [ "$
|
|
284
|
+
if [ -z "$cache_state" ] || [ "$cache_response_count" = "-1" ]; then
|
|
216
285
|
no_cache_entry=1
|
|
217
286
|
fi
|
|
218
287
|
|
|
219
288
|
if [ "$FORCE_RECHECK" -eq 1 ] || [ "$no_cache_entry" -eq 1 ]; then
|
|
220
|
-
delta="$
|
|
289
|
+
delta="$current_response_count"
|
|
221
290
|
if [ "$no_cache_entry" -eq 0 ]; then
|
|
222
|
-
delta=$((
|
|
291
|
+
delta=$((current_response_count - cache_response_count))
|
|
223
292
|
[ "$delta" -lt 0 ] && delta=0
|
|
224
293
|
fi
|
|
225
|
-
printf "NEW %s %s state=%s new-
|
|
294
|
+
printf "NEW %s %s state=%s new-responses=%s\n" "$ticket_id" "$upstream_url" "$current_state" "$delta"
|
|
226
295
|
NEW_COUNT=$((NEW_COUNT + 1))
|
|
227
296
|
elif [ "$current_state" != "$cache_state" ]; then
|
|
228
297
|
printf "STATE %s %s state=%s→%s\n" "$ticket_id" "$upstream_url" "$cache_state" "$current_state"
|
|
229
298
|
STATE_CHANGE_COUNT=$((STATE_CHANGE_COUNT + 1))
|
|
230
|
-
elif [ "$
|
|
231
|
-
delta=$((
|
|
299
|
+
elif [ "$current_response_count" -ne "$cache_response_count" ] || { [ -n "$cache_response_updated_at" ] && [ "$current_response_updated_at" != "$cache_response_updated_at" ]; }; then
|
|
300
|
+
delta=$((current_response_count - cache_response_count))
|
|
232
301
|
[ "$delta" -lt 0 ] && delta=0
|
|
233
|
-
printf "NEW %s %s state=%s new-
|
|
302
|
+
printf "NEW %s %s state=%s new-responses=%s\n" "$ticket_id" "$upstream_url" "$current_state" "$delta"
|
|
234
303
|
NEW_COUNT=$((NEW_COUNT + 1))
|
|
235
304
|
elif [ "$current_labels_csv" != "$cache_labels_csv" ]; then
|
|
236
305
|
# Compute labels added/removed.
|
|
@@ -251,15 +320,17 @@ for ticket_file in "${TICKET_FILES[@]}"; do
|
|
|
251
320
|
--arg state "$current_state" \
|
|
252
321
|
--arg checked "$now_iso" \
|
|
253
322
|
--arg updated "$current_updated_at" \
|
|
254
|
-
--
|
|
323
|
+
--arg response_updated "$current_response_updated_at" \
|
|
324
|
+
--argjson count "$current_response_count" \
|
|
255
325
|
--argjson labels "$(echo "$gh_output" | jq '[.labels[].name] | sort')" \
|
|
256
326
|
'.tickets[$id] = {
|
|
257
327
|
upstream_url: $url,
|
|
258
328
|
last_checked_at: $checked,
|
|
259
329
|
last_seen_state: $state,
|
|
260
|
-
|
|
330
|
+
last_seen_response_count: $count,
|
|
261
331
|
last_seen_labels: $labels,
|
|
262
|
-
last_seen_updated_at: $updated
|
|
332
|
+
last_seen_updated_at: $updated,
|
|
333
|
+
last_seen_response_updated_at: $response_updated
|
|
263
334
|
}')"
|
|
264
335
|
done
|
|
265
336
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wr-itil:check-upstream-responses
|
|
3
|
-
description: Poll upstream issues we've filed via `/wr-itil:report-upstream` and surface new comments, state changes, or label changes since last check. Reads `## Reported Upstream` back-link sections in local problem tickets, queries
|
|
3
|
+
description: Poll upstream issues and pull requests we've filed via `/wr-itil:report-upstream` and surface new comments or reviews, state changes, or label changes since last check. Reads `## Reported Upstream` back-link sections in local problem tickets, queries GitHub read-only, diffs against `docs/problems/.outbound-responses-cache.json`, and appends an audit-log entry to `docs/audits/outbound-responses-log.md`. Outbound symmetric counterpart to ADR-062's inbound discovery pipeline (P249 Phase 1).
|
|
4
4
|
allowed-tools: Read, Edit, Write, Bash, Glob, Grep
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -14,14 +14,14 @@ This is **Phase 1** of P249. Phase 1 covers the **us-as-upstream-reporter** half
|
|
|
14
14
|
|
|
15
15
|
In scope:
|
|
16
16
|
- Scan `docs/problems/**/*.md` for `## Reported Upstream` back-link sections (the contract section written by `/wr-itil:report-upstream` Step 7 — see [ADR-024](../../../../docs/decisions/024-cross-project-problem-reporting-contract.proposed.md) Step 7).
|
|
17
|
-
- For each ticket with a back-link, extract the `- **URL**:` line and poll the upstream
|
|
17
|
+
- For each ticket with a back-link, extract the `- **URL**:` line and poll the upstream artefact. Read the `- **Disclosure path**:` line to pick the subcommand: any pull-request path uses `gh pr view` plus a read-only `gh api` call for inline review comments; anything else, **including an absent line**, uses `gh issue view`. Absent means a ticket written before ADR-117, which could only have been filed as a public issue — so it is read as an issue, with no probe.
|
|
18
18
|
- Diff against `docs/problems/.outbound-responses-cache.json` (cache file mirroring the inbound `.upstream-cache.json` shape per ADR-031 § "Cache files live under docs/problems/").
|
|
19
|
-
- Surface five response classes: NEW (new comments), STATE (state change), LABEL (label change), NONE (no change), FAIL (gh poll error).
|
|
19
|
+
- Surface five response classes: NEW (new comments, reviews, or other upstream activity), STATE (state change), LABEL (label change), NONE (no change), FAIL (gh poll error).
|
|
20
20
|
- Update the cache file with the latest seen state.
|
|
21
21
|
- Append a timestamped pass entry to `docs/audits/outbound-responses-log.md` (audit-log mirroring `docs/audits/inbound-discovery-log.md` per ADR-062's audit-log surface contract).
|
|
22
22
|
|
|
23
23
|
Out of scope:
|
|
24
|
-
- Posting comments back to the upstream issue. The skill is **read-only externally** — does not trip ADR-028's external-comms gate.
|
|
24
|
+
- Posting comments back to the upstream issue or pull request. The skill is **read-only externally** — does not trip ADR-028's external-comms gate.
|
|
25
25
|
- Auto-transitioning local ticket lifecycle based on upstream state change (that is P080's bidirectional update axis, separate).
|
|
26
26
|
- Polling against the inbound-discovery channels (`docs/problems/.upstream-channels.json`). That is the inverse axis, owned by `/wr-itil:review-problems` Step 4.5 per ADR-062.
|
|
27
27
|
- Phase 2 external-reporter-as-our-reporter surface (deferred).
|
|
@@ -43,7 +43,7 @@ Out of scope:
|
|
|
43
43
|
|
|
44
44
|
This skill is **AFK-safe by construction**:
|
|
45
45
|
|
|
46
|
-
- Read-only `gh issue view` calls —
|
|
46
|
+
- Read-only `gh issue view`, `gh pr view`, and pull-request review-comment `gh api` calls — none fires ADR-028's external-comms gate.
|
|
47
47
|
- No `AskUserQuestion` calls — five flag-based knobs (`--problems-dir`, `--cache-file`, `--audit-log`, `--ticket`, `--force-recheck`) are the user-direction surface per CLAUDE.md `act on obvious, AskUserQuestion for ambiguous, NEVER prose-ask` (P085).
|
|
48
48
|
- Partial-failure exit code (2) lets AFK orchestrators distinguish "some upstream URLs were unreachable" from "everything broke" without halting the loop.
|
|
49
49
|
|
|
@@ -63,7 +63,7 @@ The script:
|
|
|
63
63
|
|
|
64
64
|
1. Walks `<problems-dir>` (both flat layout `<NNN>-*.<state>.md` AND per-state subdir layout `<state>/<NNN>-*.md` per RFC-002 dual-tolerant migration).
|
|
65
65
|
2. For each ticket file, extracts the `## Reported Upstream` URL line. Tickets without that section are silently skipped.
|
|
66
|
-
3. For each URL, calls `gh issue view
|
|
66
|
+
3. For each URL, calls `gh issue view --json comments,state,labels,updatedAt` or `gh pr view --json comments,reviews,state,labels,updatedAt` (chosen by the disclosure path). Pull requests also fetch inline review comments through `gh api`. The response count and response-specific timestamp combine all three response surfaces; parent metadata changes do not masquerade as review activity. A merged pull request reports `state=MERGED`, distinct from `CLOSED`, so a rejected one is not recorded as a resolution.
|
|
67
67
|
4. Compares the response against the cached entry for that ticket and emits one of: NEW / STATE / LABEL / NONE / FAIL.
|
|
68
68
|
5. Updates the cache file and appends an audit-log entry.
|
|
69
69
|
|
|
@@ -78,7 +78,7 @@ Exit codes:
|
|
|
78
78
|
Read the stdout output and summarise the response classes in chat for the user. The audit-log is the durable surface — the agent's inline summary is the in-session affordance. Do NOT re-dump the full stdout; lead with the most-important classes:
|
|
79
79
|
|
|
80
80
|
- STATE changes (upstream state OPEN → CLOSED / REOPENED) — most actionable; usually a verification signal.
|
|
81
|
-
- NEW
|
|
81
|
+
- NEW responses or activity — second-most actionable; may carry comments, reviews, inline review comments, follow-up questions, or fix confirmation. A zero response delta means an existing response was edited.
|
|
82
82
|
- LABEL changes — informational; signals maintainer triage activity.
|
|
83
83
|
- NONE — quiet; only mention the count, not each ticket.
|
|
84
84
|
- FAIL — call out per-ticket reasons so the user can investigate (URL changed, repo renamed, auth issue).
|
|
@@ -110,7 +110,7 @@ Three invocation surfaces:
|
|
|
110
110
|
|
|
111
111
|
This skill's contract holds when:
|
|
112
112
|
|
|
113
|
-
1. The script `packages/itil/scripts/check-upstream-responses.sh` is read-only externally —
|
|
113
|
+
1. The script `packages/itil/scripts/check-upstream-responses.sh` is read-only externally — `gh issue view`, `gh pr view`, and a pull-request review-comment `gh api` read only; it makes no comment, create, edit, close, or write API call.
|
|
114
114
|
2. The script extracts the URL from `## Reported Upstream` sections matching the format `- **URL**: <url>` per ADR-024 Step 7's back-link contract.
|
|
115
115
|
3. After a successful pass, the cache file exists, is valid JSON, and contains a `tickets.<P<NNN>>` entry for every polled ticket.
|
|
116
116
|
4. After a successful pass, the audit-log file exists and has a new `## YYYY-MM-DDTHH:MM:SSZ` heading appended.
|
|
@@ -70,7 +70,7 @@ To un-park: `git mv` back to `docs/problems/open/<NNN>-<title>.md` (or `docs/pro
|
|
|
70
70
|
|
|
71
71
|
**Verification Pending problems** are also excluded from WSJF ranking — their remaining work is user-side verification, not dev effort. They appear in a dedicated "Verification Queue" section in review output so the user can see what's waiting on them without mixing with dev-work ranking. See step 9c for the queue layout.
|
|
72
72
|
|
|
73
|
-
**Allowed optional appendages**: a problem ticket file may carry a `## Reported Upstream` section appended after the standard sections. This is written by the `/wr-itil:report-upstream` skill (per ADR-024 Confirmation criterion 3a) and records the upstream issue or advisory
|
|
73
|
+
**Allowed optional appendages**: a problem ticket file may carry a `## Reported Upstream` section appended after the standard sections. This is written by the `/wr-itil:report-upstream` skill (per ADR-024 Confirmation criterion 3a) and records the upstream URL — an issue, a pull request (per ADR-117), or an advisory — plus the matched template and the disclosure path. The presence or absence of this section does not affect WSJF ranking or status transitions.
|
|
74
74
|
|
|
75
75
|
**Test-driven resolution:** When root cause is identified, create a failing test that reproduces the problem. Skip/disable the test if a feature-disabling workaround is applied. Re-enable the test when the permanent fix is implemented — the test passing confirms resolution.
|
|
76
76
|
|
|
@@ -6,13 +6,13 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Skill, Agen
|
|
|
6
6
|
|
|
7
7
|
# Report Upstream — Cross-Project Problem-Reporting Skill
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Report a local `docs/problems/<NNN>` ticket upstream. **Prefer opening a pull request when the upstream accepts pull requests**; fall back to filing an issue otherwise (or a private security advisory when the ticket is security-classified). Discover upstream templates, fall through to a structured default when none exist, route security-classified tickets via the upstream's `SECURITY.md`, and back-write a cross-reference into the local ticket.
|
|
10
10
|
|
|
11
11
|
This skill implements the contract documented in [ADR-024](../../../docs/decisions/024-cross-project-problem-reporting-contract.proposed.md) (Cross-project problem-reporting contract). All step numbering below maps 1:1 to ADR-024 Decision Outcome.
|
|
12
12
|
|
|
13
13
|
[ADR-033](../../../docs/decisions/033-report-upstream-classifier-problem-first.proposed.md) (Report-upstream classifier is problem-first) partially supersedes ADR-024 Decision Outcome **Steps 3 and 5 only** — the classifier is problem-first with best-fit backward-compat fallback (per Step 3 below), and the structured default body is problem-shaped (per Step 5 below). ADR-024 Steps 1, 2, 4, 6, 7, 8 and all Consequences / Confirmation clauses remain in force unchanged.
|
|
14
14
|
|
|
15
|
-
The **ADR-024 amendment of 2026-04-25 (P070)** adds Step 4b (dedup check — own re-run + third-party search
|
|
15
|
+
The **ADR-024 amendment of 2026-04-25 (P070)** adds Step 4b (dedup check — own re-run + third-party search across issues and pull requests, followed by inline LLM semantic matching) and Step 5c (kind-aware comment path). AFK comments use the shipped `wr-risk-scorer:external-comms` gate; unresolved above-appetite prose is queued without halting the orchestrator.
|
|
16
16
|
|
|
17
17
|
## Invocation
|
|
18
18
|
|
|
@@ -44,7 +44,7 @@ This skill files reports against an upstream's intake surface. Its reciprocal
|
|
|
44
44
|
|
|
45
45
|
## Voice-tone gate interaction (ADR-028)
|
|
46
46
|
|
|
47
|
-
The skill's `gh issue create` (Step 5) and `gh api repos/.../security-advisories` (Step 6) calls are **on the gated surface list per [ADR-028](../../../docs/decisions/028-voice-tone-gate-external-comms.proposed.md)** (Voice-tone gate on external communications). Expected behaviour during these tool calls:
|
|
47
|
+
The skill's `gh issue create` (Step 5), `gh pr create` (Step 5b), `gh issue comment` / `gh pr comment` (Step 5c) and `gh api repos/.../security-advisories` (Step 6) calls are **on the gated surface list per [ADR-028](../../../docs/decisions/028-voice-tone-gate-external-comms.proposed.md)** (Voice-tone gate on external communications). Expected behaviour during these tool calls:
|
|
48
48
|
|
|
49
49
|
1. The voice-tone gate fires `PreToolUse:Bash` with a deny-plus-delegate response.
|
|
50
50
|
2. The hook delegates to `wr-voice-tone:agent` to review the drafted body for brand-voice + tone alignment against `docs/VOICE-AND-TONE.md`.
|
|
@@ -54,6 +54,13 @@ The skill should treat this transient deny-plus-delegate as the expected path, n
|
|
|
54
54
|
|
|
55
55
|
If `wr-voice-tone:agent` is not installed in the project, the gate is dormant and the skill proceeds without delegation.
|
|
56
56
|
|
|
57
|
+
**A pull request carries two outbound surfaces; only one of them is gated.** The prose must be reviewed, but the shipped hook extracts only the `--body` value. Before `gh pr create`, explicitly send the **combined title and body** through `wr-risk-scorer:external-comms` and `wr-voice-tone:external-comms`; the hook then enforces the body review on the command itself. The **diff is not gated by anything**. Nothing in this repo scores a diff against an upstream's policy or contribution standards. Read the diff yourself before opening the pull request; that gap is tracked as P497 and is deliberately not closed by this skill.
|
|
58
|
+
|
|
59
|
+
Two mechanical notes so a re-route is not mistaken for a defect:
|
|
60
|
+
|
|
61
|
+
- The surface label is part of the gate's marker key. A body first reviewed as an issue and then re-routed to a pull request misses its marker and pays a second full review by both evaluators, even though the prose is byte-identical. Expected, not a bug.
|
|
62
|
+
- The gate's surface regex anchors on a command start, a `;`, an `&&` or a `||`. A **piped** invocation (`… | gh pr create`) does not match and the gate silently no-ops. Invoke `gh pr create` in an anchor-matching form.
|
|
63
|
+
|
|
57
64
|
## Steps
|
|
58
65
|
|
|
59
66
|
### 1. Read the local problem ticket
|
|
@@ -73,13 +80,18 @@ Extract:
|
|
|
73
80
|
|
|
74
81
|
If the ticket is not found, halt with a clear error: `Error: local ticket P<NNN> not found in docs/problems/. Did you mean a different ID?`
|
|
75
82
|
|
|
76
|
-
### 2. Discover upstream issue templates
|
|
83
|
+
### 2. Discover upstream metadata, contribution guidance, and issue templates
|
|
77
84
|
|
|
78
85
|
```bash
|
|
79
86
|
UPSTREAM_OWNER_REPO=$(echo "$UPSTREAM_URL" | sed -E 's|https?://github.com/([^/]+/[^/]+)(/.*)?|\1|')
|
|
87
|
+
REPO_JSON=$(gh api "repos/${UPSTREAM_OWNER_REPO}")
|
|
80
88
|
TEMPLATES_JSON=$(gh api "repos/${UPSTREAM_OWNER_REPO}/contents/.github/ISSUE_TEMPLATE" 2>/dev/null)
|
|
89
|
+
CONTRIBUTING_MD=$(gh api "repos/${UPSTREAM_OWNER_REPO}/contents/CONTRIBUTING.md" --jq '.content' 2>/dev/null | base64 -d)
|
|
90
|
+
PR_TEMPLATE_MD=$(gh api "repos/${UPSTREAM_OWNER_REPO}/contents/.github/PULL_REQUEST_TEMPLATE.md" --jq '.content' 2>/dev/null | base64 -d)
|
|
81
91
|
```
|
|
82
92
|
|
|
93
|
+
If the conventional contribution files are absent, also try `.github/CONTRIBUTING.md`, `.github/pull_request_template.md`, and `docs/PULL_REQUEST_TEMPLATE.md`. A 404 for any optional file means absent; cache successful responses for Steps 4c and 5b. A failure fetching `REPO_JSON` is fatal because its `archived`, `disabled`, and `default_branch` fields govern the route and checkout.
|
|
94
|
+
|
|
83
95
|
Parse the response:
|
|
84
96
|
- HTTP 200 with a JSON array → upstream has templates. List the names + types (`.yml` for forms, `.md` for legacy markdown templates).
|
|
85
97
|
- HTTP 404 → upstream has no `.github/ISSUE_TEMPLATE/` directory; treat as no-templates (proceed to structured default in Step 5).
|
|
@@ -120,13 +132,56 @@ The local ticket is **security-classified** if any of:
|
|
|
120
132
|
- The ticket body has a `## Security classification` section.
|
|
121
133
|
- The CLI `--classification security` argument was passed.
|
|
122
134
|
|
|
123
|
-
If security-classified, route to Step 6. Otherwise, route to Step 4b (dedup check)
|
|
135
|
+
If security-classified, route to Step 6. Otherwise, route to Step 4b (dedup check), then Step 4c (choose the outbound artefact), then Step 5 or Step 5b.
|
|
136
|
+
|
|
137
|
+
Security classification is checked **first and wins outright**. A security-classified ticket never reaches Step 4c, so it can never be routed to a pull request — the private disclosure path in Step 6 stands exactly as it is.
|
|
138
|
+
|
|
139
|
+
### 4c. Choose the outbound artefact — pull request or issue (ADR-117)
|
|
140
|
+
|
|
141
|
+
This step is governed by [ADR-117](../../../docs/decisions/117-prefer-an-upstream-pull-request-over-an-issue.proposed.md) (Prefer an upstream pull request over an issue when the upstream accepts pull requests), which adds this branch alongside ADR-024's confirmed reporting contract and ahead of Step 5.
|
|
142
|
+
|
|
143
|
+
**Prefer a pull request. File an issue when one of the four fallbacks below applies.**
|
|
144
|
+
|
|
145
|
+
#### The predicate is "does this upstream accept pull requests"
|
|
146
|
+
|
|
147
|
+
It is **not** "do we have write access". Fork-and-PR is the ordinary way a third party contributes, and it needs no permission we have to negotiate for. Most adopters of this plugin are consumers of their dependencies, not owners of them — a write-access predicate would make this whole branch a no-op for exactly the case the skill was written for.
|
|
148
|
+
|
|
149
|
+
Read the signal from the discovery calls Step 2 makes:
|
|
150
|
+
|
|
151
|
+
- The `repos/<owner>/<repo>` payload's `archived` and `disabled` flags. Either one true → the upstream is not accepting contributions.
|
|
152
|
+
- The presence of `CONTRIBUTING.md`, or of a pull-request template fetched in Step 2. Either is positive evidence that pull requests are wanted.
|
|
153
|
+
- An explicit statement in `CONTRIBUTING.md` that the project does not accept outside contributions.
|
|
154
|
+
|
|
155
|
+
**On an absent or ambiguous signal, default to "accepts".** Do not escalate through a probe ladder to prove acceptance — a refuse-unless-proven predicate is the same failure mode ADR-024 already rejected when it declined to refuse upstreams that ship no issue templates. State which signal was read, or say the signal was absent and the default applied. Do not write "upstream likely accepts pull requests".
|
|
156
|
+
|
|
157
|
+
#### File an issue instead when
|
|
158
|
+
|
|
159
|
+
1. **The upstream does not accept contributions.** Archived, disabled, or says so.
|
|
160
|
+
2. **The fix needs a design decision that is the maintainers' to make.** More than one defensible approach, and picking between them is their call, not ours.
|
|
161
|
+
3. **The ticket is security-classified.** Already routed at Step 4 — this fallback records that the routing exists, and it is Step 6's private path that runs, not this one. Nothing here re-asserts the pre-P270 blanket ban.
|
|
162
|
+
4. **There is no defensible fix in hand.** A symptom, or even a confident diagnosis, is not a change we can responsibly author in a codebase we do not know.
|
|
163
|
+
|
|
164
|
+
#### Fallback 4 is decided silently — never ask the reporter
|
|
165
|
+
|
|
166
|
+
The reporter describes a symptom. That is the whole of their obligation and this decision must not touch it. **Do not raise an `AskUserQuestion` about whether to write a patch.** The skill already carries two interactive gates (Step 4b dedup, Step 6 missing-`SECURITY.md`); a third would push intake past the couple of minutes beyond which a report simply gets abandoned, and it would be asking the reporter to ratify a judgement the agent is there to make.
|
|
167
|
+
|
|
168
|
+
The preference is a preference. It never converts the reporter into a patch author. The added cost lands on agent time and token budget, not on anyone's attention.
|
|
169
|
+
|
|
170
|
+
#### Draft the issue body first; the pull request is an upgrade of it
|
|
171
|
+
|
|
172
|
+
Compose the Step 5 issue body **before** attempting the pull request, always. Then attempt the pull request. If the attempt fails for any reason — fork refused, patch will not apply, upstream tests red, effort budget spent — **file the already-drafted issue** rather than aborting.
|
|
173
|
+
|
|
174
|
+
Without this, a failed pull-request attempt loses a report that the issue-only path would have filed, which is strictly worse than not having tried.
|
|
175
|
+
|
|
176
|
+
#### Effort budget
|
|
177
|
+
|
|
178
|
+
Spend at most **20 minutes of wall-clock and 3 attempts** on getting a working patch. Past that, fall back to fallback 4 and file the drafted issue. The budget is a real number rather than a judgement call because an open-ended "author a fix in an unfamiliar codebase" is how a loop burns a quota and how an idle guard that measures progress in commits kills a session doing legitimate work.
|
|
124
179
|
|
|
125
180
|
### 4b. Dedup check (P070)
|
|
126
181
|
|
|
127
|
-
This step is governed by the [ADR-024](../../../docs/decisions/024-cross-project-problem-reporting-contract.proposed.md) 2026-04-25 amendment, which adds dedup checking to the Decision Outcome step list (P070). Two duplication windows close at the same insertion point: own re-run (4b.1) and third-party search (4b.2). Both branches share the same AskUserQuestion surface and
|
|
182
|
+
This step is governed by the [ADR-024](../../../docs/decisions/024-cross-project-problem-reporting-contract.proposed.md) 2026-04-25 amendment, which adds dedup checking to the Decision Outcome step list (P070). Two duplication windows close at the same insertion point: own re-run (4b.1) and third-party search (4b.2). Both branches share the same AskUserQuestion surface and AFK risk-gated queue-and-continue behaviour.
|
|
128
183
|
|
|
129
|
-
> **Serves**: JTBD-004 (cross-repo coordination — dedup is the difference between coordination and spam), JTBD-001 (solo developer "without slowing down" — dedup protects the user from policing upstream duplicates manually), JTBD-006 (AFK persona —
|
|
184
|
+
> **Serves**: JTBD-004 (cross-repo coordination — dedup is the difference between coordination and spam), JTBD-001 (solo developer "without slowing down" — dedup protects the user from policing upstream duplicates manually), JTBD-006 (AFK persona — risk-gated queue-and-continue avoids duplicate firing), JTBD-101 (clear pattern — pattern ships without a duplication hole).
|
|
130
185
|
|
|
131
186
|
#### 4b.1. Own re-run check
|
|
132
187
|
|
|
@@ -134,8 +189,13 @@ Detect whether the local ticket already records a previous upstream report. The
|
|
|
134
189
|
|
|
135
190
|
```bash
|
|
136
191
|
LOCAL_URL=$(grep -A5 '^## Reported Upstream' "$LOCAL_TICKET" | grep -oE 'https?://[^ )]+' | head -1)
|
|
192
|
+
LOCAL_DISCLOSURE=$(grep -A5 '^## Reported Upstream' "$LOCAL_TICKET" | sed -n 's/^- \*\*Disclosure path\*\*: //p' | head -1)
|
|
193
|
+
case "$(printf '%s' "$LOCAL_DISCLOSURE" | tr '[:upper:]' '[:lower:]')" in
|
|
194
|
+
*pull*request*) LOCAL_KIND="pull-request" ;;
|
|
195
|
+
*) LOCAL_KIND="issue" ;;
|
|
196
|
+
esac
|
|
137
197
|
if [ -n "$LOCAL_URL" ]; then
|
|
138
|
-
echo "Local ticket P${LOCAL_ID} already records an existing upstream
|
|
198
|
+
echo "Local ticket P${LOCAL_ID} already records an existing upstream ${LOCAL_KIND}: $LOCAL_URL"
|
|
139
199
|
# Branch interactive vs AFK below.
|
|
140
200
|
fi
|
|
141
201
|
```
|
|
@@ -146,10 +206,10 @@ fi
|
|
|
146
206
|
- `multiSelect: false`
|
|
147
207
|
- Options:
|
|
148
208
|
1. `Halt — local ticket already records ${LOCAL_URL}` (Recommended) — abort the invocation; the existing report is current.
|
|
149
|
-
2. `Comment on the existing upstream report` — route to Step 5c with the existing URL's
|
|
209
|
+
2. `Comment on the existing upstream report` — route to Step 5c with the existing URL's number and `${LOCAL_KIND}`; appropriate when new evidence has emerged since the previous report.
|
|
150
210
|
3. `File a new upstream issue anyway (override)` — explicit override after user has reviewed the existing record and judged the second filing warranted (e.g. previous report was closed without resolution and a fresh tracker is needed).
|
|
151
211
|
|
|
152
|
-
**AFK / non-interactive branch** — apply the
|
|
212
|
+
**AFK / non-interactive branch** — apply the external-communications gate in the AFK behaviour summary. Below-appetite prose comments on the existing `${LOCAL_KIND}`; unresolved above-appetite prose is queued and the orchestrator continues.
|
|
153
213
|
|
|
154
214
|
#### 4b.2. Third-party search
|
|
155
215
|
|
|
@@ -158,26 +218,48 @@ Detect whether a different reporter (or another agent in a parallel session) has
|
|
|
158
218
|
```bash
|
|
159
219
|
# Stage 1: gh-search pre-filter on title keywords (cheap, ~500ms-2s).
|
|
160
220
|
KEYWORDS=$(extract_3-5_keywords_from "$LOCAL_TICKET_TITLE + $LOCAL_TICKET_DESCRIPTION")
|
|
161
|
-
|
|
221
|
+
ISSUE_MATCHES=$(gh issue list \
|
|
222
|
+
--repo "$UPSTREAM_OWNER_REPO" \
|
|
223
|
+
--state all \
|
|
224
|
+
--search "$KEYWORDS" \
|
|
225
|
+
--json number,title,state,url \
|
|
226
|
+
--limit 10)
|
|
227
|
+
|
|
228
|
+
# ADR-117: `gh issue list` does NOT return pull requests. Now that this skill
|
|
229
|
+
# opens pull requests, searching issues alone leaves the whole pull-request
|
|
230
|
+
# window open — including against pull requests we opened ourselves.
|
|
231
|
+
PR_MATCHES=$(gh pr list \
|
|
162
232
|
--repo "$UPSTREAM_OWNER_REPO" \
|
|
163
233
|
--state all \
|
|
164
234
|
--search "$KEYWORDS" \
|
|
165
235
|
--json number,title,state,url \
|
|
166
236
|
--limit 10)
|
|
237
|
+
|
|
238
|
+
MATCHES=$(jq -cn \
|
|
239
|
+
--argjson issues "$ISSUE_MATCHES" \
|
|
240
|
+
--argjson prs "$PR_MATCHES" \
|
|
241
|
+
'($issues | map(. + {kind: "issue"})) +
|
|
242
|
+
($prs | map(. + {kind: "pull-request"}))')
|
|
167
243
|
```
|
|
168
244
|
|
|
245
|
+
Carry the artefact kind alongside each candidate. Step 5c needs it to pick between `gh issue comment` and `gh pr comment`, and the user needs it to judge a match — "already fixed in an open pull request" is a different situation from "already reported in an open issue".
|
|
246
|
+
|
|
169
247
|
For each candidate returned by Stage 1, fetch the full body and run **Stage 2 — inline LLM semantic judgement**:
|
|
170
248
|
|
|
171
249
|
```bash
|
|
172
250
|
# Stage 2: per-candidate body fetch + inline classification.
|
|
173
|
-
|
|
174
|
-
|
|
251
|
+
while IFS=$'\t' read -r kind n; do
|
|
252
|
+
if [ "$kind" = "pull-request" ]; then
|
|
253
|
+
CANDIDATE=$(gh pr view "$n" --repo "$UPSTREAM_OWNER_REPO" --json title,body,state,url)
|
|
254
|
+
else
|
|
255
|
+
CANDIDATE=$(gh issue view "$n" --repo "$UPSTREAM_OWNER_REPO" --json title,body,state,url)
|
|
256
|
+
fi
|
|
175
257
|
# Inline LLM judgement: read {local ticket Description + Symptoms, candidate title + body}
|
|
176
258
|
# and return one of: same-problem | different-problem | uncertain.
|
|
177
259
|
# No subagent dispatch — Direction decision 2026-04-21 pins inline classification
|
|
178
260
|
# for simplicity. Promotion to a `wr-itil:dedup-check` subagent is a future
|
|
179
261
|
# ADR amendment if architect review later flags context-isolation concerns.
|
|
180
|
-
done
|
|
262
|
+
done < <(echo "$MATCHES" | jq -r '.[] | [.kind, .number] | @tsv')
|
|
181
263
|
```
|
|
182
264
|
|
|
183
265
|
Notes on inline LLM classification:
|
|
@@ -197,31 +279,23 @@ If Stage 2 produces one or more `same-problem` matches, surface them to the user
|
|
|
197
279
|
|
|
198
280
|
`uncertain` matches surface alongside `same-problem` matches with their verdict labelled, so the user can review. The skill never auto-resolves an `uncertain` verdict.
|
|
199
281
|
|
|
200
|
-
**AFK / non-interactive branch** — apply the
|
|
201
|
-
|
|
202
|
-
#### AFK static heuristic (interim, until `wr-risk-scorer:external-comms` ships)
|
|
203
|
-
|
|
204
|
-
The Direction decision (2026-04-21) pins the AFK auto-comment branch on **two gates passing together**: the maintainer-annoyance risk evaluator AND the P064 external-comms leak gate, both within RISK-POLICY.md's commit-layer appetite (Low, ≤4/25). Neither gate exists yet — ADR-028 declares the `wr-risk-scorer:external-comms` subagent type but P064's implementation is open at WSJF 3.0 (Effort L), and the maintainer-annoyance evaluator was deferred by architect review on P070 to compose with the same subagent rather than ship as a separate evaluator (per ADR-028 line 117 — *"Third evaluator (licence-compliance, etc.) adding to the same gate — when it emerges, amend this ADR's evaluator list and the composite marker's `evaluator_set` component; no new ADR expected."*).
|
|
282
|
+
**AFK / non-interactive branch** — apply the external-communications gate in the AFK behaviour summary, preserving the matched artefact kind. Below-appetite prose comments on the matching issue or pull request; unresolved above-appetite prose is queued and the orchestrator continues.
|
|
205
283
|
|
|
206
|
-
**
|
|
207
|
-
|
|
208
|
-
**Re-wire trigger**: when `wr-risk-scorer:external-comms` lands (ADR-028 implementation, P064 closure), amend this section to invoke both evaluators and proceed with auto-comment ONLY when both verdicts return PASS within appetite. Update the AFK behaviour summary table accordingly. Until then, the static heuristic stands.
|
|
209
|
-
|
|
210
|
-
**Queued Upstream Report save format** (used by both 4b.1 and 4b.2 AFK halts; mirrors the security-path halt pattern from Step 6 per ADR-024 Consequences lines 116, 123. **Renamed from `## Drafted Upstream Report` per ADR-024 2026-06-04 second-amendment ratification (c)** — same shape; new name reflects post-amendment semantics, queue-for-review-on-return not loop-stopping halt-and-save):
|
|
284
|
+
**Queued Upstream Report save format** (used by both 4b.1 and 4b.2 when risk cannot be reduced within appetite):
|
|
211
285
|
|
|
212
286
|
```markdown
|
|
213
287
|
## Queued Upstream Report
|
|
214
288
|
|
|
215
289
|
- **Drafted**: <YYYY-MM-DD>
|
|
216
290
|
- **Target upstream**: <upstream-repo-url>
|
|
217
|
-
- **
|
|
291
|
+
- **Queue reason**: dedup match (own re-run | third-party `same-problem`) — external-communications risk remains above appetite
|
|
218
292
|
- **Matched URL(s)**: <existing-issue-or-report-URL(s)>
|
|
219
293
|
- **Drafted body**:
|
|
220
294
|
|
|
221
|
-
<the body that would have been posted as a
|
|
295
|
+
<the body that would have been posted as a comment or new report, ready for review>
|
|
222
296
|
```
|
|
223
297
|
|
|
224
|
-
|
|
298
|
+
Queue an `outstanding_questions` entry and include the queued report in the progress summary; do not halt the orchestrator.
|
|
225
299
|
|
|
226
300
|
### 5. Public-issue path
|
|
227
301
|
|
|
@@ -395,25 +469,118 @@ Reported from <downstream-repo-url>/<local-ticket-relative-path>
|
|
|
395
469
|
This issue is tracked locally as P<NNN> in the downstream project's `docs/problems/` directory.
|
|
396
470
|
```
|
|
397
471
|
|
|
472
|
+
Retain the completed issue-shaped body separately so a failed pull-request attempt cannot overwrite its fallback:
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
ISSUE_BODY="${FILLED_BODY}"
|
|
476
|
+
```
|
|
477
|
+
|
|
398
478
|
Open the issue:
|
|
399
479
|
|
|
400
480
|
```bash
|
|
401
481
|
gh issue create \
|
|
402
482
|
--repo "${UPSTREAM_OWNER_REPO}" \
|
|
403
483
|
--title "${TITLE_PREFIXED_BY_TEMPLATE}" \
|
|
404
|
-
--body "${
|
|
484
|
+
--body "${ISSUE_BODY}"
|
|
405
485
|
```
|
|
406
486
|
|
|
407
487
|
Do **not** pass `--label` on this call (P207). Labels are supplied by the matched template's YAML `labels:` frontmatter and applied by GitHub when the issue form is submitted; passing `--label <name>` for a label that has not been pre-created on the upstream repo causes `gh issue create` to hard-fail with `could not add label: '<name>' not found`. The flag is redundant when the matched template carries `labels:` and a hard-fail surface when it does not. If the upstream has no matched template at all (structured-default body path, Step 3 preference order item 7), omit labels entirely — leave triage to the upstream maintainer's existing routing.
|
|
408
488
|
|
|
409
489
|
Capture the returned issue URL. The voice-tone gate per ADR-028 may delegate-and-retry; treat this as expected (see "Voice-tone gate interaction" above). Proceed to Step 7 once the issue is created.
|
|
410
490
|
|
|
491
|
+
### 5b. Pull-request path (ADR-117)
|
|
492
|
+
|
|
493
|
+
Reached when Step 4c chose a pull request. The Step 5 issue body is already drafted at this point and is held as the fallback.
|
|
494
|
+
|
|
495
|
+
#### The body defers to the upstream's own template
|
|
496
|
+
|
|
497
|
+
ADR-033's structured default is a **problem report** — Description, Symptoms, Workaround, Affected plugin, Frequency, Versions, Evidence, Cross-reference. That shape is incoherent on a pull request, where the diff already resolves the symptoms it would recite. ADR-033's classifier and its structured default remain unchanged and in force on the issue branch; only this branch differs.
|
|
498
|
+
|
|
499
|
+
1. Fetch `.github/PULL_REQUEST_TEMPLATE.md` (also try `.github/pull_request_template.md` and `docs/PULL_REQUEST_TEMPLATE.md`). **If one exists, fill it.** This is the same posture as respecting the upstream's curated issue templates, applied to a second artefact kind.
|
|
500
|
+
2. If none exists, use the reduced shape below — rationale plus cross-reference, not a problem report:
|
|
501
|
+
|
|
502
|
+
```markdown
|
|
503
|
+
## What this changes
|
|
504
|
+
|
|
505
|
+
<one paragraph: the change, in the upstream's own vocabulary>
|
|
506
|
+
|
|
507
|
+
## Why
|
|
508
|
+
|
|
509
|
+
<the problem this fixes, stated as the upstream experiences it — not as our
|
|
510
|
+
ticket experiences it. Link an existing upstream issue if one covers it.>
|
|
511
|
+
|
|
512
|
+
## Cross-reference
|
|
513
|
+
|
|
514
|
+
Reported from <downstream-repo-url>/<local-ticket-relative-path>, where this
|
|
515
|
+
is tracked as P<NNN>.
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Keep it short. A pull request body that recites symptoms the diff already answers reads as noise to a reviewer.
|
|
519
|
+
|
|
520
|
+
Retain this separately as `PR_BODY`; never overwrite `ISSUE_BODY`.
|
|
521
|
+
|
|
522
|
+
#### Show the diff before opening it
|
|
523
|
+
|
|
524
|
+
The external-comms gate forces a turn here to review the prose. Nothing reviews the **patch** — no surface in this repo reads a diff against an upstream's conventions (P497). So make the patch visible at the stop that is already happening, rather than letting it go out unseen:
|
|
525
|
+
|
|
526
|
+
1. Print the full patch to the session — `git diff` against the upstream's base branch — immediately before `gh pr create`.
|
|
527
|
+
2. State in the drafted body which of the upstream's own convention files you read and complied with: `CONTRIBUTING.md`, `.github/PULL_REQUEST_TEMPLATE.md`, a linter config, a CI workflow. Name the file. If you read none, say so.
|
|
528
|
+
|
|
529
|
+
This adds no `AskUserQuestion` and does not reopen Step 4c's silent determination. It converts an invisible diff into a visible one at a turn the reviewer is already stopped on, which is the cheapest control available until P497 is closed.
|
|
530
|
+
|
|
531
|
+
#### Build and open it from an isolated upstream checkout
|
|
532
|
+
|
|
533
|
+
Do not author the upstream patch in the downstream project's working tree. Use a temporary clone, create one branch, apply the smallest defensible patch, run the upstream's documented checks, commit it, and push it to a fork (or directly only when write access is already available).
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
UPSTREAM_BASE=$(echo "$REPO_JSON" | jq -r '.default_branch')
|
|
537
|
+
UPSTREAM_CHECKOUT=$(mktemp -d)
|
|
538
|
+
BRANCH="windyroad/p${LOCAL_ID}-$(printf '%s' "$TITLE" | tr '[:upper:] ' '[:lower:]-' | tr -cd '[:alnum:]-' | cut -c1-40)"
|
|
539
|
+
|
|
540
|
+
PR_URL=$(
|
|
541
|
+
gh repo clone "$UPSTREAM_OWNER_REPO" "$UPSTREAM_CHECKOUT/repo" -- --filter=blob:none
|
|
542
|
+
cd "$UPSTREAM_CHECKOUT/repo"
|
|
543
|
+
git switch -c "$BRANCH" "origin/$UPSTREAM_BASE"
|
|
544
|
+
|
|
545
|
+
# Apply the patch, run the upstream's documented checks, then:
|
|
546
|
+
git add --all
|
|
547
|
+
git commit -m "fix: <upstream-facing summary>"
|
|
548
|
+
gh repo fork "$UPSTREAM_OWNER_REPO" --remote --remote-name fork
|
|
549
|
+
git push --set-upstream fork "$BRANCH"
|
|
550
|
+
|
|
551
|
+
GH_USER=$(gh api user --jq .login)
|
|
552
|
+
git diff "origin/$UPSTREAM_BASE...HEAD" >&2
|
|
553
|
+
gh pr create \
|
|
554
|
+
--repo "${UPSTREAM_OWNER_REPO}" \
|
|
555
|
+
--base "${UPSTREAM_BASE}" \
|
|
556
|
+
--head "${GH_USER}:${BRANCH}" \
|
|
557
|
+
--title "${TITLE}" \
|
|
558
|
+
--body "${PR_BODY}"
|
|
559
|
+
)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
The command substitution is a subshell, so Step 7 resumes in the downstream checkout. Immediately before `gh pr create`, explicitly review `${TITLE}` plus `${PR_BODY}` together through both external-communications evaluators. Do not rely on the command hook for the title: it extracts the body only.
|
|
563
|
+
|
|
564
|
+
Invoke it at a command start or after `;` / `&&` / `||`, never through a pipe, or the external-comms gate does not match the surface and silently skips the review. Do not pass `--label` for the same reason Step 5 does not (P207).
|
|
565
|
+
|
|
566
|
+
Capture the returned pull-request URL and proceed to Step 7, recording disclosure path `pull request`.
|
|
567
|
+
|
|
568
|
+
If the attempt fails at any point, file the drafted Step 5 issue instead and record `public issue`. A failed pull request must never cost us the report.
|
|
569
|
+
|
|
411
570
|
### 5c. Comment path (P070)
|
|
412
571
|
|
|
413
572
|
Used when Step 4b's dedup check (own re-run or third-party search) finds a match AND the user picks the "comment instead" option. Skips `gh issue create` and posts a cross-reference comment on the existing upstream issue:
|
|
414
573
|
|
|
574
|
+
**Branch on what the dedup check actually matched (ADR-117).** Step 4b.2 now searches pull requests as well as issues, so the match may be either. `gh issue comment` errors on a pull request:
|
|
575
|
+
|
|
415
576
|
```bash
|
|
416
|
-
|
|
577
|
+
# Issue match:
|
|
578
|
+
gh issue comment "${EXISTING_NUMBER}" \
|
|
579
|
+
--repo "${UPSTREAM_OWNER_REPO}" \
|
|
580
|
+
--body "${COMMENT_BODY}"
|
|
581
|
+
|
|
582
|
+
# Pull-request match:
|
|
583
|
+
gh pr comment "${EXISTING_NUMBER}" \
|
|
417
584
|
--repo "${UPSTREAM_OWNER_REPO}" \
|
|
418
585
|
--body "${COMMENT_BODY}"
|
|
419
586
|
```
|
|
@@ -437,7 +604,7 @@ Empty subsections are skipped — the comment should add information, not restat
|
|
|
437
604
|
|
|
438
605
|
The voice-tone gate per ADR-028 also fires on `gh issue comment` (per the canonical hook's regex list at ADR-028 line 61); treat the deny-plus-delegate-and-retry as expected, same as Step 5.
|
|
439
606
|
|
|
440
|
-
Capture the returned comment URL
|
|
607
|
+
Capture the returned comment URL. The Step 7 back-write records an issue match as `commented-on-existing-issue` and a pull-request match as `commented-on-existing-pull-request`. Proceed to Step 7.
|
|
441
608
|
|
|
442
609
|
### 6. Security path
|
|
443
610
|
|
|
@@ -489,10 +656,12 @@ After the upstream issue or advisory is created (or drafted-and-saved in the sec
|
|
|
489
656
|
- **URL**: <upstream-issue-or-advisory-url>
|
|
490
657
|
- **Reported**: <YYYY-MM-DD>
|
|
491
658
|
- **Template used**: <template-name-or-"structured default">
|
|
492
|
-
- **Disclosure path**: <public issue | security advisory | drafted-and-saved (mailbox / out-of-band) | commented-on-existing-issue (Step 5c, P070)>
|
|
659
|
+
- **Disclosure path**: <public issue | pull request (Step 5b, ADR-117) | security advisory | drafted-and-saved (mailbox / out-of-band) | commented-on-existing-issue (Step 5c, P070) | commented-on-existing-pull-request (Step 5c, ADR-117)>
|
|
493
660
|
- **Cross-reference confirmed**: <yes/no — true once the upstream issue body contains the local ticket reference>
|
|
494
661
|
```
|
|
495
662
|
|
|
663
|
+
The `pull request` and `commented-on-existing-pull-request` values are what `/wr-itil:check-upstream-responses` and `/wr-itil:update-upstream` read to pick `gh pr view` and `gh pr comment` over their issue equivalents. Write them exactly; an absent line is read as `public issue`, which is correct for tickets predating ADR-117 but wrong for a pull request.
|
|
664
|
+
|
|
496
665
|
### 8. Commit per ADR-014
|
|
497
666
|
|
|
498
667
|
Follow the ADR-014 ordering:
|
|
@@ -505,14 +674,15 @@ If the cumulative pipeline risk lands above appetite and `AskUserQuestion` is un
|
|
|
505
674
|
|
|
506
675
|
## AFK behaviour summary
|
|
507
676
|
|
|
508
|
-
|
|
677
|
+
Six distinct AFK branches; **per the ADR-024 2026-06-04 (P270) amendment, ALL pre-commit branches now route through the `wr-risk-scorer:external-comms` gate** (ADR-028) — below-appetite proceeds, above-appetite risk-reduces then queues. The legacy "halt the orchestrator" semantics for dedup-match and security-path-without-declared-channel are **superseded** by queue-and-continue per P352:
|
|
509
678
|
|
|
510
679
|
| Branch | AFK behaviour | Authority |
|
|
511
680
|
|---|---|---|
|
|
512
681
|
| Public-issue path (Step 5) | Score drafted prose via `wr-risk-scorer:external-comms` (ADR-028). Below-appetite → proceed via `gh issue create`. Above-appetite → risk-reduce + re-score; if within → proceed; else → save draft to `## Queued Upstream Report` + queue `outstanding_questions` entry; orchestrator continues. Risk-reducing measures vocabulary is **open-ended LLM judgement** per ADR-024 2026-06-04 second-amendment leaf (a) — `wr-risk-scorer:external-comms` picks the remedy case-by-case. Voice-tone gate per ADR-028 may also delegate-and-retry on the proceed path. | ADR-024 2026-06-04 amendment (P270); ADR-024 2026-06-04 second-amendment (leaf a — open vocabulary); ADR-028 line 126 |
|
|
513
|
-
| Dedup match — Step 4b (own re-run OR third-party `same-problem`) | Score the proposed comment body via `wr-risk-scorer:external-comms`. Below-appetite →
|
|
682
|
+
| Dedup match — Step 4b (own re-run OR third-party `same-problem`) | Score the proposed comment body via `wr-risk-scorer:external-comms`. Below-appetite → comment on the matching issue or pull request (Step 5c). Above-appetite → risk-reduce + re-score (open-ended LLM judgement per leaf (a)); if within → proceed; else → save draft to `## Queued Upstream Report` + queue `outstanding_questions` entry; orchestrator continues. | ADR-024 2026-06-04 amendment (P270); ADR-024 2026-04-25 amendment (P070); ADR-024 2026-06-04 second-amendment (leaf a) |
|
|
514
683
|
| Security path with declared channel (Step 6, GitHub Advisories — upstream has `SECURITY.md`) | Per ADR-024 2026-06-04 second-amendment leaf (b) — ratified: if upstream has `SECURITY.md` AND below-appetite → **file** via the SECURITY.md-declared channel. Above-appetite → risk-reduce + re-score (open-ended LLM judgement per leaf (a)); if within → proceed; else → save draft + queue; orchestrator continues. | ADR-024 2026-06-04 amendment (P270); ADR-024 2026-06-04 second-amendment (leaf b); ADR-024 Decision Outcome step 6 |
|
|
515
684
|
| Security path with `security@` / other / missing-SECURITY.md (Step 6) | Per ADR-024 2026-06-04 second-amendment leaf (b) — ratified: when upstream has NO `SECURITY.md` but another disclosure channel exists, score drafted prose via `wr-risk-scorer:external-comms` considering impact to (i) our repository, (ii) our reputation, (iii) the party we are reporting to. Below-appetite → save drafted report to `## Queued Upstream Report` + queue `outstanding_questions` entry naming the channel the user must follow on return (the no-infra-for-email constraint still holds — the channel-action remains user-side, but the queue surface replaces the loop-stopping halt). Above-appetite → risk-reduce + re-score (open-ended LLM judgement per leaf (a)) then queue per the same shape. Orchestrator continues. The pre-2026-06-04 "AFK orchestrators must never auto-report a security-classified ticket" rule is **superseded** by the external-comms-gated per-classification branching. | ADR-024 2026-06-04 amendment (P270); ADR-024 2026-06-04 second-amendment (leaf b); ADR-024 Consequences lines 116, 123 (superseded) |
|
|
685
|
+
| **Pull-request path (Step 5b)** | **Degrade to the issue branch.** No unattended session opens a pull request against a repository we do not own. File the issue per the Public-issue-path row above, and append the drafted pull request (body + the change it would have made) to `## Queued Upstream Report` for the interactive return. **Also surface it in the orchestrator's progress summary** — a returning developer must see "a pull request was drafted and queued" without opening the ticket. The external-comms gate reads prose; it cannot authorise pushing code into a third party's repository under our name, and that is a judgement call an unattended loop does not get to make. | ADR-117 (AFK degrade); JTBD-006 (no unattended judgement calls; visible on return) |
|
|
516
686
|
| Above-appetite commit (Step 8) | Skip the commit, report uncommitted state. | ADR-013 Rule 6 |
|
|
517
687
|
|
|
518
688
|
## References
|
|
@@ -521,7 +691,9 @@ Five distinct AFK branches; **per the ADR-024 2026-06-04 (P270) amendment, ALL p
|
|
|
521
691
|
- [ADR-033](../../../docs/decisions/033-report-upstream-classifier-problem-first.proposed.md) — partially supersedes ADR-024 Decision Outcome Steps 3 + 5; governs the problem-first classifier and problem-shaped structured default body.
|
|
522
692
|
- [P070](../../../docs/problems/) — driver ticket for the Step 4b dedup check + Step 5c comment path; carries the 2026-04-21 Direction decision (gh search + inline LLM, no subagent dispatch) and the AFK static-heuristic interim behaviour.
|
|
523
693
|
- [ADR-027](../../../docs/decisions/027-governance-skill-auto-delegation.proposed.md) — Step-0 deferral rationale (held for reassessment).
|
|
524
|
-
- [ADR-
|
|
694
|
+
- [ADR-117](../../../docs/decisions/117-prefer-an-upstream-pull-request-over-an-issue.proposed.md) — prefer an upstream pull request over an issue when the upstream accepts pull requests. Adds Step 4c (artefact choice) and Step 5b (pull-request path), extends Step 4b.2's dedup search to pull requests, branches Step 5c's comment call, and widens Step 7's disclosure-path enumeration without editing ADR-024 or ADR-033.
|
|
695
|
+
- [P497](../../../docs/problems/open/497-upstream-pull-request-diff-is-unscored.md) — the pull-request **diff** is unscored. The prose is gated; nothing reads the diff against an upstream's policy. Named by ADR-117 and deliberately left open.
|
|
696
|
+
- [ADR-028](../../../docs/decisions/028-voice-tone-gate-external-comms.proposed.md) — voice-tone gate on `gh issue create`, `gh pr create`, `gh issue comment`, `gh pr comment` and `gh api .../security-advisories`.
|
|
525
697
|
- [ADR-013](../../../docs/decisions/013-structured-user-interaction-for-governance-decisions.proposed.md) — interaction policy; Rule 1 governs Step 6 missing-SECURITY.md `AskUserQuestion`; Rule 6 governs the commit-gate AFK branch.
|
|
526
698
|
- [ADR-014](../../../docs/decisions/014-governance-skills-commit-their-own-work.proposed.md) — work → score → commit ordering.
|
|
527
699
|
- [ADR-015](../../../docs/decisions/015-on-demand-assessment-skills.proposed.md) — fallback path for `wr-risk-scorer:assess-release`.
|
|
@@ -23,7 +23,7 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Skill, Agen
|
|
|
23
23
|
|
|
24
24
|
# Update Upstream — Lifecycle-Update Skill
|
|
25
25
|
|
|
26
|
-
Post a lifecycle-update comment to an upstream issue
|
|
26
|
+
Post a lifecycle-update comment to an upstream issue or pull request when the local problem ticket transitions. Reads the local ticket's `## Reported Upstream` section, drafts a transition-specific update from the templates below, composes the draft through the external-comms risk gate (`wr-risk-scorer:external-comms`) and voice-tone gate (`wr-voice-tone:external-comms`), auto-posts via `gh issue comment` or `gh pr comment` when both gates pass within appetite, closes only issue targets on Verifying → Closed, and queues an `outstanding_questions` entry when either gate scores above appetite.
|
|
27
27
|
|
|
28
28
|
This skill is the **reciprocal sibling** to [`/wr-itil:report-upstream`](../report-upstream/SKILL.md) — that skill files the initial upstream report; this skill keeps the upstream record in sync as the local ticket walks its lifecycle. The split is per [ADR-010](../../../docs/decisions/010-rename-wr-problem-to-wr-itil.proposed.md) amended Skill Granularity rule (one skill per distinct user intent) — initial-filing and lifecycle-update are distinct user intents with distinct autocomplete surfaces.
|
|
29
29
|
|
|
@@ -49,7 +49,7 @@ The single-ticket form is typically invoked from `/wr-itil:transition-problem` S
|
|
|
49
49
|
- Determine the local ticket's current Status from the filename suffix.
|
|
50
50
|
- Draft a transition-specific lifecycle-update comment per the templates below (Open→KE / KE→Verifying / Verifying→Closed).
|
|
51
51
|
- Compose the drafted prose through `wr-risk-scorer:external-comms` + `wr-voice-tone:external-comms` gates.
|
|
52
|
-
- Within appetite → post via `gh issue comment <n
|
|
52
|
+
- Within appetite → post via `gh issue comment <n>`, or `gh pr comment <n>` when the disclosure path records a pull request (ADR-117); on Verifying→Closed also run `gh issue close <n>` — **but never `gh pr close`** (see below).
|
|
53
53
|
- Above appetite → AskUserQuestion (interactive) / queue `outstanding_questions` (AFK, per P352 queue-and-continue).
|
|
54
54
|
- Back-write a `## Upstream Lifecycle Updates` log entry to the local ticket recording the transition, the matched URL, the posted comment URL, and the disclosure path.
|
|
55
55
|
- **Historical catch-up migration (`--catchup`, P080 Phase 2)** — one-shot retroactive scan of the existing `.verifying.md` + `.closed.md` corpus; posts the lifecycle update each linked-upstream ticket should already carry. Idempotent — re-running is safe. See [§ Catchup migration mode](#catchup-migration-mode-phase-2).
|
|
@@ -269,6 +269,14 @@ gh issue close "${UPSTREAM_ISSUE_NUMBER}" \
|
|
|
269
269
|
|
|
270
270
|
If the issue is already closed upstream (someone else closed it manually), `gh issue close` returns a benign error — capture the existing state and continue to Step 6 with `closed-already-upstream` recorded in the back-write disclosure path.
|
|
271
271
|
|
|
272
|
+
#### Pull-request targets never get closed (ADR-117)
|
|
273
|
+
|
|
274
|
+
When the `## Reported Upstream` disclosure path records a **pull request**, post the comment with `gh pr comment` and **stop there**. Do not run `gh pr close`, on this transition or any other.
|
|
275
|
+
|
|
276
|
+
A merged pull request closes itself, so closing is redundant. An unmerged one is still work we authored and offered; closing it is a hostile act against our own contribution and withdraws it from the maintainer's queue without saying so. The local ticket reaching `.closed.md` means *we* consider the problem resolved locally — it does not mean the upstream has finished deciding.
|
|
277
|
+
|
|
278
|
+
Record `posted-pr-comment` in the back-write disclosure path. On a pull-request target `posted-comment-and-closed` is unreachable by construction.
|
|
279
|
+
|
|
272
280
|
### 6. Back-write to local ticket
|
|
273
281
|
|
|
274
282
|
Append a log entry to the local ticket's `## Upstream Lifecycle Updates` section (create the section if absent — never inserted mid-document; appended after all existing sections per the same discipline as `## Reported Upstream` in `/wr-itil:report-upstream` Step 7):
|
|
@@ -279,7 +287,7 @@ Append a log entry to the local ticket's `## Upstream Lifecycle Updates` section
|
|
|
279
287
|
- **<YYYY-MM-DD>** — Open → Known Error
|
|
280
288
|
- **Target URL**: <upstream-issue-url>
|
|
281
289
|
- **Comment URL**: <posted-comment-url> (or "queued — see ## Queued Upstream Update" when above-appetite)
|
|
282
|
-
- **Disclosure path**: posted-comment | posted-comment-and-closed (Verifying → Closed) | queued-above-appetite | closed-already-upstream | skipped-out-of-band
|
|
290
|
+
- **Disclosure path**: posted-comment | posted-pr-comment (pull-request target, ADR-117 — never closed) | posted-comment-and-closed (Verifying → Closed, issue targets only) | queued-above-appetite | closed-already-upstream | skipped-out-of-band
|
|
283
291
|
- **Gate verdict**: external-comms <band/score> + voice-tone <pass|fail>
|
|
284
292
|
|
|
285
293
|
- **<YYYY-MM-DD>** — Known Error → Verification Pending
|
|
@@ -495,9 +503,9 @@ The append-only log (written by Step 6 on every post) is the source of truth —
|
|
|
495
503
|
|
|
496
504
|
### C3. Process each CATCHUP entry
|
|
497
505
|
|
|
498
|
-
For each `CATCHUP` line, run the **existing per-ticket flow** (Steps 4–6) against that ticket ID:
|
|
506
|
+
For each `CATCHUP` line, parse its `disclosure=` token and run the **existing per-ticket flow** (Steps 4–6) against that ticket ID. `disclosure=pull-request` selects `gh pr comment` and forbids any close command; `disclosure=issue` selects `gh issue comment` and permits `gh issue close` for Verifying → Closed:
|
|
499
507
|
|
|
500
|
-
1. Draft the transition template (Step 4) for the entry's transition (`KE->Verifying` → Known Error → Verification Pending template; `Verifying->Closed` → Verification Pending → Closed template
|
|
508
|
+
1. Draft the transition template (Step 4) for the entry's transition (`KE->Verifying` → Known Error → Verification Pending template; `Verifying->Closed` → Verification Pending → Closed template). Only an issue disclosure also runs `gh issue close`; a pull-request disclosure never runs `gh pr close`.
|
|
501
509
|
2. Compose through the external-comms + voice-tone gates (Step 5) — **identical** dual-gate composition as the per-ticket path. Above-appetite handling (Step 5c) is unchanged: silent risk-reduce + re-score, then queue to `## Queued Upstream Update` + `outstanding_questions` (category `deviation-approval`) per P352 if still above. Catchup does NOT bypass the gates.
|
|
502
510
|
3. Post within appetite (Step 5b final) and back-write the `## Upstream Lifecycle Updates` log (Step 6).
|
|
503
511
|
|