@muggleai/works 5.12.0-staging.84 → 5.12.0-staging.86

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.
@@ -32,7 +32,8 @@ state_file="$home/.muggle-ai/guardrails/$sid.json"
32
32
  if [ ! -f "$state_file" ] \
33
33
  || ! grep -q '"failedRuns"' "$state_file" \
34
34
  || grep -q '"failedRuns": \[\]' "$state_file" \
35
- || grep -q '"debugReleased": true' "$state_file" \n || grep -q '"debugSkipped": true' "$state_file"; then
35
+ || grep -q '"debugReleased": true' "$state_file" \
36
+ || grep -q '"debugSkipped": true' "$state_file"; then
36
37
  printf '{}'
37
38
  exit 0
38
39
  fi
@@ -29,7 +29,8 @@ fi
29
29
  state_file="$home/.muggle-ai/guardrails/$sid.json"
30
30
  if [ ! -f "$state_file" ] \
31
31
  || ! grep -q '"unitTestsGreen": true' "$state_file" \
32
- || grep -q '"e2eReleased": true' "$state_file" \n || grep -q '"e2eRun": true' "$state_file"; then
32
+ || grep -q '"e2eReleased": true' "$state_file" \
33
+ || grep -q '"e2eRun": true' "$state_file"; then
33
34
  printf '{}'
34
35
  exit 0
35
36
  fi
@@ -34,7 +34,8 @@ state_file="$home/.muggle-ai/guardrails/$sid.json"
34
34
  if [ ! -f "$state_file" ] \
35
35
  || ! grep -q '"mandatoryStages"' "$state_file" \
36
36
  || grep -q '"mandatoryStages": \[\]' "$state_file" \
37
- || grep -q '"stageReleased": true' "$state_file" \n || grep -q '"stageSkipped": true' "$state_file"; then
37
+ || grep -q '"stageReleased": true' "$state_file" \
38
+ || grep -q '"stageSkipped": true' "$state_file"; then
38
39
  printf '{}'
39
40
  exit 0
40
41
  fi
@@ -32,7 +32,8 @@ state_file="$home/.muggle-ai/guardrails/$sid.json"
32
32
  if [ ! -f "$state_file" ] \
33
33
  || ! grep -q '"e2eRun": true' "$state_file" \
34
34
  || grep -q '"walkthroughPosted": true' "$state_file" \
35
- || grep -q '"walkthroughReleased": true' "$state_file" \n || grep -q '"walkthroughSkipped": true' "$state_file"; then
35
+ || grep -q '"walkthroughReleased": true' "$state_file" \
36
+ || grep -q '"walkthroughSkipped": true' "$state_file"; then
36
37
  printf '{}'
37
38
  exit 0
38
39
  fi
@@ -31,7 +31,8 @@ state_file="$home/.muggle-ai/guardrails/$sid.json"
31
31
  if [ ! -f "$state_file" ] \
32
32
  || ! grep -q '"prsHandled"' "$state_file" \
33
33
  || grep -q '"prsHandled": \[\]' "$state_file" \
34
- || grep -q '"watchReleased": true' "$state_file" \n || grep -q '"watchSkipped": true' "$state_file"; then
34
+ || grep -q '"watchReleased": true' "$state_file" \
35
+ || grep -q '"watchSkipped": true' "$state_file"; then
35
36
  printf '{}'
36
37
  exit 0
37
38
  fi
@@ -30,6 +30,15 @@ MUGGLE_PR_WATCH_POLL_INTERVAL="${MUGGLE_PR_WATCH_POLL_INTERVAL:-60}"
30
30
  # revoked auth) exhausts it.
31
31
  MUGGLE_PR_WATCH_MAX_FETCH_FAILURES="${MUGGLE_PR_WATCH_MAX_FETCH_FAILURES:-60}"
32
32
 
