@muggleai/works 4.11.3 → 4.12.0
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/dist/{chunk-7WRAMBOS.js → chunk-2DVZ2LYO.js} +7 -2
- package/dist/{chunk-AYWLYB3F.js → chunk-I4VLYJ7M.js} +45 -12
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/scripts/ensure-electron-app.sh +29 -4
- package/dist/plugin/skills/_shared/github-cli-recipes/line-comments-for-review.md +8 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/loop-user-identity.md +9 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/pr-edit.md +8 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +12 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/push-to-branch.md +10 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/reply-line-comment.md +10 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/submitted-reviews.md +15 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/top-level-comment.md +7 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/unresolved-threads.md +33 -0
- package/dist/plugin/skills/_shared/github-cli-recipes/verify-working-tree.md +17 -0
- package/dist/plugin/skills/_shared/github-cli-recipes.md +20 -0
- package/dist/plugin/skills/_shared/pr-followup-helpers/allow-list.md +53 -0
- package/dist/plugin/skills/_shared/pr-followup-helpers/classify.md +48 -0
- package/dist/plugin/skills/_shared/pr-followup-helpers/reply-routing.md +40 -0
- package/dist/plugin/skills/_shared/pr-followup-helpers.md +8 -146
- package/dist/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +25 -0
- package/dist/plugin/skills/_shared/telemetry-events/muggle-do-escalation.md +19 -0
- package/dist/plugin/skills/_shared/telemetry-events/muggle-do-resolve-reminder.md +19 -0
- package/dist/plugin/skills/_shared/telemetry-events/pr-followup-bootstrap.md +20 -0
- package/dist/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +23 -0
- package/dist/plugin/skills/_shared/telemetry-events.md +15 -0
- package/dist/plugin/skills/do/address-reviews.md +137 -0
- package/dist/plugin/skills/do/build.md +5 -5
- package/dist/plugin/skills/do/open-prs/forward.md +80 -0
- package/dist/plugin/skills/do/open-prs/update.md +54 -0
- package/dist/plugin/skills/do/open-prs.md +6 -102
- package/dist/plugin/skills/do/per-comment-replies.md +61 -0
- package/dist/plugin/skills/do/resolve-reminder.md +70 -0
- package/dist/plugin/skills/muggle-do/SKILL.md +41 -45
- package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +25 -0
- package/dist/plugin/skills/muggle-pr-followup/SKILL.md +21 -43
- package/dist/plugin/skills/muggle-pr-followup/bootstrap.md +88 -0
- package/dist/plugin/skills/muggle-pr-followup/contract.md +44 -161
- package/dist/plugin/skills/muggle-pr-followup/output-templates/bootstrap.md +42 -0
- package/dist/plugin/skills/muggle-pr-followup/output-templates/escalation.md +32 -0
- package/dist/plugin/skills/muggle-pr-followup/output-templates/help.md +17 -0
- package/dist/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +17 -0
- package/dist/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +12 -0
- package/dist/plugin/skills/muggle-pr-followup/output-templates/watcher-log.md +21 -0
- package/dist/plugin/skills/muggle-pr-followup/output-templates.md +14 -0
- package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +104 -0
- package/dist/plugin/skills/muggle-test-regenerate-missing/SKILL.md +3 -3
- package/dist/release-manifest.json +4 -4
- package/dist/{src-NJUUHKPW.js → src-ARTTHWNP.js} +1 -1
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/scripts/ensure-electron-app.sh +29 -4
- package/plugin/skills/_shared/github-cli-recipes/line-comments-for-review.md +8 -0
- package/plugin/skills/_shared/github-cli-recipes/loop-user-identity.md +9 -0
- package/plugin/skills/_shared/github-cli-recipes/pr-edit.md +8 -0
- package/plugin/skills/_shared/github-cli-recipes/pr-metadata.md +12 -0
- package/plugin/skills/_shared/github-cli-recipes/push-to-branch.md +10 -0
- package/plugin/skills/_shared/github-cli-recipes/reply-line-comment.md +10 -0
- package/plugin/skills/_shared/github-cli-recipes/submitted-reviews.md +15 -0
- package/plugin/skills/_shared/github-cli-recipes/top-level-comment.md +7 -0
- package/plugin/skills/_shared/github-cli-recipes/unresolved-threads.md +33 -0
- package/plugin/skills/_shared/github-cli-recipes/verify-working-tree.md +17 -0
- package/plugin/skills/_shared/github-cli-recipes.md +20 -0
- package/plugin/skills/_shared/pr-followup-helpers/allow-list.md +53 -0
- package/plugin/skills/_shared/pr-followup-helpers/classify.md +48 -0
- package/plugin/skills/_shared/pr-followup-helpers/reply-routing.md +40 -0
- package/plugin/skills/_shared/pr-followup-helpers.md +8 -146
- package/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +25 -0
- package/plugin/skills/_shared/telemetry-events/muggle-do-escalation.md +19 -0
- package/plugin/skills/_shared/telemetry-events/muggle-do-resolve-reminder.md +19 -0
- package/plugin/skills/_shared/telemetry-events/pr-followup-bootstrap.md +20 -0
- package/plugin/skills/_shared/telemetry-events/pr-followup-tick.md +23 -0
- package/plugin/skills/_shared/telemetry-events.md +15 -0
- package/plugin/skills/do/address-reviews.md +137 -0
- package/plugin/skills/do/build.md +5 -5
- package/plugin/skills/do/open-prs/forward.md +80 -0
- package/plugin/skills/do/open-prs/update.md +54 -0
- package/plugin/skills/do/open-prs.md +6 -102
- package/plugin/skills/do/per-comment-replies.md +61 -0
- package/plugin/skills/do/resolve-reminder.md +70 -0
- package/plugin/skills/muggle-do/SKILL.md +41 -45
- package/plugin/skills/muggle-pr-followup/CLAUDE.md +25 -0
- package/plugin/skills/muggle-pr-followup/SKILL.md +21 -43
- package/plugin/skills/muggle-pr-followup/bootstrap.md +88 -0
- package/plugin/skills/muggle-pr-followup/contract.md +44 -161
- package/plugin/skills/muggle-pr-followup/output-templates/bootstrap.md +42 -0
- package/plugin/skills/muggle-pr-followup/output-templates/escalation.md +32 -0
- package/plugin/skills/muggle-pr-followup/output-templates/help.md +17 -0
- package/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +17 -0
- package/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +12 -0
- package/plugin/skills/muggle-pr-followup/output-templates/watcher-log.md +21 -0
- package/plugin/skills/muggle-pr-followup/output-templates.md +14 -0
- package/plugin/skills/muggle-pr-followup/state-schemas.md +104 -0
- package/plugin/skills/muggle-test-regenerate-missing/SKILL.md +3 -3
- package/scripts/postinstall.mjs +36 -5
|
@@ -1,190 +1,73 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Watcher Per-Tick Contract
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The procedure for the **tick mode** of `muggle-pr-followup` — one polling iteration scoped to one PR. The watcher is a dumb pipe: it polls for new submitted reviews, dispatches `/muggle-do` if there are any, and exits. It does not classify, amend requirements, post replies, run cycles, or escalate.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
**PR follow-up** — polling <repo>#<pr-number>, tick #<K>.
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Resolve `<K>` from `idle_tick_count + cycles_completed` in this PR's state slot.
|
|
12
|
-
|
|
13
|
-
## When this contract may break the caller's "no questions" rule
|
|
14
|
-
|
|
15
|
-
Most callers' forward pipelines never ask the user mid-cycle. This loop, however, may emit a single escalation message when a submitted review is ambiguous (see [classify](#step-6-classify-the-review) below). By the time the loop is polling, the user has walked away from the forward pipeline; forcing a guess on an ambiguous review is worse than pausing.
|
|
16
|
-
|
|
17
|
-
Escalation is the **only** user-facing path. Otherwise the cycle runs silently end to end.
|
|
18
|
-
|
|
19
|
-
## Inputs
|
|
20
|
-
|
|
21
|
-
Read these from `.muggle-<caller>/sessions/<slug>/` (the caller's session dir):
|
|
5
|
+
Routing into this mode is documented in [`SKILL.md`](SKILL.md#routing). The architectural rationale lives in the brain doc `architecture/2026-05-08-muggle-do-pr-comment-loop-design.md`.
|
|
22
6
|
|
|
23
|
-
|
|
24
|
-
- `prs.json` — list of `{repo, number, url, head_sha, state, escalated?, cycling?}`. This loop touches only the entry whose `number` matches the dispatched PR number.
|
|
25
|
-
- `last_seen.json` — keyed by `"<owner>/<repo>#<n>"`. For this PR: `{reviewId, last_pushed_sha, idle_tick_count, cycles_completed, escalated_review_ids[]}`.
|
|
26
|
-
- `cycle.json` — caller's declared implementation cycle (see [SKILL.md](SKILL.md#caller-supplied-implementation-cycle)).
|
|
27
|
-
|
|
28
|
-
If any of these don't exist or the PR isn't in `prs.json`, the tick is a no-op — log an error to `followup.log` and exit.
|
|
29
|
-
|
|
30
|
-
## Per-tick contract
|
|
31
|
-
|
|
32
|
-
### Step 1: Refresh this PR's state
|
|
7
|
+
## Turn preamble
|
|
33
8
|
|
|
34
|
-
```bash
|
|
35
|
-
gh pr view <number> --repo <repo> --json state,mergedAt,closedAt,headRefOid
|
|
36
9
|
```
|
|
37
|
-
|
|
38
|
-
If `state` is `MERGED` or `CLOSED`, mark this entry terminal in `prs.json`. Update `head_sha` if it changed.
|
|
39
|
-
|
|
40
|
-
### Step 2: Termination check
|
|
41
|
-
|
|
42
|
-
If this PR is terminal: write a per-PR section into `result.md` (URL, final state, `cycles_completed`, count escalated, final SHA), emit final telemetry, **do not schedule another tick**. Other PRs in the session have their own loops; they terminate independently.
|
|
43
|
-
|
|
44
|
-
### Step 3: Resolve the reviewer allow-list (every tick)
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
gh pr view <number> --repo <repo> --json reviewRequests,author
|
|
10
|
+
**muggle-pr-followup tick** — polling <repo>#<pr-number>.
|
|
48
11
|
```
|
|
49
12
|
|
|
50
|
-
|
|
13
|
+
## Input
|
|
51
14
|
|
|
52
|
-
|
|
15
|
+
`$ARGUMENTS = <slug> <pr-number>` (or `<pr-number>` alone — slug inferred from on-disk state per [`SKILL.md`](SKILL.md#routing)).
|
|
53
16
|
|
|
54
|
-
|
|
55
|
-
gh api repos/<owner>/<repo>/pulls/<number>/reviews --paginate
|
|
56
|
-
```
|
|
17
|
+
## Inputs from disk
|
|
57
18
|
|
|
58
|
-
|
|
19
|
+
Read these from `.muggle-do/sessions/<slug>/`:
|
|
59
20
|
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
62
|
-
- `user.login` is in the allow-list.
|
|
63
|
-
- `id` is not in `escalated_review_ids`.
|
|
64
|
-
- `state` is `CHANGES_REQUESTED` or `COMMENTED`, OR `APPROVED` with at least one line comment or a non-empty body.
|
|
21
|
+
- `prs.json` — see [`state-schemas.md`](state-schemas.md#prsjson). The watcher touches the single entry whose `number` matches the dispatched PR number.
|
|
22
|
+
- `last_seen.json` — see [`state-schemas.md`](state-schemas.md#last_seenjson). Keyed by `"<owner>/<repo>#<n>"`.
|
|
65
23
|
|
|
66
|
-
|
|
24
|
+
If either file is missing or the PR is not in `prs.json`, the tick is a no-op. Log an error line in `followup.log` and exit. The watcher must not be invoked in this state — if it happens, the slot is corrupt.
|
|
67
25
|
|
|
68
|
-
|
|
26
|
+
## Procedure
|
|
69
27
|
|
|
70
|
-
|
|
28
|
+
### Step 1 — Refresh PR state
|
|
71
29
|
|
|
72
|
-
|
|
30
|
+
Per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md). Update `prs.json[0].head_sha` and `prs.json[0].state` from the response.
|
|
73
31
|
|
|
74
|
-
### Step
|
|
32
|
+
### Step 2 — Termination check
|
|
75
33
|
|
|
76
|
-
|
|
34
|
+
If `state` is `MERGED` or `CLOSED`:
|
|
77
35
|
|
|
78
|
-
|
|
79
|
-
|
|
36
|
+
1. Mark the entry terminal in `prs.json`.
|
|
37
|
+
2. Write `result.md` per [`state-schemas.md`](state-schemas.md#resultmd).
|
|
38
|
+
3. Append a terminal line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md).
|
|
39
|
+
4. Emit a `tick` event with `terminal: true` per [`../_shared/telemetry-events/pr-followup-tick.md`](../_shared/telemetry-events/pr-followup-tick.md).
|
|
40
|
+
5. Exit. **Do not schedule another tick.** The `/loop` framework stops invoking this skill once it sees no follow-up dispatch.
|
|
80
41
|
|
|
81
|
-
### Step
|
|
42
|
+
### Step 3 — Fetch new submitted reviews
|
|
82
43
|
|
|
83
|
-
|
|
44
|
+
Per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md). **Also exclude review ids that appear in `last_seen.escalated_review_ids`** — those have already been escalated and the watcher must not re-dispatch them.
|
|
84
45
|
|
|
85
|
-
|
|
86
|
-
2. **Amend `requirements.md`** in the session dir with a new `## Amendment — review <review_id> by <login> (<timestamp>)` section pasting the review body and each comment (with `<file>:<line>` context).
|
|
87
|
-
3. **Invoke the implementation cycle** declared in the caller's `cycle.json`. Iterate the `steps[]` in order. Each step is either a markdown file to follow, a skill to invoke, or a shell command (per the `cycle.json` schema in SKILL.md). When a step fails, the cycle returns `failed: <step-name>`; the loop escalates per Step 8 with the failure as the reason.
|
|
88
|
-
4. **Push** via `cycle.json`'s `pushHandler`. Set `last_seen.last_pushed_sha` to the new HEAD.
|
|
89
|
-
5. **Reply** per [helpers § Reply routing](../_shared/pr-followup-helpers.md#reply-routing) and [§ Classify](../_shared/pr-followup-helpers.md#classify) (reply shape). For each line comment in the review, derive `<attribution>` from `git diff <last_pushed_sha>..HEAD -- <comment.path>` near `comment.line` ±5 (fall back to `addressed indirectly — see walkthrough` if empty). `<status>` = `ran clean` or `had <N> failures, see walkthrough`. If the review is body-only (no line comments), post the top-level fallback shape; if both body and line comments, threaded replies cover it — no top-level.
|
|
90
|
-
6. **Resume polling**: clear `cycling: true`, increment `cycles_completed`, advance `last_seen.reviewId` past this review.
|
|
91
|
-
7. Emit per-cycle telemetry.
|
|
46
|
+
### Step 4 — If zero new reviews → idle
|
|
92
47
|
|
|
93
|
-
|
|
48
|
+
1. Increment `last_seen.idle_tick_count`.
|
|
49
|
+
2. Append an idle line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md).
|
|
50
|
+
3. Emit a `tick` event with `idle: true`, `reviews_seen: 0`, `dispatched_review_ids: []`.
|
|
51
|
+
4. Exit. The next tick fires in 1 min via `/loop`.
|
|
94
52
|
|
|
95
|
-
### Step
|
|
53
|
+
### Step 5 — If one or more new reviews → dispatch
|
|
96
54
|
|
|
97
|
-
|
|
55
|
+
The watcher does **not** classify. Classification, batching, replying, escalation, and cycle execution all live in `/muggle-do`. The watcher's job is to hand over the list of new review ids and exit.
|
|
98
56
|
|
|
99
|
-
1.
|
|
100
|
-
2.
|
|
101
|
-
|
|
102
|
-
|
|
57
|
+
1. Reset `last_seen.idle_tick_count` to 0.
|
|
58
|
+
2. Dispatch `/muggle-do` with an *address-reviews* directive carrying:
|
|
59
|
+
- PR URL (from `prs.json[0].url`)
|
|
60
|
+
- Session slug (from the invocation arguments)
|
|
61
|
+
- Every new review id from Step 3, as a space-separated list
|
|
103
62
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
Comments:
|
|
112
|
-
- <file>:<line> — <quoted comment body>
|
|
113
|
-
|
|
114
|
-
[For ambiguous]
|
|
115
|
-
Best two interpretations:
|
|
116
|
-
1. <one-line interpretation A>
|
|
117
|
-
2. <one-line interpretation B>
|
|
118
|
-
|
|
119
|
-
[For cycle failure]
|
|
120
|
-
The implementation cycle failed at <step-name>: <reason>.
|
|
121
|
-
|
|
122
|
-
Reply on the review yourself, leave a follow-up comment, or tell me which way to go.
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
The user clears the escalation by replying on GitHub (next tick sees a new submitted review past the cursor) or by giving a directive in this terminal session.
|
|
126
|
-
|
|
127
|
-
### Step 9: Emit tick-summary telemetry and exit
|
|
128
|
-
|
|
129
|
-
Emit one tick event per `muggle-local-telemetry-skill-emit`. Exit the turn.
|
|
130
|
-
|
|
131
|
-
## Reply routing
|
|
132
|
-
|
|
133
|
-
- **Threaded reply per line comment** (default): `POST /repos/{owner}/{repo}/pulls/{n}/comments/{comment_id}/replies`. Use for every line comment in the review so each thread can be resolved in GitHub's UI.
|
|
134
|
-
- **Top-level summary on a body-only review** (fallback): `gh pr comment <number> --body "..."` referencing the review id and the new SHA. Used only when the review has body content and zero line comments — GitHub has no "reply to a review body" endpoint.
|
|
135
|
-
- **Never post the same reply twice** — `last_seen.reviewId` is the only re-entry guard.
|
|
136
|
-
- **Never post a top-level summary alongside threaded replies** — duplication pollutes the Conversation tab.
|
|
137
|
-
|
|
138
|
-
## Telemetry
|
|
139
|
-
|
|
140
|
-
**Per-cycle** (one event per actionable review handled):
|
|
141
|
-
|
|
142
|
-
```json
|
|
143
|
-
{
|
|
144
|
-
"skill": "muggle-pr-followup",
|
|
145
|
-
"event": "cycle",
|
|
146
|
-
"caller": "<caller>",
|
|
147
|
-
"session_slug": "<slug>",
|
|
148
|
-
"repo": "<repo>",
|
|
149
|
-
"pr_number": <n>,
|
|
150
|
-
"review_id": <id>,
|
|
151
|
-
"outcome": "pushed|escalated|failed:<step>",
|
|
152
|
-
"comment_count": <count>,
|
|
153
|
-
"head_sha_before": "<sha>",
|
|
154
|
-
"head_sha_after": "<sha-or-null>"
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
**Per-tick summary** (always one, even idle):
|
|
159
|
-
|
|
160
|
-
```json
|
|
161
|
-
{
|
|
162
|
-
"skill": "muggle-pr-followup",
|
|
163
|
-
"event": "tick",
|
|
164
|
-
"session_slug": "<slug>",
|
|
165
|
-
"repo": "<repo>",
|
|
166
|
-
"pr_number": <n>,
|
|
167
|
-
"reviews_seen": <count>,
|
|
168
|
-
"review_picked": true|false,
|
|
169
|
-
"cycle_dispatched": true|false,
|
|
170
|
-
"tick_duration_ms": <ms>
|
|
171
|
-
}
|
|
172
|
-
```
|
|
63
|
+
Exact phrasing belongs to `/muggle-do`'s intent-routing. A reasonable shape is:
|
|
64
|
+
```
|
|
65
|
+
/muggle-do address reviews <id1> <id2> ... on <pr-url> slug=<slug>
|
|
66
|
+
```
|
|
67
|
+
3. Append a dispatching line to `followup.log` per [`output-templates/watcher-log.md`](output-templates/watcher-log.md).
|
|
68
|
+
4. Emit a `tick` event with `reviews_seen: <count>`, `dispatched_review_ids: [<id>, ...]`.
|
|
69
|
+
5. Exit. **Do not schedule another tick.** `/muggle-do` will respawn the watcher at the end of its cycle.
|
|
173
70
|
|
|
174
71
|
## Output
|
|
175
72
|
|
|
176
|
-
|
|
177
|
-
- The turn preamble (always).
|
|
178
|
-
- An escalation terminal message (only when escalating).
|
|
179
|
-
- The final `result.md` summary section for this PR (only on the terminating tick — written to disk, not printed).
|
|
180
|
-
|
|
181
|
-
## Self-check before ending the turn
|
|
182
|
-
|
|
183
|
-
- [ ] `last_seen.json` advanced for any review handled.
|
|
184
|
-
- [ ] `prs.json` reflects current state (terminal marked; `escalated`/`cycling` flags consistent).
|
|
185
|
-
- [ ] `followup.log` has at minimum a heartbeat or per-review line for this tick.
|
|
186
|
-
- [ ] Telemetry events emitted (per-cycle when applicable + per-tick).
|
|
187
|
-
- [ ] If pushed, `last_pushed_sha` is set and `cycles_completed` incremented.
|
|
188
|
-
- [ ] If actionable, one threaded reply posted per line comment (or one top-level reply for body-only reviews) — never both, never zero.
|
|
189
|
-
- [ ] If escalated, `escalated_review_ids` contains the review id.
|
|
190
|
-
- [ ] If terminal, the loop is NOT continued.
|
|
73
|
+
No console output beyond the turn preamble and (if Step 5 fires) the `/muggle-do` dispatch. The watcher is invisible to the reviewer.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Bootstrap output templates
|
|
2
|
+
|
|
3
|
+
## Success summary (printed just before dispatch)
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Bootstrapped PR follow-up for <owner>/<repo>#<n>
|
|
7
|
+
Slug: <slug>
|
|
8
|
+
Cursor: review #<id> (forward-only) | empty (no prior reviews)
|
|
9
|
+
Working tree: <toplevel>
|
|
10
|
+
Dispatching: /loop 1m /muggle:muggle-pr-followup <slug> <n>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Aborts
|
|
14
|
+
|
|
15
|
+
**Terminal PR:**
|
|
16
|
+
```
|
|
17
|
+
PR <owner>/<repo>#<n> is <state>; nothing to poll. Bootstrap aborted.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Wrong working tree / wrong branch:**
|
|
21
|
+
```
|
|
22
|
+
Bootstrap needs the PR's branch checked out locally.
|
|
23
|
+
Current cwd: <toplevel-or-"not in a git repo">
|
|
24
|
+
Current branch: <HEAD-or-"n/a">
|
|
25
|
+
Expected: a clone of <owner>/<repo> on branch <headRefName>.
|
|
26
|
+
From that clone, run: gh pr checkout <n>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Slot conflict (no `--resume`):**
|
|
30
|
+
```
|
|
31
|
+
Session <slug> already exists at <path>.
|
|
32
|
+
To reuse it, pass --resume on this invocation.
|
|
33
|
+
To start fresh, rm -rf the directory and re-run.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Malformed URL:**
|
|
37
|
+
```
|
|
38
|
+
Could not parse <input> as a GitHub PR URL.
|
|
39
|
+
Expected: https://github.com/<owner>/<repo>/pull/<number>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**GitHub API failure (PR not found, no auth, etc.):** repeat the underlying `gh` error verbatim. Do not paraphrase.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Escalation messages
|
|
2
|
+
|
|
3
|
+
Emitted by `/muggle-do` in address-reviews mode. One per invocation, at most. Anchored here so wording stays consistent.
|
|
4
|
+
|
|
5
|
+
## Ambiguous escalation
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
**Review-followup escalation — <owner>/<repo>#<n>**
|
|
9
|
+
|
|
10
|
+
I can't act on <count> review(s) without your input. Listed below; reply on GitHub by submitting a new review with clearer direction, or tell me here which way to go.
|
|
11
|
+
|
|
12
|
+
Review #<id> from <login>:
|
|
13
|
+
> <body or "(no body)">
|
|
14
|
+
|
|
15
|
+
Comments:
|
|
16
|
+
- <file>:<line> — <body>
|
|
17
|
+
- ...
|
|
18
|
+
|
|
19
|
+
[Repeat per ambiguous review in the batch.]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Design-adjustment escalation
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
**Design-adjustment needed — <owner>/<repo>#<n>**
|
|
26
|
+
|
|
27
|
+
While addressing review #<id>, the cycle surfaced a conflict with the current design:
|
|
28
|
+
|
|
29
|
+
<one-paragraph description of the conflict>
|
|
30
|
+
|
|
31
|
+
This is beyond a routine code change. Confirm the design intent (reply here, or update the requirements doc) before I retry.
|
|
32
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Help output
|
|
2
|
+
|
|
3
|
+
For `/muggle:muggle-pr-followup` with no args, or with `help` / `?`:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
muggle-pr-followup — watcher loop for PR review follow-ups
|
|
7
|
+
|
|
8
|
+
Active loops:
|
|
9
|
+
<slug> → <owner>/<repo>#<n> (cursor @ review #<id>, <N> cycles)
|
|
10
|
+
...
|
|
11
|
+
(or "no active loops")
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
/muggle:muggle-pr-followup <pr-url> → bootstrap a new loop
|
|
15
|
+
/muggle:muggle-pr-followup <slug> <pr-number> → run one tick (called by /loop)
|
|
16
|
+
/muggle:muggle-pr-followup <pr-number> → run one tick (slug inferred from on-disk state)
|
|
17
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Per-comment inline reply
|
|
2
|
+
|
|
3
|
+
Posted via `gh api .../comments/<comment-id>/replies` per cycle, one per line comment.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Addressed in <short-sha>: <one-line summary of the change made for THIS comment>.
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`<short-sha>` is the first 7 chars of the new SHA. The body must contain that substring — the resolve-reminder stage greps for it to classify threads as addressed-by-loop.
|
|
10
|
+
|
|
11
|
+
## Top-level fallback (review-body-only)
|
|
12
|
+
|
|
13
|
+
When an actionable review has a non-empty body but zero line comments, GitHub has no `/replies` endpoint for the body. Post a top-level PR comment instead, at most once per such review:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Re: review #<review_id> — addressed in <short-sha>: <one-line summary>.
|
|
17
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Resolve-reminder top-level PR comment
|
|
2
|
+
|
|
3
|
+
Posted via `gh pr comment` after the resolve-reminder stage scans threads. Only when at least one addressed-by-loop thread exists.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
I addressed these threads in <short-sha> — mark them resolved when satisfied:
|
|
7
|
+
- #<thread-id-1>
|
|
8
|
+
- #<thread-id-2>
|
|
9
|
+
- ...
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If `addressed_by_loop == 0`, no comment is posted (silent).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Watcher tick log lines
|
|
2
|
+
|
|
3
|
+
The watcher does **not** print to the user during normal operation. It only appends to `followup.log`. All user-facing escalations come from `/muggle-do`.
|
|
4
|
+
|
|
5
|
+
## Idle tick
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
<ISO-8601> tick pr=<n> reviews_seen=0 idle
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Dispatching tick
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
<ISO-8601> tick pr=<n> reviews_seen=<count> dispatched=<id1>,<id2>,...
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Terminal tick
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
<ISO-8601> tick pr=<n> terminal=<merged|closed> result.md written
|
|
21
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Output templates
|
|
2
|
+
|
|
3
|
+
All user-facing message text used by the watcher, bootstrap, and `/muggle-do` in address-reviews mode. Each template group is its own file — load only what the current step emits.
|
|
4
|
+
|
|
5
|
+
## Index
|
|
6
|
+
|
|
7
|
+
| Group | Use case |
|
|
8
|
+
| :---- | :------- |
|
|
9
|
+
| [`bootstrap`](output-templates/bootstrap.md) | Bootstrap success summary + all bootstrap aborts. |
|
|
10
|
+
| [`watcher-log`](output-templates/watcher-log.md) | The three `followup.log` line shapes (idle, dispatching, terminal). |
|
|
11
|
+
| [`escalation`](output-templates/escalation.md) | `/muggle-do` terminal escalation messages (ambiguous, design-adjustment). |
|
|
12
|
+
| [`inline-reply`](output-templates/inline-reply.md) | Per-comment inline reply + top-level fallback for body-only reviews. |
|
|
13
|
+
| [`resolve-reminder`](output-templates/resolve-reminder.md) | Top-level PR comment for the resolve-reminder stage. |
|
|
14
|
+
| [`help`](output-templates/help.md) | Skill help output (no args / `help` / `?`). |
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Session State Schemas
|
|
2
|
+
|
|
3
|
+
Canonical shapes for the JSON files in a PR-follow-up session slot. The slot path is `.muggle-do/sessions/<slug>/` (the caller's session dir; `muggle-do` is the current and only caller).
|
|
4
|
+
|
|
5
|
+
All files are atomic writes — the caller rewrites the whole file each time, never mutates in place. Use a temp file + rename if the platform supports it.
|
|
6
|
+
|
|
7
|
+
## `prs.json`
|
|
8
|
+
|
|
9
|
+
A list of one entry. (Historical: the file is an array for forward-compat with the original session-wide model. Today, each PR has its own session slot, so the array always has exactly one entry.)
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
[
|
|
13
|
+
{
|
|
14
|
+
"repo": "<owner>/<repo>",
|
|
15
|
+
"number": <int>,
|
|
16
|
+
"url": "https://github.com/<owner>/<repo>/pull/<number>",
|
|
17
|
+
"head_sha": "<40-char-hex-sha>",
|
|
18
|
+
"state": "open" | "merged" | "closed"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `state` is the **observed** state from the last `gh pr view`. The watcher refreshes it each tick.
|
|
24
|
+
- Terminal states (`merged`, `closed`) are sticky — once set, the watcher writes `result.md` and exits without rescheduling.
|
|
25
|
+
|
|
26
|
+
## `last_seen.json`
|
|
27
|
+
|
|
28
|
+
Keyed by `"<owner>/<repo>#<n>"`. One key per PR in the slot.
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"<owner>/<repo>#<n>": {
|
|
33
|
+
"reviewId": <int>,
|
|
34
|
+
"last_pushed_sha": "<sha-or-null>",
|
|
35
|
+
"idle_tick_count": <int>,
|
|
36
|
+
"cycles_completed": <int>,
|
|
37
|
+
"escalated_review_ids": [<int>, ...],
|
|
38
|
+
"pushed_shas": ["<sha>", ...]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `reviewId`: the cursor. The watcher fetches reviews with `id > reviewId`. Bootstrap pins this to the highest existing submitted review id (or `0` if none).
|
|
44
|
+
- `last_pushed_sha`: most recent SHA `/muggle-do` pushed in this PR's life; `null` until the first push.
|
|
45
|
+
- `idle_tick_count`: incremented each tick that sees zero new reviews. Reset to 0 on any tick that dispatches `/muggle-do`. Diagnostic only — does not gate behavior.
|
|
46
|
+
- `cycles_completed`: incremented each time `/muggle-do` completes an address-reviews invocation (regardless of actionable/ambiguous/mixed).
|
|
47
|
+
- `escalated_review_ids`: review ids classified as ambiguous by `/muggle-do`. The watcher excludes these from future review fetches so the same ambiguous review is never re-dispatched.
|
|
48
|
+
- `pushed_shas`: every SHA `/muggle-do` has pushed for this PR. Append-only. Used by the resolve-reminder stage to recognize threads addressed by the loop.
|
|
49
|
+
|
|
50
|
+
## `state.md`
|
|
51
|
+
|
|
52
|
+
Free-form markdown. No required schema beyond a few well-known fields the caller-agnostic code reads:
|
|
53
|
+
|
|
54
|
+
```markdown
|
|
55
|
+
# Session — <slug>
|
|
56
|
+
|
|
57
|
+
**PR:** <url>
|
|
58
|
+
**Slug:** <slug>
|
|
59
|
+
**Loop user:** <github-login> ← cached from `gh api user`
|
|
60
|
+
**Created:** <ISO-8601>
|
|
61
|
+
**Bootstrapped from URL:** <yes | no>
|
|
62
|
+
|
|
63
|
+
...free-form notes added by /muggle-do and bootstrap...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The watcher does **not** read or write `state.md`. Only bootstrap, `/muggle-do`, and the caller's stages touch it.
|
|
67
|
+
|
|
68
|
+
## `followup.log`
|
|
69
|
+
|
|
70
|
+
Append-only line-per-tick log. One line per watcher tick, plus one line per `/muggle-do` invocation. Format is loose, but each line starts with an ISO-8601 timestamp:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
2026-05-20T12:34:56Z tick pr=154 reviews_seen=0 idle
|
|
74
|
+
2026-05-20T12:35:56Z tick pr=154 reviews_seen=1 dispatched=4295962800
|
|
75
|
+
2026-05-20T12:36:14Z muggle-do cycle review_ids=[4295962800] outcome=pushed head_sha=abc1234
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Used for forensics only — never read back by skills.
|
|
79
|
+
|
|
80
|
+
## `result.md`
|
|
81
|
+
|
|
82
|
+
Written exactly once when the PR's watcher exits terminally (PR merged or closed). Free-form markdown summarizing the PR's life under this loop:
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
# Result — <slug>
|
|
86
|
+
|
|
87
|
+
**PR:** <url>
|
|
88
|
+
**Final state:** merged | closed
|
|
89
|
+
**Cycles completed:** <int>
|
|
90
|
+
**Pushed SHAs:** <comma-separated list>
|
|
91
|
+
**Escalated review ids:** <comma-separated list or "none">
|
|
92
|
+
|
|
93
|
+
## Timeline
|
|
94
|
+
|
|
95
|
+
- <ISO-8601> bootstrap (cursor pinned at <reviewId>)
|
|
96
|
+
- <ISO-8601> review <id> from <login> — actionable, pushed <sha>
|
|
97
|
+
- <ISO-8601> review <id> from <login> — ambiguous, escalated
|
|
98
|
+
- ...
|
|
99
|
+
- <ISO-8601> PR <merged|closed> — watcher terminal
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Not in the slot
|
|
103
|
+
|
|
104
|
+
`cycle.json` and `requirements.md` are not seeded or read. `/muggle-do` reads reviews off GitHub each invocation.
|
|
@@ -180,15 +180,15 @@ Bulk regen does not run replays, so the section B (replay) router does not apply
|
|
|
180
180
|
|
|
181
181
|
### Step 8 — Open the Dashboard
|
|
182
182
|
|
|
183
|
-
Open the Muggle AI dashboard so the user can watch progress visually:
|
|
183
|
+
Open the Muggle AI dashboard so the user can watch progress visually. Point them at the **scripts** page (where DRAFT → GENERATING → ACTIVE transitions are visible), not `/runs` (which is for replay batches and shows nothing for a regen):
|
|
184
184
|
|
|
185
185
|
```bash
|
|
186
|
-
open "https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/
|
|
186
|
+
open "https://www.muggle-ai.com/muggleTestV0/dashboard/projects/{projectId}/scripts"
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
Tell them:
|
|
190
190
|
|
|
191
|
-
> "I've opened the project's
|
|
191
|
+
> "I've opened the project's scripts page. Generation jobs typically take a few minutes each — your test cases will move from `DRAFT` through `GENERATING` to `ACTIVE` as scripts complete."
|
|
192
192
|
|
|
193
193
|
### Step 9 (optional) — Poll Status
|
|
194
194
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release": "4.
|
|
3
|
-
"buildId": "run-
|
|
4
|
-
"commitSha": "
|
|
5
|
-
"buildTime": "2026-05-
|
|
2
|
+
"release": "4.12.0",
|
|
3
|
+
"buildId": "run-38-1",
|
|
4
|
+
"commitSha": "8a01015dc5a95c7b5a34e982132e8ca4a1c0d9f1",
|
|
5
|
+
"buildTime": "2026-05-21T18:38:53Z",
|
|
6
6
|
"serviceName": "muggle-ai-works-mcp"
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { DEFAULT_PREFERENCES, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, calculateFileChecksum, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getPlatformKey, getQaTools, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, mcp_exports as mcp, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, readGlobalPreferences, readProjectPreferences, resetConfig, resetLogger, resetPreference, resolveElectronAppPathOrNull, resolvePreferences, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences } from './chunk-
|
|
1
|
+
export { DEFAULT_PREFERENCES, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, buildElectronAppChecksumsUrl, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, calculateFileChecksum, createApiKeyWithToken, createChildLogger, deleteApiKeyData, deleteCredentials, e2e_exports as e2e, formatPreferencesOneLiner, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppVersion, getElectronAppVersionSource, getLocalQaTools, getLogger, getPlatformKey, getQaTools, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isFirstRun, loadApiKeyData, loadCredentials, local_exports as localQa, mcp_exports as mcp, openBrowserUrl, performLogin, performLogout, pollDeviceCode, e2e_exports as qa, readGlobalPreferences, readProjectPreferences, resetConfig, resetLogger, resetPreference, resolveElectronAppPathOrNull, resolvePreferences, saveApiKey, saveApiKeyData, saveCredentials, startDeviceCodeFlow, toolRequiresAuth, validatePreference, verifyFileChecksum, writePreferences } from './chunk-I4VLYJ7M.js';
|
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": "4.
|
|
4
|
+
"version": "4.12.0",
|
|
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",
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"test:watch": "vitest"
|
|
42
42
|
},
|
|
43
43
|
"muggleConfig": {
|
|
44
|
-
"electronAppVersion": "1.0.
|
|
44
|
+
"electronAppVersion": "1.0.90",
|
|
45
45
|
"downloadBaseUrl": "https://github.com/multiplex-ai/muggle-ai-works/releases/download",
|
|
46
46
|
"runtimeTargetDefault": "production",
|
|
47
47
|
"checksums": {
|
|
48
|
-
"darwin-arm64": "
|
|
49
|
-
"darwin-x64": "
|
|
50
|
-
"win32-x64": "
|
|
51
|
-
"linux-x64": "
|
|
48
|
+
"darwin-arm64": "13fc830ede44af479761302e3477c4f2516be142885445c8c5baf574041a7069",
|
|
49
|
+
"darwin-x64": "02007eb996d2fa2cf5be6546b6a1a01de769f1b12c7e3b2af1e93eb1b4deda0e",
|
|
50
|
+
"win32-x64": "5ea49f43ce6c432eac42dd10179873c78067ff1fc1328d8f664ce2c81da38f49",
|
|
51
|
+
"linux-x64": "f92c350bde46c95fb35ceab8c1c115352663a53d44d210ea98316fe1a92cf41e"
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Run real-browser end-to-end (E2E) acceptance tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.12.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Ship quality products with AI-powered end-to-end (E2E) acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.12.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
|
@@ -3,10 +3,35 @@
|
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
5
|
# Ensure the Electron browser test runner is installed/up to date (silent, best-effort).
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
#
|
|
7
|
+
# Bounded + cached: this script runs from a SessionStart hook on every Claude
|
|
8
|
+
# session, so a hung `muggle setup` (e.g. one blocked by host security policy
|
|
9
|
+
# on Windows) must not leak a ~100 MB orphan per session start. We cap the
|
|
10
|
+
# attempt with `timeout` and skip the call entirely if we already checked
|
|
11
|
+
# within the last day.
|
|
12
|
+
ensure_marker_dir="${HOME}/.cache/muggle"
|
|
13
|
+
ensure_marker="${ensure_marker_dir}/electron-app-checked"
|
|
14
|
+
ensure_ttl=$((24 * 60 * 60))
|
|
15
|
+
|
|
16
|
+
ensure_now=$(date +%s)
|
|
17
|
+
ensure_last=0
|
|
18
|
+
if [ -f "${ensure_marker}" ]; then
|
|
19
|
+
ensure_last=$(stat -f %m "${ensure_marker}" 2>/dev/null || stat -c %Y "${ensure_marker}" 2>/dev/null || echo 0)
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if [ $((ensure_now - ensure_last)) -ge "${ensure_ttl}" ]; then
|
|
23
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
24
|
+
ensure_timeout="timeout -k 5 60"
|
|
25
|
+
else
|
|
26
|
+
ensure_timeout=""
|
|
27
|
+
fi
|
|
28
|
+
if command -v muggle >/dev/null 2>&1; then
|
|
29
|
+
${ensure_timeout} muggle setup >/dev/null 2>&1 || true
|
|
30
|
+
else
|
|
31
|
+
${ensure_timeout} npx -y @muggleai/works setup >/dev/null 2>&1 || true
|
|
32
|
+
fi
|
|
33
|
+
mkdir -p "${ensure_marker_dir}" 2>/dev/null || true
|
|
34
|
+
touch "${ensure_marker}" 2>/dev/null || true
|
|
10
35
|
fi
|
|
11
36
|
|
|
12
37
|
# --- Context injection ---
|