@1aboveio/skills 1.0.7 → 1.0.8

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.
Files changed (75) hide show
  1. package/package.json +2 -2
  2. package/skills/app-dev/airflow/SKILL.md +1 -1
  3. package/skills/app-dev/android-foundation/SKILL.md +92 -34
  4. package/skills/app-dev/android-foundation/assets/android-ci-job.yml +13 -7
  5. package/skills/app-dev/android-foundation/references/ci.md +23 -13
  6. package/skills/app-dev/android-foundation/references/delivery-focus.md +29 -0
  7. package/skills/app-dev/android-foundation/references/diagnosis.md +136 -0
  8. package/skills/app-dev/android-foundation/references/gotchas.md +20 -11
  9. package/skills/app-dev/android-foundation/references/setup.md +15 -6
  10. package/skills/app-dev/android-foundation/scripts/device-run.sh +148 -42
  11. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +322 -58
  12. package/skills/app-dev/app-debug/SKILL.md +1 -1
  13. package/skills/app-dev/better-auth/SKILL.md +1 -1
  14. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  15. package/skills/app-dev/data-quality/SKILL.md +1 -1
  16. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  17. package/skills/app-dev/monorepo/SKILL.md +1 -1
  18. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  19. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  20. package/skills/app-dev/pyspark/SKILL.md +1 -1
  21. package/skills/app-dev/python-backend/SKILL.md +1 -1
  22. package/skills/app-dev/shadcn/SKILL.md +1 -1
  23. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  24. package/skills/cicd/cloud-build/SKILL.md +1 -1
  25. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  26. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  27. package/skills/cicd/docker/SKILL.md +1 -1
  28. package/skills/cicd/github-actions/SKILL.md +1 -1
  29. package/skills/cicd/github-flow/SKILL.md +1 -1
  30. package/skills/cicd/google-cloud/SKILL.md +1 -1
  31. package/skills/cicd/mergify/SKILL.md +1 -1
  32. package/skills/cicd/podman/SKILL.md +1 -1
  33. package/skills/content/article-review/SKILL.md +1 -1
  34. package/skills/content/article-to-image/SKILL.md +1 -1
  35. package/skills/content/flowchart-svg/SKILL.md +1 -1
  36. package/skills/content/image-gen/SKILL.md +1 -1
  37. package/skills/content/mermaid/SKILL.md +1 -1
  38. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  39. package/skills/content/wechat-publisher/SKILL.md +1 -1
  40. package/skills/content/wechat-search/SKILL.md +1 -1
  41. package/skills/content/xai-search/SKILL.md +1 -1
  42. package/skills/core/distill-writing/SKILL.md +1 -1
  43. package/skills/core/doc-to-md/SKILL.md +1 -1
  44. package/skills/core/editorial-review/SKILL.md +1 -1
  45. package/skills/core/house-style/SKILL.md +1 -1
  46. package/skills/core/plain-language/SKILL.md +1 -1
  47. package/skills/core/polish-cn/SKILL.md +1 -1
  48. package/skills/core/reg-parser/SKILL.md +1 -1
  49. package/skills/core/terminology/SKILL.md +1 -1
  50. package/skills/core/write-doc/SKILL.md +1 -1
  51. package/skills/engineering/better-review/SKILL.md +1 -1
  52. package/skills/engineering/contributing/SKILL.md +1 -1
  53. package/skills/engineering/e2e-test/SKILL.md +1 -1
  54. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  55. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  56. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  57. package/skills/engineering/golden-path/SKILL.md +1 -1
  58. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  59. package/skills/engineering/resolve-issues/SKILL.md +11 -20
  60. package/skills/engineering/resolve-issues/references/delivery-progress.md +63 -0
  61. package/skills/engineering/resolve-issues/references/expire.md +24 -11
  62. package/skills/engineering/resolve-issues/references/implementation.md +9 -1
  63. package/skills/engineering/resolve-issues/references/task-plan.md +7 -4
  64. package/skills/engineering/resolve-release/SKILL.md +1 -1
  65. package/skills/engineering/rush-issues/SKILL.md +5 -2
  66. package/skills/engineering/rush-release/SKILL.md +1 -1
  67. package/skills/engineering/test-quality/SKILL.md +1 -1
  68. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  69. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  70. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  71. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  72. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  73. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  74. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  75. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
