@muggleai/works 5.9.0 → 5.10.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/README.md +8 -0
- package/dist/{chunk-5PYK7DWB.js → chunk-7NC2DJTK.js} +10 -4
- package/dist/{chunk-LVUJ4E62.js → chunk-OCLMXAVJ.js} +245 -167
- 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/README.md +19 -13
- package/dist/plugin/agents/test-prepare-runner.md +1 -0
- package/dist/plugin/scripts/ensure-electron-app.sh +62 -47
- package/dist/plugin/scripts/guardrails.mjs +47 -46
- package/dist/plugin/skills/_shared/resolve-e2e-validation-context.md +1 -0
- package/dist/plugin/skills/muggle/SKILL.md +1 -1
- package/dist/plugin/skills/muggle-preferences/SKILL.md +1 -1
- package/dist/plugin/skills/muggle-preferences/evals/evals.json +45 -0
- package/dist/plugin/skills/muggle-preferences/ops/change-one.md +1 -1
- package/dist/plugin/skills/muggle-preferences/ops/configure.md +4 -5
- package/dist/plugin/skills/muggle-preferences/ops/list.md +1 -1
- package/dist/plugin/skills/muggle-preferences/ops/set.md +3 -4
- package/dist/plugin/skills/muggle-preferences/preference-gates/README.md +3 -3
- package/dist/plugin/skills/muggle-preferences/preference-gates/autoSelectLocalHost.md +2 -2
- package/dist/plugin/skills/muggle-preferences/preference-gates/autoSelectProject.md +1 -1
- package/dist/plugin/skills/muggle-test/SKILL.md +1 -1
- package/dist/plugin/skills/muggle-test/execute-local.md +2 -0
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +3 -1
- package/dist/plugin/skills/muggle-test-import/SKILL.md +1 -1
- package/dist/plugin/skills/muggle-test-prepare/SKILL.md +10 -4
- package/dist/plugin/skills/muggle-test-prepare/steps/check-running.md +1 -1
- package/dist/plugin/skills/muggle-test-prepare/steps/e2e-instructions.md +100 -0
- package/dist/plugin/skills/muggle-test-prepare/steps/readiness-report.md +9 -4
- package/dist/plugin/skills/muggle-test-prepare/steps/reuse-plan.md +5 -4
- package/dist/plugin/skills/muggle-test-regenerate-missing/SKILL.md +1 -1
- package/dist/release-manifest.json +4 -4
- package/dist/src-AWUORQZZ.js +1 -0
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/README.md +19 -13
- package/plugin/agents/test-prepare-runner.md +1 -0
- package/plugin/scripts/ensure-electron-app.sh +62 -47
- package/plugin/scripts/guardrails.mjs +47 -46
- package/plugin/skills/_shared/resolve-e2e-validation-context.md +1 -0
- package/plugin/skills/muggle/SKILL.md +1 -1
- package/plugin/skills/muggle-preferences/SKILL.md +1 -1
- package/plugin/skills/muggle-preferences/evals/evals.json +45 -0
- package/plugin/skills/muggle-preferences/ops/change-one.md +1 -1
- package/plugin/skills/muggle-preferences/ops/configure.md +4 -5
- package/plugin/skills/muggle-preferences/ops/list.md +1 -1
- package/plugin/skills/muggle-preferences/ops/set.md +3 -4
- package/plugin/skills/muggle-preferences/preference-gates/README.md +3 -3
- package/plugin/skills/muggle-preferences/preference-gates/autoSelectLocalHost.md +2 -2
- package/plugin/skills/muggle-preferences/preference-gates/autoSelectProject.md +1 -1
- package/plugin/skills/muggle-test/SKILL.md +1 -1
- package/plugin/skills/muggle-test/execute-local.md +2 -0
- package/plugin/skills/muggle-test-feature-local/SKILL.md +3 -1
- package/plugin/skills/muggle-test-import/SKILL.md +1 -1
- package/plugin/skills/muggle-test-prepare/SKILL.md +10 -4
- package/plugin/skills/muggle-test-prepare/steps/check-running.md +1 -1
- package/plugin/skills/muggle-test-prepare/steps/e2e-instructions.md +100 -0
- package/plugin/skills/muggle-test-prepare/steps/readiness-report.md +9 -4
- package/plugin/skills/muggle-test-prepare/steps/reuse-plan.md +5 -4
- package/plugin/skills/muggle-test-regenerate-missing/SKILL.md +1 -1
- package/dist/src-FDK2KQSZ.js +0 -1
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-7NC2DJTK.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-OCLMXAVJ.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": "5.
|
|
4
|
+
"version": "5.10.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": "5.
|
|
5
|
+
"version": "5.10.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/dist/plugin/README.md
CHANGED
|
@@ -21,19 +21,25 @@ This updates the CLI, configures Cursor MCP (`~/.cursor/mcp.json`), and syncs `m
|
|
|
21
21
|
|
|
22
22
|
Type `muggle` to discover the full command family.
|
|
23
23
|
|
|
24
|
-
| Skill | What it does |
|
|
25
|
-
|
|
26
|
-
| `/muggle:muggle` | Router and menu for all Muggle Test commands. |
|
|
27
|
-
| `/muggle:muggle-do` | Autonomous dev pipeline: requirements, code, unit tests, E2E acceptance tests, PR. |
|
|
28
|
-
| `/muggle:muggle-test` | Change-driven E2E acceptance router: detects code changes, maps to use cases, runs test generation locally or remotely, publishes to dashboard, opens in browser, posts E2E acceptance results to PR. |
|
|
29
|
-
| `/muggle:muggle-test-feature-local` | Test a feature on localhost with AI-driven browser automation. Offers publish to cloud after each run. |
|
|
30
|
-
| `/muggle:muggle-test-
|
|
31
|
-
| `/muggle:muggle-test-
|
|
32
|
-
| `/muggle:muggle-
|
|
33
|
-
| `/muggle:muggle-
|
|
34
|
-
| `/muggle:muggle-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
| Skill | Shorthand | What it does |
|
|
25
|
+
|:---|:---|:---|
|
|
26
|
+
| `/muggle:muggle` | `/m` | Router and menu for all Muggle Test commands. |
|
|
27
|
+
| `/muggle:muggle-do` | `/mdo` | Autonomous dev pipeline: requirements, code, unit tests, E2E acceptance tests, PR. |
|
|
28
|
+
| `/muggle:muggle-test` | `/mtest` | Change-driven E2E acceptance router: detects code changes, maps to use cases, runs test generation locally or remotely, publishes to dashboard, opens in browser, posts E2E acceptance results to PR. |
|
|
29
|
+
| `/muggle:muggle-test-feature-local` | `/mtestlocal` | Test a feature on localhost with AI-driven browser automation. Offers publish to cloud after each run. |
|
|
30
|
+
| `/muggle:muggle-test-prepare` | `/mtestprep` | Verify the dev servers and sibling services a test run needs, and start whatever is missing. |
|
|
31
|
+
| `/muggle:muggle-test-import` | `/mimport` | Import existing tests into Muggle Test — from Playwright/Cypress specs, PRDs, Gherkin feature files, test plan docs, or any test artifact. |
|
|
32
|
+
| `/muggle:muggle-test-regenerate-missing` | `/mregen` | Bulk-regenerate test scripts for every test case in a project that doesn't currently have an active script. Scans DRAFT + GENERATION_PENDING, confirms the list with the user, and dispatches remote generation workflows for each. |
|
|
33
|
+
| `/muggle:muggle-browser-task` | `/mbt` | Perform a real action on a website from plain English — log in and submit the form, create the ticket, refund the charge. |
|
|
34
|
+
| `/muggle:muggle-pr-visual-walkthrough` | `/mpr` | Post per-test-case dashboard links, step-by-step screenshots, and a pass/fail summary to a PR. |
|
|
35
|
+
| `/muggle:muggle-pr-followup` | `/mprfollowup` | Watch one PR's review thread and dispatch the work to address incoming feedback. |
|
|
36
|
+
| `/muggle:muggle-feedback` | `/mfeedback` | Flag a generated action script, or one step in it, as wrong so Muggle can analyze and regenerate affected scripts. |
|
|
37
|
+
| `/muggle:muggle-preferences` | `/mprefs` | View, set, or reset the preferences that gate Muggle Test behavior. |
|
|
38
|
+
| `/muggle:muggle-status` | `/mstatus` | Health check for Electron browser test runner, MCP server, and authentication. |
|
|
39
|
+
| `/muggle:muggle-repair` | `/mrepair` | Diagnose and fix broken installation automatically. |
|
|
40
|
+
| `/muggle:muggle-upgrade` | `/mupgrade` | Update Electron browser test runner and MCP server to latest version. |
|
|
41
|
+
|
|
42
|
+
Every skill ships the short alias in the table above. Type `/m` (Claude Code) or `m` (Cursor) to open the menu, or jump straight to one (e.g. `/mtest`). Claude Code namespaces plugin commands, so the aliases resolve as `/muggle:mtest`; mirror them into `~/.claude/commands/` to type `/mtest` bare.
|
|
37
43
|
|
|
38
44
|
## MCP Tools
|
|
39
45
|
|
|
@@ -16,6 +16,7 @@ The dispatch prompt carries the resolved prepare plan:
|
|
|
16
16
|
- `testingScope` and `excludedServices[]` (with reasons).
|
|
17
17
|
- The recorded dev-server URL (from the `autoSelectLocalHost` resolution) — never invent or default a host/port; a framework default like `:3000` is not a fallback.
|
|
18
18
|
- Resolved gate values the stages read (`autoRebase` outcome already applied or explicitly skipped upstream).
|
|
19
|
+
- The E2E run instructions — startup order, manual steps, local gotchas — already captured from the user. Honour the recorded startup order when starting services, and write the instructions out per the readiness-report stage. Absent means the skill had nothing to record; never invent them.
|
|
19
20
|
|
|
20
21
|
## Stages
|
|
21
22
|
|
|
@@ -110,6 +110,10 @@ version_check() {
|
|
|
110
110
|
version_check || true
|
|
111
111
|
|
|
112
112
|
# --- Preferences injection ---
|
|
113
|
+
# Preferences are user-level: defaults overlaid by the global file only. A legacy
|
|
114
|
+
# <cwd>/.muggle-ai/preferences.json is left on disk but no longer read, so the
|
|
115
|
+
# keys it can no longer apply are named once — the stamp file suppresses the
|
|
116
|
+
# repeat until that key set changes.
|
|
113
117
|
prefs_global_file="${HOME}/.muggle-ai/preferences.json"
|
|
114
118
|
prefs_line=""
|
|
115
119
|
prefs_file_note=""
|
|
@@ -119,8 +123,13 @@ if [ -f "$prefs_global_file" ]; then
|
|
|
119
123
|
# Uses node for reliable JSON parsing (already required for muggle).
|
|
120
124
|
prefs_line=$(node -e "
|
|
121
125
|
const fs = require('fs');
|
|
126
|
+
const os = require('os');
|
|
127
|
+
const path = require('path');
|
|
122
128
|
try {
|
|
123
|
-
|
|
129
|
+
// Resolved through node, not the shell's \$HOME: under Git Bash the shell
|
|
130
|
+
// reports a POSIX path that Windows node cannot open.
|
|
131
|
+
const globalFile = path.join(os.homedir(), '.muggle-ai', 'preferences.json');
|
|
132
|
+
const g = JSON.parse(fs.readFileSync(globalFile, 'utf-8')).preferences || {};
|
|
124
133
|
const defaults = {
|
|
125
134
|
autoLogin:'ask', autoSelectProject:'ask', autoSelectLocalHost:'ask',
|
|
126
135
|
showElectronBrowser:'ask', openTestResultsAfterRun:'ask',
|
|
@@ -131,15 +140,30 @@ if [ -f "$prefs_global_file" ]; then
|
|
|
131
140
|
autoUseWorktree:'ask', autoRebase:'ask', autoCleanup:'ask',
|
|
132
141
|
autoE2ETest:'always', autoRouteBuildToMuggleDo:'ask'
|
|
133
142
|
};
|
|
143
|
+
const resolved = { ...defaults, ...g };
|
|
144
|
+
const line = Object.entries(resolved).map(([k,v]) => k+'='+v).join(' ');
|
|
145
|
+
const blocks = ['Muggle Test Preferences (~/.muggle-ai/preferences.json):\\\\n' + line];
|
|
146
|
+
|
|
134
147
|
const cwd = process.env.CLAUDE_CWD || process.env.CURSOR_CWD || process.cwd();
|
|
135
|
-
const pPath =
|
|
148
|
+
const pPath = path.join(cwd, '.muggle-ai', 'preferences.json');
|
|
136
149
|
let p = {};
|
|
137
150
|
try { p = JSON.parse(fs.readFileSync(pPath, 'utf-8')).preferences || {}; } catch {}
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
const inertKeys = Object.keys(p).filter((k) => p[k] !== resolved[k]).sort();
|
|
152
|
+
if (inertKeys.length > 0) {
|
|
153
|
+
const stampDir = path.join(os.homedir(), '.cache', 'muggle');
|
|
154
|
+
const stampFile = path.join(stampDir, 'project-prefs-inert');
|
|
155
|
+
const stamp = cwd + '|' + inertKeys.join(',');
|
|
156
|
+
let lastStamp = '';
|
|
157
|
+
try { lastStamp = fs.readFileSync(stampFile, 'utf-8'); } catch {}
|
|
158
|
+
if (lastStamp !== stamp) {
|
|
159
|
+
try { fs.mkdirSync(stampDir, { recursive: true }); fs.writeFileSync(stampFile, stamp); } catch {}
|
|
160
|
+
blocks.push(
|
|
161
|
+
'Muggle Test: per-project preferences were removed — ' + pPath + ' is no longer read, so these keys no longer take effect: ' + inertKeys.join(', ') + '.\\\\n' +
|
|
162
|
+
'Tell the user to re-apply any they want everywhere with \`/muggle-preferences\`; the file is safe to delete.'
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
console.log(blocks.join('\\\\n\\\\n'));
|
|
143
167
|
} catch { console.log(''); }
|
|
144
168
|
" 2>/dev/null || true)
|
|
145
169
|
if [ -n "$prefs_line" ]; then
|
|
@@ -149,53 +173,44 @@ else
|
|
|
149
173
|
prefs_file_note="\\n\\nMuggle Test Preferences: not configured. Run \\\`muggle setup\\\` or tell the agent to set preferences."
|
|
150
174
|
fi
|
|
151
175
|
|
|
152
|
-
# --- Last-
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
const cwd = process.env.CLAUDE_CWD || process.env.CURSOR_CWD || process.cwd();
|
|
162
|
-
const lpPath = path.join(cwd, '.muggle-ai', 'last-project.json');
|
|
163
|
-
if (!fs.existsSync(lpPath)) { console.log(''); return; }
|
|
164
|
-
const raw = JSON.parse(fs.readFileSync(lpPath, 'utf-8'));
|
|
165
|
-
const lp = raw && raw.lastProject;
|
|
166
|
-
if (!lp || !lp.projectId) { console.log(''); return; }
|
|
167
|
-
const safeName = String(lp.projectName || '').replace(/\"/g, '\\\\\"');
|
|
168
|
-
console.log('Muggle Test Last Project: id=' + lp.projectId + ' url=' + lp.projectUrl + ' name=\"' + safeName + '\"');
|
|
169
|
-
} catch { console.log(''); }
|
|
170
|
-
" 2>/dev/null || true)
|
|
171
|
-
if [ -n "$last_project_line" ]; then
|
|
172
|
-
last_project_note="\\n\\n${last_project_line}"
|
|
173
|
-
fi
|
|
174
|
-
|
|
175
|
-
# --- Last-host cache injection ---
|
|
176
|
-
# Per-repo cache of the local dev server URL the user picked on the previous
|
|
177
|
-
# run. Lives at <cwd>/.muggle-ai/last-host.json. Skills silently reuse it
|
|
178
|
-
# when the user has set the autoSelectLocalHost preference to "always".
|
|
179
|
-
last_host_line=""
|
|
180
|
-
last_host_note=""
|
|
181
|
-
last_host_line=$(node -e "
|
|
176
|
+
# --- Last-used cache injection ---
|
|
177
|
+
# The "last used Muggle Test project" and "last used local dev server URL"
|
|
178
|
+
# caches live in ~/.muggle-ai/, keyed by working directory, and are honored by
|
|
179
|
+
# skills when autoSelectProject / autoSelectLocalHost = always. A cache written
|
|
180
|
+
# before the move to the home directory still sits in <cwd>/.muggle-ai/ and is
|
|
181
|
+
# read as a fallback, so those sessions keep their context lines.
|
|
182
|
+
last_cache_notes=""
|
|
183
|
+
last_cache_notes=$(node -e "
|
|
182
184
|
const fs = require('fs');
|
|
185
|
+
const os = require('os');
|
|
183
186
|
const path = require('path');
|
|
184
187
|
try {
|
|
185
188
|
const cwd = process.env.CLAUDE_CWD || process.env.CURSOR_CWD || process.cwd();
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
const parseFile = (filePath) => {
|
|
190
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } catch { return null; }
|
|
191
|
+
};
|
|
192
|
+
const readEntry = (fileName, legacyEntryKey) => {
|
|
193
|
+
const home = parseFile(path.join(os.homedir(), '.muggle-ai', fileName));
|
|
194
|
+
const homeEntry = home && home.entries && home.entries[path.resolve(cwd)];
|
|
195
|
+
if (homeEntry) { return homeEntry; }
|
|
196
|
+
const legacy = parseFile(path.join(cwd, '.muggle-ai', fileName));
|
|
197
|
+
return (legacy && legacy[legacyEntryKey]) || null;
|
|
198
|
+
};
|
|
199
|
+
const lines = [];
|
|
200
|
+
const lastProject = readEntry('last-project.json', 'lastProject');
|
|
201
|
+
if (lastProject && lastProject.projectId) {
|
|
202
|
+
const safeName = String(lastProject.projectName || '').replace(/\"/g, '\\\\\"');
|
|
203
|
+
lines.push('Muggle Test Last Project: id=' + lastProject.projectId + ' url=' + lastProject.projectUrl + ' name=\"' + safeName + '\"');
|
|
204
|
+
}
|
|
205
|
+
const lastHost = readEntry('last-host.json', 'lastHost');
|
|
206
|
+
if (lastHost && lastHost.host) {
|
|
207
|
+
lines.push('Muggle Test Last Host: ' + lastHost.host);
|
|
208
|
+
}
|
|
209
|
+
console.log(lines.map((line) => '\\\\n\\\\n' + line).join(''));
|
|
192
210
|
} catch { console.log(''); }
|
|
193
211
|
" 2>/dev/null || true)
|
|
194
|
-
if [ -n "$last_host_line" ]; then
|
|
195
|
-
last_host_note="\\n\\n${last_host_line}"
|
|
196
|
-
fi
|
|
197
212
|
|
|
198
|
-
context="<EXTREMELY_IMPORTANT>\nYou have access to Muggle AI — a real-browser E2E acceptance testing tool.\n\nWhenever the user asks you to test, validate, verify, or check if their web app works — use the muggle MCP tools. This includes:\n- Testing user flows (signup, login, checkout, forms, dashboards)\n- Verifying UI changes didn't break anything\n- Running regression tests after code changes\n- Validating frontend behavior on localhost or a dev server\n- Checking if a feature works before merging a PR\n\nMuggle Test launches a real Electron browser that clicks buttons, fills forms, navigates pages, and captures screenshots. It generates replayable test scripts that persist across sessions.\n\nDo NOT write test code (Playwright, Cypress, Selenium) or try to test UI manually when muggle tools are available. Use the muggle skill or muggle MCP tools instead — they are faster, capture visual evidence, and produce reusable test scripts.\n\nTrigger phrases: 'test my app', 'check if it works', 'run E2E acceptance tests', 'validate the UI', 'verify the flow', 'regression test', 'make sure it still works', 'test before merging'.\n</EXTREMELY_IMPORTANT>${upgrade_notice}${prefs_file_note}${
|
|
213
|
+
context="<EXTREMELY_IMPORTANT>\nYou have access to Muggle AI — a real-browser E2E acceptance testing tool.\n\nWhenever the user asks you to test, validate, verify, or check if their web app works — use the muggle MCP tools. This includes:\n- Testing user flows (signup, login, checkout, forms, dashboards)\n- Verifying UI changes didn't break anything\n- Running regression tests after code changes\n- Validating frontend behavior on localhost or a dev server\n- Checking if a feature works before merging a PR\n\nMuggle Test launches a real Electron browser that clicks buttons, fills forms, navigates pages, and captures screenshots. It generates replayable test scripts that persist across sessions.\n\nDo NOT write test code (Playwright, Cypress, Selenium) or try to test UI manually when muggle tools are available. Use the muggle skill or muggle MCP tools instead — they are faster, capture visual evidence, and produce reusable test scripts.\n\nTrigger phrases: 'test my app', 'check if it works', 'run E2E acceptance tests', 'validate the UI', 'verify the flow', 'regression test', 'make sure it still works', 'test before merging'.\n</EXTREMELY_IMPORTANT>${upgrade_notice}${prefs_file_note}${last_cache_notes}"
|
|
199
214
|
|
|
200
215
|
escaped_context=$(escape_for_json "$context")
|
|
201
216
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync
|
|
1
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
|
|
2
2
|
import { isAbsolute, resolve, join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
|
|
@@ -43,6 +43,7 @@ ${input2.tool_response?.output ?? ""}`;
|
|
|
43
43
|
// src/guardrails/constants.ts
|
|
44
44
|
var GH_PR_MERGED_LINE = /\b(?:Merged|Squashed and merged|Rebased and merged) pull request [\w./-]*#(\d+)/;
|
|
45
45
|
var GH_PR_CLOSED_LINE = /\bClosed pull request [\w./-]*#(\d+)/;
|
|
46
|
+
var GH_PR_REOPENED_LINE = /\bReopened pull request [\w./-]*#(\d+)/;
|
|
46
47
|
var PR_MONITOR_TERMINAL_LINE = /\bTERMINAL pr=(\d+): (MERGED|CLOSED)\b/;
|
|
47
48
|
var MAX_PR_TERMINAL_BLOCKS = 3;
|
|
48
49
|
var MAX_WATCH_BLOCKS = 3;
|
|
@@ -71,6 +72,23 @@ function detectPrTerminal(input2) {
|
|
|
71
72
|
}
|
|
72
73
|
return null;
|
|
73
74
|
}
|
|
75
|
+
function detectPrReopened(input2) {
|
|
76
|
+
if (input2.tool_name !== "Bash") return null;
|
|
77
|
+
const response = input2.tool_response;
|
|
78
|
+
const haystack = [response?.stdout, response?.stderr, response?.output, response?.content].filter((part) => typeof part === "string").join("\n");
|
|
79
|
+
const reopenedMatch = haystack.match(GH_PR_REOPENED_LINE);
|
|
80
|
+
return reopenedMatch ? Number(reopenedMatch[1]) : null;
|
|
81
|
+
}
|
|
82
|
+
function applyPrReopened(state, prNumber) {
|
|
83
|
+
const pending = state.terminalPending ?? [];
|
|
84
|
+
const handled = state.terminalHandled ?? [];
|
|
85
|
+
if (!pending.includes(prNumber) && !handled.includes(prNumber)) return state;
|
|
86
|
+
return {
|
|
87
|
+
...state,
|
|
88
|
+
terminalPending: pending.filter((number) => number !== prNumber),
|
|
89
|
+
terminalHandled: handled.filter((number) => number !== prNumber)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
74
92
|
function applyPrTerminalDetected(state, prNumber) {
|
|
75
93
|
const pending = state.terminalPending ?? [];
|
|
76
94
|
const handled = state.terminalHandled ?? [];
|
|
@@ -144,7 +162,6 @@ function e2eGateDecision(state, maxBlocks = MAX_E2E_BLOCKS) {
|
|
|
144
162
|
if (blockCount >= maxBlocks) return { action: "release" /* Release */, blockCount };
|
|
145
163
|
return { action: "block" /* Block */, blockCount: blockCount + 1 };
|
|
146
164
|
}
|
|
147
|
-
var HEARTBEAT_FRESH_MS = 15 * 60 * 1e3;
|
|
148
165
|
var WATCH_SKIP_MARKER = /^\s*echo\s+["']?MUGGLE_WATCH_SKIP\b/;
|
|
149
166
|
function isWatchSkipMarker(cmd) {
|
|
150
167
|
return WATCH_SKIP_MARKER.test(cmd);
|
|
@@ -153,38 +170,13 @@ function applyWatchSkip(state, skipped) {
|
|
|
153
170
|
if (!skipped || state.watchSkipped === true) return state;
|
|
154
171
|
return { ...state, watchSkipped: true };
|
|
155
172
|
}
|
|
156
|
-
function
|
|
157
|
-
if (existsSync(join(slotDir, "result.md"))) return true;
|
|
158
|
-
const pidFile = join(slotDir, "watch.pid");
|
|
159
|
-
if (existsSync(pidFile)) {
|
|
160
|
-
const pid = Number.parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
161
|
-
if (Number.isInteger(pid) && pid > 0) {
|
|
162
|
-
try {
|
|
163
|
-
process.kill(pid, 0);
|
|
164
|
-
return true;
|
|
165
|
-
} catch (err) {
|
|
166
|
-
if (err.code === "EPERM") return true;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
const beat = join(slotDir, "watch-heartbeat");
|
|
171
|
-
if (existsSync(beat)) {
|
|
172
|
-
try {
|
|
173
|
-
if (Date.now() - statSync(beat).mtimeMs < HEARTBEAT_FRESH_MS) return true;
|
|
174
|
-
} catch {
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
function findUnarmedHandledPrs(handledUrls, sessionsDirOverride) {
|
|
173
|
+
function findUntrackedHandledPrs(handledUrls, sessionsDirOverride) {
|
|
181
174
|
if (handledUrls.length === 0) return [];
|
|
182
175
|
const sessionsDir = join(homedir(), ".muggle-ai", "muggle-do", "sessions");
|
|
183
176
|
if (!existsSync(sessionsDir)) return [...handledUrls];
|
|
184
|
-
const
|
|
177
|
+
const trackedUrls = /* @__PURE__ */ new Set();
|
|
185
178
|
for (const slug of readdirSync(sessionsDir)) {
|
|
186
|
-
const
|
|
187
|
-
const prsFile = join(slotDir, "prs.json");
|
|
179
|
+
const prsFile = join(sessionsDir, slug, "prs.json");
|
|
188
180
|
if (!existsSync(prsFile)) continue;
|
|
189
181
|
let slotUrl;
|
|
190
182
|
try {
|
|
@@ -194,21 +186,23 @@ function findUnarmedHandledPrs(handledUrls, sessionsDirOverride) {
|
|
|
194
186
|
} catch {
|
|
195
187
|
continue;
|
|
196
188
|
}
|
|
197
|
-
if (slotUrl
|
|
198
|
-
watchedUrls.add(slotUrl);
|
|
199
|
-
}
|
|
189
|
+
if (slotUrl) trackedUrls.add(slotUrl);
|
|
200
190
|
}
|
|
201
|
-
return handledUrls.filter((url) => !
|
|
191
|
+
return handledUrls.filter((url) => !trackedUrls.has(url));
|
|
202
192
|
}
|
|
203
|
-
function watchGateDecision(state,
|
|
193
|
+
function watchGateDecision(state, untrackedPrUrls, maxBlocks = MAX_WATCH_BLOCKS) {
|
|
204
194
|
const blockCount = state.watchBlockCount ?? 0;
|
|
205
|
-
if (state.watchSkipped === true ||
|
|
206
|
-
return { action: "none" /* None */, blockCount,
|
|
195
|
+
if (state.watchSkipped === true || untrackedPrUrls.length === 0) {
|
|
196
|
+
return { action: "none" /* None */, blockCount, untracked: untrackedPrUrls };
|
|
207
197
|
}
|
|
208
198
|
if (blockCount >= maxBlocks) {
|
|
209
|
-
return { action: "release" /* Release */, blockCount,
|
|
199
|
+
return { action: "release" /* Release */, blockCount, untracked: untrackedPrUrls };
|
|
210
200
|
}
|
|
211
|
-
return {
|
|
201
|
+
return {
|
|
202
|
+
action: "block" /* Block */,
|
|
203
|
+
blockCount: blockCount + 1,
|
|
204
|
+
untracked: untrackedPrUrls
|
|
205
|
+
};
|
|
212
206
|
}
|
|
213
207
|
|
|
214
208
|
// src/guardrails/detectBuildIntent.ts
|
|
@@ -323,6 +317,13 @@ Per the autoWatchPR preference, a muggle-pr-followup watcher should handle its i
|
|
|
323
317
|
return envelope("PostToolUse", ctx, host);
|
|
324
318
|
}
|
|
325
319
|
function prTerminal() {
|
|
320
|
+
const reopenedPrNumber = detectPrReopened(input);
|
|
321
|
+
if (reopenedPrNumber !== null) {
|
|
322
|
+
const state2 = readState(sessionId);
|
|
323
|
+
const next2 = applyPrReopened(state2, reopenedPrNumber);
|
|
324
|
+
if (next2 !== state2) writeState(next2);
|
|
325
|
+
return "{}";
|
|
326
|
+
}
|
|
326
327
|
const terminalEvent = detectPrTerminal(input);
|
|
327
328
|
if (!terminalEvent) return "{}";
|
|
328
329
|
const state = readState(sessionId);
|
|
@@ -372,21 +373,21 @@ function e2eGate() {
|
|
|
372
373
|
}
|
|
373
374
|
function watchGate() {
|
|
374
375
|
const state = readState(sessionId);
|
|
375
|
-
const
|
|
376
|
-
const decision = watchGateDecision(state,
|
|
376
|
+
const untrackedPrUrls = findUntrackedHandledPrs(state.prsHandled);
|
|
377
|
+
const decision = watchGateDecision(state, untrackedPrUrls);
|
|
377
378
|
if (decision.action === "none" /* None */ || decision.action === "release" /* Release */) {
|
|
378
379
|
return "{}";
|
|
379
380
|
}
|
|
380
381
|
state.watchBlockCount = decision.blockCount;
|
|
381
382
|
writeState(state);
|
|
382
|
-
const prList = decision.
|
|
383
|
-
const reason = decision.blockCount === 1 ? `Do not end the turn yet. A PR was opened this session but
|
|
383
|
+
const prList = decision.untracked.join(", ");
|
|
384
|
+
const reason = decision.blockCount === 1 ? `Do not end the turn yet. A PR was opened this session but no muggle-do session slot tracks it: ${prList}. Seed the slot and hand off per muggle-do Stage 8 \u2014 /muggle:muggle-pr-followup ${decision.untracked[0]} does both. Seeding is what matters: once a slot exists, reconcile arms it at the next session start and finalizes it when the PR goes terminal, so an unarmed slot is fine but no slot means nothing ever picks this PR up. If it genuinely should not be tracked (autoWatchPR=never, handed off elsewhere), tell the user why and run \`echo "MUGGLE_WATCH_SKIP: <reason>"\` \u2014 that records the skip and keeps this gate quiet for the rest of the session.` : `PR hand-off still owed for ${prList} (reminder ${decision.blockCount}/${MAX_WATCH_BLOCKS}): seed a slot via /muggle:muggle-pr-followup, or record a legitimate skip via \`echo "MUGGLE_WATCH_SKIP: <reason>"\`.`;
|
|
384
385
|
return blockStop(reason, host);
|
|
385
386
|
}
|
|
386
387
|
function reportGate() {
|
|
387
|
-
const
|
|
388
|
-
if (!
|
|
389
|
-
return denyTool(
|
|
388
|
+
const reportPostVerdict = evaluateReportPost(input);
|
|
389
|
+
if (!reportPostVerdict.deny || !reportPostVerdict.reason) return "{}";
|
|
390
|
+
return denyTool(reportPostVerdict.reason, host);
|
|
390
391
|
}
|
|
391
392
|
function buildRouter() {
|
|
392
393
|
if (!detectBuildIntent(input.prompt ?? "")) return "{}";
|
|
@@ -25,6 +25,7 @@ Resolve without prompting; use as questionnaire defaults:
|
|
|
25
25
|
3. Candidate projects — `muggle-remote-project-list`, ranked against the repo's dev URL and the PR title.
|
|
26
26
|
4. Existing test-user secrets — `muggle-remote-secret-list` per candidate project (`managed_profile_email` / `managed_profile_password`).
|
|
27
27
|
5. Auth0 tenant for local dev — grep the repo env file for `*AUTH0_DOMAIN*`.
|
|
28
|
+
6. E2E run instructions — read `~/.muggle-ai/e2e-instructions/<key>.md` when it exists, keyed on this stack's identity. It records startup order, manual steps, and local gotchas. Treat it as read-only input: surface its gotchas rather than re-deriving them, and never re-ask what it already answers. Absent is normal — proceed without it.
|
|
28
29
|
|
|
29
30
|
## Questions
|
|
30
31
|
|
|
@@ -12,7 +12,7 @@ Use this as the top-level Muggle Test command router.
|
|
|
12
12
|
|
|
13
13
|
## Preferences
|
|
14
14
|
|
|
15
|
-
User preferences are injected by the SessionStart hook into a `Muggle Test Preferences` line in session context (key=value pairs). Resolution: defaults → `~/.muggle-ai/preferences.json
|
|
15
|
+
User preferences are injected by the SessionStart hook into a `Muggle Test Preferences` line in session context (key=value pairs). Resolution: defaults → `~/.muggle-ai/preferences.json`. Preferences are user-level and apply to every repo. Treat absent prefs as `ask`.
|
|
16
16
|
|
|
17
17
|
This router skill itself does not gate any decision on a preference — it just routes user intent to a downstream skill. Each downstream skill consults its own preferences. For example, `checkForUpdates` is consulted by `muggle-status` (Check 4), not here.
|
|
18
18
|
|
|
@@ -29,4 +29,4 @@ Pick the operation, then read its op file for the procedure.
|
|
|
29
29
|
- **Current values**: session-context line `Muggle Test Preferences key=value …`. Default `ask`.
|
|
30
30
|
- **Per-key files**: `preference-gates/<key>.md`. Key list = `ls preference-gates/*.md` minus `README.md`.
|
|
31
31
|
- **Allowed values**: `always`/`never`/`ask` (or `local`/`remote`/`ask` for `defaultExecutionMode`).
|
|
32
|
-
- **Scope**:
|
|
32
|
+
- **Scope**: preferences are user-level. Every write lands in `~/.muggle-ai/preferences.json` and applies to every repo. If the user asks for a per-project setting, say it isn't supported and confirm before setting it everywhere.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "muggle-preferences",
|
|
3
|
+
"notes": "These evals test the PLAN behavior after per-project preferences were removed. Preferences resolve from defaults overlaid by ~/.muggle-ai/preferences.json only; muggle-local-preferences-set rejects a `scope` argument. The prompts ask for a step-by-step plan rather than execution, since writing preferences mutates the user's home directory. Assertions check that the plan never offers a per-project scope, never passes `scope`, and confirms before applying a setting the user framed as project-specific.",
|
|
4
|
+
"evals": [
|
|
5
|
+
{
|
|
6
|
+
"id": 0,
|
|
7
|
+
"eval_name": "per-project-request-must-not-be-honoured-silently",
|
|
8
|
+
"prompt": "For this repo only, I want the Electron browser to actually show up when tests run — it's a UI-heavy project and I like watching it. Leave my other projects alone. Walk me through what you'd do.",
|
|
9
|
+
"files": [],
|
|
10
|
+
"assertions": [
|
|
11
|
+
{ "name": "states_preferences_are_user_level", "text": "Plan states that preferences are user-level and apply to every repo, so a per-repo setting is not supported." },
|
|
12
|
+
{ "name": "does_not_offer_project_scope", "text": "Plan does NOT offer a 'this project only' option, and does not claim a project-scoped preferences file can be written." },
|
|
13
|
+
{ "name": "no_scope_argument", "text": "Plan does not pass a `scope` argument to muggle-local-preferences-set; it calls the tool with key and value only." },
|
|
14
|
+
{ "name": "confirms_before_global_write", "text": "Plan explicitly confirms with the user before writing, because the user asked for one repo and the write will affect all of them." },
|
|
15
|
+
{ "name": "names_the_correct_key", "text": "Plan identifies showElectronBrowser as the key to set." },
|
|
16
|
+
{ "name": "does_not_invent_a_workaround", "text": "Plan does not invent a per-repo mechanism (env var, local override file, wrapper script) to simulate project scope." }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": 1,
|
|
21
|
+
"eval_name": "list-shows-user-level-resolution",
|
|
22
|
+
"prompt": "show me my muggle settings and where they're stored",
|
|
23
|
+
"files": [],
|
|
24
|
+
"assertions": [
|
|
25
|
+
{ "name": "resolution_is_two_layer", "text": "Plan describes resolution as defaults overlaid by ~/.muggle-ai/preferences.json, with no third per-project layer." },
|
|
26
|
+
{ "name": "names_the_global_path", "text": "Plan names ~/.muggle-ai/preferences.json as the storage location." },
|
|
27
|
+
{ "name": "no_project_file_mentioned", "text": "Plan does not tell the user that a preferences file inside the repo is read or honoured." },
|
|
28
|
+
{ "name": "reads_from_session_context_or_tool", "text": "Plan reads current values from the session-context preferences line or the preferences tool rather than guessing." }
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 2,
|
|
33
|
+
"eval_name": "stale-in-repo-file-explained",
|
|
34
|
+
"prompt": "I've got a .muggle-ai/preferences.json checked into my repo with autoLogin set to never, but muggle keeps logging me in automatically. Why isn't it being picked up?",
|
|
35
|
+
"files": [],
|
|
36
|
+
"assertions": [
|
|
37
|
+
{ "name": "explains_file_is_not_read", "text": "Plan explains that a preferences file inside the project is no longer read — preferences are user-level." },
|
|
38
|
+
{ "name": "points_at_global_file", "text": "Plan points the user at ~/.muggle-ai/preferences.json as the file that actually takes effect." },
|
|
39
|
+
{ "name": "offers_to_reapply_globally", "text": "Plan offers to re-apply the affected key globally, noting it will then apply to every repo." },
|
|
40
|
+
{ "name": "does_not_delete_user_file", "text": "Plan does not delete or rewrite the user's in-repo preferences file without asking." },
|
|
41
|
+
{ "name": "no_scope_argument", "text": "Plan does not attempt to pass a `scope` argument to make the project file work." }
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
1. Verify `preference-gates/<key>.md` exists. If not, list `preference-gates/*.md` and ask.
|
|
4
4
|
2. Read it. Run **Picker 1** with the options defined in the gate file, plus an additional `Ask me each time` (sub: `Prompt me at decision time.`) → `ask`.
|
|
5
|
-
3. `muggle-local-preferences-set` with the mapped value
|
|
5
|
+
3. `muggle-local-preferences-set` with the mapped value.
|
|
6
6
|
4. Confirm: `Set <key> to <value>.`
|
|
7
7
|
|
|
8
8
|
Skip Picker 2 — user explicitly asked to change.
|
|
@@ -33,18 +33,17 @@ For each option: label = key name, description = first paragraph of `preference-
|
|
|
33
33
|
- `multiSelect: true`, `header: "Branch hygiene"` — `autoUseWorktree`, `autoRebase`, `autoCleanup`
|
|
34
34
|
- `multiSelect: false`, `header: "E2E acceptance"` — `autoE2ETest`. Options: `Always run Stage 6 at the end` (`always` — default), `Ask each cycle` (`ask`). No `never` option.
|
|
35
35
|
- `multiSelect: false`, `header: "Default mode"` — `defaultExecutionMode`. Options: `Local — run on my computer` (`local`), `Remote — run in the Muggle Test cloud` (`remote`), `Ask each time` (don't change).
|
|
36
|
-
- `multiSelect: false`, `header: "Scope"` — final scope question. Options: `Global (all repos)` (~/.muggle-ai/), `This project only` (.muggle-ai/ in repo).
|
|
37
36
|
|
|
38
|
-
`AskUserQuestion` accepts up to 4 questions per call — split
|
|
37
|
+
`AskUserQuestion` accepts up to 4 questions per call — split across calls.
|
|
39
38
|
|
|
40
39
|
## Step 4 — apply selections
|
|
41
40
|
|
|
42
|
-
For each toggled key (multi-select questions): `muggle-local-preferences-set` with `value: "always"`. For `defaultExecutionMode`: only set if user picked Local/Remote (skip "Ask each time").
|
|
41
|
+
For each toggled key (multi-select questions): `muggle-local-preferences-set` with `value: "always"`. For `defaultExecutionMode`: only set if user picked Local/Remote (skip "Ask each time").
|
|
43
42
|
|
|
44
43
|
## Step 5 — `never` follow-up
|
|
45
44
|
|
|
46
|
-
Ask: `Want any of these set to "never" (auto-skip without asking)? Name them, e.g. "never on showElectronBrowser", or say "no".`. For named keys, call `muggle-local-preferences-set` with `value: "never"
|
|
45
|
+
Ask: `Want any of these set to "never" (auto-skip without asking)? Name them, e.g. "never on showElectronBrowser", or say "no".`. For named keys, call `muggle-local-preferences-set` with `value: "never"`.
|
|
47
46
|
|
|
48
47
|
## Step 6 — confirm
|
|
49
48
|
|
|
50
|
-
One-liner summary: `Set autoLogin=always, openTestResultsAfterRun=always
|
|
49
|
+
One-liner summary: `Set autoLogin=always, openTestResultsAfterRun=always.`
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# Set — direct (key + value)
|
|
2
2
|
|
|
3
|
-
Trigger: user names both key and value (e.g. "set autoLogin to always", "make showElectronBrowser never
|
|
3
|
+
Trigger: user names both key and value (e.g. "set autoLogin to always", "make showElectronBrowser never").
|
|
4
4
|
|
|
5
5
|
1. Parse `key` and `value`.
|
|
6
6
|
2. Verify `preference-gates/<key>.md` exists. If not, list `preference-gates/*.md` and ask.
|
|
7
7
|
3. Validate `value` per Shared context.
|
|
8
|
-
4.
|
|
9
|
-
5. `
|
|
10
|
-
6. Confirm: `Set {key} to {value} ({scope}).`
|
|
8
|
+
4. `muggle-local-preferences-set`.
|
|
9
|
+
5. Confirm: `Set {key} to {value}.`
|
|
@@ -11,8 +11,8 @@ which uses `local` / `remote` / `ask`).
|
|
|
11
11
|
## Resolution
|
|
12
12
|
|
|
13
13
|
`SessionStart` injects a `Muggle Test Preferences` line (`key=value` pairs) from
|
|
14
|
-
`~/.muggle-ai/preferences.json
|
|
15
|
-
|
|
14
|
+
`~/.muggle-ai/preferences.json`. Preferences are user-level — there is no
|
|
15
|
+
per-project layer. Absent → treat as `ask`.
|
|
16
16
|
|
|
17
17
|
## Gate behavior
|
|
18
18
|
|
|
@@ -48,7 +48,7 @@ Header `Remember this choice?`. Question: `"Always <restate Picker 1 choice in
|
|
|
48
48
|
plain language> from now on, without asking?"`. Never put the raw key or
|
|
49
49
|
`=` in the user-visible text.
|
|
50
50
|
|
|
51
|
-
- `Yes, always` (sub: `You can change this later in muggle preferences.`) → `muggle-local-preferences-set` with the value Picker 1 mapped to
|
|
51
|
+
- `Yes, always` (sub: `You can change this later in muggle preferences.`) → `muggle-local-preferences-set` with the value Picker 1 mapped to.
|
|
52
52
|
- `Just this once` (sub: `I'll ask again next time.`) → don't save.
|
|
53
53
|
|
|
54
54
|
A few keys override this template (their per-key file says how).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Reuse the saved local dev server URL for this repo, or pick one each run. Substitute `{lastHost}` (the URL used in the previous run for this repo — omit the option entirely when no cache exists) and `{suggestedHost}` (auto-detected from running ports, e.g. `http://localhost:3000`).
|
|
4
4
|
|
|
5
|
-
Cache lives
|
|
5
|
+
Cache lives in `~/.muggle-ai/last-host.json`, in the entry keyed on `<cwd>`. The calling skill **always** updates the cache after the user picks/types a URL — independent of Picker 2 — so `Use {lastHost}` reflects the most recent run.
|
|
6
6
|
|
|
7
7
|
**Picker 1** — header `Local server`, question `"Which local URL should the test target?"`
|
|
8
8
|
- `Use {lastHost}` — `From your last run in this repo.` → reuse cached URL. *Skip this option when no cache exists.*
|
|
@@ -11,7 +11,7 @@ Cache lives at `<cwd>/.muggle-ai/last-host.json`. The calling skill **always** u
|
|
|
11
11
|
|
|
12
12
|
**Picker 2 — overrides shared template.** Fires after the user picks a URL.
|
|
13
13
|
- Header `Remember this URL?`, question `"Always use {chosenHost} for this repo from now on, without asking?"`
|
|
14
|
-
- `Yes, always` (sub: `You can change this later in muggle preferences.`) → `muggle-local-preferences-set` (`autoSelectLocalHost=always
|
|
14
|
+
- `Yes, always` (sub: `You can change this later in muggle preferences.`) → `muggle-local-preferences-set` (`autoSelectLocalHost=always`). The cache is already up to date.
|
|
15
15
|
- `Just this once` (sub: `I'll ask again next time.`) → don't save the preference. The cache still updates.
|
|
16
16
|
|
|
17
17
|
**Silent action**
|
|
@@ -8,7 +8,7 @@ tail options like "Show full list" / "Create new project" are skill-defined).
|
|
|
8
8
|
**Picker 2 — overrides shared template.** Fires only after picking an
|
|
9
9
|
*existing* project; skip if user picked "Create new project".
|
|
10
10
|
- Header `Reuse this project next time?`, question `"Always reuse {projectName} for this repo from now on, without asking?"`
|
|
11
|
-
- `Yes, always` (sub: `You can change this later in muggle preferences.`) → call BOTH `muggle-local-preferences-set` (`autoSelectProject=always
|
|
11
|
+
- `Yes, always` (sub: `You can change this later in muggle preferences.`) → call BOTH `muggle-local-preferences-set` (`autoSelectProject=always`) AND `muggle-local-last-project-set` (`cwd`, `projectId`, `projectUrl`, `projectName`).
|
|
12
12
|
- `Just this once` (sub: `I'll ask again next time.`) → don't save.
|
|
13
13
|
|
|
14
14
|
**Silent action**
|
|
@@ -128,7 +128,7 @@ If auth fails repeatedly, suggest: `muggle logout && muggle login` from terminal
|
|
|
128
128
|
|
|
129
129
|
A **project** is where all your test results, use cases, and test scripts are grouped on the Muggle AI dashboard. Pick the project that matches what you're working on.
|
|
130
130
|
|
|
131
|
-
The per-repo cache lives
|
|
131
|
+
The per-repo cache lives in `~/.muggle-ai/last-project.json`, in the entry keyed on `<cwd>` (managed via the `muggle-local-last-project-get` / `muggle-local-last-project-set` MCP tools). Look for the `Muggle Test Last Project: id=… url=… name="…"` line in session context — if present, that's this repo's cached pick.
|
|
132
132
|
|
|
133
133
|
Gate `autoSelectProject` (per `preference-gates/README.md`). Cache: `Muggle Test Last Project` session line.
|
|
134
134
|
- `always` + cache → use cached `projectId`, skip to Step 5. No cache → fall through to `ask`.
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
Before anything else, invoke [`muggle-test-prepare`](../muggle-test-prepare/SKILL.md) — the readiness/service-start owner (idempotent; halt on what it surfaces). The URL gate below only *selects* the target; prepare is what guarantees something is listening and compiled.
|
|
14
14
|
|
|
15
|
+
Then read `~/.muggle-ai/e2e-instructions/<key>.md` when it exists — this stack's recorded startup order, manual steps, and local gotchas. Use it to interpret what you see: a gotcha listed there explains a symptom that would otherwise read as a failure. Absent is normal.
|
|
16
|
+
|
|
15
17
|
## Pre-flight question — Local URL (gated by `autoSelectLocalHost`)
|
|
16
18
|
|
|
17
19
|
Skill responsibilities (the rest is in `preference-gates/autoSelectLocalHost.md`):
|