@1aboveio/cicd-skills 0.1.21 → 0.1.22
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 +2 -2
- package/skills/cloud-build/SKILL.md +1 -1
- package/skills/cloud-debug/SKILL.md +1 -1
- package/skills/cloud-deploy/SKILL.md +1 -1
- package/skills/docker/SKILL.md +1 -1
- package/skills/github-actions/SKILL.md +4 -2
- package/skills/github-actions/references/debugging.md +21 -0
- package/skills/github-actions/references/post-deploy-and-publishing.md +7 -0
- package/skills/github-actions/scripts/watch-run.mjs +511 -0
- package/skills/github-flow/SKILL.md +35 -9
- package/skills/github-flow/scripts/watch-checks.mjs +561 -0
- package/skills/google-cloud/SKILL.md +1 -1
- package/skills/mergify/SKILL.md +2 -2
- package/skills/mergify/references/diagnosis.md +6 -1
- package/skills/mergify/references/watch-contract.md +10 -7
- package/skills/mergify/scripts/watch-pr-delivery-core.mjs +15 -30
- package/skills/podman/SKILL.md +1 -1
|
@@ -43,11 +43,16 @@ Queue states worth recognising:
|
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
gh pr checks <PR> # check-run table, exit 1 if any failed
|
|
46
|
-
gh pr checks <PR> --watch # block until they settle
|
|
47
46
|
gh pr view <PR> --json state,isDraft,mergeable,mergeStateStatus,statusCheckRollup
|
|
48
47
|
gh pr view <PR> --json labels --jq '.labels[].name' # queued / dequeued labels
|
|
49
48
|
```
|
|
50
49
|
|
|
50
|
+
A one-shot table is the right tool here. Waiting is not: `gh pr checks <PR> --watch` repaints
|
|
51
|
+
its table and its elapsed clock every 30 seconds, which reads as progress to anything watching
|
|
52
|
+
the log while the actual CI steps slide out of view. To wait for CI, run the delivery watcher
|
|
53
|
+
([`watch-contract.md`](watch-contract.md)) — or, for a PR outside a queue, the `github-flow`
|
|
54
|
+
skill's `scripts/watch-checks.mjs`, which emits one record per real check-state change.
|
|
55
|
+
|
|
51
56
|
Mergify labels a PR `queued` on entry and `dequeued` on removal
|
|
52
57
|
(`merge_queue.queued_label` / `dequeued_label`). These are **lifecycle** signals,
|
|
53
58
|
not a cheap membership check — what they mean depends on what you saw before
|
|
@@ -49,7 +49,7 @@ Two of the four were written *after* the agent had been told to check for dequeu
|
|
|
49
49
|
|
|
50
50
|
### `phases` — the progression as output
|
|
51
51
|
|
|
52
|
-
The watcher emits `phases`: the ordered list of lifecycle phases actually observed, on both the terminal verdict and every
|
|
52
|
+
The watcher emits `phases`: the ordered list of lifecycle phases actually observed, on both the terminal verdict and every transition record. It resolves the one ambiguity the terminal outcome cannot:
|
|
53
53
|
|
|
54
54
|
| terminal | `phases` | meaning | fix |
|
|
55
55
|
|---|---|---|---|
|
|
@@ -181,7 +181,8 @@ so the newest batch wins over stale comment fallback.
|
|
|
181
181
|
- **Run the bundled script. Do not hand-roll a watcher** — see the progression section above for why the hand-rolled shape cannot hold the contract, whatever care goes into the code. (If you are reading a legacy shell watcher: avoid shell-reserved variable names such as `status`.)
|
|
182
182
|
- Keep enqueue and watch separate. A watch observes only; it does not post `@mergifyio queue`, `@mergifyio dequeue`, or merge.
|
|
183
183
|
- Confirm the PR is queued, or can become queued, *before* launching the watch — see [Enqueue Before You Watch](#enqueue-before-you-watch). A watch that cannot enqueue, on a PR nothing else will enqueue either, can only end `STALLED`.
|
|
184
|
-
- Emit
|
|
184
|
+
- Emit the initial observation, then delivery-state or provider-readability transitions. Repeated identical polls stay quiet. A process supervisor counts new log bytes as activity even when a JSON record says `progress: false`, so labelling every heartbeat is insufficient. The initial record makes a waiting watch visible; the terminal verdict and existing no-change bounds explain how it ended. stdout remains exactly one terminal JSON document; stderr carries transition records and diagnostics.
|
|
185
|
+
- Mark observation records `activity: true`; set `progress`/`changed` only when the canonical delivery state changed. Provider readability changes remain visible without resetting that change clock. `at`, `sinceChangeMs` and `elapsedMs` are diagnostic clocks, never progress. Keep the queue memory, readable-source snapshots, and existing stall bounds across quiet polls.
|
|
185
186
|
- Bound on stalled state, not total elapsed time alone. `waiting_for_previous_batches` can be slow but healthy if position/state changes.
|
|
186
187
|
|
|
187
188
|
## Script
|
|
@@ -203,14 +204,14 @@ Options:
|
|
|
203
204
|
| `--admission-stall-minutes` | `3` | pre-admission terminal stall bound — no observable change *before this process has observed membership* is `STALLED` with `reason: "admission-stall"`. Shorter on purpose: after enqueue, queue entry should appear quickly, and a dead admission path must not wait out the full post-entry bound. Capped at `--stall-minutes` so a caller that shortens the post-entry bound keeps both phases tight. Applies only while no non-Mergify check is pending; while CI runs, the post-entry bound applies, so a CI-gated admission needs no relaunch |
|
|
204
205
|
| `--max-minutes` | `60` | absolute safety ceiling; bounds the watch even while the queue keeps reporting change, so a busy queue whose position keeps moving cannot keep the stall clock resetting forever. **60 minutes is also a hard maximum** — a larger value exits `2` rather than being silently clamped, because a caller that believes it has a four-hour watch and has one hour is worse off than one told plainly to relaunch. An hour without a verdict is a queue to hand back to a human or relaunch. The sibling `resolve-issues` watcher enforces the same ceiling; a cap honoured by one of two watchers is not a cap |
|
|
205
206
|
| `--slow-job-minutes` | `5` | warn when a single CI job runs longer than this (see [CI profile](#ci-profile)) |
|
|
206
|
-
| `--quiet` | off | suppress
|
|
207
|
+
| `--quiet` | off | suppress delivery/readability transition records on stderr (slow-job warnings and the closing profile summary still print) |
|
|
207
208
|
|
|
208
209
|
Output streams:
|
|
209
210
|
|
|
210
211
|
| stream | content |
|
|
211
212
|
|---|---|
|
|
212
213
|
| stdout | exactly one pretty-printed terminal JSON verdict, at exit — carrying `phases`, `seenQueued`, the outcome, and `ciProfile` |
|
|
213
|
-
| stderr | one compact JSON record per
|
|
214
|
+
| stderr | one compact JSON record per delivery/readability transition, marked `activity: true` (an observation was emitted) with `progress` set to whether the delivery state actually changed, plus the live outcome, PR labels, queue membership/position, `phases`, `changed`, and `sinceChangeMs` — the stall clock made visible; plus one `{"warning":"slow-ci-job"}` record per slow CI job; and, last, one `{"ciProfileSummary":true}` line after the verdict |
|
|
214
215
|
|
|
215
216
|
Each regular-poll subprocess read (`gh pr view`, `mergify queue show`) also carries its own bounded
|
|
216
217
|
timeout — a small multiple of `--interval-seconds`, clamped to never exceed the **effective
|
|
@@ -265,9 +266,11 @@ returns the PR's check runs with their start and finish times.
|
|
|
265
266
|
`{"ciProfileSummary":true,"outcome","verdict","jobCount","slowJobMinutes","slowest","slowJobs"}`,
|
|
266
267
|
with at most three slow jobs named as `"<name> <sec>s"`. The verdict is pretty-printed over
|
|
267
268
|
many lines, so a short log tail lands inside `phases` and misses the profile; this line is
|
|
268
|
-
what a 10-line tail shows. A background runner that reports only the exit code
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
what a 10-line tail shows. A background runner that reports only the exit code shows
|
|
270
|
+
neither: read the log tail when the watch ends, and report the profile. Diagnose movement
|
|
271
|
+
from the emitted `progress`/`outcome` and phases. Repeated polls stay quiet, so a process
|
|
272
|
+
supervisor cannot mistake periodic heartbeat writes for progress. Identity #894
|
|
273
|
+
(2026-09-29) warned about a 338s job that no one read.
|
|
271
274
|
|
|
272
275
|
Profiling is observation only: it never changes the outcome or the exit code. A slow-job
|
|
273
276
|
warning is a lead for whoever owns the CI (split the job, cache, move it to a nightly lane) —
|
|
@@ -319,9 +319,8 @@ export const DEFAULT_SLOW_JOB_MINUTES = 5
|
|
|
319
319
|
//
|
|
320
320
|
// Over-ceiling values are REFUSED (exit 2), not silently clamped. Clamping would let a caller
|
|
321
321
|
// believe it had a four-hour watch while getting one hour, and the two streams here are already
|
|
322
|
-
// committed: stdout is exactly one terminal JSON verdict
|
|
323
|
-
//
|
|
324
|
-
// exit-2 usage error is loud, immediate, and matches how every other bad bound is handled.
|
|
322
|
+
// committed: stdout is exactly one terminal JSON verdict; stderr contains NDJSON transitions
|
|
323
|
+
// and diagnostics. An exit-2 usage error is loud, immediate, and matches other bad bounds.
|
|
325
324
|
export const MAX_MINUTES_CEILING = 60
|
|
326
325
|
|
|
327
326
|
export function readTimeoutMs(intervalSeconds, boundMinutes, readsPerPoll = 1) {
|
|
@@ -730,28 +729,12 @@ export function classifyDequeue({ rawReason = null, failedChecks = [] } = {}) {
|
|
|
730
729
|
return 'unknown'
|
|
731
730
|
}
|
|
732
731
|
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
// `NOT_YET_QUEUED` or `QUEUED` — the overwhelmingly common case, and the entire healthy path —
|
|
738
|
-
// wrote zero bytes for its whole life. Reproduced live on PR #1396: 25 minutes of watching a
|
|
739
|
-
// PR that was never queued produced a log containing only the harness's own spawn banner, and
|
|
740
|
-
// the watch was killed 4 minutes short of the then-30-minute stall bound, so it never printed
|
|
741
|
-
// even its terminal verdict. (The bound is 10 now, so that same watch would settle on its own
|
|
742
|
-
// well before a human gave up on it — but the heartbeat is what makes the wait legible while
|
|
743
|
-
// it runs, and no bound short enough to substitute for it would be long enough to be correct.) A silent process is indistinguishable from a hung or dead one, which is
|
|
744
|
-
// exactly the state the caller is trying to resolve, and the hand-written shell watchers this
|
|
745
|
-
// script is meant to replace all printed a per-poll status line. Losing that on the way to a
|
|
746
|
-
// correct state machine traded one failure (wrong answers) for another (no answers).
|
|
747
|
-
//
|
|
748
|
-
// Deliberately stderr, not stdout: stdout is the machine contract — exactly one pretty-printed
|
|
749
|
-
// terminal JSON object, which callers parse whole — and interleaving heartbeats into it would
|
|
750
|
-
// break every such caller. stderr is unparsed by the contract and is merged into the log by
|
|
751
|
-
// every process supervisor that would run this, which is the place a human actually looks.
|
|
752
|
-
// `terminal: false` and the separate stream both distinguish these from the real verdict.
|
|
732
|
+
// Emit the initial delivery snapshot and subsequent delivery/readability transitions.
|
|
733
|
+
// Repeated snapshots stay quiet: process supervisors count newly written bytes as activity
|
|
734
|
+
// even when a record says progress:false. A readability transition remains visible without
|
|
735
|
+
// resetting the delivery change clock. stdout remains one terminal JSON verdict.
|
|
753
736
|
export function progressRecord({ payload, changed, sinceChangeMs, nowIso }) {
|
|
754
|
-
return JSON.stringify({ ...payload,
|
|
737
|
+
return JSON.stringify({ ...payload, activity: true, progress: changed, changed, sinceChangeMs, at: nowIso })
|
|
755
738
|
}
|
|
756
739
|
|
|
757
740
|
// Defaults. `stallMinutes` is a NO-CHANGE bound, not an elapsed-time one — any observable
|
|
@@ -813,11 +796,12 @@ Options:
|
|
|
813
796
|
--seen-queued Resume hint only: keep phases/callers aware the PR was queued.
|
|
814
797
|
Does NOT seed lost-membership counting — membership must be
|
|
815
798
|
observed in this process (everActive) before DEQUEUED can fire.
|
|
816
|
-
--quiet Suppress
|
|
799
|
+
--quiet Suppress delivery/readability transition records on stderr
|
|
817
800
|
|
|
818
801
|
Output:
|
|
819
802
|
stdout exactly one pretty-printed terminal JSON verdict, at exit
|
|
820
|
-
stderr one compact JSON
|
|
803
|
+
stderr one compact JSON record per delivery/readability change (activity:true, progress:<the delivery state
|
|
804
|
+
actually changed>), unless --quiet;
|
|
821
805
|
plus one {"warning":"slow-ci-job"} record per CI job that runs past
|
|
822
806
|
--slow-job-minutes (emitted even with --quiet); and, last of all, one
|
|
823
807
|
{"ciProfileSummary":true} line (outcome, profile verdict, slow jobs) so a
|
|
@@ -882,6 +866,7 @@ export async function runCli(argv) {
|
|
|
882
866
|
let missingActiveReads = 0
|
|
883
867
|
let stallObservationCache = null
|
|
884
868
|
let lastChangeMs = startedMs
|
|
869
|
+
let lastReadability = null
|
|
885
870
|
let observations = 0
|
|
886
871
|
let phases = []
|
|
887
872
|
let lastBatchPrNumber = null
|
|
@@ -890,7 +875,7 @@ export async function runCli(argv) {
|
|
|
890
875
|
const warnedSlowJobs = new Set()
|
|
891
876
|
const slowThresholdMs = args.slowJobMinutes * 60_000
|
|
892
877
|
// Warn once per slow job run. Warnings go to stderr even under --quiet: they are the point
|
|
893
|
-
// of profiling, and --quiet only silences
|
|
878
|
+
// of profiling, and --quiet only silences routine transition records.
|
|
894
879
|
const warnSlowJobs = () => {
|
|
895
880
|
for (const { key, job } of newSlowJobs(ciJobs, { thresholdMs: slowThresholdMs, warned: warnedSlowJobs })) {
|
|
896
881
|
warnedSlowJobs.add(key)
|
|
@@ -1025,12 +1010,12 @@ export async function runCli(argv) {
|
|
|
1025
1010
|
lastChangeMs = Date.now()
|
|
1026
1011
|
}
|
|
1027
1012
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
if (!args.quiet) {
|
|
1013
|
+
const readability = JSON.stringify([pr.readable !== false, queue.readable !== false])
|
|
1014
|
+
if (!args.quiet && (changed || readability !== lastReadability)) {
|
|
1031
1015
|
const now = Date.now()
|
|
1032
1016
|
console.error(progressRecord({ payload, changed, sinceChangeMs: now - lastChangeMs, nowIso: new Date(now).toISOString() }))
|
|
1033
1017
|
}
|
|
1018
|
+
lastReadability = readability
|
|
1034
1019
|
|
|
1035
1020
|
const elapsedMs = Date.now() - startedMs
|
|
1036
1021
|
if (elapsedMs >= args.maxMinutes * 60_000) {
|
package/skills/podman/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: podman
|
|
3
3
|
description: "Debug, design, or fix Podman: rootless/rootful containers, pods, podman-compose, the socket and systemd services, Docker-compatible API (DOCKER_HOST/CONTAINER_HOST), networking, volumes, SELinux labels, Docker migration, and CI/SSH provisioning of rootless sockets (user lingering, XDG_RUNTIME_DIR). Covers FailedToOpenSocket and systemctl --user DBUS/XDG errors."
|
|
4
|
-
release-version: 0.50.
|
|
4
|
+
release-version: 0.50.2
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Podman Operations
|