@@ -16,6 +16,13 @@
16
16
  # Optional environment:
17
17
  # POSTGRES_IMAGE (postgres:17) DB_URL_VAR (SHARD_ADMIN_URL) DB_NAME_VAR (SHARD_DB_NAME)
18
18
  # LOG_DIR (.cache/logs) BOUNDED_SECONDS (30) BOOT_ATTEMPTS (60) KILL_GRACE_SECONDS (10)
19
+ # JOURNEY_SECONDS (0 = no deadline; caller chooses a positive whole-journey limit)
20
+ #
21
+ # ADB_SERVER_SOCKET / ANDROID_ADB_SERVER_PORT are inherited unchanged. The caller
22
+ # selects a private ADB endpoint before entry; this script never stops that server.
23
+ # Journey children must stay in their process group (no daemonizing / setsid).
24
+ # Send TERM to this launcher and allow cleanup to finish, rather than killing it.
25
+ # A forced kill or host crash cannot clean up; keep the printed resource directory.
19
26
  #
20
27
  # Exports ANDROID_EMULATOR_SERIAL, $DB_URL_VAR and $DB_NAME_VAR to both commands.
21
28
  # Prints `original_stage_exit=<n> cleanup_exit=<n>` last and exits with the
@@ -47,6 +54,26 @@ LOG_DIR="${LOG_DIR:-.cache/logs}"
47
54
  BOUNDED_SECONDS="${BOUNDED_SECONDS:-30}"
48
55
  BOOT_ATTEMPTS="${BOOT_ATTEMPTS:-60}"
49
56
  KILL_GRACE_SECONDS="${KILL_GRACE_SECONDS:-10}"
57
+ JOURNEY_SECONDS="${JOURNEY_SECONDS:-0}"
58
+
59
+ # Reject invalid limits before creating any owned resources.
60
+ python3 -c 'import math, sys
61
+ for value in sys.argv[1:4]:
62
+ number = float(value)
63
+ if not math.isfinite(number) or number < 0: sys.exit("limits must be finite and non-negative")
64
+ if float(sys.argv[1]) == 0: sys.exit("BOUNDED_SECONDS must be positive")
65
+ if not sys.argv[4].isdigit() or int(sys.argv[4]) < 1: sys.exit("BOOT_ATTEMPTS must be a positive integer")' \
66
+ "$BOUNDED_SECONDS" "$KILL_GRACE_SECONDS" "$JOURNEY_SECONDS" "$BOOT_ATTEMPTS" || exit 2
67
+ for variable in "$DB_URL_VAR" "$DB_NAME_VAR"; do
68
+ if ! [[ "$variable" =~ ^[a-zA-Z_][a-zA-Z_0-9]*$ ]]; then
69
+ echo "invalid database environment variable: $variable" >&2
70
+ exit 2
71
+ fi
72
+ done
73
+ if test "$DB_URL_VAR" = "$DB_NAME_VAR"; then
74
+ echo "DB_URL_VAR and DB_NAME_VAR must differ" >&2
75
+ exit 2
76
+ fi
50
77
 
51
78
  if ! [[ "$EMULATOR_PORT" =~ ^[0-9]+$ ]] || (( EMULATOR_PORT < 5554 || EMULATOR_PORT > 5584 || EMULATOR_PORT % 2 )); then
52
79
  echo "EMULATOR_PORT must be an even port in 5554-5584: $EMULATOR_PORT" >&2