33
+ # Seconds a loop will wait for the arming session to write watch-watermark.env
34
+ # before giving up. The loop cannot evaluate a single wake condition without it,
35
+ # so an unseeded slot is a watcher that will never report anything — and one that
36
+ # looks perfectly healthy while it does nothing, because it still holds its PID
37
+ # lease and still touches its heartbeat. Arming writes the file in the same turn
38
+ # it starts the loop, so anything past a couple of minutes means the arming
39
+ # sequence was not followed and the watch is inert.
40
+ MUGGLE_PR_WATCH_MAX_UNSEEDED="${MUGGLE_PR_WATCH_MAX_UNSEEDED:-180}"
41
+
33
42
  # Seconds to sleep after `fails` consecutive failed fetches: the poll interval,
34
43
  # then a linear back-off capped at 5 minutes so a sustained outage is retried
35
44
  # calmly rather than hammered every 60s.
@@ -59,6 +68,15 @@ watcher_lifetime_exceeded() {
59
68
  [ $((now - started)) -ge "$max" ]
60
69
  }
61
70
 
71
+ # True when the slot has gone unseeded longer than the cap allows. 0 is
72
+ # unbounded, matching `watcher_lifetime_exceeded`, for a caller that seeds the
73
+ # watermark out of band.
74
+ watcher_unseeded_too_long() {
75
+ local waited="$1" max="${2:-$MUGGLE_PR_WATCH_MAX_UNSEEDED}"
76
+ [ "$max" -eq 0 ] 2>/dev/null && return 1
77
+ [ "$waited" -ge "$max" ]
78
+ }
79
+
62
80
  # True when pid names a running process. `kill -0` sends no signal; EPERM means
63
81
  # the process exists but is foreign, which still counts as alive. Used by
64
82
  # arm-watcher's pre-arm dedup to decide whether a watcher already owns the slot.
@@ -54,6 +54,7 @@ state_projection="$(cat "${script_dir}/pr-watch-state.jq")"
54
54
  echo "$$" > "${slot}/watch.pid"
55
55
  started=$(date +%s)
56
56
  fails=0
57
+ unseeded=0
57
58
 
58
59
  # In-memory floors, above the on-disk watermark. The watermark is advanced by
59
60
  # the session after it handles a wave; these stop the loop re-reporting an event
@@ -137,11 +138,24 @@ while :; do
137
138
  touch "${slot}/watch-heartbeat" 2>/dev/null
138
139
 
139
140
  # No watermark yet means the arming session has not finished seeding. Wait
140
- # rather than treat every floor as zero, which would fire the whole backlog.
141
+ # rather than treat every floor as zero, which would fire the whole backlog
142
+ # but not forever. Every wake condition below is evaluated against a floor
143
+ # read from this file, so a loop that never gets one polls nothing, reports
144
+ # nothing and never reaches the terminal check, while still holding its PID
145
+ # lease and touching its heartbeat. That combination is the worst kind of
146
+ # broken: reconcile reads the live lease and fresh beacon as healthy and
147
+ # declines to re-arm, so the PR is silently unwatched for as long as the
148
+ # session lives. Fail loudly instead.
141
149
  if [ ! -f "${slot}/watch-watermark.env" ]; then
150
+ unseeded=$((unseeded + MUGGLE_PR_WATCH_POLL_INTERVAL))
151
+ if watcher_unseeded_too_long "$unseeded"; then
152
+ echo "WATCH-FAIL pr=$pr_number no watch-watermark.env after ${unseeded}s — arming never seeded it, so this watch can detect nothing (arm-watcher.md steps 1-2)"
153
+ exit 1
154
+ fi
142
155
  sleep "$MUGGLE_PR_WATCH_POLL_INTERVAL"
143
156
  continue
144
157
  fi
158
+ unseeded=0
145
159
 
146
160
  watermark_review=$(read_watermark_value REV)
147
161
  watermark_comment=$(read_watermark_value COM)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "release": "5.11.0",
3
- "buildId": "run-84-1",
4
- "commitSha": "936a62e2fe1a6861faf2f4eb10643d9e00bf8f6f",
5
- "buildTime": "2026-08-29T05:05:31Z",
3
+ "buildId": "run-86-1",
4
+ "commitSha": "795c9069663dd1ea34e9f2e58ac31ba832ec4137",
5
+ "buildTime": "2026-08-30T05:45:55Z",
6
6
  "serviceName": "muggle-ai-works-mcp"
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@muggleai/works",
3
3
  "mcpName": "io.github.multiplex-ai/muggle",
