@muggleai/works 4.12.4 → 4.13.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-JNI7INIO.js → chunk-QUWM3JQY.js} +2 -2
- package/dist/{chunk-CPF6AR2I.js → chunk-TWILR37J.js} +37 -37
- 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/skills/_shared/github-cli-recipes/submitted-reviews.md +1 -1
- package/dist/plugin/skills/_shared/github-cli-recipes/unresolved-threads.md +6 -4
- package/dist/plugin/skills/_shared/pr-followup-helpers/allow-list.md +1 -1
- package/dist/plugin/skills/_shared/pr-followup-helpers/classify.md +3 -4
- package/dist/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +22 -0
- package/dist/plugin/skills/_shared/pr-followup-helpers.md +1 -0
- package/dist/plugin/skills/_shared/resolve-e2e-validation-context.md +56 -0
- package/dist/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +1 -1
- package/dist/plugin/skills/do/address-reviews.md +14 -8
- package/dist/plugin/skills/do/e2e-acceptance.md +7 -3
- package/dist/plugin/skills/do/per-comment-replies.md +8 -2
- package/dist/plugin/skills/do/resolve-reminder.md +16 -18
- package/dist/plugin/skills/muggle-pr-followup/CLAUDE.md +5 -3
- package/dist/plugin/skills/muggle-pr-followup/SKILL.md +10 -3
- package/dist/plugin/skills/muggle-pr-followup/auto-track.md +91 -0
- package/dist/plugin/skills/muggle-pr-followup/bootstrap.md +11 -4
- package/dist/plugin/skills/muggle-pr-followup/output-templates/help.md +2 -1
- package/dist/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +7 -1
- package/dist/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +6 -3
- package/dist/plugin/skills/muggle-pr-followup/state-schemas.md +12 -0
- package/dist/plugin/skills/muggle-preferences/ops/configure.md +1 -1
- package/dist/plugin/skills/muggle-preferences/preference-gates/autoReuseValidationContext.md +11 -0
- package/dist/release-manifest.json +4 -4
- package/dist/{src-YR5UKLPC.js → src-BD5AM6OH.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/skills/_shared/github-cli-recipes/submitted-reviews.md +1 -1
- package/plugin/skills/_shared/github-cli-recipes/unresolved-threads.md +6 -4
- package/plugin/skills/_shared/pr-followup-helpers/allow-list.md +1 -1
- package/plugin/skills/_shared/pr-followup-helpers/classify.md +3 -4
- package/plugin/skills/_shared/pr-followup-helpers/loop-signature.md +22 -0
- package/plugin/skills/_shared/pr-followup-helpers.md +1 -0
- package/plugin/skills/_shared/resolve-e2e-validation-context.md +56 -0
- package/plugin/skills/_shared/telemetry-events/muggle-do-cycle.md +1 -1
- package/plugin/skills/do/address-reviews.md +14 -8
- package/plugin/skills/do/e2e-acceptance.md +7 -3
- package/plugin/skills/do/per-comment-replies.md +8 -2
- package/plugin/skills/do/resolve-reminder.md +16 -18
- package/plugin/skills/muggle-pr-followup/CLAUDE.md +5 -3
- package/plugin/skills/muggle-pr-followup/SKILL.md +10 -3
- package/plugin/skills/muggle-pr-followup/auto-track.md +91 -0
- package/plugin/skills/muggle-pr-followup/bootstrap.md +11 -4
- package/plugin/skills/muggle-pr-followup/output-templates/help.md +2 -1
- package/plugin/skills/muggle-pr-followup/output-templates/inline-reply.md +7 -1
- package/plugin/skills/muggle-pr-followup/output-templates/resolve-reminder.md +6 -3
- package/plugin/skills/muggle-pr-followup/state-schemas.md +12 -0
- package/plugin/skills/muggle-preferences/ops/configure.md +1 -1
- package/plugin/skills/muggle-preferences/preference-gates/autoReuseValidationContext.md +11 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __export, getLogger, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, isFirstRun, writePreferences, DEFAULT_PREFERENCES, getDataDir, PREFERENCES_FILE_NAME, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, verifyFileChecksum, calculateFileChecksum, initTelemetry, Surface, ServiceName, track, EventName, getQaTools, getLocalQaTools, performLogout, performLogin, toolRequiresAuth, getCallerCredentials, hasShownDisclosure, getDisclosureCopy, markDisclosureShown, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, buildElectronAppChecksumsUrl, __require } from './chunk-
|
|
1
|
+
import { __export, getLogger, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, isFirstRun, writePreferences, DEFAULT_PREFERENCES, getDataDir, PREFERENCES_FILE_NAME, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, verifyFileChecksum, calculateFileChecksum, initTelemetry, Surface, ServiceName, track, EventName, getQaTools, getLocalQaTools, performLogout, performLogin, toolRequiresAuth, getCallerCredentials, hasShownDisclosure, getDisclosureCopy, markDisclosureShown, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, buildElectronAppChecksumsUrl, __require } from './chunk-TWILR37J.js';
|
|
2
2
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
3
|
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { v4 } from 'uuid';
|
|
@@ -736,7 +736,7 @@ async function resolveGsScreenshotUrls(report, opts) {
|
|
|
736
736
|
if (gsUrls.length === 0) {
|
|
737
737
|
return report;
|
|
738
738
|
}
|
|
739
|
-
const mcps = await import('./src-
|
|
739
|
+
const mcps = await import('./src-BD5AM6OH.js');
|
|
740
740
|
const credentials = await mcps.getCallerCredentialsAsync();
|
|
741
741
|
if (!credentials.bearerToken && !credentials.apiKey) {
|
|
742
742
|
stderrWrite(
|
|
@@ -3611,6 +3611,15 @@ var EventName;
|
|
|
3611
3611
|
EventName2["SystemDisclosureShown"] = "system.disclosure_shown";
|
|
3612
3612
|
})(EventName || (EventName = {}));
|
|
3613
3613
|
var MuggleEntityIdSchema = z.string().uuid();
|
|
3614
|
+
var RunEnvironment = /* @__PURE__ */ ((RunEnvironment2) => {
|
|
3615
|
+
RunEnvironment2["Local"] = "local";
|
|
3616
|
+
RunEnvironment2["Remote"] = "remote";
|
|
3617
|
+
return RunEnvironment2;
|
|
3618
|
+
})(RunEnvironment || {});
|
|
3619
|
+
var RunEnvironmentSchema = z.enum(["local" /* Local */, "remote" /* Remote */]);
|
|
3620
|
+
var RunEnvironmentInputSchema = RunEnvironmentSchema.optional().describe(
|
|
3621
|
+
"Environment lane for the run: 'local' (developer localhost) or 'remote' (deployed). Selects which versioned runSettings/credentials lane the cloud resolves. Omit for remote (default)."
|
|
3622
|
+
);
|
|
3614
3623
|
var LocalExecutionContextInputSchema = z.object({
|
|
3615
3624
|
originalUrl: z.string().url().describe("Original local URL used during local execution (typically localhost)"),
|
|
3616
3625
|
productionUrl: z.string().url().describe("Cloud production URL for the test case"),
|
|
@@ -3627,6 +3636,7 @@ var LocalRunUploadInputSchema = z.object({
|
|
|
3627
3636
|
useCaseId: MuggleEntityIdSchema.describe("Use case ID (UUID) for the local run"),
|
|
3628
3637
|
testCaseId: MuggleEntityIdSchema.describe("Test case ID (UUID) for the local run"),
|
|
3629
3638
|
runType: z.enum(["generation", "replay"]).describe("Type of local run to upload"),
|
|
3639
|
+
runEnvironmentType: RunEnvironmentInputSchema,
|
|
3630
3640
|
productionUrl: z.string().url().describe("Cloud production URL associated with the run"),
|
|
3631
3641
|
localExecutionContext: LocalExecutionContextInputSchema.describe("Local execution metadata"),
|
|
3632
3642
|
actionScript: z.array(z.unknown()).min(1).describe("Generated action script steps from local execution"),
|
|
@@ -3906,6 +3916,7 @@ var WorkflowStartTestScriptGenerationInputSchema = z.object({
|
|
|
3906
3916
|
precondition: z.string().min(1).describe("Preconditions"),
|
|
3907
3917
|
instructions: z.string().min(1).describe("Step-by-step instructions"),
|
|
3908
3918
|
expectedResult: z.string().min(1).describe("Expected result"),
|
|
3919
|
+
runEnvironmentType: RunEnvironmentInputSchema,
|
|
3909
3920
|
workflowParams: WorkflowParamsSchema
|
|
3910
3921
|
});
|
|
3911
3922
|
var WorkflowStartTestScriptGenerationBulkInputSchema = z.object({
|
|
@@ -4403,42 +4414,16 @@ function slimEntry(raw) {
|
|
|
4403
4414
|
if (raw.latestWorkflowRun?.id) slim.latestWorkflowRunId = raw.latestWorkflowRun.id;
|
|
4404
4415
|
return slim;
|
|
4405
4416
|
}
|
|
4406
|
-
function
|
|
4407
|
-
const
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
counts[status] = (counts[status] ?? 0) + 1;
|
|
4411
|
-
}
|
|
4412
|
-
return counts;
|
|
4413
|
-
}
|
|
4414
|
-
function sortRuns(runs, sortBy, sortOrder) {
|
|
4415
|
-
const sign = sortOrder === "asc" ? 1 : -1;
|
|
4416
|
-
return [...runs].sort((a, b) => {
|
|
4417
|
-
const av = a[sortBy];
|
|
4418
|
-
const bv = b[sortBy];
|
|
4419
|
-
if (av === void 0 && bv === void 0) return 0;
|
|
4420
|
-
if (av === void 0) return 1;
|
|
4421
|
-
if (bv === void 0) return -1;
|
|
4422
|
-
if (av < bv) return -1 * sign;
|
|
4423
|
-
if (av > bv) return 1 * sign;
|
|
4424
|
-
return 0;
|
|
4425
|
-
});
|
|
4426
|
-
}
|
|
4427
|
-
function mapTestRunsSummary(response, input) {
|
|
4428
|
-
const params = input;
|
|
4429
|
-
const raw = Array.isArray(response.data) ? response.data : [];
|
|
4430
|
-
const byStatus = aggregateByStatus(raw);
|
|
4431
|
-
const sorted = sortRuns(raw.map(slimEntry), params.sortBy, params.sortOrder);
|
|
4432
|
-
const total = sorted.length;
|
|
4433
|
-
const totalPages = Math.max(1, Math.ceil(total / params.pageSize));
|
|
4434
|
-
const start = (params.page - 1) * params.pageSize;
|
|
4435
|
-
const runs = sorted.slice(start, start + params.pageSize);
|
|
4417
|
+
function mapTestRunsSummary(response) {
|
|
4418
|
+
const envelope = response.data ?? {};
|
|
4419
|
+
const rawRuns = Array.isArray(envelope.data) ? envelope.data : [];
|
|
4420
|
+
const runs = rawRuns.map(slimEntry);
|
|
4436
4421
|
return {
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
totalPages,
|
|
4441
|
-
hasMore:
|
|
4422
|
+
page: envelope.page ?? 1,
|
|
4423
|
+
pageSize: envelope.pageSize ?? runs.length,
|
|
4424
|
+
totalCount: envelope.totalCount ?? runs.length,
|
|
4425
|
+
totalPages: envelope.totalPages ?? (runs.length === 0 ? 0 : 1),
|
|
4426
|
+
hasMore: envelope.hasMore ?? false,
|
|
4442
4427
|
runs
|
|
4443
4428
|
};
|
|
4444
4429
|
}
|
|
@@ -5010,6 +4995,7 @@ var workflowTools = [
|
|
|
5010
4995
|
precondition: data.precondition,
|
|
5011
4996
|
instructions: data.instructions,
|
|
5012
4997
|
expectedResult: data.expectedResult,
|
|
4998
|
+
...data.runEnvironmentType && { runEnvironmentType: data.runEnvironmentType },
|
|
5013
4999
|
...data.workflowParams && { workflowParams: data.workflowParams }
|
|
5014
5000
|
},
|
|
5015
5001
|
timeoutMs: getWorkflowTimeoutMs()
|
|
@@ -5191,6 +5177,7 @@ var workflowTools = [
|
|
|
5191
5177
|
useCaseId: data.useCaseId,
|
|
5192
5178
|
testCaseId: data.testCaseId,
|
|
5193
5179
|
runType: data.runType,
|
|
5180
|
+
...data.runEnvironmentType && { runEnvironmentType: data.runEnvironmentType },
|
|
5194
5181
|
productionUrl: data.productionUrl,
|
|
5195
5182
|
localExecutionContext: {
|
|
5196
5183
|
originalUrl: data.localExecutionContext.originalUrl,
|
|
@@ -5239,13 +5226,19 @@ var reportTools = [
|
|
|
5239
5226
|
},
|
|
5240
5227
|
{
|
|
5241
5228
|
name: "muggle-remote-project-test-runs-summary-get",
|
|
5242
|
-
description: "Get a paginated, slimmed summary of latest test runs for a project. Response shape: {
|
|
5229
|
+
description: "Get a paginated, slimmed summary of latest test runs for a project. Response shape: { page, pageSize, totalCount, totalPages, hasMore, runs: [{ status, testCaseId, testCaseTitle, useCaseId, useCaseTitle, lastRunAt, error, latestWorkflowRunId }] }. Returns 20 runs per page by default (max 100). `totalCount` is the project-wide total after the replay-status filter; `runs` is the page slice. Check `hasMore` to decide whether to fetch additional pages. Use muggle-remote-test-case-get / muggle-remote-wf-get-ts-replay-latest-run for full per-run detail.",
|
|
5243
5230
|
inputSchema: ProjectTestRunsSummaryInputSchema,
|
|
5244
5231
|
mapToUpstream: (input) => {
|
|
5245
5232
|
const data = input;
|
|
5246
5233
|
return {
|
|
5247
5234
|
method: "GET",
|
|
5248
|
-
path: `${MUGGLE_TEST_PREFIX}/projects/${data.projectId}/test-runs/summary
|
|
5235
|
+
path: `${MUGGLE_TEST_PREFIX}/projects/${data.projectId}/test-runs/summary/paginated`,
|
|
5236
|
+
queryParams: {
|
|
5237
|
+
page: data.page,
|
|
5238
|
+
pageSize: data.pageSize,
|
|
5239
|
+
sortBy: data.sortBy,
|
|
5240
|
+
sortOrder: data.sortOrder
|
|
5241
|
+
}
|
|
5249
5242
|
};
|
|
5250
5243
|
},
|
|
5251
5244
|
mapFromUpstream: mapTestRunsSummary
|
|
@@ -6925,6 +6918,10 @@ var publishTestScriptTool = {
|
|
|
6925
6918
|
useCaseId: runResult.useCaseId,
|
|
6926
6919
|
testCaseId: input.cloudTestCaseId,
|
|
6927
6920
|
runType: runResult.runType,
|
|
6921
|
+
// A published run always originates from local Electron execution, so it
|
|
6922
|
+
// belongs to the local lane — the cloud must resolve the developer's
|
|
6923
|
+
// localhost credentials, not the remote managed-profile pool.
|
|
6924
|
+
runEnvironmentType: "local" /* Local */,
|
|
6928
6925
|
productionUrl: runResult.productionUrl,
|
|
6929
6926
|
localExecutionContext: {
|
|
6930
6927
|
originalUrl: runResult.localExecutionContext.originalUrl,
|
|
@@ -7299,6 +7296,9 @@ __export(e2e_exports2, {
|
|
|
7299
7296
|
ReportPreferencesUpsertInputSchema: () => ReportPreferencesUpsertInputSchema,
|
|
7300
7297
|
ReportStatsSummaryInputSchema: () => ReportStatsSummaryInputSchema,
|
|
7301
7298
|
RunBatchIdSchema: () => RunBatchIdSchema,
|
|
7299
|
+
RunEnvironment: () => RunEnvironment,
|
|
7300
|
+
RunEnvironmentInputSchema: () => RunEnvironmentInputSchema,
|
|
7301
|
+
RunEnvironmentSchema: () => RunEnvironmentSchema,
|
|
7302
7302
|
SecretCreateInputSchema: () => SecretCreateInputSchema,
|
|
7303
7303
|
SecretDeleteInputSchema: () => SecretDeleteInputSchema,
|
|
7304
7304
|
SecretGetInputSchema: () => SecretGetInputSchema,
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-
|
|
2
|
-
export { createChildLogger, e2e_exports as e2e, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, e2e_exports as qa, src_exports as shared } from './chunk-
|
|
1
|
+
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-QUWM3JQY.js';
|
|
2
|
+
export { createChildLogger, e2e_exports as e2e, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, e2e_exports as qa, src_exports as shared } from './chunk-TWILR37J.js';
|
|
@@ -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.13.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.13.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
|
@@ -13,4 +13,4 @@ Filter client-side:
|
|
|
13
13
|
- `id` not in `last_seen.escalated_review_ids`
|
|
14
14
|
- `user.login` in the resolved allow-list
|
|
15
15
|
- `state` in `{CHANGES_REQUESTED, COMMENTED}`, OR `APPROVED` with a non-empty body or at least one line comment
|
|
16
|
-
- **Not a
|
|
16
|
+
- **Not a loop echo.** `POST /pulls/<n>/comments/<id>/replies` creates an implicit review whose comments all have `in_reply_to_id` set. Fetch each candidate review's comments via `gh api repos/<owner>/<repo>/pulls/<n>/reviews/<id>/comments` and drop the review **only if every comment is a reply (`in_reply_to_id != null`) and carries the loop marker `<!-- muggle-do:bot -->`** (see [`../pr-followup-helpers/loop-signature.md`](../pr-followup-helpers/loop-signature.md)). A reply-only wrapper with any comment **lacking** the marker is a human follow-up — keep it; the round addresses it. Matching the marker, not structure, is what skips the loop's own replies (posted under the author's identity in single-account workflows) without dropping genuine follow-ups.
|
|
@@ -26,8 +26,10 @@ query($owner: String!, $name: String!, $number: Int!) {
|
|
|
26
26
|
}'
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Filter client-side to `isResolved == false`.
|
|
29
|
+
Filter client-side to `isResolved == false`. Walk each thread's comments in `createdAt` order and classify by the loop marker (see [`../pr-followup-helpers/loop-signature.md`](../pr-followup-helpers/loop-signature.md)), not by `author.login` — the login is ambiguous under a shared account:
|
|
30
30
|
|
|
31
|
-
- **Addressed
|
|
32
|
-
- **
|
|
33
|
-
- **Not addressed** —
|
|
31
|
+
- **Addressed, awaiting resolve** — the **newest** comment carries the loop marker `<!-- muggle-do:bot -->`. The loop has replied and nothing newer is waiting. → resolve-reminder.
|
|
32
|
+
- **Unaddressed human comment** — the newest comment lacks the marker and is newer than the thread's newest loop-marked comment (or the thread has no loop comment yet). → actionable: the round should address it.
|
|
33
|
+
- **Not addressed** — indeterminate (e.g. no comments).
|
|
34
|
+
|
|
35
|
+
A loop comment also cites a `<short-sha>` from `last_seen.pushed_shas[]` in its body, which tells *which* push addressed the thread.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The address-reviews flow only acts on reviews submitted by users in the **allow-list** = (requested reviewers ∪ CODEOWNERS ∪ {PR author}) − bots. Re-resolve every invocation — never cache across cycles.
|
|
4
4
|
|
|
5
|
-
The PR author is implicitly a valid reviewer: in single-account workflows, the human running the agent and the PR's author are the same identity, and the agent must honor their reviews. Self-loop is prevented at the watcher's filter layer rather than here: `POST /pulls/<n>/comments/<id>/replies` does create an implicit review under the loop user's identity, and the watcher drops it via the
|
|
5
|
+
The PR author is implicitly a valid reviewer: in single-account workflows, the human running the agent and the PR's author are the same identity, and the agent must honor their reviews. Self-loop is prevented at the watcher's filter layer rather than here: `POST /pulls/<n>/comments/<id>/replies` does create an implicit review under the loop user's identity, and the watcher drops it via the loop-echo clause in [`../github-cli-recipes/submitted-reviews.md`](../github-cli-recipes/submitted-reviews.md) — matched by the loop signature ([`loop-signature.md`](loop-signature.md)), so the author's *genuine* reviews and thread replies still get through. Including the PR author in this allow-list is therefore safe.
|
|
6
6
|
|
|
7
7
|
## Step 1: requested reviewers
|
|
8
8
|
|
|
@@ -4,12 +4,11 @@ Classify the **review as a unit** — but reply per line comment (threaded), not
|
|
|
4
4
|
|
|
5
5
|
## Pre-check: self-loop filter
|
|
6
6
|
|
|
7
|
-
GitHub auto-creates a synthetic review every time the agent posts `POST /comments/<id>/replies`.
|
|
7
|
+
GitHub auto-creates a synthetic review every time the agent posts `POST /comments/<id>/replies`. In single-account workflows the loop posts under the PR author's own identity, so that synthetic review is indistinguishable from a human's thread reply by author or structure alone — both are reply-only wrappers. The **loop signature** is what separates them (see [`loop-signature.md`](loop-signature.md)).
|
|
8
8
|
|
|
9
|
-
A review is a **self-loop** iff:
|
|
9
|
+
A review is a **self-loop** iff **every** line comment under it is a reply (`in_reply_to_id != null`) **and** carries the loop marker `<!-- muggle-do:bot -->`.
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
- every line comment under it has `in_reply_to_id != null`
|
|
11
|
+
If any comment in a reply-only wrapper **lacks** the marker, it is a **human follow-up** on an existing thread — not a self-loop. It carries reviewer intent; treat it as actionable and address it in this round (the caller's unresolved-thread sweep picks up the thread context).
|
|
13
12
|
|
|
14
13
|
Self-loops bypass the actionable/ambiguous decision entirely. Action: advance the cursor silently. No push, no reply, no resolve-reminder, no escalation, no entry in `escalated_review_ids`. Telemetry: emit one `cycle` event with `outcome: "self-loop-skip"`.
|
|
15
14
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Loop comment signature
|
|
2
|
+
|
|
3
|
+
Every GitHub comment `/muggle-do` posts — inline thread replies, top-level reference comments, resolve-reminders — **must** end with the signature block below. It is the only reliable way to tell loop-authored comments from human comments: in single-account workflows the loop posts under the PR author's own identity, so `author.login` cannot distinguish them. Echo-protection and addressed-by-loop classification both depend on this marker.
|
|
4
|
+
|
|
5
|
+
## The signature
|
|
6
|
+
|
|
7
|
+
Append these two lines as the end of every loop-posted comment body:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
<!-- muggle-do:bot -->
|
|
11
|
+
🤖 _Automated reply from muggle-do._
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- `<!-- muggle-do:bot -->` — hidden HTML marker; GitHub renders it invisibly and humans never type it. This is the **detection token**.
|
|
15
|
+
- The visible line makes the automation clear to a reader.
|
|
16
|
+
|
|
17
|
+
## Detection
|
|
18
|
+
|
|
19
|
+
- **Loop-authored** — the comment body contains the literal `<!-- muggle-do:bot -->`.
|
|
20
|
+
- **Human** — the body does not contain the marker.
|
|
21
|
+
|
|
22
|
+
Classify by the marker, never by `author.login` alone — the login is ambiguous under a shared account. A comment that carries the marker is the loop's own and must never re-trigger a cycle; a comment without it is human intent to act on.
|
|
@@ -11,3 +11,4 @@ Each section is its own file — load only what the current step needs.
|
|
|
11
11
|
| [`allow-list`](pr-followup-helpers/allow-list.md) | Resolve who counts as a reviewer (requested reviewers ∪ CODEOWNERS − bots − author). |
|
|
12
12
|
| [`reply-routing`](pr-followup-helpers/reply-routing.md) | Pick the right reply endpoint per comment type (line, body-only, CI failure). |
|
|
13
13
|
| [`classify`](pr-followup-helpers/classify.md) | Per-review binary label: actionable vs ambiguous, with worked examples + borderline rule. |
|
|
14
|
+
| [`loop-signature`](pr-followup-helpers/loop-signature.md) | The signature every loop-posted comment carries, and how to detect loop vs human comments by it. |
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Resolving the E2E Validation Context
|
|
2
|
+
|
|
3
|
+
**How-to procedure.** Followed by any seeder that prepares a session for unattended Stage 6 (E2E acceptance) runs.
|
|
4
|
+
|
|
5
|
+
**Goal:** resolve — once, while the user is present — everything Stage 6 needs to run without further prompts (validation strategy, local URL, backend status, Muggle Test project, test credentials, auth), and persist it to `state.md` so every later non-interactive run reads it instead of asking.
|
|
6
|
+
|
|
7
|
+
The sections below are the steps in order: reuse an existing context if one is on disk, else detect what's resolvable silently, ask the rest in one question, and write the result. The final [`## Persisted fields`](#persisted-fields) section doubles as the schema the cycle reads back.
|
|
8
|
+
|
|
9
|
+
## Reuse an existing context
|
|
10
|
+
|
|
11
|
+
If a `## Pre-flight answers` block already exists for this working tree — the current session slot, or the most recent sibling session under `.muggle-do/sessions/*` — fire the [`autoReuseValidationContext`](../muggle-preferences/preference-gates/autoReuseValidationContext.md) gate before gathering anything:
|
|
12
|
+
|
|
13
|
+
- `always` → copy the existing block into this session; skip the questionnaire.
|
|
14
|
+
- `never` → ignore it; run the full gather below.
|
|
15
|
+
- `ask` → prompt reuse-vs-re-gather.
|
|
16
|
+
|
|
17
|
+
Run the gather only when no block exists or the user chose to re-gather.
|
|
18
|
+
|
|
19
|
+
## Silent detection
|
|
20
|
+
|
|
21
|
+
Resolve without prompting; use as questionnaire defaults:
|
|
22
|
+
|
|
23
|
+
1. Dev server + backend health — per [`dev-server-readiness.md`](dev-server-readiness.md).
|
|
24
|
+
2. Muggle Test MCP auth — `muggle-remote-auth-status`.
|
|
25
|
+
3. Candidate projects — `muggle-remote-project-list`, ranked against the repo's dev URL and the PR title.
|
|
26
|
+
4. Existing test-user secrets — `muggle-remote-secret-list` per candidate project (`managed_profile_email` / `managed_profile_password`).
|
|
27
|
+
5. Auth0 tenant for local dev — grep the repo env file for `*AUTH0_DOMAIN*`.
|
|
28
|
+
|
|
29
|
+
## Questions
|
|
30
|
+
|
|
31
|
+
One `AskUserQuestion` for the validation subset, detected values as defaults. Canonical wording lives in [`../do/pre-flight.md`](../do/pre-flight.md) — reference, don't fork:
|
|
32
|
+
|
|
33
|
+
- Validation strategy — pre-flight Q4
|
|
34
|
+
- Local URL — pre-flight Q5 (defers to [`autoSelectLocalHost`](../muggle-preferences/preference-gates/autoSelectLocalHost.md))
|
|
35
|
+
- Backend reachable — pre-flight Q6
|
|
36
|
+
- Muggle Test project — pre-flight Q7 (defers to [`autoSelectProject`](../muggle-preferences/preference-gates/autoSelectProject.md))
|
|
37
|
+
- Test-user credentials — pre-flight Q8
|
|
38
|
+
- Re-auth Muggle Test MCP — pre-flight Q10
|
|
39
|
+
|
|
40
|
+
The chosen **validation strategy is the standing decision for every cycle** — no per-tick re-prompt. It subsumes the [`autoE2ETest`](../muggle-preferences/preference-gates/autoE2ETest.md) gate (pre-flight Q13), meaningless in a loop: `local-e2e` runs Stage 6 each cycle; `unit-only` / `skip` stands down. The gate's `always` default makes `local-e2e` the default when a dev server is detected.
|
|
41
|
+
|
|
42
|
+
Skip the forward-only questions (task scope, repo, branch, PR target, worktree, rebase) — the targeted repo and head branch are already checked out.
|
|
43
|
+
|
|
44
|
+
## Persisted fields
|
|
45
|
+
|
|
46
|
+
Write to `state.md` under a `## Pre-flight answers` block:
|
|
47
|
+
|
|
48
|
+
- `Validation: <local-e2e | staging-replay | unit-only | skip>`
|
|
49
|
+
- `Local URL: <url | N/A>`
|
|
50
|
+
- `Backend status: <up | down | N/A>`
|
|
51
|
+
- `Muggle Test project: <name> (<uuid>)`
|
|
52
|
+
- `Test credentials: <existing | new | skip>`
|
|
53
|
+
- `Auth status: <ok | re-authed | N/A>`
|
|
54
|
+
- `Working tree: <path>` — the verified checkout the cycle runs against
|
|
55
|
+
|
|
56
|
+
Missing any required field is a seeding bug: escalate with the session path and halt. Never silently skip E2E.
|
|
@@ -23,4 +23,4 @@ One per address-reviews invocation, regardless of outcome.
|
|
|
23
23
|
- `"escalated"` — all reviews were ambiguous; no push.
|
|
24
24
|
- `"mixed"` — both branches happened in the same invocation.
|
|
25
25
|
- `"no-op"` — every input id was already in the escalated set; no work.
|
|
26
|
-
- `"self-loop-skip"` — review was a synthetic wrapper around the agent's own reply (
|
|
26
|
+
- `"self-loop-skip"` — review was a synthetic wrapper around the agent's own reply (every line comment is a reply carrying the loop marker `<!-- muggle-do:bot -->`). Cursor advanced silently; no work, no escalation.
|
|
@@ -27,14 +27,18 @@ Read from `.muggle-do/sessions/<slug>/`:
|
|
|
27
27
|
|
|
28
28
|
## Procedure
|
|
29
29
|
|
|
30
|
-
### Step 1 —
|
|
30
|
+
### Step 1 — Assemble the work set
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Two sources, combined into one batch (dedupe by comment id):
|
|
33
|
+
|
|
34
|
+
**(a) The dispatched reviews.** For each review id in the input:
|
|
33
35
|
|
|
34
36
|
- Fetch reviews per [`../_shared/github-cli-recipes/submitted-reviews.md`](../_shared/github-cli-recipes/submitted-reviews.md) (cursor 0; filter to the specific id).
|
|
35
37
|
- Fetch its line comments per [`../_shared/github-cli-recipes/line-comments-for-review.md`](../_shared/github-cli-recipes/line-comments-for-review.md).
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
**(b) Unaddressed comments on every unresolved thread.** Fetch unresolved threads per [`../_shared/github-cli-recipes/unresolved-threads.md`](../_shared/github-cli-recipes/unresolved-threads.md). For each thread classified **unaddressed human comment** — newest comment lacks the loop marker `<!-- muggle-do:bot -->` ([`loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)) and post-dates the loop's last marked reply — add it to the batch, even if its review predates the cursor. This is how a human thread follow-up (a marker-less reply) gets addressed. **Exclude** comments whose review id is in `last_seen.escalated_review_ids` — paused awaiting the user, not re-work.
|
|
40
|
+
|
|
41
|
+
Group (a) and (b) into one combined batch.
|
|
38
42
|
|
|
39
43
|
### Step 2 — Classify each review
|
|
40
44
|
|
|
@@ -57,7 +61,7 @@ The user clarifies on GitHub by submitting a new review. The next watcher tick p
|
|
|
57
61
|
|
|
58
62
|
### Step 4 — Handle actionables (if any)
|
|
59
63
|
|
|
60
|
-
If `actionable_review_ids` is empty, skip
|
|
64
|
+
If `actionable_review_ids` is empty, skip the rest of Step 4 and Step 5; proceed to Step 5.5 (resolve-reminder) then Step 6. Otherwise:
|
|
61
65
|
|
|
62
66
|
#### 4a. Flatten the work
|
|
63
67
|
|
|
@@ -75,7 +79,7 @@ Invoke [`unit-tests.md`](unit-tests.md). Cover the surface that just changed; re
|
|
|
75
79
|
|
|
76
80
|
#### 4d. Run ONE E2E acceptance pass
|
|
77
81
|
|
|
78
|
-
Invoke [`e2e-acceptance.md`](e2e-acceptance.md). One pass covering all related test cases for this PR, not one
|
|
82
|
+
Invoke [`e2e-acceptance.md`](e2e-acceptance.md). One pass covering all related test cases for this PR, not one per comment. The stage reads the persisted validation context (seeded by pre-flight or by bootstrap Step 6.5); a poll-only session with no context (e.g. auto-track) is reported `SKIPPED`. The persisted `Validation` strategy is the standing decision — no per-tick `autoE2ETest` prompt. See [`e2e-acceptance.md`](e2e-acceptance.md) Step 0 and [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md).
|
|
79
83
|
|
|
80
84
|
#### 4e. Create or update the PR
|
|
81
85
|
|
|
@@ -85,9 +89,7 @@ Invoke [`open-prs/update.md`](open-prs/update.md) (pass the PR URL + slug + exis
|
|
|
85
89
|
|
|
86
90
|
Invoke [`per-comment-replies.md`](per-comment-replies.md) with the actionable reviews and the new SHA. One reply per comment, in its own thread, describing what was done for it.
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Invoke [`resolve-reminder.md`](resolve-reminder.md). Scans unresolved threads, classifies, and posts a top-level PR comment listing addressed-by-loop thread ids.
|
|
92
|
+
(The resolve-reminder runs once per round in Step 5.5 below — not only after a push — so a round that pushed nothing still nudges addressed-but-unresolved threads.)
|
|
91
93
|
|
|
92
94
|
### Step 5 — Update session state
|
|
93
95
|
|
|
@@ -95,6 +97,10 @@ Invoke [`resolve-reminder.md`](resolve-reminder.md). Scans unresolved threads, c
|
|
|
95
97
|
- `last_seen.last_pushed_sha` = the new head SHA (update.md already wrote this; verify)
|
|
96
98
|
- `last_seen.reviewId` = max(input review ids ∪ last_seen.reviewId)
|
|
97
99
|
|
|
100
|
+
### Step 5.5 — Resolve-reminder (runs every round)
|
|
101
|
+
|
|
102
|
+
Invoke [`resolve-reminder.md`](resolve-reminder.md) once, regardless of whether this round pushed. It scans unresolved threads, finds those whose newest comment is loop-marked (addressed, awaiting resolve), and posts one top-level PR comment nudging the reviewer to resolve them — or stays silent if there are none. Threads with a newer human comment were already pulled into this round's work set in Step 1(b).
|
|
103
|
+
|
|
98
104
|
### Step 6 — Respawn the watcher
|
|
99
105
|
|
|
100
106
|
Refresh PR state per [`../_shared/github-cli-recipes/pr-metadata.md`](../_shared/github-cli-recipes/pr-metadata.md). If the PR is now merged or closed:
|
|
@@ -44,11 +44,15 @@ You receive everything from `state.md` already — pre-flight resolved it:
|
|
|
44
44
|
|
|
45
45
|
## Your Job
|
|
46
46
|
|
|
47
|
-
### Step 0: Consume
|
|
47
|
+
### Step 0: Consume the validation context (no user questions)
|
|
48
48
|
|
|
49
|
-
Read `state.md`.
|
|
49
|
+
Read `state.md`.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
**No `## Pre-flight answers` block at all** → the session was seeded poll-only (e.g. by auto-track, [`../muggle-pr-followup/auto-track.md`](../muggle-pr-followup/auto-track.md)). Treat `Validation` as `skip`: emit a `SKIPPED` report with reason `no validation context seeded` and exit cleanly. The watcher owns no E2E context by design; "no context" is a clean skip, not a failure.
|
|
52
|
+
|
|
53
|
+
Otherwise the block was seeded by pre-flight or bootstrap per [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md) — read it the same way regardless of seeder. The persisted `Validation` field (`local-e2e`, `staging-replay`, `unit-only`, `skip`) picks execution vs early-exit below. In a forward run, [`autoE2ETest`](../muggle-preferences/preference-gates/autoE2ETest.md) `ask` was resolved by pre-flight Q13; in a watcher cycle there is no per-tick pre-flight, so `Validation` **is** the standing decision — don't re-resolve `ask`.
|
|
54
|
+
|
|
55
|
+
For a `local-e2e` block, use `localUrl`, `projectId`, and the working-tree path from `state.md`. Missing any → seeding bug; escalate with the session path and halt; do not ask the user.
|
|
52
56
|
|
|
53
57
|
### Step 0.5: Pre-flight verification probes
|
|
54
58
|
|
|
@@ -36,9 +36,12 @@ Reply body uses the template in [`../muggle-pr-followup/output-templates/inline-
|
|
|
36
36
|
|
|
37
37
|
```
|
|
38
38
|
Addressed in <short-sha>: <one-line summary of the change made for THIS comment>.
|
|
39
|
+
|
|
40
|
+
<!-- muggle-do:bot -->
|
|
41
|
+
🤖 _Automated reply from muggle-do._
|
|
39
42
|
```
|
|
40
43
|
|
|
41
|
-
`<short-sha>` is the first 7 chars of `new_sha
|
|
44
|
+
`<short-sha>` is the first 7 chars of `new_sha`; the body must contain that substring so the resolve-reminder stage knows which push addressed the thread. The trailing signature block is mandatory — its `<!-- muggle-do:bot -->` marker is what identifies the reply as loop-authored (see [`../_shared/pr-followup-helpers/loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)).
|
|
42
45
|
|
|
43
46
|
### Step 3 — Handle review-body-only comments
|
|
44
47
|
|
|
@@ -46,6 +49,9 @@ If an actionable review has a non-empty `body` and **zero** line comments, GitHu
|
|
|
46
49
|
|
|
47
50
|
```
|
|
48
51
|
Re: review #<review_id> — addressed in <short-sha>: <one-line summary>.
|
|
52
|
+
|
|
53
|
+
<!-- muggle-do:bot -->
|
|
54
|
+
🤖 _Automated reply from muggle-do._
|
|
49
55
|
```
|
|
50
56
|
|
|
51
57
|
Posted per [`../_shared/github-cli-recipes/top-level-comment.md`](../_shared/github-cli-recipes/top-level-comment.md). Fires at most once per actionable review-with-no-line-comments. Does not fire if the review has line comments — Step 2 covers those.
|
|
@@ -58,4 +64,4 @@ Posted per [`../_shared/github-cli-recipes/top-level-comment.md`](../_shared/git
|
|
|
58
64
|
## Invariants
|
|
59
65
|
|
|
60
66
|
- One reply per line comment. No per-review summary reply anywhere.
|
|
61
|
-
- Every reply body contains the new SHA's 7-char prefix — the
|
|
67
|
+
- Every reply body contains the new SHA's 7-char prefix (which push addressed it) and ends with the loop signature block — the `<!-- muggle-do:bot -->` marker, not the author login, is what identifies loop-authored comments.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Resolve-Reminder Stage
|
|
2
2
|
|
|
3
|
-
A `/muggle-do` stage that runs in **address-reviews mode only**,
|
|
3
|
+
A `/muggle-do` stage that runs in **address-reviews mode only**, near the end of each review round. Scans every unresolved comment thread on the PR, classifies them by the loop signature, and posts ONE top-level PR comment listing the threads the loop has addressed that are still unresolved with no newer human reply.
|
|
4
4
|
|
|
5
|
-
Runs
|
|
5
|
+
Runs once per `/muggle-do` address-reviews invocation (one review round):
|
|
6
6
|
|
|
7
|
-
- Runs
|
|
8
|
-
-
|
|
7
|
+
- Runs every round, whether or not this round pushed — it nudges **all** still-unresolved loop-addressed threads, not only ones touched by this push.
|
|
8
|
+
- Posts a comment only when at least one such thread exists; otherwise silent.
|
|
9
9
|
- Does not run in the forward pipeline (a fresh PR has no review threads to remind about).
|
|
10
10
|
|
|
11
11
|
## Turn preamble (inline within `/muggle-do` cycle)
|
|
@@ -28,33 +28,31 @@ If the API call fails, log the error to `followup.log` and skip the stage. Do no
|
|
|
28
28
|
|
|
29
29
|
### Step 2 — Classify each thread
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Per [`../_shared/github-cli-recipes/unresolved-threads.md`](../_shared/github-cli-recipes/unresolved-threads.md): walk each thread's comments in `createdAt` order and classify by the loop marker `<!-- muggle-do:bot -->` ([`../_shared/pr-followup-helpers/loop-signature.md`](../_shared/pr-followup-helpers/loop-signature.md)), not by `author.login`:
|
|
32
32
|
|
|
33
|
-
- **Addressed
|
|
34
|
-
- **
|
|
35
|
-
- **Not addressed** —
|
|
33
|
+
- **Addressed, awaiting resolve** — the **newest** comment carries the marker. The loop replied and nothing newer is waiting. These feed the reminder.
|
|
34
|
+
- **Unaddressed human comment** — the newest comment lacks the marker and post-dates the loop's last marked reply (or there is no loop reply yet). The address-reviews round handles these as work (Step 1 sweep), not the reminder.
|
|
35
|
+
- **Not addressed** — indeterminate.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Consider only the threads' own comments. Do not cross-reference timeline events from outside the threads.
|
|
38
38
|
|
|
39
39
|
### Step 3 — Build the resolve-reminder list
|
|
40
40
|
|
|
41
|
-
Collect the thread `databaseId` of every thread classified
|
|
42
|
-
|
|
43
|
-
Note: the watcher does not maintain a "addressed-this-cycle" set; this stage derives it by comparing thread comments to the most-recent appended SHA. The simplest deterministic rule: include a thread iff at least one of its bot replies cites the **most recent** `pushed_shas[-1]`. Earlier SHAs were already addressed in past reminders.
|
|
41
|
+
Collect the thread `databaseId` of every thread classified **addressed, awaiting resolve** in Step 2 — every still-unresolved thread whose newest comment is loop-marked, regardless of which push addressed it. A thread stays on the list across rounds until the reviewer resolves it or replies; a human reply moves it to **unaddressed human comment** (into the round's work set, Step 1), so it drops off the reminder automatically.
|
|
44
42
|
|
|
45
43
|
### Step 4 — Post the top-level reminder comment
|
|
46
44
|
|
|
47
|
-
If the resolve-reminder list is non-empty, post **one** top-level PR comment using the template in [`../muggle-pr-followup/output-templates/resolve-reminder.md`](../muggle-pr-followup/output-templates/resolve-reminder.md) per [`../_shared/github-cli-recipes/top-level-comment.md`](../_shared/github-cli-recipes/top-level-comment.md).
|
|
45
|
+
If the resolve-reminder list is non-empty, post **one** top-level PR comment using the template in [`../muggle-pr-followup/output-templates/resolve-reminder.md`](../muggle-pr-followup/output-templates/resolve-reminder.md) per [`../_shared/github-cli-recipes/top-level-comment.md`](../_shared/github-cli-recipes/top-level-comment.md). The comment carries the loop signature, so a later round's scan won't read it back as a human comment.
|
|
48
46
|
|
|
49
|
-
If the list is empty
|
|
47
|
+
If the list is empty, post **nothing**. Still emit telemetry so the stage's run is observable.
|
|
50
48
|
|
|
51
49
|
### Step 5 — Emit telemetry
|
|
52
50
|
|
|
53
51
|
Emit one event per [`../_shared/telemetry-events/muggle-do-resolve-reminder.md`](../_shared/telemetry-events/muggle-do-resolve-reminder.md). Include:
|
|
54
52
|
|
|
55
|
-
- `addressed_by_loop` — count of threads
|
|
56
|
-
- `addressed_by_human` — count
|
|
57
|
-
- `not_addressed` — count from Step 2's
|
|
53
|
+
- `addressed_by_loop` — count of threads on the reminder list (Step 3; newest comment loop-marked).
|
|
54
|
+
- `addressed_by_human` — count of threads with an unaddressed human comment (newest comment unmarked; handled by the round's Step 1 sweep, not the reminder).
|
|
55
|
+
- `not_addressed` — count from Step 2's indeterminate category.
|
|
58
56
|
- `comment_posted` — true iff Step 4 actually posted a comment.
|
|
59
57
|
|
|
60
58
|
## Failure modes
|
|
@@ -66,5 +64,5 @@ The one exception: do not silently swallow a `gh pr comment` failure if Step 4 r
|
|
|
66
64
|
## Invariants
|
|
67
65
|
|
|
68
66
|
- Telemetry fires once per invocation, even when no comment is posted.
|
|
69
|
-
- The reminder
|
|
67
|
+
- The reminder covers **every** still-unresolved thread whose newest comment is loop-marked — not just the most recent push. A thread leaves the reminder only when the reviewer resolves it or replies (a reply routes it to the round's work set).
|
|
70
68
|
- This stage suggests; it does not resolve threads on the reviewer's behalf.
|
|
@@ -4,8 +4,9 @@ This folder holds the watcher loop for PR review follow-ups. The watcher is a **
|
|
|
4
4
|
|
|
5
5
|
## Files in this folder
|
|
6
6
|
|
|
7
|
-
- [`SKILL.md`](SKILL.md) — public entry.
|
|
8
|
-
- [`
|
|
7
|
+
- [`SKILL.md`](SKILL.md) — public entry. Routing between bootstrap (URL input), tick (slug + PR number), and auto-track (no args). Read first.
|
|
8
|
+
- [`auto-track.md`](auto-track.md) — the no-args procedure: discovers PRs pushed this session (any repo) and seeds one poll-only watcher each. Seeds no E2E context — the watcher only watches.
|
|
9
|
+
- [`bootstrap.md`](bootstrap.md) — the bootstrap procedure (asks once for the E2E validation context, seeds state, dispatches the first watcher).
|
|
9
10
|
- [`contract.md`](contract.md) — the watcher per-tick procedure (poll → dispatch → exit).
|
|
10
11
|
- [`state-schemas.md`](state-schemas.md) — canonical JSON shapes of session state files.
|
|
11
12
|
- [`output-templates.md`](output-templates.md) — TOC of message templates; per-group files in `output-templates/`.
|
|
@@ -14,6 +15,7 @@ This folder holds the watcher loop for PR review follow-ups. The watcher is a **
|
|
|
14
15
|
|
|
15
16
|
Shared with other skills, under `../_shared/`:
|
|
16
17
|
|
|
18
|
+
- [`resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md) — the E2E validation-context contract bootstrap seeds into `state.md` (Step 6.5) and `do/e2e-acceptance.md` consumes.
|
|
17
19
|
- [`pr-followup-helpers.md`](../_shared/pr-followup-helpers.md) — TOC of allow-list / reply-routing / classify; per-section files in `_shared/pr-followup-helpers/`. Called by `/muggle-do`, not by this folder.
|
|
18
20
|
- [`telemetry-emit.md`](../_shared/telemetry-emit.md) — how to emit a telemetry event.
|
|
19
21
|
- [`telemetry-events.md`](../_shared/telemetry-events.md) — TOC of canonical event shapes; per-event files in `_shared/telemetry-events/`.
|
|
@@ -22,4 +24,4 @@ Shared with other skills, under `../_shared/`:
|
|
|
22
24
|
Caller-specific, under `../do/`:
|
|
23
25
|
|
|
24
26
|
- [`open-prs.md`](../do/open-prs.md) — TOC for the create-or-update PR stage; per-mode files in `do/open-prs/`.
|
|
25
|
-
- [`resolve-reminder.md`](../do/resolve-reminder.md) — `/muggle-do`'s
|
|
27
|
+
- [`resolve-reminder.md`](../do/resolve-reminder.md) — `/muggle-do`'s per-round stage that nudges the reviewer to resolve addressed-but-still-open threads.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: muggle-pr-followup
|
|
3
|
-
description: Watcher loop for PR review follow-ups. Polls one PR for new submitted reviews and dispatches `/muggle-do` (address-reviews mode) when there are any. A dumb pipe — no classification, no cycle execution, no replies. Use `/loop 1m /muggle:muggle-pr-followup <slug> <pr-number>` for ongoing polling, or `/muggle:muggle-pr-followup <pr-url>` to bootstrap a fresh watcher on an existing PR.
|
|
3
|
+
description: Watcher loop for PR review follow-ups. Polls one PR for new submitted reviews and dispatches `/muggle-do` (address-reviews mode) when there are any. A dumb pipe — no classification, no cycle execution, no replies. Run it with no args to auto-track every PR you pushed this session (poll-only watchers, any repo). Use `/loop 1m /muggle:muggle-pr-followup <slug> <pr-number>` for ongoing polling, or `/muggle:muggle-pr-followup <pr-url>` to bootstrap a fresh watcher on an existing PR (asks once for the E2E validation context, then runs unattended).
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -16,7 +16,7 @@ A watcher that babysits one open PR's review thread. Polls for new submitted rev
|
|
|
16
16
|
|
|
17
17
|
## Routing
|
|
18
18
|
|
|
19
|
-
The skill recognizes
|
|
19
|
+
The skill recognizes its mode by inspecting `$ARGUMENTS` and falling back to on-disk state. It never runs procedure inline — it identifies the mode and routes to the appropriate procedure file.
|
|
20
20
|
|
|
21
21
|
| Input | On-disk check | Mode |
|
|
22
22
|
| :---- | :------------ | :--- |
|
|
@@ -25,7 +25,8 @@ The skill recognizes two modes by inspecting `$ARGUMENTS` and falling back to on
|
|
|
25
25
|
| `<slug> <pr-number>` | session dir missing | **error:** "no session at `<path>`; pass a PR URL to start one" |
|
|
26
26
|
| `<pr-number>` alone | exactly one existing session contains it | **tick** for that PR |
|
|
27
27
|
| `<pr-number>` alone | zero or multiple matches | **error:** ambiguous; list candidates and exit |
|
|
28
|
-
| empty
|
|
28
|
+
| empty | — | **auto-track** → [`auto-track.md`](auto-track.md) |
|
|
29
|
+
| `help` / `?` | — | **help:** list active loops per [`output-templates/help.md`](output-templates/help.md) |
|
|
29
30
|
|
|
30
31
|
Bootstrap accepts three optional trailing flags:
|
|
31
32
|
|
|
@@ -33,6 +34,12 @@ Bootstrap accepts three optional trailing flags:
|
|
|
33
34
|
- `--resume` — opt in to reusing an existing session slot (default is refuse on conflict)
|
|
34
35
|
- `--forward-only` — pin cursor past existing reviews (skip history). Default is cursor 0, which processes prior submitted reviews on the first tick.
|
|
35
36
|
|
|
37
|
+
## Preferences
|
|
38
|
+
|
|
39
|
+
| Preference | Gate |
|
|
40
|
+
| :--------- | :--- |
|
|
41
|
+
| `autoReuseValidationContext` | Bootstrap reuses an existing validation context instead of re-asking — fired in the Step 6.5 gather per [`../_shared/resolve-e2e-validation-context.md`](../_shared/resolve-e2e-validation-context.md) |
|
|
42
|
+
|
|
36
43
|
## Folder TOC
|
|
37
44
|
|
|
38
45
|
See [`CLAUDE.md`](CLAUDE.md) for the one-line index of every file in this folder.
|