@@ -56,67 +83,139 @@ fi
56
83
  mkdir -p "$LOG_DIR"
57
84
  run_dir="$(mktemp -d "${TMPDIR:-/tmp}/android-device-run.XXXXXX")"
58
85
  emulator_pid=""
86
+ active_pid=""
59
87
  db_ready=""
88
+ container_started=""
89
+ launch_in_progress=0
90
+ pending_signal=0
91
+ handle_signal() {
92
+ pending_signal="$1"
93
+ # Do not abandon a spawned supervisor before its PID has been recorded.
94
+ if test "$launch_in_progress" -eq 0; then exit "$pending_signal"; fi
95
+ }
96
+ honor_pending_signal() {
97
+ if test "$pending_signal" -ne 0; then exit "$pending_signal"; fi
98
+ }
99
+ echo "Owned resource records: $run_dir" >&2
60
100
  export ANDROID_EMULATOR_SERIAL="emulator-$EMULATOR_PORT"
61
101
 
62
- bounded() {
63
- # Kill the whole process group on timeout: a surviving grandchild would hold
64
- # the output pipe open and stall the caller past the deadline.
65
- python3 -c 'import os, signal, subprocess, sys
66
- limit = float(sys.argv[1])
67
- child = subprocess.Popen(sys.argv[2:], start_new_session=True)
102
+ supervise() {
103
+ # Only called in a background subshell: exec makes $! the signal-aware owner.
104
+ # Keep the process group owned even if its original leader exits first.
105
+ exec python3 -c 'import os, signal, subprocess, sys, time
106
+ limit, grace = map(float, sys.argv[1:3])
107
+ mode = sys.argv[3]
108
+ cancelled = 0
109
+ def cancel(signum, frame):
110
+ global cancelled
111
+ cancelled = 128 + signum
112
+ signal.signal(signal.SIGTERM, cancel)
113
+ signal.signal(signal.SIGINT, cancel)
68
114
  try:
69
- sys.exit(child.wait(timeout=limit))
70
- except subprocess.TimeoutExpired:
71
- os.killpg(child.pid, signal.SIGKILL)
72
- child.wait()
73
- print("Command exceeded %s seconds: %s" % (sys.argv[1], " ".join(sys.argv[2:])), file=sys.stderr)
74
- sys.exit(124)' "$BOUNDED_SECONDS" "$@"
115
+ child = subprocess.Popen(sys.argv[4:], start_new_session=True)
116
+ except FileNotFoundError as error:
117
+ print(error, file=sys.stderr)
118
+ sys.exit(127)
119
+ except OSError as error:
120
+ print(error, file=sys.stderr)
121
+ sys.exit(126)
122
+ def group_alive():
123
+ # Ignore unreapable orphan zombies. macOS can return EPERM, not ESRCH, for
124
+ # an empty group under a sandbox, so inspect membership before signalling.
125
+ listing = subprocess.check_output(["ps", "-A", "-o", "pgid=,stat="], text=True)
126
+ return any(int(parts[0]) == child.pid and not parts[1].startswith("Z")
127
+ for line in listing.splitlines() if len(parts := line.split()) == 2)
128
+ def send(sig):
129
+ try: os.killpg(child.pid, sig)
130
+ except ProcessLookupError: pass
131
+ except PermissionError:
132
+ if group_alive(): raise
133
+ end = time.monotonic() + limit if limit else float("inf")
134
+ timed_out = False
135
+ while child.poll() is None and not cancelled:
136
+ if time.monotonic() >= end:
137
+ timed_out = True
138
+ break
139
+ time.sleep(0.02)
140
+ forced = False
141
+ if timed_out:
142
+ send(signal.SIGKILL)
143
+ else:
144
+ send(signal.SIGTERM)
145
+ stop = time.monotonic() + grace
146
+ while group_alive() and time.monotonic() < stop:
147
+ child.poll()
148
+ time.sleep(0.02)
149
+ if group_alive():
150
+ forced = True
151
+ send(signal.SIGKILL)
152
+ status = child.wait()
153
+ status = 128 - status if status < 0 else status
154
+ if timed_out:
155
+ print("Command exceeded %s seconds: %s" % (sys.argv[1], " ".join(sys.argv[4:])), file=sys.stderr)
156
+ sys.exit(124)
157
+ if mode == "emulator" and forced: sys.exit(1)
158
+ sys.exit(cancelled or status or (1 if forced else 0))' "$1" "$KILL_GRACE_SECONDS" "${@:2}"
159
+ }
160
+
161
+ managed() {
162
+ local limit="$1" status=0
163
+ shift
164
+ launch_in_progress=1
165
+ supervise "$limit" command "$@" <&0 &
166
+ active_pid=$!
167
+ launch_in_progress=0
168
+ honor_pending_signal
169
+ # Bash defers signal traps for foreground commands, but not for this wait.
170
+ wait "$active_pid" || status=$?
171
+ active_pid=""
172
+ return "$status"
75
173
  }
76
174
 
175
+ bounded() { managed "$BOUNDED_SECONDS" "$@"; }
176
+
77
177
  cleanup() {
78
178
  original_status=$?
79
- trap - EXIT INT TERM
179
+ trap - EXIT
180
+ trap '' INT TERM
181
+ pending_signal=0
182
+ launch_in_progress=0
80
183
  set +e
81
184
  cleanup_status=0
185
+ if test -n "$active_pid"; then
186
+ kill -TERM "$active_pid" 2>/dev/null
187
+ wait "$active_pid" || :
188
+ active_pid=""
189
+ fi
82
190
  if test -n "$db_ready"; then
83
191
  bounded "${drop_cmd[@]}" || cleanup_status=1
84
192
  fi
85
193
  if test -n "$emulator_pid"; then
86
- if kill -0 "$emulator_pid" 2>/dev/null; then
87
- kill -TERM "$emulator_pid" || cleanup_status=1
88
- fi
89
- for _ in $(seq 1 "$KILL_GRACE_SECONDS"); do
90
- kill -0 "$emulator_pid" 2>/dev/null || break
91
- sleep 1
92
- done
93
- if kill -0 "$emulator_pid" 2>/dev/null; then
94
- cleanup_status=1
95
- kill -KILL "$emulator_pid"
96
- sleep 1
97
- fi
98
- if kill -0 "$emulator_pid" 2>/dev/null; then
99
- cleanup_status=1
100
- else
101
- wait "$emulator_pid"
102
- emulator_status=$?
103
- # 0 = clean exit; 143 = ended by our TERM. A KILL was already counted above.
104
- case "$emulator_status" in 0|143) ;; *) cleanup_status=1 ;; esac
105
- fi
194
+ kill -TERM "$emulator_pid" 2>/dev/null
195
+ emulator_status=0
196
+ wait "$emulator_pid" || emulator_status=$?
197
+ case "$emulator_status" in 0|143) ;; *) cleanup_status=1 ;; esac
106
198
  fi