4
- "version": "5.12.0-staging.84",
4
+ "version": "5.12.0-staging.86",
5
5
  "description": "Ship quality products with AI-powered E2E acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -32,7 +32,8 @@ state_file="$home/.muggle-ai/guardrails/$sid.json"
32
32
  if [ ! -f "$state_file" ] \
33
33
  || ! grep -q '"failedRuns"' "$state_file" \
34
34
  || grep -q '"failedRuns": \[\]' "$state_file" \
35
- || grep -q '"debugReleased": true' "$state_file" \n || grep -q '"debugSkipped": true' "$state_file"; then
35
+ || grep -q '"debugReleased": true' "$state_file" \
36
+ || grep -q '"debugSkipped": true' "$state_file"; then
36
37
  printf '{}'
37
38
  exit 0
38
39
  fi
@@ -29,7 +29,8 @@ fi
29
29
  state_file="$home/.muggle-ai/guardrails/$sid.json"
30
30
  if [ ! -f "$state_file" ] \
31
31
  || ! grep -q '"unitTestsGreen": true' "$state_file" \
32
- || grep -q '"e2eReleased": true' "$state_file" \n || grep -q '"e2eRun": true' "$state_file"; then
32
+ || grep -q '"e2eReleased": true' "$state_file" \
33
+ || grep -q '"e2eRun": true' "$state_file"; then
33
34
  printf '{}'
34
35
  exit 0
35
36
  fi
@@ -34,7 +34,8 @@ state_file="$home/.muggle-ai/guardrails/$sid.json"
34
34
  if [ ! -f "$state_file" ] \
35
35
  || ! grep -q '"mandatoryStages"' "$state_file" \
36
36
  || grep -q '"mandatoryStages": \[\]' "$state_file" \
37
- || grep -q '"stageReleased": true' "$state_file" \n || grep -q '"stageSkipped": true' "$state_file"; then
37
+ || grep -q '"stageReleased": true' "$state_file" \
38
+ || grep -q '"stageSkipped": true' "$state_file"; then
38
39
  printf '{}'
39
40
  exit 0
40
41
  fi
@@ -32,7 +32,8 @@ state_file="$home/.muggle-ai/guardrails/$sid.json"
32
32
  if [ ! -f "$state_file" ] \
33
33
  || ! grep -q '"e2eRun": true' "$state_file" \
34
34
  || grep -q '"walkthroughPosted": true' "$state_file" \
35
- || grep -q '"walkthroughReleased": true' "$state_file" \n || grep -q '"walkthroughSkipped": true' "$state_file"; then
35
+ || grep -q '"walkthroughReleased": true' "$state_file" \
36
+ || grep -q '"walkthroughSkipped": true' "$state_file"; then
36
37
  printf '{}'
37
38
  exit 0
38
39
  fi
@@ -31,7 +31,8 @@ state_file="$home/.muggle-ai/guardrails/$sid.json"
31
31
  if [ ! -f "$state_file" ] \
32
32
  || ! grep -q '"prsHandled"' "$state_file" \
33
33
  || grep -q '"prsHandled": \[\]' "$state_file" \
34
- || grep -q '"watchReleased": true' "$state_file" \n || grep -q '"watchSkipped": true' "$state_file"; then
34
+ || grep -q '"watchReleased": true' "$state_file" \
35
+ || grep -q '"watchSkipped": true' "$state_file"; then
35
36
  printf '{}'
36
37
  exit 0
37
38
  fi
@@ -30,6 +30,15 @@ MUGGLE_PR_WATCH_POLL_INTERVAL="${MUGGLE_PR_WATCH_POLL_INTERVAL:-60}"
30
30
  # revoked auth) exhausts it.
