@windyroad/itil 0.55.3 → 0.55.4
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
package/scripts/catchup-scan.sh
CHANGED
|
@@ -31,14 +31,28 @@
|
|
|
31
31
|
# 0 = success (zero or more worklist lines on stdout)
|
|
32
32
|
# 1 = error (problems-dir missing, malformed CLI args)
|
|
33
33
|
#
|
|
34
|
+
# Two surfaces are walked (P376 — cross-direction parity):
|
|
35
|
+
# OUTBOUND — tickets with a `## Reported Upstream` section (issues WE filed
|
|
36
|
+
# against an upstream we depend on).
|
|
37
|
+
# INBOUND — tickets with an `**Origin**: inbound-reported (#NN)` field
|
|
38
|
+
# (ADR-076 — issues someone else filed *against us* on our own repo, which
|
|
39
|
+
# the P363 rework made dispatchable). Without this leg the inbound catchup
|
|
40
|
+
# candidates were a manual `grep -lE '^\*\*Origin\*\*:\s*inbound-reported'`
|
|
41
|
+
# surface the maintainer had to remember after every `--catchup` run.
|
|
42
|
+
# A ticket carrying BOTH surfaces emits BOTH an outbound and an inbound line
|
|
43
|
+
# (the two legs are independent, mirroring the update-upstream SKILL).
|
|
44
|
+
#
|
|
34
45
|
# Structured stdout (one per actionable upstream entry; <= 150 bytes per
|
|
35
46
|
# line per ADR-038). ASCII `->` for the transition arrow per the P334
|
|
36
47
|
# awk/script portability lesson (no Unicode in machine-read output):
|
|
37
48
|
# CATCHUP P<NNN> <url> state=<state> transition=<KE->Verifying|Verifying->Closed>
|
|
49
|
+
# CATCHUP P<NNN> inbound-<ref> state=<state> transition=<…> direction=inbound
|
|
38
50
|
# SKIP P<NNN> <url> reason=already-logged
|
|
51
|
+
# SKIP P<NNN> inbound-<ref> reason=already-logged
|
|
39
52
|
# SKIP P<NNN> <url> reason=out-of-band
|
|
40
|
-
# Tickets with
|
|
41
|
-
#
|
|
53
|
+
# Tickets with neither surface (and inbound tickets whose Origin ref carries no
|
|
54
|
+
# actionable `#NN`, e.g. `inbound-reported (relayed from other projects)`) are
|
|
55
|
+
# skipped silently — the common case.
|
|
42
56
|
#
|
|
43
57
|
# Trailing summary line (stderr) for the SKILL / human reader:
|
|
44
58
|
# SUMMARY scanned=<N> catchup=<N> skip-logged=<N> skip-out-of-band=<N>
|
|
@@ -49,6 +63,9 @@
|
|
|
49
63
|
# @adr ADR-038 (progressive disclosure — per-row byte budget)
|
|
50
64
|
# @adr ADR-049 (invoked via wr-itil-catchup-scan bin shim, never repo-relative path)
|
|
51
65
|
# @adr ADR-032 (foreground synchronous skill)
|
|
66
|
+
# @adr ADR-076 (reads the `**Origin**: inbound-reported (#NN)` field for the inbound leg)
|
|
67
|
+
# @problem P376 — catchup scanner misses the inbound direction (cross-direction parity)
|
|
68
|
+
# @rfc RFC-028 (consume the Origin field for inbound-reported verdict — extended to the catchup surface)
|
|
52
69
|
# @jtbd JTBD-301 (reporter feedback loop — the catchup's primary job)
|
|
53
70
|
# @jtbd JTBD-006 (AFK-safe worklist scanner)
|
|
54
71
|
# @jtbd JTBD-004 (cross-repo coordination — reconcile local corpus vs upstream trackers)
|
|
@@ -148,6 +165,37 @@ extract_ticket_id() {
|
|
|
148
165
|
echo "P${base%%-*}"
|
|
149
166
|
}
|
|
150
167
|
|
|
168
|
+
# Extract the actionable inbound issue ref from the `**Origin**:
|
|
169
|
+
# inbound-reported (<ref>)` field (ADR-076). Returns the `#NN` /
|
|
170
|
+
# `<repo>#NN` token, or empty when the Origin is not inbound-reported OR
|
|
171
|
+
# carries no actionable issue number (e.g. "relayed from other projects").
|
|
172
|
+
extract_inbound_ref() {
|
|
173
|
+
local line ref
|
|
174
|
+
line="$(grep -m1 -E '^\*\*Origin\*\*:[[:space:]]*inbound-reported' "$1" 2>/dev/null)"
|
|
175
|
+
[ -z "$line" ] && return 0
|
|
176
|
+
# First parenthesised group after `inbound-reported`.
|
|
177
|
+
ref="$(printf '%s\n' "$line" | sed -n 's/.*inbound-reported[[:space:]]*(\([^)]*\)).*/\1/p')"
|
|
178
|
+
[ -z "$ref" ] && return 0
|
|
179
|
+
# Must contain an actionable `#NN` (optionally repo-qualified). Emit the
|
|
180
|
+
# normalised `<repo>#<num>` token; drop trailing prose / spaces.
|
|
181
|
+
printf '%s\n' "$ref" | grep -oE '[A-Za-z0-9._/-]*#[0-9]+' | head -1
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# Does the `## Upstream Lifecycle Updates` log already record an
|
|
185
|
+
# `(inbound)`-tagged entry for the target transition? Distinct from
|
|
186
|
+
# log_has_target: the inbound leg's idempotency must NOT be satisfied by an
|
|
187
|
+
# outbound-tagged entry for the same target (a ticket reported BOTH ways may
|
|
188
|
+
# have posted its outbound verdict but not its inbound one).
|
|
189
|
+
log_has_inbound_target() {
|
|
190
|
+
local file="$1" target="$2"
|
|
191
|
+
awk -v target="$target" '
|
|
192
|
+
/^## Upstream Lifecycle Updates/ { in_section = 1; next }
|
|
193
|
+
/^## / && in_section { in_section = 0 }
|
|
194
|
+
in_section && index($0, "(inbound)") > 0 && index($0, target) > 0 { found = 1 }
|
|
195
|
+
END { exit(found ? 0 : 1) }
|
|
196
|
+
' "$file"
|
|
197
|
+
}
|
|
198
|
+
|
|
151
199
|
# ── Per-ticket scan loop ────────────────────────────────────────────────────
|
|
152
200
|
|
|
153
201
|
SCANNED=0
|
|
@@ -173,14 +221,20 @@ for ticket_file in "${TICKET_FILES[@]}"; do
|
|
|
173
221
|
fi
|
|
174
222
|
SEEN_IDS[$ticket_id]="$ticket_file"
|
|
175
223
|
|
|
176
|
-
#
|
|
177
|
-
|
|
224
|
+
# Detect both surfaces. Outbound = `## Reported Upstream` section;
|
|
225
|
+
# inbound = an actionable `**Origin**: inbound-reported (#NN)` ref.
|
|
226
|
+
has_outbound=0
|
|
227
|
+
grep -q '^## Reported Upstream' "$ticket_file" && has_outbound=1
|
|
228
|
+
inbound_ref="$(extract_inbound_ref "$ticket_file")"
|
|
229
|
+
|
|
230
|
+
# Neither surface → silent skip (the common case).
|
|
231
|
+
if [ "$has_outbound" -eq 0 ] && [ -z "$inbound_ref" ]; then
|
|
178
232
|
continue
|
|
179
233
|
fi
|
|
180
234
|
|
|
181
235
|
SCANNED=$((SCANNED + 1))
|
|
182
236
|
|
|
183
|
-
# Derive the transition the current suffix implies.
|
|
237
|
+
# Derive the transition the current suffix implies (direction-agnostic).
|
|
184
238
|
case "$ticket_file" in
|
|
185
239
|
*.verifying.md|*/verifying/*)
|
|
186
240
|
state="verifying"
|
|
@@ -194,28 +248,40 @@ for ticket_file in "${TICKET_FILES[@]}"; do
|
|
|
194
248
|
continue ;;
|
|
195
249
|
esac
|
|
196
250
|
|
|
197
|
-
|
|
198
|
-
|
|
251
|
+
# ── Outbound leg (`## Reported Upstream`) ──────────────────────────────────
|
|
252
|
+
if [ "$has_outbound" -eq 1 ]; then
|
|
253
|
+
upstream_url="$(extract_upstream_url "$ticket_file")"
|
|
254
|
+
disclosure="$(extract_disclosure_path "$ticket_file")"
|
|
199
255
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
256
|
+
if [ -z "$upstream_url" ] \
|
|
257
|
+
|| [[ "$disclosure" == *out-of-band* ]] \
|
|
258
|
+
|| [[ "$disclosure" == *mailbox* ]]; then
|
|
259
|
+
# Out-of-band / non-gh disclosure path, or no actionable URL → SKIP.
|
|
260
|
+
printf "SKIP %s %s reason=out-of-band\n" "$ticket_id" "${upstream_url:-none}"
|
|
261
|
+
SKIP_OUT_OF_BAND=$((SKIP_OUT_OF_BAND + 1))
|
|
262
|
+
elif log_has_target "$ticket_file" "$log_target"; then
|
|
263
|
+
# Idempotency: the lifecycle log already records this target → SKIP.
|
|
264
|
+
printf "SKIP %s %s reason=already-logged\n" "$ticket_id" "$upstream_url"
|
|
265
|
+
SKIP_LOGGED=$((SKIP_LOGGED + 1))
|
|
266
|
+
else
|
|
267
|
+
printf "CATCHUP %s %s state=%s transition=%s\n" \
|
|
268
|
+
"$ticket_id" "$upstream_url" "$state" "$transition"
|
|
269
|
+
CATCHUP_COUNT=$((CATCHUP_COUNT + 1))
|
|
270
|
+
fi
|
|
207
271
|
fi
|
|
208
272
|
|
|
209
|
-
#
|
|
210
|
-
if
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
273
|
+
# ── Inbound leg (`**Origin**: inbound-reported (#NN)`) ─────────────────────
|
|
274
|
+
if [ -n "$inbound_ref" ]; then
|
|
275
|
+
if log_has_inbound_target "$ticket_file" "$log_target"; then
|
|
276
|
+
# Idempotency: an (inbound)-tagged log entry already records this target.
|
|
277
|
+
printf "SKIP %s inbound-%s reason=already-logged\n" "$ticket_id" "$inbound_ref"
|
|
278
|
+
SKIP_LOGGED=$((SKIP_LOGGED + 1))
|
|
279
|
+
else
|
|
280
|
+
printf "CATCHUP %s inbound-%s state=%s transition=%s direction=inbound\n" \
|
|
281
|
+
"$ticket_id" "$inbound_ref" "$state" "$transition"
|
|
282
|
+
CATCHUP_COUNT=$((CATCHUP_COUNT + 1))
|
|
283
|
+
fi
|
|
214
284
|
fi
|
|
215
|
-
|
|
216
|
-
printf "CATCHUP %s %s state=%s transition=%s\n" \
|
|
217
|
-
"$ticket_id" "$upstream_url" "$state" "$transition"
|
|
218
|
-
CATCHUP_COUNT=$((CATCHUP_COUNT + 1))
|
|
219
285
|
done
|
|
220
286
|
|
|
221
287
|
printf "SUMMARY scanned=%s catchup=%s skip-logged=%s skip-out-of-band=%s\n" \
|
|
@@ -61,6 +61,25 @@ append_lifecycle_log() {
|
|
|
61
61
|
} >> "$path"
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
# Write a ticket carrying an inbound `**Origin**: inbound-reported (<ref>)`
|
|
65
|
+
# field (ADR-076) — the INBOUND surface. Args:
|
|
66
|
+
# $1 = filename (relative to $FIX)
|
|
67
|
+
# $2 = origin-paren content (e.g. "#63", "bbstats#195", "relayed from other projects")
|
|
68
|
+
make_inbound_ticket() {
|
|
69
|
+
local path="$FIX/$1" ref="$2"
|
|
70
|
+
mkdir -p "$(dirname "$path")"
|
|
71
|
+
{
|
|
72
|
+
echo "# Problem: inbound fixture"
|
|
73
|
+
echo ""
|
|
74
|
+
echo "**Status**: fixture"
|
|
75
|
+
echo "**Origin**: inbound-reported ($ref)"
|
|
76
|
+
echo ""
|
|
77
|
+
echo "## Description"
|
|
78
|
+
echo ""
|
|
79
|
+
echo "fixture body"
|
|
80
|
+
} > "$path"
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
@test "catchup-scan: emits CATCHUP for a closed ticket with Reported Upstream and no lifecycle log" {
|
|
65
84
|
make_reported_ticket "113-foo.closed.md" "https://github.com/o/r/issues/5" "public issue"
|
|
66
85
|
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
@@ -173,3 +192,83 @@ append_lifecycle_log() {
|
|
|
173
192
|
[[ "$output" == *"Usage:"* ]]
|
|
174
193
|
[[ "$output" == *"--catchup"* ]] || [[ "$output" == *"catchup-scan"* ]]
|
|
175
194
|
}
|
|
195
|
+
|
|
196
|
+
# ── Inbound direction (P376) ────────────────────────────────────────────────
|
|
197
|
+
# The scanner must ALSO enumerate inbound-reported tickets (`**Origin**:
|
|
198
|
+
# inbound-reported (#NN)`, ADR-076) so the maintainer no longer hand-greps
|
|
199
|
+
# them after each catchup run. Direction-tagged so the worklist stays
|
|
200
|
+
# unambiguous; idempotency re-checks the `(inbound)`-tagged lifecycle log.
|
|
201
|
+
|
|
202
|
+
@test "catchup-scan: emits inbound CATCHUP for a verifying ticket with Origin inbound-reported (KE->Verifying)" {
|
|
203
|
+
make_inbound_ticket "220-inbound.verifying.md" "#63"
|
|
204
|
+
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
205
|
+
[ "$status" -eq 0 ]
|
|
206
|
+
[[ "$output" == *"CATCHUP P220 inbound-#63 state=verifying transition=KE->Verifying direction=inbound"* ]]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@test "catchup-scan: emits inbound CATCHUP for a closed ticket with Origin inbound-reported (Verifying->Closed)" {
|
|
210
|
+
make_inbound_ticket "211-inbound.closed.md" "#97"
|
|
211
|
+
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
212
|
+
[ "$status" -eq 0 ]
|
|
213
|
+
[[ "$output" == *"CATCHUP P211 inbound-#97 state=closed transition=Verifying->Closed direction=inbound"* ]]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@test "catchup-scan: inbound idempotency — SKIP/already-logged when an (inbound)-tagged log records the target" {
|
|
217
|
+
make_inbound_ticket "220-inbound.verifying.md" "#63"
|
|
218
|
+
append_lifecycle_log "220-inbound.verifying.md" "Known Error → Verification Pending (inbound)"
|
|
219
|
+
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
220
|
+
[ "$status" -eq 0 ]
|
|
221
|
+
[[ "$output" == *"SKIP P220 inbound-#63 reason=already-logged"* ]]
|
|
222
|
+
[[ "$output" != *"CATCHUP P220"* ]]
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@test "catchup-scan: inbound idempotency does NOT match an outbound-tagged log for the same target" {
|
|
226
|
+
# A Closed log entry without (inbound) is the outbound leg's marker; the
|
|
227
|
+
# inbound leg must still emit CATCHUP (its own verdict was never posted).
|
|
228
|
+
make_inbound_ticket "228-inbound.closed.md" "#42"
|
|
229
|
+
append_lifecycle_log "228-inbound.closed.md" "Verification Pending → Closed"
|
|
230
|
+
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
231
|
+
[ "$status" -eq 0 ]
|
|
232
|
+
[[ "$output" == *"CATCHUP P228 inbound-#42 state=closed transition=Verifying->Closed direction=inbound"* ]]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@test "catchup-scan: non-actionable Origin (no #NN) emits no inbound line" {
|
|
236
|
+
make_inbound_ticket "300-relayed.verifying.md" "relayed from other projects"
|
|
237
|
+
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
238
|
+
[ "$status" -eq 0 ]
|
|
239
|
+
[[ "$output" != *"P300"* ]]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@test "catchup-scan: cross-repo inbound ref is preserved in the worklist line" {
|
|
243
|
+
make_inbound_ticket "250-xrepo.closed.md" "bbstats#195"
|
|
244
|
+
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
245
|
+
[ "$status" -eq 0 ]
|
|
246
|
+
[[ "$output" == *"CATCHUP P250 inbound-bbstats#195 state=closed transition=Verifying->Closed direction=inbound"* ]]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@test "catchup-scan: a ticket with BOTH surfaces emits an outbound AND an inbound line" {
|
|
250
|
+
make_reported_ticket "330-both.closed.md" "https://github.com/o/r/issues/5" "public issue"
|
|
251
|
+
printf '**Origin**: inbound-reported (#71)\n' >> "$FIX/330-both.closed.md"
|
|
252
|
+
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
253
|
+
[ "$status" -eq 0 ]
|
|
254
|
+
[[ "$output" == *"CATCHUP P330 https://github.com/o/r/issues/5 state=closed transition=Verifying->Closed"* ]]
|
|
255
|
+
[[ "$output" == *"CATCHUP P330 inbound-#71 state=closed transition=Verifying->Closed direction=inbound"* ]]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@test "catchup-scan: inbound worklist line stays within the ADR-038 150-byte budget" {
|
|
259
|
+
make_inbound_ticket "999-long.closed.md" "averylongupstreamreponame/withdeepsubpath#123456"
|
|
260
|
+
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
261
|
+
[ "$status" -eq 0 ]
|
|
262
|
+
line="$(printf '%s\n' "$output" | grep '^CATCHUP P999')"
|
|
263
|
+
[ -n "$line" ]
|
|
264
|
+
[ "${#line}" -le 150 ]
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@test "catchup-scan: inbound-only Open/Known-Error tickets are out of the catchup corpus" {
|
|
268
|
+
make_inbound_ticket "400-open.open.md" "#12"
|
|
269
|
+
make_inbound_ticket "401-ke.known-error.md" "#13"
|
|
270
|
+
run bash "$SCRIPT" --problems-dir "$FIX"
|
|
271
|
+
[ "$status" -eq 0 ]
|
|
272
|
+
[[ "$output" != *"P400"* ]]
|
|
273
|
+
[[ "$output" != *"P401"* ]]
|
|
274
|
+
}
|