107
199
  if test -s "$run_dir/postgres.cid"; then
108
200
  container_id="$(< "$run_dir/postgres.cid")"
109
201
  bounded docker rm --force "$container_id" >/dev/null || cleanup_status=1
110
- remaining="$(bounded docker ps -a --no-trunc --filter "id=$container_id" --format '{{.ID}}')" || cleanup_status=1
111
- test -z "$remaining" || cleanup_status=1
202
+ bounded docker ps -a --no-trunc --filter "id=$container_id" --format '{{.ID}}' \
203
+ > "$run_dir/remaining" || cleanup_status=1
204
+ test ! -s "$run_dir/remaining" || cleanup_status=1
205
+ elif test -n "$container_started"; then
206
+ echo "Container startup not acknowledged; ownership unknown, inspect $run_dir before recovery" >&2
207
+ cleanup_status=1
112
208
  fi
113
- printf 'original_stage_exit=%s cleanup_exit=%s\n' "$original_status" "$cleanup_status"
209
+ printf 'original_stage_exit=%s cleanup_exit=%s\n' "$original_status" "$cleanup_status" >&3
114
210
  if test "$original_status" -ne 0; then exit "$original_status"; fi
115
211
  exit "$cleanup_status"
116
212
  }
213
+ # EXIT can run inside a redirected function (notably docker run >/dev/null).
214
+ # Keep the caller's output for the final status line, even on startup failure.
215
+ exec 3>&1
117
216
  trap cleanup EXIT