31
31
  MUGGLE_PR_WATCH_MAX_FETCH_FAILURES="${MUGGLE_PR_WATCH_MAX_FETCH_FAILURES:-60}"
32
32
 
33
+ # Seconds a loop will wait for the arming session to write watch-watermark.env
34
+ # before giving up. The loop cannot evaluate a single wake condition without it,
35
+ # so an unseeded slot is a watcher that will never report anything — and one that
36
+ # looks perfectly healthy while it does nothing, because it still holds its PID
37
+ # lease and still touches its heartbeat. Arming writes the file in the same turn
38
+ # it starts the loop, so anything past a couple of minutes means the arming
39
+ # sequence was not followed and the watch is inert.
40
+ MUGGLE_PR_WATCH_MAX_UNSEEDED="${MUGGLE_PR_WATCH_MAX_UNSEEDED:-180}"
41
+
33
42
  # Seconds to sleep after `fails` consecutive failed fetches: the poll interval,
34
43
  # then a linear back-off capped at 5 minutes so a sustained outage is retried
35
44
  # calmly rather than hammered every 60s.
@@ -59,6 +68,15 @@ watcher_lifetime_exceeded() {
59
68
  [ $((now - started)) -ge "$max" ]
60
69
  }
61
70
 
71
+ # True when the slot has gone unseeded longer than the cap allows. 0 is
72
+ # unbounded, matching `watcher_lifetime_exceeded`, for a caller that seeds the
73
+ # watermark out of band.
74
+ watcher_unseeded_too_long() {
75
+ local waited="$1" max="${2:-$MUGGLE_PR_WATCH_MAX_UNSEEDED}"
76
+ [ "$max" -eq 0 ] 2>/dev/null && return 1
77
+ [ "$waited" -ge "$max" ]
78
+ }
79
+
62
80
  # True when pid names a running process. `kill -0` sends no signal; EPERM means
63
81
  # the process exists but is foreign, which still counts as alive. Used by
64
82
  # arm-watcher's pre-arm dedup to decide whether a watcher already owns the slot.
@@ -54,6 +54,7 @@ state_projection="$(cat "${script_dir}/pr-watch-state.jq")"
54
54
  echo "$$" > "${slot}/watch.pid"
55
55
  started=$(date +%s)
56
56
  fails=0
57
+ unseeded=0
57
58
 
58
59
  # In-memory floors, above the on-disk watermark. The watermark is advanced by
59
60
  # the session after it handles a wave; these stop the loop re-reporting an event
@@ -137,11 +138,24 @@ while :; do
137
138
  touch "${slot}/watch-heartbeat" 2>/dev/null
138
139
 
139
140
  # No watermark yet means the arming session has not finished seeding. Wait
140
- # rather than treat every floor as zero, which would fire the whole backlog.
141
+ # rather than treat every floor as zero, which would fire the whole backlog
142
+ # but not forever. Every wake condition below is evaluated against a floor
143
+ # read from this file, so a loop that never gets one polls nothing, reports
144
+ # nothing and never reaches the terminal check, while still holding its PID
145
+ # lease and touching its heartbeat. That combination is the worst kind of
146
+ # broken: reconcile reads the live lease and fresh beacon as healthy and
147
+ # declines to re-arm, so the PR is silently unwatched for as long as the
148
+ # session lives. Fail loudly instead.
141
149
  if [ ! -f "${slot}/watch-watermark.env" ]; then
150
+ unseeded=$((unseeded + MUGGLE_PR_WATCH_POLL_INTERVAL))
151
+ if watcher_unseeded_too_long "$unseeded"; then
152
+ echo "WATCH-FAIL pr=$pr_number no watch-watermark.env after ${unseeded}s — arming never seeded it, so this watch can detect nothing (arm-watcher.md steps 1-2)"
153
+ exit 1
154
+ fi
142
155
  sleep "$MUGGLE_PR_WATCH_POLL_INTERVAL"
143
156
  continue
144
157
  fi
158
+ unseeded=0
145
159
 
146
160
  watermark_review=$(read_watermark_value REV)
147
161
  watermark_comment=$(read_watermark_value COM)