118
- trap 'exit 130' INT
119
- trap 'exit 143' TERM
217
+ trap 'handle_signal 130' INT
218
+ trap 'handle_signal 143' TERM
120
219
 
121
220
  # Every port this run listens on or dials must be free before anything starts.
122
221
  python3 -c 'import socket, sys
@@ -130,21 +229,28 @@ bounded adb start-server
130
229
 
131
230
  # -memory 4096 overrides the AVD's hw.ramSize, so a reused 2 GB AVD cannot starve the
132
231
  # guest kernel's vsock allocations and drop adb mid-journey (references/gotchas.md).
133
- "$ANDROID_HOME/emulator/emulator" -avd "$SETUP_AVD" -port "$EMULATOR_PORT" -memory 4096 \
232
+ launch_in_progress=1
233
+ supervise 0 emulator "$ANDROID_HOME/emulator/emulator" -avd "$SETUP_AVD" -port "$EMULATOR_PORT" -memory 4096 \
134
234
  -no-window -no-audio -no-snapshot -gpu swiftshader_indirect -accel on \
135
235
  > "$LOG_DIR/emulator.log" 2>&1 &
136
236
  emulator_pid=$!
237
+ launch_in_progress=0
238
+ honor_pending_signal
137
239
 
138
240
  container_name="android-device-run-$(date +%s)-$$"
241
+ container_started=1
242
+ printf '%s\n' "$container_name" > "$run_dir/container-name"
139
243
  bounded docker run --detach --name "$container_name" --cidfile "$run_dir/postgres.cid" \
140
244
  --publish 127.0.0.1::5432 --env POSTGRES_USER=postgres \
141
245
  --env POSTGRES_PASSWORD=postgres --env POSTGRES_DB=postgres "$POSTGRES_IMAGE" >/dev/null
142
- db_port="$(bounded docker port "$container_name" 5432/tcp | head -1 | awk -F: '{print $NF}')"
246
+ bounded docker port "$container_name" 5432/tcp > "$run_dir/postgres.port"
247
+ db_port="$(head -1 "$run_dir/postgres.port" | awk -F: '{print $NF}')"
143
248
  test -n "$db_port"
144
249
 
145
250
  boot_state=""
146
251
  for _ in $(seq 1 "$BOOT_ATTEMPTS"); do
147
- if boot_state="$(bounded adb -s "$ANDROID_EMULATOR_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')"; then
252
+ if bounded adb -s "$ANDROID_EMULATOR_SERIAL" shell getprop sys.boot_completed > "$run_dir/boot" 2>/dev/null; then
253
+ boot_state="$(tr -d '\r' < "$run_dir/boot")"
148
254
  if test "$boot_state" = 1; then break; fi
149
255
  else
150
256
  probe_status=$?
@@ -173,4 +279,4 @@ export "$DB_URL_VAR=postgresql://postgres:postgres@127.0.0.1:$db_port/postgres"
173
279
  export "$DB_NAME_VAR=${DB_NAME_PREFIX}$(date +%s)_$$"
174
280
  db_ready=1
175
281
 
176
- "${journey_cmd[@]}"
282
+ managed "$JOURNEY_SECONDS" "${journey_cmd[@]}"