@hover-dev/mcp 0.25.0 → 0.26.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/mcp.js +52 -1
- package/dist/mcp.js.map +1 -1
- package/package.json +2 -2
package/dist/mcp.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
lintWiki,
|
|
22
22
|
appendWikiLog
|
|
23
23
|
} from "@hover-dev/core/engine";
|
|
24
|
+
import { fetchHealRequests, readCloudCredentials } from "@hover-dev/core/cloud";
|
|
24
25
|
|
|
25
26
|
// src/mcp/controller.ts
|
|
26
27
|
import { request as pwRequest } from "playwright-core";
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
groundedLocate,
|
|
29
30
|
replayOnPage
|
|
30
31
|
} from "@hover-dev/core/engine";
|
|
32
|
+
import { healSlug } from "@hover-dev/core/cloud";
|
|
31
33
|
function describe(g) {
|
|
32
34
|
if (g.role && g.name) return `${g.role} "${g.name}"`;
|
|
33
35
|
if (g.testId) return `testId "${g.testId}"`;
|
|
@@ -258,6 +260,30 @@ var HoverMcpController = class {
|
|
|
258
260
|
2
|
|
259
261
|
);
|
|
260
262
|
}
|
|
263
|
+
/** The Hover Cloud heal queue: specs whose CI run drifted, pulled from
|
|
264
|
+
* cloud.gethover.dev. Read-only surfacing — the heal itself is the same
|
|
265
|
+
* local replay_spec → re-ground → crystallize loop, human-reviewed. */
|
|
266
|
+
async cloudFailures(repo) {
|
|
267
|
+
if (!this.deps.cloudFailures) return "Hover Cloud unavailable in this server.";
|
|
268
|
+
const res = await this.deps.cloudFailures(repo);
|
|
269
|
+
if (!Array.isArray(res)) return `\u2717 ${res.error}`;
|
|
270
|
+
if (res.length === 0) {
|
|
271
|
+
return `\u2713 Hover Cloud reports no drifted specs${repo ? ` for ${repo}` : ""} \u2014 the CI heal queue is empty.`;
|
|
272
|
+
}
|
|
273
|
+
const lines = res.map((r) => {
|
|
274
|
+
const what = r.hint.failingLocator ? `${r.hint.failingAction ?? "failure"} on \`${r.hint.failingLocator}\`` : r.hint.error;
|
|
275
|
+
const where = [r.project.repo, r.run.branch && `@ ${r.run.branch}`, r.run.ciUrl].filter(Boolean).join(" \xB7 ");
|
|
276
|
+
return `- **${healSlug(r.specFile)}** \u2014 ${what}
|
|
277
|
+
${where}`;
|
|
278
|
+
});
|
|
279
|
+
return [
|
|
280
|
+
`${res.length} spec(s) drifted in CI (Hover Cloud heal queue):`,
|
|
281
|
+
"",
|
|
282
|
+
...lines,
|
|
283
|
+
"",
|
|
284
|
+
"Heal locally, one at a time: `/mcp__hover__heal <slug>` (replays the recorded steps, re-grounds the drifted one in the live app; the user reviews the diff). A queue entry closes automatically when CI next sees that spec pass."
|
|
285
|
+
].join("\n");
|
|
286
|
+
}
|
|
261
287
|
/** LLM-Wiki P1 Lint: run the deterministic `.hover/` health check (map vs
|
|
262
288
|
* specs vs runs) and return it as a readable report the agent acts on (heal a
|
|
263
289
|
* regressed line, map an orphan spec, drop a dead ref). The LLM-judged checks
|
|
@@ -577,6 +603,16 @@ function createHoverMcpServer(c, opts = {}) {
|
|
|
577
603
|
},
|
|
578
604
|
({ slug }) => guard(() => c.promoteOptimized(slug))
|
|
579
605
|
);
|
|
606
|
+
server2.registerTool(
|
|
607
|
+
"cloud_failures",
|
|
608
|
+
{
|
|
609
|
+
description: "Hover Cloud's open heal queue: the specs whose CI runs drifted (each with its failing locator + branch + CI link). Heal one locally with `/mcp__hover__heal <slug>`; an entry closes automatically when CI next sees that spec pass. Needs a connected cloud account (HOVER_CLOUD_TOKEN or ~/.hover/credentials.json).",
|
|
610
|
+
inputSchema: {
|
|
611
|
+
repo: z.string().optional().describe('Limit to one GitHub repo ("owner/name"). Omit for all your projects.')
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
({ repo }) => guard(() => c.cloudFailures(repo))
|
|
615
|
+
);
|
|
580
616
|
server2.registerPrompt(
|
|
581
617
|
"test_app",
|
|
582
618
|
{
|
|
@@ -680,6 +716,8 @@ function healPrompt(spec) {
|
|
|
680
716
|
|
|
681
717
|
A spec "drifted" when the app changed so a recorded step no longer locates its control (a renamed button, a moved field). Healing = re-grounding ONLY the broken step against the current UI, keeping everything else, so record==replay still holds.
|
|
682
718
|
|
|
719
|
+
Drift found in CI (a Hover Cloud heal request)? Call \`cloud_failures\` first \u2014 it lists each drifted spec's slug, failing locator, and branch, so you know what to heal and why.
|
|
720
|
+
|
|
683
721
|
Work ONE spec at a time:
|
|
684
722
|
|
|
685
723
|
1. **Detect** \u2014 \`replay_spec("<slug>")\`. It replays the spec's recorded grounded steps against the live app and reports the first step that fails to locate: its index, the tool, and what it was \`lookingFor\` (role+name/text). If it replays clean, that spec is fine \u2014 move on.
|
|
@@ -825,7 +863,20 @@ var controller = new HoverMcpController({
|
|
|
825
863
|
},
|
|
826
864
|
saveOptimized: (slug, code) => saveOptimizedCandidate(DEV_ROOT, slug, code),
|
|
827
865
|
promoteOptimized: (slug) => promoteOptimizedCandidate(DEV_ROOT, slug),
|
|
828
|
-
lintWiki: () => lintWiki(DEV_ROOT)
|
|
866
|
+
lintWiki: () => lintWiki(DEV_ROOT),
|
|
867
|
+
cloudFailures: async (repo) => {
|
|
868
|
+
const creds = readCloudCredentials();
|
|
869
|
+
if (!creds) {
|
|
870
|
+
return {
|
|
871
|
+
error: 'Hover Cloud not connected \u2014 set HOVER_CLOUD_TOKEN (mint one at https://cloud.gethover.dev \u2192 Settings \u2192 Access tokens) or run "Hover: Connect Hover Cloud" in VS Code.'
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
try {
|
|
875
|
+
return await fetchHealRequests(creds, { status: "open", ...repo ? { repo } : {} });
|
|
876
|
+
} catch (e) {
|
|
877
|
+
return { error: e instanceof Error ? e.message.split("\n")[0] : String(e) };
|
|
878
|
+
}
|
|
879
|
+
}
|
|
829
880
|
});
|
|
830
881
|
var server = createHoverMcpServer(controller, { lang: LANG });
|
|
831
882
|
await server.connect(new StdioServerTransport());
|
package/dist/mcp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp.ts","../src/mcp/controller.ts","../src/mcp/server.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { chromium, type Browser, type Page } from 'playwright-core';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n launchDebugChrome,\n writeSpec,\n writeApiSpec,\n writeFact,\n recallMemory,\n readFact,\n formatFact,\n readSidecar,\n detectExtractableFlows,\n extractPageObjects,\n buildOptimizeBrief,\n saveOptimizedCandidate,\n promoteOptimizedCandidate,\n lintWiki,\n appendWikiLog,\n type SkillStep,\n type ApiCheck,\n type Redaction,\n} from '@hover-dev/core/engine';\nimport { HoverMcpController } from './mcp/controller.js';\nimport { createHoverMcpServer } from './mcp/server.js';\n\n/*\n * `hover-mcp` — the MCP-first surface. Add it to your OWN agent (Claude Code,\n * Cursor, …); the agent drives the app through Hover's grounded tools and calls\n * crystallize_spec to save a plain Playwright spec. Hover spawns no agent here —\n * the calling agent IS the intelligence; Hover guarantees record==replay at the\n * output. Config via env: HOVER_TARGET, HOVER_CDP_PORT, HOVER_PROJECT_ROOT,\n * HOVER_LANG (language the workflow prompts tell the agent to converse in).\n *\n * NOTE: stdio is the MCP transport — never write to stdout from this process.\n */\n\nconst TARGET = process.env.HOVER_TARGET || 'http://localhost:5173';\nconst PORT = Number(process.env.HOVER_CDP_PORT || 9222);\nconst LANG = process.env.HOVER_LANG;\nconst DEV_ROOT = process.env.HOVER_PROJECT_ROOT || process.cwd();\nconst CDP_URL = `http://localhost:${PORT}`;\n\nconst originOf = (u: string): string | null => {\n try {\n return new URL(u).origin;\n } catch {\n return null;\n }\n};\n\nlet browser: Browser | null = null;\n\n/** Launch/connect the debug Chrome lazily and return the page on the app. */\nasync function getPage(): Promise<Page> {\n if (!browser || !browser.isConnected()) {\n await launchDebugChrome({ port: PORT, url: TARGET });\n browser = await chromium.connectOverCDP(CDP_URL, { timeout: 8000 });\n }\n const pages = browser.contexts().flatMap((ctx) => ctx.pages());\n const want = originOf(TARGET);\n const match = want ? pages.find((p) => originOf(p.url()) === want) : undefined;\n if (match) return match;\n if (pages.length) return pages[pages.length - 1];\n const ctx = browser.contexts()[0] ?? (await browser.newContext());\n return ctx.newPage();\n}\n\nconst controller = new HoverMcpController({\n getPage,\n crystallize: async (name: string, description: string | undefined, steps: SkillStep[], redactions: Redaction[]) => {\n const res = await writeSpec({ devRoot: DEV_ROOT, name, description, steps, redactions, startUrl: TARGET, overwrite: true });\n await appendWikiLog(DEV_ROOT, 'crystallize', `${basename(res.path)} — ${name}`);\n return { path: res.path };\n },\n crystallizeApi: async (name: string, description: string | undefined, checks: ApiCheck[]) => {\n const res = await writeApiSpec({ devRoot: DEV_ROOT, name, description, checks, startUrl: TARGET, overwrite: true });\n await appendWikiLog(DEV_ROOT, 'api', `${basename(res.path)} — ${name}`);\n return { path: res.path };\n },\n recordFact: (title, rule, type) =>\n writeFact(DEV_ROOT, { name: title, description: title, type, body: rule }),\n recall: () => recallMemory(DEV_ROOT),\n recallFact: async (name: string) => {\n const fact = await readFact(DEV_ROOT, name);\n return fact ? formatFact(fact) : null;\n },\n readSpecSteps: async (slug: string) => {\n const sc = await readSidecar(DEV_ROOT, slug);\n return sc ? { steps: sc.steps, startUrl: TARGET } : null;\n },\n detectSharedFlows: () => detectExtractableFlows(DEV_ROOT),\n extractPageObjects: async () => {\n const res = await extractPageObjects(DEV_ROOT);\n if (res.pages.length) {\n await appendWikiLog(DEV_ROOT, 'extract', `${res.pages.length} page object(s), folded ${res.folded.length} spec(s)`);\n }\n return res;\n },\n optimizeBrief: async (slug: string) => {\n try {\n const { prompt } = await buildOptimizeBrief(DEV_ROOT, slug);\n return { prompt };\n } catch (e) {\n return { error: e instanceof Error ? e.message.split('\\n')[0] : String(e) };\n }\n },\n saveOptimized: (slug: string, code: string) => saveOptimizedCandidate(DEV_ROOT, slug, code),\n promoteOptimized: (slug: string) => promoteOptimizedCandidate(DEV_ROOT, slug),\n lintWiki: () => lintWiki(DEV_ROOT),\n});\n\nconst server = createHoverMcpServer(controller, { lang: LANG });\nawait server.connect(new StdioServerTransport());\n","import { request as pwRequest, type Page } from 'playwright-core';\nimport {\n groundedLocate,\n replayOnPage,\n type GroundedTarget,\n type SkillStep,\n type ApiCheck,\n type ReplayStep,\n type Redaction,\n type SharedFlow,\n type ExtractResult,\n type LintResult,\n} from '@hover-dev/core/engine';\n\n/*\n * The hover-mcp engine, decoupled from the MCP wire layer so it's testable with\n * a mock Page. The user's OWN agent (Claude Code / Cursor) calls these via MCP:\n * it reads the page (snapshot), actuates with GROUNDED targets (role+name →\n * testId → text), and Hover buffers each successful actuation as a SkillStep.\n * `crystallize` turns the buffer into a plain Playwright spec — record==replay,\n * because the buffered selectors ARE the ones that drove the page.\n *\n * API layer: while the agent drives the UI, Hover passively buffers the app's\n * xhr/fetch traffic off the SAME CDP connection (no MITM proxy). The agent reads\n * it with `capture_requests`, verifies a contract/authz check with\n * `replay_request`, and crystallizes the worthwhile ones into a\n * `*.api-test.spec.ts`. record == replay holds for the API layer too.\n */\n\nexport type FactType = 'business-rule' | 'expected-behavior' | 'validation' | 'access-policy';\n\n/** A passively-observed xhr/fetch call (metadata + a light body shape). */\nexport interface CapturedRequest {\n method: string;\n url: string;\n status: number;\n contentType?: string;\n /** Request post data, truncated. */\n requestBody?: string;\n /** Top-level keys of a JSON response (a light shape hint). */\n responseKeys?: string[];\n}\n\nexport interface McpDeps {\n /** Resolve the live page on the app under test (launch/connect lazily). */\n getPage: () => Promise<Page>;\n /** Write the buffered steps to a UI spec; returns the written path. `redactions`\n * parameterize captured credentials into `process.env.<envVar>` refs (and let\n * auth-fixture detect the login prefix). */\n crystallize: (\n name: string,\n description: string | undefined,\n steps: SkillStep[],\n redactions: Redaction[],\n ) => Promise<{ path: string }>;\n /** Write selected API checks to a `*.api-test.spec.ts`; returns the path. */\n crystallizeApi: (name: string, description: string | undefined, checks: ApiCheck[]) => Promise<{ path: string }>;\n /** Persist a learned business rule to .hover/memory/ (rules only — no secrets). */\n recordFact?: (title: string, rule: string, type: FactType) => Promise<{ path: string } | { error: string }>;\n /** Recall known business knowledge from .hover/memory/ ('' if none). Progressive:\n * full bodies when the set is small, the index alone when it's large. */\n recall?: () => Promise<string>;\n /** Read ONE remembered rule's full text by name/slug (behind recall_fact), or\n * null if nothing matches — the on-demand tier of progressive recall. */\n recallFact?: (name: string) => Promise<string | null>;\n /** Read a saved spec's recorded grounded steps (its `.hover/sidecars/<slug>.json`)\n * so self-heal can replay them against the live app. */\n readSpecSteps?: (slug: string) => Promise<{ steps: SkillStep[]; startUrl?: string } | null>;\n /** Detect NON-login flows shared across saved specs (for the extract offer). */\n detectSharedFlows?: () => Promise<SharedFlow[]>;\n /** Lift shared flows into Page Objects + fold the specs that use them. */\n extractPageObjects?: () => Promise<ExtractResult>;\n /** Build the optimize (F7) brief for a spec — the improvement rules + the spec\n * + its observed session + reusable Page Objects — for the user's OWN agent to\n * work from. `{ error }` when the spec doesn't exist. No model runs. */\n optimizeBrief?: (slug: string) => Promise<{ prompt: string } | { error: string }>;\n /** File an agent-improved spec as a REVIEW candidate: validate it against the\n * deterministic guardrails, soft-batch, and write `.hover/cache/optimized/\n * <slug>.spec.ts.draft` (never the original). Throws if it fails validation. */\n saveOptimized?: (slug: string, code: string) => Promise<{ candidatePath: string }>;\n /** Promote a reviewed candidate: overwrite the real spec with its draft + drop\n * the draft. The one place a candidate replaces the original — user-approved. */\n promoteOptimized?: (slug: string) => Promise<{ path: string }>;\n /** Deterministic health check over `.hover/`: map vs spec files vs run ledger. */\n lintWiki?: () => Promise<LintResult>;\n}\n\nfunction describe(g: GroundedTarget): string {\n if (g.role && g.name) return `${g.role} \"${g.name}\"`;\n if (g.testId) return `testId \"${g.testId}\"`;\n if (g.text) return `text \"${g.text}\"`;\n return '(no target)';\n}\n\nconst MAX_CAPTURED = 200; // ring-buffer cap so a long run can't grow unbounded\n\nexport class HoverMcpController {\n /** The grounded-action buffer — sliced by `crystallize`. */\n readonly steps: SkillStep[] = [];\n /** Credentials typed into password fields — parameterized to process.env in\n * the crystallized spec (never written literally) + used to detect the login\n * prefix for auth-as-fixture. */\n private readonly redactions: Redaction[] = [];\n /** Passively-observed xhr/fetch traffic — read by `capture_requests`. */\n private readonly captured: CapturedRequest[] = [];\n /** Pages we've already attached a network listener to (avoid duplicates). */\n private readonly listening = new WeakSet<Page>();\n\n constructor(private readonly deps: McpDeps) {}\n\n private push(tool: string, input: unknown): void {\n this.steps.push({ kind: 'step', tool, input });\n }\n\n /** getPage + ensure the passive network listener is attached to it. */\n private async livePage(): Promise<Page> {\n const page = await this.deps.getPage();\n if (!this.listening.has(page)) {\n this.listening.add(page);\n page.on('response', (response) => {\n void this.onResponse(response).catch(() => {});\n });\n }\n return page;\n }\n\n private async onResponse(response: import('playwright-core').Response): Promise<void> {\n const req = response.request();\n const rt = req.resourceType();\n if (rt !== 'xhr' && rt !== 'fetch') return; // API calls only — skip docs/assets\n const contentType = (response.headers()['content-type'] || '').split(';')[0] || undefined;\n let responseKeys: string[] | undefined;\n if (contentType === 'application/json') {\n try {\n const body = await response.json();\n if (body && typeof body === 'object' && !Array.isArray(body)) {\n responseKeys = Object.keys(body as Record<string, unknown>).slice(0, 24);\n }\n } catch {\n /* body unavailable / not JSON — metadata only */\n }\n }\n const post = req.postData();\n this.captured.push({\n method: req.method(),\n url: req.url(),\n status: response.status(),\n contentType,\n requestBody: post ? post.slice(0, 2000) : undefined,\n responseKeys,\n });\n if (this.captured.length > MAX_CAPTURED) this.captured.splice(0, this.captured.length - MAX_CAPTURED);\n }\n\n async navigate(url: string): Promise<string> {\n const page = await this.livePage();\n await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 20000 });\n this.push('browser_navigate', { url });\n return `navigated to ${url}`;\n }\n\n /** ARIA snapshot of the page — the agent reads role+name from here. */\n async snapshot(): Promise<string> {\n const page = await this.livePage();\n return await page.locator('body').ariaSnapshot();\n }\n\n private async resolve(g: GroundedTarget) {\n const page = await this.livePage();\n const loc = groundedLocate(page, g);\n if (!loc) throw new Error(`pass role+name (preferred), or testId, or text — taken from the snapshot`);\n return loc;\n }\n\n async click(g: GroundedTarget): Promise<string> {\n const loc = await this.resolve(g);\n await loc.click({ timeout: 8000 });\n this.push('click_control', g);\n return `✓ clicked ${describe(g)}`;\n }\n\n async fill(g: GroundedTarget, value: string): Promise<string> {\n const loc = await this.resolve(g);\n await loc.fill(value, { timeout: 8000 });\n this.push('fill_control', { ...g, value });\n // A value typed into a password field is a secret: parameterize it to\n // process.env so it never lands literally in the spec/sidecar, and so\n // auth-as-fixture can detect this fill as part of the login prefix.\n if (value) {\n let isPassword = false;\n try {\n isPassword = (await loc.getAttribute('type')) === 'password';\n } catch {\n /* locator has no getAttribute (test mock) or field gone — treat as non-secret */\n }\n if (isPassword && !this.redactions.some((r) => r.value === value)) {\n this.redactions.push({ value, envVar: 'HOVER_PASSWORD' });\n }\n }\n return `✓ filled ${describe(g)}`;\n }\n\n async select(g: GroundedTarget, value: string): Promise<string> {\n const loc = await this.resolve(g);\n await loc.selectOption(value, { timeout: 8000 });\n this.push('select_control', { ...g, value });\n return `✓ selected ${value} in ${describe(g)}`;\n }\n\n async check(g: GroundedTarget, checked: boolean): Promise<string> {\n const loc = await this.resolve(g);\n if (checked) await loc.check({ timeout: 8000 });\n else await loc.uncheck({ timeout: 8000 });\n this.push('check_control', { ...g, checked });\n return `✓ ${checked ? 'checked' : 'unchecked'} ${describe(g)}`;\n }\n\n async assertVisible(g: GroundedTarget): Promise<string> {\n const loc = await this.resolve(g);\n const visible = await loc.first().isVisible();\n if (!visible) throw new Error(`${describe(g)} is not visible`);\n this.push('assert_visible', { ...g, matcher: 'visible' });\n return `✓ ${describe(g)} is visible`;\n }\n\n /** Return the passively-observed xhr/fetch traffic (optionally filtered) as\n * JSON for the agent to reason over when deciding API checks. */\n captureRequests(filter?: { urlContains?: string; method?: string }): string {\n let rows = this.captured;\n if (filter?.urlContains) rows = rows.filter((r) => r.url.includes(filter.urlContains!));\n if (filter?.method) rows = rows.filter((r) => r.method.toUpperCase() === filter.method!.toUpperCase());\n if (rows.length === 0) {\n return 'No xhr/fetch traffic captured yet. Drive the app (navigate / click) so its API calls fire, then call this again.';\n }\n return JSON.stringify(rows.slice(-60), null, 2);\n }\n\n /** Send a (possibly mutated) request and return the response summary — the\n * API analogue of replaying a grounded step, so an authz/contract check is\n * VERIFIED against the live app before it's crystallized (no confabulation).\n * `authenticated` (default true) replays with the browser session's cookies;\n * false uses a fresh context (no session) for \"requires auth\" checks. */\n async replayRequest(opts: {\n method: string;\n url: string;\n headers?: Record<string, string>;\n body?: unknown;\n authenticated?: boolean;\n }): Promise<string> {\n const page = await this.livePage();\n const authed = opts.authenticated !== false;\n const ctx = authed ? page.context().request : await pwRequest.newContext();\n try {\n const m = opts.method.toUpperCase();\n const reqOpts: { headers?: Record<string, string>; data?: unknown } = {};\n if (opts.headers) reqOpts.headers = opts.headers;\n if (opts.body !== undefined && m !== 'GET' && m !== 'HEAD') reqOpts.data = opts.body;\n const res = await ctx.fetch(opts.url, { method: m, ...reqOpts });\n const contentType = (res.headers()['content-type'] || '').split(';')[0] || '';\n let preview = '';\n try {\n preview = contentType === 'application/json' ? JSON.stringify(await res.json()).slice(0, 800) : (await res.text()).slice(0, 400);\n } catch {\n /* no body */\n }\n return JSON.stringify({ status: res.status(), ok: res.ok(), contentType, body: preview, authenticated: authed }, null, 2);\n } finally {\n if (!authed) await ctx.dispose();\n }\n }\n\n /** Recall what earlier runs learned about this app's business rules. Progressive:\n * a large memory comes back as an INDEX; use recallFact to pull one rule's body. */\n async recall(): Promise<string> {\n if (!this.deps.recall) return 'No business memory available.';\n const known = await this.deps.recall();\n return known || 'No business knowledge recorded yet for this app.';\n }\n\n /** Read one remembered rule's full text by name (the on-demand tier — used when\n * recall returned only the index and the agent needs a specific rule's body). */\n async recallFact(name: string): Promise<string> {\n if (!this.deps.recallFact) return 'Rule lookup unavailable in this server.';\n const body = await this.deps.recallFact(name);\n return body ?? `No remembered rule matches \"${name}\". Call recall_business_knowledge to see the index of known rules.`;\n }\n\n /** Persist a confirmed business RULE so future runs don't re-ask it. Rules\n * only — never secrets / credentials / PII. */\n async recordFact(title: string, rule: string, type: FactType = 'business-rule'): Promise<string> {\n if (!this.deps.recordFact) return 'Memory channel unavailable; continuing.';\n const res = await this.deps.recordFact(title, rule, type);\n return 'error' in res ? `✗ could not save fact: ${res.error}` : `✓ remembered: ${title}`;\n }\n\n /** Crystallize the buffered UI flow → a plain Playwright spec, then clear the\n * buffer for the next flow. */\n async crystallize(name: string, description?: string): Promise<string> {\n if (this.steps.length === 0) return 'Nothing to crystallize yet — actuate some controls first.';\n const flow = [...this.steps];\n const { path } = await this.deps.crystallize(name, description, flow, [...this.redactions]);\n this.steps.length = 0;\n return `✓ wrote ${path} (${flow.length} step${flow.length === 1 ? '' : 's'})`;\n }\n\n /** Crystallize agent-selected API checks → a `*.api-test.spec.ts`. The agent\n * decides WHICH calls are worth locking (a real contract / authz boundary),\n * not every captured request. */\n async crystallizeApiSpec(name: string, description: string | undefined, checks: ApiCheck[]): Promise<string> {\n if (!checks?.length) return 'No checks provided — pass the API checks you verified worth locking.';\n const { path } = await this.deps.crystallizeApi(name, description, checks);\n return `✓ wrote ${path} (${checks.length} check${checks.length === 1 ? '' : 's'})`;\n }\n\n /** Self-heal detection: replay a saved spec's RECORDED grounded steps against\n * the live app and report the first step that no longer locates — the drift\n * point the agent re-grounds. No `playwright test`, no install; the same\n * grounded replay as creation-verification, seeded from the spec's sidecar. */\n async replaySpec(slug: string): Promise<string> {\n if (!this.deps.readSpecSteps) return 'Spec replay unavailable in this server.';\n const sc = await this.deps.readSpecSteps(slug);\n if (!sc) {\n return `No sidecar for \"${slug}\" — only Hover-crystallized specs can be replayed (looked for .hover/sidecars/${slug}.json).`;\n }\n const page = await this.livePage();\n const devUrl = sc.startUrl ?? page.url();\n const res = await replayOnPage(page, devUrl, sc.steps as ReplayStep[]);\n if (res.ok) {\n return `✓ \"${slug}\" still replays clean — ${res.ran}/${res.total} grounded steps located. No drift to heal.`;\n }\n const f = res.failures[0];\n return JSON.stringify(\n {\n spec: slug,\n drifted: true,\n ranBeforeBreak: res.ran,\n total: res.total,\n brokeAtStep: f.index,\n tool: f.tool,\n lookingFor: f.target,\n error: f.error,\n next: 'Re-snapshot at this point, re-locate the control by the intent in \"lookingFor\" (its label/role may have changed), re-drive from here, then crystallize_spec with the SAME name to overwrite the healed spec.',\n },\n null,\n 2,\n );\n }\n\n /** LLM-Wiki P1 Lint: run the deterministic `.hover/` health check (map vs\n * specs vs runs) and return it as a readable report the agent acts on (heal a\n * regressed line, map an orphan spec, drop a dead ref). The LLM-judged checks\n * (contradictory rules, code routes missing from the map) are driven on top of\n * this by the /mcp__hover__lint prompt. */\n async lintWiki(): Promise<string> {\n if (!this.deps.lintWiki) return 'Wiki lint is unavailable in this server.';\n const res = await this.deps.lintWiki();\n if (!res.hasMap) {\n return 'No .hover/hover-map.md yet — nothing to lint. Run /mcp__hover__test_app first to map the app and crystallize specs.';\n }\n const { areas, lines, covered, specs } = res.summary;\n const head = `Wiki lint — ${covered}/${lines} lines covered across ${areas} area${areas === 1 ? '' : 's'}, ${specs} spec file${specs === 1 ? '' : 's'}.`;\n if (res.findings.length === 0) {\n return `${head}\\n✓ No drift: every mapped spec exists, no covered line is failing, no spec is unmapped.`;\n }\n const icon = { error: '✗', warn: '⚠', info: '·' } as const;\n const body = res.findings\n .map((f) => `${icon[f.severity]} [${f.kind}] ${f.message}${f.fix ? `\\n → ${f.fix}` : ''}`)\n .join('\\n');\n return `${head}\\n${res.findings.length} finding${res.findings.length === 1 ? '' : 's'}:\\n${body}`;\n }\n\n /** Report NON-login flows repeated across the saved specs — so the agent can\n * ASK the user whether to lift them into a shared Page Object. Read-only. */\n async detectSharedFlows(): Promise<string> {\n if (!this.deps.detectSharedFlows) return 'Shared-flow detection unavailable in this server.';\n const flows = await this.deps.detectSharedFlows();\n if (!flows.length) {\n return 'No extractable shared flows — no set of specs shares a non-login entry flow (login is already handled by the auth setup). Nothing to lift.';\n }\n return JSON.stringify(\n flows.map((f) => ({ sharedBy: f.specs, steps: f.prose })),\n null,\n 2,\n );\n }\n\n /** Optimize (F7), MCP-first: return the improvement brief for the user's own\n * agent to work from (the agent IS the model — Hover picks none). Never\n * throws; a missing spec / unavailable channel comes back as a plain message\n * the agent reads (this feeds a PROMPT, which isn't wrapped in the ✗ guard). */\n async optimizeBrief(slug: string): Promise<string> {\n if (!this.deps.optimizeBrief) return `Optimize is unavailable in this server (heal/optimize need spec sidecars).`;\n try {\n const res = await this.deps.optimizeBrief(slug);\n if ('error' in res) {\n return `Can't optimize \"${slug}\": ${res.error}. Optimize only works on a Hover-crystallized spec — list __vibe_tests__/*.spec.ts and pass one by slug.`;\n }\n return res.prompt;\n } catch (e) {\n return `Can't build the optimize brief for \"${slug}\": ${e instanceof Error ? e.message.split('\\n')[0] : String(e)}`;\n }\n }\n\n /** File an agent-improved spec as a review candidate. Validation failures throw\n * (the tool guard turns them into a ✗ the agent can fix and retry against). */\n async saveOptimized(slug: string, code: string): Promise<string> {\n if (!this.deps.saveOptimized) return 'Optimize is unavailable in this server.';\n const { candidatePath } = await this.deps.saveOptimized(slug, code);\n return `✓ filed optimized candidate at ${candidatePath} (your spec is untouched). Show the user the diff vs __vibe_tests__/${slug}.spec.ts; if they approve, call promote_optimized_spec(\"${slug}\") to apply it (or they can review + promote in the VS Code cockpit).`;\n }\n\n /** Apply a reviewed candidate over the real spec (user-approved). */\n async promoteOptimized(slug: string): Promise<string> {\n if (!this.deps.promoteOptimized) return 'Optimize is unavailable in this server.';\n const { path } = await this.deps.promoteOptimized(slug);\n return `✓ promoted the candidate → ${path} (draft removed). Run it to confirm: npx playwright test ${path}`;\n }\n\n /** Lift the detected shared flows into `pages/*` + `fixtures.ts` and fold the\n * specs that use them. Call ONLY after the user approves (detectSharedFlows\n * → ask → this). Deterministic; login flows are excluded (auth-fixture owns). */\n async extractPageObjects(): Promise<string> {\n if (!this.deps.extractPageObjects) return 'Page Object extraction unavailable in this server.';\n const res = await this.deps.extractPageObjects();\n if (!res.pages.length) return 'Nothing to extract — no shared non-login flow found.';\n return `✓ lifted ${res.pages.length} Page Object${res.pages.length === 1 ? '' : 's'} (${res.pages\n .map((p) => p.className)\n .join(', ')}) into __vibe_tests__/pages + fixtures.ts; folded ${res.folded.length} spec${\n res.folded.length === 1 ? '' : 's'\n } (${res.folded.join(', ')}) to consume them.`;\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { HoverMcpController } from './controller.js';\n\n/* Wrap the controller as an MCP server. Tool names + the GROUNDED target shape\n * mirror Hover's control MCP so an agent that knows one knows the other. Every\n * handler returns text (✓/✗) and never throws — a failed locate/action becomes\n * a ✗ message the calling agent can react to, not an MCP transport error. */\n\nconst md = (text: string) => ({ content: [{ type: 'text' as const, text }] });\nconst errLine = (e: unknown) => (e instanceof Error ? e.message.split('\\n')[0] : String(e));\n\n/** Common language-code aliases → a name the model reads unambiguously. Anything\n * else passes through verbatim (so HOVER_LANG=Français or =中文 also works). */\nconst LANG_NAMES: Record<string, string> = {\n zh: 'Chinese (简体中文)', 'zh-cn': 'Chinese (简体中文)', 'zh-hans': 'Chinese (简体中文)',\n 'zh-tw': 'Chinese (繁體中文)', 'zh-hant': 'Chinese (繁體中文)',\n ja: 'Japanese', ko: 'Korean', es: 'Spanish', fr: 'French', de: 'German',\n pt: 'Portuguese', it: 'Italian', ru: 'Russian', ar: 'Arabic', hi: 'Hindi',\n};\n\n/** A prompt prefix telling the agent which language to CONVERSE in with the user.\n * Empty for English / unset (the default). Code stays English regardless — we\n * localize the interaction, not the artifacts. */\nexport function languageDirective(lang?: string): string {\n const raw = (lang ?? '').trim();\n if (!raw || /^(en|en-.*|english)$/i.test(raw)) return '';\n const name = LANG_NAMES[raw.toLowerCase()] ?? raw;\n return (\n `IMPORTANT — Communicate with the user in ${name}: every question you ask, ` +\n `status update, summary, and explanation must be in ${name}. Keep code, spec / ` +\n `test names, identifiers, file paths, and \\`slash-commands\\` in English. Only the ` +\n `human-facing prose is translated.\\n\\n`\n );\n}\n\nconst GROUND = {\n role: z.string().optional().describe(\"ARIA role from the snapshot, e.g. 'button', 'textbox', 'link'. Pair with `name`.\"),\n name: z.string().optional().describe('Accessible name from the snapshot, exactly as shown. Pair with `role`.'),\n testId: z.string().optional().describe('A data-testid, if the element has one and no clean role+name.'),\n text: z.string().optional().describe('Real visible text on the element — last resort.'),\n within: z\n .object({ role: z.string(), name: z.string() })\n .optional()\n .describe('Scope the search to a container first (role+name) when a label repeats across groups.'),\n};\n\n/** One asserted API call for crystallize_api_spec — mirrors core's ApiCheck. */\nconst API_CHECK = z.object({\n title: z.string().describe('Short test name, e.g. \"GET /api/cart returns the cart\".'),\n method: z.string(),\n url: z.string().describe('Full URL or same-origin path (same-origin is relativized in the spec).'),\n requestBody: z.any().optional(),\n headers: z.record(z.string(), z.string()).optional(),\n expectStatus: z.number().optional().describe('Expected status — verified with replay_request.'),\n expectBodyKeys: z.array(z.string()).optional().describe('Top-level response keys to assert present.'),\n note: z.string().optional().describe('Emitted as a leading comment, e.g. \"authz: no session → 401\".'),\n});\n\n/** Options for the MCP server. `lang` (from HOVER_LANG) makes the workflow\n * prompts tell the agent which language to CONVERSE in with the user. */\nexport interface HoverServerOptions {\n lang?: string;\n}\n\nexport function createHoverMcpServer(c: HoverMcpController, opts: HoverServerOptions = {}): McpServer {\n const server = new McpServer({ name: 'hover', version: '0.1.0' });\n const guard = (fn: () => Promise<string>) => fn().then(md, (e) => md(`✗ ${errLine(e)}`));\n const lang = languageDirective(opts.lang);\n\n server.registerTool(\n 'browser_navigate',\n { description: 'Open a URL in the app under test (the debug Chrome).', inputSchema: { url: z.string() } },\n ({ url }) => guard(() => c.navigate(url)),\n );\n\n server.registerTool(\n 'browser_snapshot',\n {\n description:\n 'Read the current page as an ARIA snapshot (role + accessible-name tree). Call this BEFORE actuating to get the exact role+name to pass to the *_control tools.',\n inputSchema: {},\n },\n () => guard(() => c.snapshot()),\n );\n\n server.registerTool(\n 'click_control',\n {\n description:\n 'Click a control by a GROUNDED target read off the snapshot (role+name preferred → testId → text). Grounded so the saved spec replays exactly what you did.',\n inputSchema: GROUND,\n },\n (g) => guard(() => c.click(g)),\n );\n\n server.registerTool(\n 'fill_control',\n { description: 'Type a value into a textbox/field by a grounded target.', inputSchema: { ...GROUND, value: z.string() } },\n ({ value, ...g }) => guard(() => c.fill(g, value)),\n );\n\n server.registerTool(\n 'select_control',\n { description: 'Choose an option in a <select> by a grounded target.', inputSchema: { ...GROUND, value: z.string() } },\n ({ value, ...g }) => guard(() => c.select(g, value)),\n );\n\n server.registerTool(\n 'check_control',\n {\n description: 'Check or uncheck a checkbox/radio by a grounded target (handles sr-only / hidden inputs).',\n inputSchema: { ...GROUND, checked: z.boolean().optional().describe('true (default) = check; false = uncheck.') },\n },\n ({ checked, ...g }) => guard(() => c.check(g, checked !== false)),\n );\n\n server.registerTool(\n 'assert_visible',\n {\n description: 'Assert a control/text is visible now — captures an expect(...).toBeVisible() into the saved spec.',\n inputSchema: GROUND,\n },\n (g) => guard(() => c.assertVisible(g)),\n );\n\n server.registerTool(\n 'recall_business_knowledge',\n {\n description:\n 'Recall what earlier Hover runs learned about this app (business rules, expected behaviors, access policies). Call this at the START so you do not re-ask settled questions. Treat what it returns as ground truth. For an app with a lot of remembered rules this returns an INDEX (one line per rule); when a rule is relevant to what you are testing, read its full text with recall_fact.',\n inputSchema: {},\n },\n () => guard(() => c.recall()),\n );\n\n server.registerTool(\n 'recall_fact',\n {\n description:\n \"Read ONE remembered business rule's full text by name (the name shown in the recall_business_knowledge index). Use it when recall returned only the index and you need a specific rule's detail before deciding how to test.\",\n inputSchema: {\n name: z.string().describe('The rule name from the recall index (a slug like \"guests-cannot-checkout\").'),\n },\n },\n ({ name }) => guard(() => c.recallFact(name)),\n );\n\n server.registerTool(\n 'record_fact',\n {\n description:\n \"Remember a durable BUSINESS RULE about this app so neither you nor a future run re-asks it — e.g. an expected behavior, a validation rule, an access policy, or the answer to a 'bug or by-design?' the user just confirmed. State it as a clean self-contained rule. RULES ONLY — never store secrets, passwords, tokens, or personal data.\",\n inputSchema: {\n title: z.string().describe('Short title for the rule (becomes its memory filename + index entry).'),\n rule: z.string().describe('The rule itself, stated clearly and self-contained (no secrets/PII).'),\n type: z\n .enum(['business-rule', 'expected-behavior', 'validation', 'access-policy'])\n .optional()\n .describe('What kind of knowledge this is. Defaults to business-rule.'),\n },\n },\n ({ title, rule, type }) => guard(() => c.recordFact(title, rule, type ?? 'business-rule')),\n );\n\n server.registerTool(\n 'crystallize_spec',\n {\n description:\n 'Save the flow you JUST performed (the grounded click/fill/select/check/assert actions since the last crystallize) as a plain Playwright .spec.ts in __vibe_tests__/. Call it the moment you finish a coherent end-to-end flow. Name it in English, imperative (e.g. \"Log in\").',\n inputSchema: {\n name: z.string().describe('Short imperative flow name in English — becomes the spec filename + test name.'),\n description: z.string().optional().describe('One line on what this flow verifies.'),\n },\n },\n ({ name, description }) => guard(() => c.crystallize(name, description)),\n );\n\n // ── API layer ──────────────────────────────────────────────────────────────\n // While you drive the UI, Hover passively buffers the app's xhr/fetch traffic\n // off the same CDP connection (no MITM). Read it, verify a check, crystallize.\n\n server.registerTool(\n 'capture_requests',\n {\n description:\n \"Return the app's xhr/fetch API calls observed while you drove the UI (method, url, status, content-type, request body, response shape). Call it after exercising a flow to see which endpoints it hit. Optionally filter.\",\n inputSchema: {\n urlContains: z.string().optional().describe('Only calls whose URL contains this substring.'),\n method: z.string().optional().describe('Only calls with this HTTP method.'),\n },\n },\n ({ urlContains, method }) => guard(() => Promise.resolve(c.captureRequests({ urlContains, method }))),\n );\n\n server.registerTool(\n 'replay_request',\n {\n description:\n 'Send a (possibly mutated) request and return the response, to VERIFY an API check before crystallizing it (no confabulated status codes). For an authz check, set authenticated:false (fresh context, no session) and expect 401/403; or drop/alter headers / swap an id for IDOR.',\n inputSchema: {\n method: z.string().describe('HTTP method, e.g. GET / POST.'),\n url: z.string().describe('Full URL or same-origin path.'),\n headers: z.record(z.string(), z.string()).optional().describe('Headers to send (omit/blank the auth header for a \"requires auth\" check).'),\n body: z.any().optional().describe('Request body for POST/PUT/PATCH.'),\n authenticated: z\n .boolean()\n .optional()\n .describe('true (default) = replay with the browser session; false = fresh context with NO session (for \"requires auth\" checks).'),\n },\n },\n ({ method, url, headers, body, authenticated }) =>\n guard(() => c.replayRequest({ method, url, headers, body, authenticated })),\n );\n\n server.registerTool(\n 'crystallize_api_spec',\n {\n description:\n 'Save selected API checks as a plain Playwright `*.api-test.spec.ts` (uses the `request` fixture). Use it ALONGSIDE crystallize_spec when a flow exercised a worthwhile API surface — a real contract, a data mutation, or an authz boundary. Be SELECTIVE: lock checks that matter, not every captured call. Verify each with replay_request first.',\n inputSchema: {\n name: z.string().describe('Short imperative English name — becomes the <name>.api-test.spec.ts filename.'),\n description: z.string().optional().describe('One line on what this API spec verifies.'),\n checks: z.array(API_CHECK).describe('The API checks to lock — each a request + its expected status / shape / authz outcome.'),\n },\n },\n ({ name, description, checks }) => guard(() => c.crystallizeApiSpec(name, description, checks)),\n );\n\n // ── Self-heal ────────────────────────────────────────────────────────────\n server.registerTool(\n 'replay_spec',\n {\n description:\n \"Detect drift in a saved spec: replay its RECORDED grounded steps against the LIVE app and report the first step that no longer locates (its index + what it was looking for). No `playwright test` needed. Use this to find what to heal, then re-ground that step and re-crystallize.\",\n inputSchema: {\n slug: z.string().describe('The spec slug = its filename without .spec.ts (e.g. \"login\" for login.spec.ts).'),\n },\n },\n ({ slug }) => guard(() => c.replaySpec(slug)),\n );\n\n // ── Page Object extraction (detect → ask → extract) ──────────────────────\n server.registerTool(\n 'detect_shared_flows',\n {\n description:\n 'After crystallizing the suite, report NON-login flows repeated across specs (login is already handled by the auth setup). Use it to decide whether to OFFER lifting a shared flow into a Page Object — then ASK the user before extracting.',\n inputSchema: {},\n },\n () => guard(() => c.detectSharedFlows()),\n );\n\n server.registerTool(\n 'extract_page_objects',\n {\n description:\n 'Lift the shared flows into __vibe_tests__/pages/* + fixtures.ts and fold the specs that use them (they call `await xPage.x()` from ./fixtures). Deterministic. Call ONLY after the user approves the offer from detect_shared_flows.',\n inputSchema: {},\n },\n () => guard(() => c.extractPageObjects()),\n );\n\n // ── Wiki lint (LLM-Wiki P1) ──────────────────────────────────────────────\n server.registerTool(\n 'lint_map',\n {\n description:\n \"Health-check the app's test wiki (.hover/): cross-check the business map against the real spec files and the run ledger. Reports deleted specs (a line points at a missing *.spec.ts), regressed coverage (a covered line's spec last ran fail/flaky → heal it), and orphan specs (a spec no line maps). Deterministic; no LLM. Run it to find drift, then fix each finding (heal / re-map / drop the stale ref).\",\n inputSchema: {},\n },\n () => guard(() => c.lintWiki()),\n );\n\n // ── Optimize (F7) ────────────────────────────────────────────────────────\n // The IMPROVEMENT is the agent's (the /mcp__hover__optimize prompt gives it the\n // brief); these tools are Hover's guardrail + write path.\n server.registerTool(\n 'optimize_brief',\n {\n description:\n \"Get the improvement brief for ONE spec (its current code + the outcome its recording session observed + your Page Objects + the improvement rules). Use this when optimizing every spec (`/mcp__hover__optimize` with no arg): call it per spec, follow the brief it returns, then call save_optimized_spec. For a single spec, `/mcp__hover__optimize <slug>` already hands you the brief.\",\n inputSchema: {\n slug: z.string().describe('The spec slug to optimize (its filename without .spec.ts).'),\n },\n },\n ({ slug }) => guard(() => c.optimizeBrief(slug)),\n );\n\n server.registerTool(\n 'save_optimized_spec',\n {\n description:\n \"File an improved spec (produced from the /optimize brief) as a REVIEW CANDIDATE. Hover validates it (semantic selectors, no waitForTimeout/XPath, parses), soft-batches trailing assertions, and writes .hover/cache/optimized/<slug>.spec.ts.draft — it does NOT overwrite your spec. On a ✗ (rejected check), fix it and call again. This is the only way to file an optimization; don't write the .draft yourself.\",\n inputSchema: {\n slug: z.string().describe('The spec slug being optimized (its filename without .spec.ts).'),\n code: z.string().describe('The COMPLETE improved .ts file contents.'),\n },\n },\n ({ slug, code }) => guard(() => c.saveOptimized(slug, code)),\n );\n\n server.registerTool(\n 'promote_optimized_spec',\n {\n description:\n \"Apply a reviewed optimization candidate: overwrite __vibe_tests__/<slug>.spec.ts with its .hover/cache/optimized/<slug>.spec.ts.draft and remove the draft. Call this ONLY after the user has seen the diff and approved — it's the one action that replaces the original spec. Re-validates the draft first.\",\n inputSchema: {\n slug: z.string().describe('The spec slug whose candidate to promote (its filename without .spec.ts).'),\n },\n },\n ({ slug }) => guard(() => c.promoteOptimized(slug)),\n );\n\n // The workflow ships WITH the server as an MCP prompt — Claude Code surfaces\n // it as `/mcp__hover__test_app`, so adding the server brings both the tools\n // AND the command. No project scaffolding needed.\n server.registerPrompt(\n 'test_app',\n {\n title: 'Hover — map & crystallize a test suite',\n description: 'Map this app\\'s business lines and crystallize a Playwright suite (incremental, scales to large apps).',\n argsSchema: { scope: z.string().optional().describe('An area/flow to focus on. Omit to cover the whole app.') },\n },\n ({ scope }) => ({\n messages: [{ role: 'user', content: { type: 'text', text: lang + workflowPrompt(scope) } }],\n }),\n );\n\n // Optimize workflow — surfaced as `/mcp__hover__optimize`. Hover builds the\n // brief (spec + observed session + Page Objects); the agent does the thinking.\n server.registerPrompt(\n 'optimize',\n {\n title: 'Hover — enrich specs with observed assertions',\n description: 'Improve crystallized specs: add assertions for what the session observed, de-literalize volatile values, reuse Page Objects. Pass a spec to optimize one, or omit to optimize every spec. Files review candidates; never overwrites a spec.',\n argsSchema: { spec: z.string().optional().describe('A spec slug to optimize (e.g. \"checkout\"). Omit to optimize EVERY spec.') },\n },\n async ({ spec }) => ({\n messages: [\n {\n role: 'user' as const,\n content: { type: 'text' as const, text: lang + (spec?.trim() ? await c.optimizeBrief(spec.trim()) : optimizeAllPrompt()) },\n },\n ],\n }),\n );\n\n // Wiki-lint workflow — surfaced as `/mcp__hover__lint`. The tool does the\n // mechanical checks; the prompt drives the LLM-judged half on top.\n server.registerPrompt(\n 'lint',\n {\n title: 'Hover — lint the test wiki',\n description: \"Health-check .hover/: deterministic drift (dead spec refs, regressed coverage, unmapped specs) plus LLM-judged checks (contradictory rules, code routes missing from the map), then offer fixes.\",\n argsSchema: {},\n },\n () => ({\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text: lang + lintPrompt() } }],\n }),\n );\n\n // Query workflow (LLM-Wiki P4) — surfaced as `/mcp__hover__ask`. Read the wiki,\n // answer with citations, optionally file a confirmed new rule back.\n server.registerPrompt(\n 'ask',\n {\n title: 'Hover — ask the test wiki',\n description: \"Answer a question about this app from its .hover/ wiki (business map + remembered rules + specs + run log), with citations. Read-only; can file a confirmed new rule back.\",\n argsSchema: { question: z.string().describe('The question to answer, e.g. \"what happens when a guest tries to check out?\"') },\n },\n ({ question }) => ({\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text: lang + askPrompt(question) } }],\n }),\n );\n\n // Self-heal workflow — surfaced as `/mcp__hover__heal`.\n server.registerPrompt(\n 'heal',\n {\n title: 'Hover — heal a drifted spec',\n description: \"Replay a saved spec against the live app; where the UI drifted, re-ground the broken step and re-crystallize. Pass a spec to heal one, or omit to check all.\",\n argsSchema: { spec: z.string().optional().describe('A spec slug to heal (e.g. \"login\"). Omit to check every spec.') },\n },\n ({ spec }) => ({\n messages: [{ role: 'user', content: { type: 'text', text: lang + healPrompt(spec) } }],\n }),\n );\n\n return server;\n}\n\n/** Optimize-ALL workflow body: enrich every spec, one at a time. Each spec's\n * brief comes from the optimize_brief tool (same brief the single-spec prompt\n * delivers inline), so quality is identical; only the loop differs. */\nfunction optimizeAllPrompt(): string {\n return `Optimize EVERY crystallized spec for this app using the **Hover MCP tools** — enrich each with the assertions its recording session observed, without changing what it tests.\n\n1. **List the specs** — the \\`*.spec.ts\\` files under \\`__vibe_tests__/\\` (skip \\`*.api-test.spec.ts\\` and \\`pages/\\`). Use your own file tools.\n2. **For each spec, ONE at a time:**\n - \\`optimize_brief(\"<slug>\")\\` — returns that spec's current code + the outcome its session observed + your Page Objects + the improvement rules.\n - Follow the brief: add assertions for the observed feedback, de-literalize volatile values (a generated id, an order number → a stable anchor), reuse a Page Object where a step sequence matches. Don't invent steps the session didn't perform.\n - \\`save_optimized_spec(\"<slug>\", <the complete improved .ts>)\\` — Hover validates it and files a candidate at \\`.hover/cache/optimized/<slug>.spec.ts.draft\\`. It NEVER overwrites your spec. On a ✗, fix it and call again.\n3. **Be selective per spec** — only add assertions that matter (a real outcome, a stable heading). A spec that's already tight needs no candidate; skip it and say so. Over-asserting a changing value is the failure we're avoiding.\n4. **Report + apply** — list which specs got a candidate. For each, show the user the diff vs \\`__vibe_tests__/<slug>.spec.ts\\`; on their approval call \\`promote_optimized_spec(\"<slug>\")\\` to apply it (overwrites the spec + removes the draft — no manual mv). Nothing lands until they approve.`;\n}\n\n/** Query workflow body (LLM-Wiki P4): read the wiki, answer with citations, and\n * optionally file a confirmed new rule back so the next question is cheaper. */\nfunction askPrompt(question: string): string {\n return `Answer a question about this app using its **test wiki** (\\`.hover/\\`) as the source of truth. The wiki = the business map (\\`.hover/hover-map.md\\`), the remembered business rules (\\`.hover/memory/\\` — via \\`recall_business_knowledge\\` / \\`recall_fact\\`), the crystallized specs under \\`__vibe_tests__/\\`, and the run history (\\`.hover/log.md\\`).\n\nQuestion: ${question}\n\n1. **Gather (read-only — don't drive the browser).** Call \\`recall_business_knowledge\\` for the rule index and pull specifics with \\`recall_fact\\`. Read \\`.hover/hover-map.md\\` for the business lines, coverage, and relationships. Skim the relevant \\`*.spec.ts\\` and \\`.hover/log.md\\` with your own file tools.\n2. **Answer directly, with CITATIONS.** Ground every claim in what you read — name the rule, the business line, or the spec file it came from. If the wiki genuinely doesn't cover it, say so plainly instead of guessing (and suggest running \\`/mcp__hover__test_app\\` to cover that area).\n3. **Optionally file it back.** If answering established a durable business RULE the wiki was missing and the user confirms it, persist it with \\`record_fact\\` (RULES only — never secrets / credentials / PII) so the next run doesn't re-derive it.\n\nStay on what the wiki + code actually say; don't invent behavior.`;\n}\n\n/** Wiki-lint workflow body. `lint_map` does the mechanical checks; the agent\n * layers the LLM-judged checks (contradictions, unmapped code routes) and fixes. */\nfunction lintPrompt(): string {\n return `Lint this app's **test wiki** (\\`.hover/\\`) using the Hover MCP tools, then fix what you find. The wiki = the business map (\\`.hover/hover-map.md\\`), the business-rule memory (\\`.hover/memory/*.md\\`), and the crystallized specs it points at.\n\n1. **Deterministic drift** — call \\`lint_map\\`. It cross-checks the map against the real spec files and the run ledger and reports:\n - **deleted-spec** — a line points at a \\`*.spec.ts\\` that's gone. Re-crystallize the flow, or drop the stale reference from the map.\n - **regressed-coverage** — a covered line whose spec last ran fail/flaky. Heal it with \\`/mcp__hover__heal <slug>\\` (or say it's a real app bug to fix).\n - **orphan-spec** — a spec no line maps. Add its business line to the map (\\`[x]\\` with the spec).\n2. **Contradictory rules (LLM-judged)** — read \\`.hover/memory/*.md\\`. If two facts conflict (e.g. one says guests can check out, another says they can't), surface the pair and ASK the user which holds; correct the wrong one with \\`record_fact\\` (or delete it).\n3. **Unmapped code routes (LLM-judged)** — with your own file tools, grep the app's router for user-facing routes. Any real route absent from \\`.hover/hover-map.md\\` is a coverage gap — add it as an uncovered \\`[ ]\\` line under the right area so the map stays honest.\n4. **Report + fix** — summarize the findings, apply the safe fixes (re-map, add gaps, correct rules), and for anything destructive (dropping a spec, deleting a rule) ASK first. Update \\`.hover/hover-map.md\\` as you go.\n\nStay on the app under test. Don't invent business lines that don't exist in the code.`;\n}\n\n/** Self-heal workflow body. The agent uses `replay_spec` to find the drift,\n * re-grounds the broken step by its recorded intent, and re-crystallizes. */\nfunction healPrompt(spec?: string): string {\n const scope = spec?.trim()\n ? `the spec \\`${spec.trim()}\\``\n : 'every spec under `__vibe_tests__/` (list them first, then heal each that drifted)';\n return `Heal ${scope} for this app using the **Hover MCP tools** — repair specs whose UI drifted, without rewriting them by hand.\n\nA spec \"drifted\" when the app changed so a recorded step no longer locates its control (a renamed button, a moved field). Healing = re-grounding ONLY the broken step against the current UI, keeping everything else, so record==replay still holds.\n\nWork ONE spec at a time:\n\n1. **Detect** — \\`replay_spec(\"<slug>\")\\`. It replays the spec's recorded grounded steps against the live app and reports the first step that fails to locate: its index, the tool, and what it was \\`lookingFor\\` (role+name/text). If it replays clean, that spec is fine — move on.\n2. **Re-ground the broken step** — \\`browser_navigate\\` to the spec's route, \\`browser_snapshot\\`, and find the control that NOW serves the intent in \\`lookingFor\\` (e.g. the submit button whose label changed \"Sign in\" → \"Log in\"). Re-drive from the break with the grounded \\`*_control\\` tools. Change ONLY what drifted — don't redesign the flow.\n3. **Re-crystallize** — when the flow runs green again, \\`crystallize_spec\\` with the SAME name as the broken spec to overwrite it with the healed version.\n4. **Report** — say which step drifted, what changed (old target → new target), and that it's re-crystallized. The user reviews the old-vs-new diff in the cockpit before keeping it.\n\nRules: heal by the recorded INTENT (re-locate the same control), never invent a new flow or new assertions. If a step is gone because the FEATURE was removed (not just renamed), don't guess — say so and ASK whether to drop that step or the whole spec. Stay on the app under test.`;\n}\n\n/** The phased, scale-aware workflow, delivered as the prompt body. Mirrors the\n * hover-mcp tool surface; the agent's own file tools do the code-reading. */\nfunction workflowPrompt(scope?: string): string {\n const target = scope?.trim() ? scope.trim() : 'the whole app';\n return `Build (or extend) a Playwright test suite for this web app using the **Hover MCP tools**.\nDrive the browser ONLY through these tools — they actuate via grounded selectors\n(role+name → testId → text), so every spec you save replays EXACTLY what you did\n(record==replay). Never write spec files yourself; only \\`crystallize_spec\\` does.\n\nTools: \\`recall_business_knowledge\\` / \\`recall_fact\\` · \\`browser_navigate\\` ·\n\\`browser_snapshot\\` (ARIA tree — read before acting) · \\`click_control\\` /\n\\`fill_control\\` / \\`select_control\\` / \\`check_control\\` (grounded target from the\nsnapshot) · \\`assert_visible\\` · \\`record_fact\\` · \\`crystallize_spec(name, description?)\\`.\nAPI layer: \\`capture_requests\\` · \\`replay_request\\` · \\`crystallize_api_spec\\`. Suite:\n\\`detect_shared_flows\\` · \\`extract_page_objects\\` · \\`replay_spec\\` · \\`lint_map\\`.\n\nTarget: the app at HOVER_TARGET (set in the server's env). Scope: ${target}.\n\n## First: are you bootstrapping or extending? (load only what this run needs)\nCheck whether \\`.hover/hover-map.md\\` already exists.\n- **It exists → you're EXTENDING.** Read it + call \\`recall_business_knowledge\\`, then go straight to Phase 2 and cover the uncovered \\`[ ]\\` lines. Skip the Phase-1 code-mapping — the map already IS the plan. Only re-map if the user says the app changed.\n- **It's absent → you're BOOTSTRAPPING.** Do the full Phase 1 below to build the map first.\nThis keeps a returning run cheap: you don't re-derive a map you already have.\n\n## Ground rules (they protect record==replay AND the user's real app)\n- **Grounded targets only.** Pass role+name EXACTLY as they appear in the LATEST \\`browser_snapshot\\`. If a locate fails, re-snapshot and read the real target — never guess, invent, or reuse a stale name.\n- **It's the user's REAL app.** Avoid irreversible / destructive actions — real payments, deleting data you didn't create, sending real emails or SMS — unless the user confirms this is a safe test environment. When unsure, ASK first.\n- **Assert stable outcomes.** Assert on semantic, durable signals (a success message, a heading, a new row's label) — NEVER volatile instance data (timestamps, generated ids, \"today\", a one-off order number), which makes the saved spec flaky on replay.\n- **Log in first.** If the app needs auth, do that before anything else — ask for credentials if you don't have them — then crystallize it as its own \"Log in\" spec and stay logged in for the rest of the run.\n\nWork in PHASES — this is what lets it scale from a tiny app to a large one.\n\n## Phase 1 — Map the business lines (read the CODE, don't click around)\n- FIRST call \\`recall_business_knowledge\\` — rules earlier runs learned (and read \\`.hover/hover-map.md\\` if it exists, the running map; CONTINUE it, don't start over). Treat both as ground truth; don't re-ask what they already answer. For an app with many remembered rules this returns an INDEX (one line per rule); when a rule is relevant to what you're about to test, pull its full text with \\`recall_fact(\"<name>\")\\`.\n- Use YOUR OWN file tools (read / grep / glob) to find the app's ROUTES + navigation: the router config, route/page files, the nav components. Enumerate the user-facing BUSINESS LINES (a coherent task a user performs), each with its entry route, grouped by area. Reading code is cheaper + more complete than clicking around, and finds areas behind auth / nav you'd otherwise miss.\n- Write/update \\`.hover/hover-map.md\\` as a checklist (4-space indent = a code block):\n\n # Business map — <app>\n ## Auth\n - [ ] Log in — /login\n - [x] Checkout — /checkout — checkout.spec.ts\n ## Relationships\n - Checkout depends-on Log in\n - Cart shares-state Checkout\n\n- Optionally add a \\`## Relationships\\` block recording inter-line edges you notice — \\`<line> depends-on <line>\\`, \\`<line> shares-state <line>\\`, or \\`<line> navigates-to <line>\\` (names must match lines above). These become graph edges in the cockpit's Business Map; they also tell a later run what a flow depends on. Record only real edges; skip the block if none stand out.\n- Don't test yet. For a large app, this map IS the plan.\n\n## Phase 2 — Pick the scope\n- If a scope was given, cover that. Otherwise show the uncovered lines and ask which to cover now (offer \"all uncovered\"). For a small app, just cover them all.\n\n## Phase 3 — Cover each chosen line, ONE AT A TIME\nFor each line: \\`browser_navigate\\` to its route → \\`browser_snapshot\\` → EXERCISE the real flow (click / fill / select / check — you are a tester, never just describe the page) → \\`assert_visible\\` on the OUTCOME (a success message, the new row, the next screen) → \\`crystallize_spec(\"<short imperative English name>\")\\`. Crystallize the MOMENT each flow is done, before the next — the buffer is per-flow. The tools share ONE browser, so cover lines SEQUENTIALLY.\n\n## Phase 3.5 — Lock the API layer too (SELECTIVELY)\nAs you drive each flow, Hover passively captures the app's xhr/fetch traffic. After a flow, call \\`capture_requests\\` to see what it hit. For a line that exercised a **worthwhile API surface — a data mutation (POST/PUT/DELETE), a clear contract, or an authz boundary** — also lock an API spec:\n- Decide the checks. Contract: the call returns its status + key fields. Authz: the same call WITHOUT the session must be refused — call \\`replay_request\\` with \\`authenticated:false\\` and confirm it's 401/403 before asserting it.\n- VERIFY each check with \\`replay_request\\` first (so you never assert a confabulated status), then \\`crystallize_api_spec(name, checks)\\` → a \\`*.api-test.spec.ts\\`.\n- Be SELECTIVE — skip pure-display reads, analytics, third-party pings. Most UI flows need 0–1 API specs. A static/read-only flow needs none. This is judgment, not a per-flow quota.\n\n## Phase 4 — Update coverage\n- Mark each covered line \\`[x]\\` in \\`.hover/hover-map.md\\` with its spec filename. Report covered vs still-open. A LARGE app doesn't have to finish in one go — covering a batch + updating the map is a complete, resumable unit; re-invoke to continue the uncovered lines.\n- Then call \\`lint_map\\` to catch wiki drift you may have introduced (a covered line whose spec now fails, a stale spec reference, a spec no line maps) and fix or report it. \\`/mcp__hover__lint\\` runs the deeper check any time.\n\n## Phase 5 — Lift shared flows into Page Objects (ASK first)\nOnce specs are crystallized, call \\`detect_shared_flows\\`. If it reports a NON-login flow repeated across specs (login is already handled by the auth setup), tell the user which specs share it and ASK whether to lift it into a shared Page Object (so a UI change to that flow is a one-place fix). On yes → \\`extract_page_objects\\` (generates \\`pages/*\\` + \\`fixtures.ts\\` and folds the specs to \\`await xPage.x()\\`). If nothing is shared, skip silently — most small suites have nothing to lift; don't force it.\n\n## Understand the business — ASK, then REMEMBER\nWhen you genuinely can't resolve something on your own — is this a bug or by-design? which flows matter? what does this domain term mean? — ASK the user (don't guess, don't stop). When they confirm a durable business RULE, call \\`record_fact\\` to persist it (RULES ONLY — never credentials/secrets/PII). Also ASK when blocked on something only they can provide (login credentials, a file). Stay on the app under test — never navigate to external origins.`;\n}\n"],"mappings":"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","names":["server","ctx"]}
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts","../src/mcp/controller.ts","../src/mcp/server.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { chromium, type Browser, type Page } from 'playwright-core';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n launchDebugChrome,\n writeSpec,\n writeApiSpec,\n writeFact,\n recallMemory,\n readFact,\n formatFact,\n readSidecar,\n detectExtractableFlows,\n extractPageObjects,\n buildOptimizeBrief,\n saveOptimizedCandidate,\n promoteOptimizedCandidate,\n lintWiki,\n appendWikiLog,\n type SkillStep,\n type ApiCheck,\n type Redaction,\n} from '@hover-dev/core/engine';\nimport { fetchHealRequests, readCloudCredentials } from '@hover-dev/core/cloud';\nimport { HoverMcpController } from './mcp/controller.js';\nimport { createHoverMcpServer } from './mcp/server.js';\n\n/*\n * `hover-mcp` — the MCP-first surface. Add it to your OWN agent (Claude Code,\n * Cursor, …); the agent drives the app through Hover's grounded tools and calls\n * crystallize_spec to save a plain Playwright spec. Hover spawns no agent here —\n * the calling agent IS the intelligence; Hover guarantees record==replay at the\n * output. Config via env: HOVER_TARGET, HOVER_CDP_PORT, HOVER_PROJECT_ROOT,\n * HOVER_LANG (language the workflow prompts tell the agent to converse in).\n *\n * NOTE: stdio is the MCP transport — never write to stdout from this process.\n */\n\nconst TARGET = process.env.HOVER_TARGET || 'http://localhost:5173';\nconst PORT = Number(process.env.HOVER_CDP_PORT || 9222);\nconst LANG = process.env.HOVER_LANG;\nconst DEV_ROOT = process.env.HOVER_PROJECT_ROOT || process.cwd();\nconst CDP_URL = `http://localhost:${PORT}`;\n\nconst originOf = (u: string): string | null => {\n try {\n return new URL(u).origin;\n } catch {\n return null;\n }\n};\n\nlet browser: Browser | null = null;\n\n/** Launch/connect the debug Chrome lazily and return the page on the app. */\nasync function getPage(): Promise<Page> {\n if (!browser || !browser.isConnected()) {\n await launchDebugChrome({ port: PORT, url: TARGET });\n browser = await chromium.connectOverCDP(CDP_URL, { timeout: 8000 });\n }\n const pages = browser.contexts().flatMap((ctx) => ctx.pages());\n const want = originOf(TARGET);\n const match = want ? pages.find((p) => originOf(p.url()) === want) : undefined;\n if (match) return match;\n if (pages.length) return pages[pages.length - 1];\n const ctx = browser.contexts()[0] ?? (await browser.newContext());\n return ctx.newPage();\n}\n\nconst controller = new HoverMcpController({\n getPage,\n crystallize: async (name: string, description: string | undefined, steps: SkillStep[], redactions: Redaction[]) => {\n const res = await writeSpec({ devRoot: DEV_ROOT, name, description, steps, redactions, startUrl: TARGET, overwrite: true });\n await appendWikiLog(DEV_ROOT, 'crystallize', `${basename(res.path)} — ${name}`);\n return { path: res.path };\n },\n crystallizeApi: async (name: string, description: string | undefined, checks: ApiCheck[]) => {\n const res = await writeApiSpec({ devRoot: DEV_ROOT, name, description, checks, startUrl: TARGET, overwrite: true });\n await appendWikiLog(DEV_ROOT, 'api', `${basename(res.path)} — ${name}`);\n return { path: res.path };\n },\n recordFact: (title, rule, type) =>\n writeFact(DEV_ROOT, { name: title, description: title, type, body: rule }),\n recall: () => recallMemory(DEV_ROOT),\n recallFact: async (name: string) => {\n const fact = await readFact(DEV_ROOT, name);\n return fact ? formatFact(fact) : null;\n },\n readSpecSteps: async (slug: string) => {\n const sc = await readSidecar(DEV_ROOT, slug);\n return sc ? { steps: sc.steps, startUrl: TARGET } : null;\n },\n detectSharedFlows: () => detectExtractableFlows(DEV_ROOT),\n extractPageObjects: async () => {\n const res = await extractPageObjects(DEV_ROOT);\n if (res.pages.length) {\n await appendWikiLog(DEV_ROOT, 'extract', `${res.pages.length} page object(s), folded ${res.folded.length} spec(s)`);\n }\n return res;\n },\n optimizeBrief: async (slug: string) => {\n try {\n const { prompt } = await buildOptimizeBrief(DEV_ROOT, slug);\n return { prompt };\n } catch (e) {\n return { error: e instanceof Error ? e.message.split('\\n')[0] : String(e) };\n }\n },\n saveOptimized: (slug: string, code: string) => saveOptimizedCandidate(DEV_ROOT, slug, code),\n promoteOptimized: (slug: string) => promoteOptimizedCandidate(DEV_ROOT, slug),\n lintWiki: () => lintWiki(DEV_ROOT),\n cloudFailures: async (repo?: string) => {\n const creds = readCloudCredentials();\n if (!creds) {\n return {\n error:\n 'Hover Cloud not connected — set HOVER_CLOUD_TOKEN (mint one at https://cloud.gethover.dev → Settings → Access tokens) or run \"Hover: Connect Hover Cloud\" in VS Code.',\n };\n }\n try {\n return await fetchHealRequests(creds, { status: 'open', ...(repo ? { repo } : {}) });\n } catch (e) {\n return { error: e instanceof Error ? e.message.split('\\n')[0] : String(e) };\n }\n },\n});\n\nconst server = createHoverMcpServer(controller, { lang: LANG });\nawait server.connect(new StdioServerTransport());\n","import { request as pwRequest, type Page } from 'playwright-core';\nimport {\n groundedLocate,\n replayOnPage,\n type GroundedTarget,\n type SkillStep,\n type ApiCheck,\n type ReplayStep,\n type Redaction,\n type SharedFlow,\n type ExtractResult,\n type LintResult,\n} from '@hover-dev/core/engine';\nimport { healSlug, type CloudHealRequest } from '@hover-dev/core/cloud';\n\n/*\n * The hover-mcp engine, decoupled from the MCP wire layer so it's testable with\n * a mock Page. The user's OWN agent (Claude Code / Cursor) calls these via MCP:\n * it reads the page (snapshot), actuates with GROUNDED targets (role+name →\n * testId → text), and Hover buffers each successful actuation as a SkillStep.\n * `crystallize` turns the buffer into a plain Playwright spec — record==replay,\n * because the buffered selectors ARE the ones that drove the page.\n *\n * API layer: while the agent drives the UI, Hover passively buffers the app's\n * xhr/fetch traffic off the SAME CDP connection (no MITM proxy). The agent reads\n * it with `capture_requests`, verifies a contract/authz check with\n * `replay_request`, and crystallizes the worthwhile ones into a\n * `*.api-test.spec.ts`. record == replay holds for the API layer too.\n */\n\nexport type FactType = 'business-rule' | 'expected-behavior' | 'validation' | 'access-policy';\n\n/** A passively-observed xhr/fetch call (metadata + a light body shape). */\nexport interface CapturedRequest {\n method: string;\n url: string;\n status: number;\n contentType?: string;\n /** Request post data, truncated. */\n requestBody?: string;\n /** Top-level keys of a JSON response (a light shape hint). */\n responseKeys?: string[];\n}\n\nexport interface McpDeps {\n /** Resolve the live page on the app under test (launch/connect lazily). */\n getPage: () => Promise<Page>;\n /** Write the buffered steps to a UI spec; returns the written path. `redactions`\n * parameterize captured credentials into `process.env.<envVar>` refs (and let\n * auth-fixture detect the login prefix). */\n crystallize: (\n name: string,\n description: string | undefined,\n steps: SkillStep[],\n redactions: Redaction[],\n ) => Promise<{ path: string }>;\n /** Write selected API checks to a `*.api-test.spec.ts`; returns the path. */\n crystallizeApi: (name: string, description: string | undefined, checks: ApiCheck[]) => Promise<{ path: string }>;\n /** Persist a learned business rule to .hover/memory/ (rules only — no secrets). */\n recordFact?: (title: string, rule: string, type: FactType) => Promise<{ path: string } | { error: string }>;\n /** Recall known business knowledge from .hover/memory/ ('' if none). Progressive:\n * full bodies when the set is small, the index alone when it's large. */\n recall?: () => Promise<string>;\n /** Read ONE remembered rule's full text by name/slug (behind recall_fact), or\n * null if nothing matches — the on-demand tier of progressive recall. */\n recallFact?: (name: string) => Promise<string | null>;\n /** Read a saved spec's recorded grounded steps (its `.hover/sidecars/<slug>.json`)\n * so self-heal can replay them against the live app. */\n readSpecSteps?: (slug: string) => Promise<{ steps: SkillStep[]; startUrl?: string } | null>;\n /** Detect NON-login flows shared across saved specs (for the extract offer). */\n detectSharedFlows?: () => Promise<SharedFlow[]>;\n /** Lift shared flows into Page Objects + fold the specs that use them. */\n extractPageObjects?: () => Promise<ExtractResult>;\n /** Build the optimize (F7) brief for a spec — the improvement rules + the spec\n * + its observed session + reusable Page Objects — for the user's OWN agent to\n * work from. `{ error }` when the spec doesn't exist. No model runs. */\n optimizeBrief?: (slug: string) => Promise<{ prompt: string } | { error: string }>;\n /** File an agent-improved spec as a REVIEW candidate: validate it against the\n * deterministic guardrails, soft-batch, and write `.hover/cache/optimized/\n * <slug>.spec.ts.draft` (never the original). Throws if it fails validation. */\n saveOptimized?: (slug: string, code: string) => Promise<{ candidatePath: string }>;\n /** Promote a reviewed candidate: overwrite the real spec with its draft + drop\n * the draft. The one place a candidate replaces the original — user-approved. */\n promoteOptimized?: (slug: string) => Promise<{ path: string }>;\n /** Deterministic health check over `.hover/`: map vs spec files vs run ledger. */\n lintWiki?: () => Promise<LintResult>;\n /** Hover Cloud's open heal queue (specs CI saw drift), or `{ error }` when\n * the cloud isn't connected / reachable. Pull-only — the cloud never\n * reaches into this machine. */\n cloudFailures?: (repo?: string) => Promise<CloudHealRequest[] | { error: string }>;\n}\n\nfunction describe(g: GroundedTarget): string {\n if (g.role && g.name) return `${g.role} \"${g.name}\"`;\n if (g.testId) return `testId \"${g.testId}\"`;\n if (g.text) return `text \"${g.text}\"`;\n return '(no target)';\n}\n\nconst MAX_CAPTURED = 200; // ring-buffer cap so a long run can't grow unbounded\n\nexport class HoverMcpController {\n /** The grounded-action buffer — sliced by `crystallize`. */\n readonly steps: SkillStep[] = [];\n /** Credentials typed into password fields — parameterized to process.env in\n * the crystallized spec (never written literally) + used to detect the login\n * prefix for auth-as-fixture. */\n private readonly redactions: Redaction[] = [];\n /** Passively-observed xhr/fetch traffic — read by `capture_requests`. */\n private readonly captured: CapturedRequest[] = [];\n /** Pages we've already attached a network listener to (avoid duplicates). */\n private readonly listening = new WeakSet<Page>();\n\n constructor(private readonly deps: McpDeps) {}\n\n private push(tool: string, input: unknown): void {\n this.steps.push({ kind: 'step', tool, input });\n }\n\n /** getPage + ensure the passive network listener is attached to it. */\n private async livePage(): Promise<Page> {\n const page = await this.deps.getPage();\n if (!this.listening.has(page)) {\n this.listening.add(page);\n page.on('response', (response) => {\n void this.onResponse(response).catch(() => {});\n });\n }\n return page;\n }\n\n private async onResponse(response: import('playwright-core').Response): Promise<void> {\n const req = response.request();\n const rt = req.resourceType();\n if (rt !== 'xhr' && rt !== 'fetch') return; // API calls only — skip docs/assets\n const contentType = (response.headers()['content-type'] || '').split(';')[0] || undefined;\n let responseKeys: string[] | undefined;\n if (contentType === 'application/json') {\n try {\n const body = await response.json();\n if (body && typeof body === 'object' && !Array.isArray(body)) {\n responseKeys = Object.keys(body as Record<string, unknown>).slice(0, 24);\n }\n } catch {\n /* body unavailable / not JSON — metadata only */\n }\n }\n const post = req.postData();\n this.captured.push({\n method: req.method(),\n url: req.url(),\n status: response.status(),\n contentType,\n requestBody: post ? post.slice(0, 2000) : undefined,\n responseKeys,\n });\n if (this.captured.length > MAX_CAPTURED) this.captured.splice(0, this.captured.length - MAX_CAPTURED);\n }\n\n async navigate(url: string): Promise<string> {\n const page = await this.livePage();\n await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 20000 });\n this.push('browser_navigate', { url });\n return `navigated to ${url}`;\n }\n\n /** ARIA snapshot of the page — the agent reads role+name from here. */\n async snapshot(): Promise<string> {\n const page = await this.livePage();\n return await page.locator('body').ariaSnapshot();\n }\n\n private async resolve(g: GroundedTarget) {\n const page = await this.livePage();\n const loc = groundedLocate(page, g);\n if (!loc) throw new Error(`pass role+name (preferred), or testId, or text — taken from the snapshot`);\n return loc;\n }\n\n async click(g: GroundedTarget): Promise<string> {\n const loc = await this.resolve(g);\n await loc.click({ timeout: 8000 });\n this.push('click_control', g);\n return `✓ clicked ${describe(g)}`;\n }\n\n async fill(g: GroundedTarget, value: string): Promise<string> {\n const loc = await this.resolve(g);\n await loc.fill(value, { timeout: 8000 });\n this.push('fill_control', { ...g, value });\n // A value typed into a password field is a secret: parameterize it to\n // process.env so it never lands literally in the spec/sidecar, and so\n // auth-as-fixture can detect this fill as part of the login prefix.\n if (value) {\n let isPassword = false;\n try {\n isPassword = (await loc.getAttribute('type')) === 'password';\n } catch {\n /* locator has no getAttribute (test mock) or field gone — treat as non-secret */\n }\n if (isPassword && !this.redactions.some((r) => r.value === value)) {\n this.redactions.push({ value, envVar: 'HOVER_PASSWORD' });\n }\n }\n return `✓ filled ${describe(g)}`;\n }\n\n async select(g: GroundedTarget, value: string): Promise<string> {\n const loc = await this.resolve(g);\n await loc.selectOption(value, { timeout: 8000 });\n this.push('select_control', { ...g, value });\n return `✓ selected ${value} in ${describe(g)}`;\n }\n\n async check(g: GroundedTarget, checked: boolean): Promise<string> {\n const loc = await this.resolve(g);\n if (checked) await loc.check({ timeout: 8000 });\n else await loc.uncheck({ timeout: 8000 });\n this.push('check_control', { ...g, checked });\n return `✓ ${checked ? 'checked' : 'unchecked'} ${describe(g)}`;\n }\n\n async assertVisible(g: GroundedTarget): Promise<string> {\n const loc = await this.resolve(g);\n const visible = await loc.first().isVisible();\n if (!visible) throw new Error(`${describe(g)} is not visible`);\n this.push('assert_visible', { ...g, matcher: 'visible' });\n return `✓ ${describe(g)} is visible`;\n }\n\n /** Return the passively-observed xhr/fetch traffic (optionally filtered) as\n * JSON for the agent to reason over when deciding API checks. */\n captureRequests(filter?: { urlContains?: string; method?: string }): string {\n let rows = this.captured;\n if (filter?.urlContains) rows = rows.filter((r) => r.url.includes(filter.urlContains!));\n if (filter?.method) rows = rows.filter((r) => r.method.toUpperCase() === filter.method!.toUpperCase());\n if (rows.length === 0) {\n return 'No xhr/fetch traffic captured yet. Drive the app (navigate / click) so its API calls fire, then call this again.';\n }\n return JSON.stringify(rows.slice(-60), null, 2);\n }\n\n /** Send a (possibly mutated) request and return the response summary — the\n * API analogue of replaying a grounded step, so an authz/contract check is\n * VERIFIED against the live app before it's crystallized (no confabulation).\n * `authenticated` (default true) replays with the browser session's cookies;\n * false uses a fresh context (no session) for \"requires auth\" checks. */\n async replayRequest(opts: {\n method: string;\n url: string;\n headers?: Record<string, string>;\n body?: unknown;\n authenticated?: boolean;\n }): Promise<string> {\n const page = await this.livePage();\n const authed = opts.authenticated !== false;\n const ctx = authed ? page.context().request : await pwRequest.newContext();\n try {\n const m = opts.method.toUpperCase();\n const reqOpts: { headers?: Record<string, string>; data?: unknown } = {};\n if (opts.headers) reqOpts.headers = opts.headers;\n if (opts.body !== undefined && m !== 'GET' && m !== 'HEAD') reqOpts.data = opts.body;\n const res = await ctx.fetch(opts.url, { method: m, ...reqOpts });\n const contentType = (res.headers()['content-type'] || '').split(';')[0] || '';\n let preview = '';\n try {\n preview = contentType === 'application/json' ? JSON.stringify(await res.json()).slice(0, 800) : (await res.text()).slice(0, 400);\n } catch {\n /* no body */\n }\n return JSON.stringify({ status: res.status(), ok: res.ok(), contentType, body: preview, authenticated: authed }, null, 2);\n } finally {\n if (!authed) await ctx.dispose();\n }\n }\n\n /** Recall what earlier runs learned about this app's business rules. Progressive:\n * a large memory comes back as an INDEX; use recallFact to pull one rule's body. */\n async recall(): Promise<string> {\n if (!this.deps.recall) return 'No business memory available.';\n const known = await this.deps.recall();\n return known || 'No business knowledge recorded yet for this app.';\n }\n\n /** Read one remembered rule's full text by name (the on-demand tier — used when\n * recall returned only the index and the agent needs a specific rule's body). */\n async recallFact(name: string): Promise<string> {\n if (!this.deps.recallFact) return 'Rule lookup unavailable in this server.';\n const body = await this.deps.recallFact(name);\n return body ?? `No remembered rule matches \"${name}\". Call recall_business_knowledge to see the index of known rules.`;\n }\n\n /** Persist a confirmed business RULE so future runs don't re-ask it. Rules\n * only — never secrets / credentials / PII. */\n async recordFact(title: string, rule: string, type: FactType = 'business-rule'): Promise<string> {\n if (!this.deps.recordFact) return 'Memory channel unavailable; continuing.';\n const res = await this.deps.recordFact(title, rule, type);\n return 'error' in res ? `✗ could not save fact: ${res.error}` : `✓ remembered: ${title}`;\n }\n\n /** Crystallize the buffered UI flow → a plain Playwright spec, then clear the\n * buffer for the next flow. */\n async crystallize(name: string, description?: string): Promise<string> {\n if (this.steps.length === 0) return 'Nothing to crystallize yet — actuate some controls first.';\n const flow = [...this.steps];\n const { path } = await this.deps.crystallize(name, description, flow, [...this.redactions]);\n this.steps.length = 0;\n return `✓ wrote ${path} (${flow.length} step${flow.length === 1 ? '' : 's'})`;\n }\n\n /** Crystallize agent-selected API checks → a `*.api-test.spec.ts`. The agent\n * decides WHICH calls are worth locking (a real contract / authz boundary),\n * not every captured request. */\n async crystallizeApiSpec(name: string, description: string | undefined, checks: ApiCheck[]): Promise<string> {\n if (!checks?.length) return 'No checks provided — pass the API checks you verified worth locking.';\n const { path } = await this.deps.crystallizeApi(name, description, checks);\n return `✓ wrote ${path} (${checks.length} check${checks.length === 1 ? '' : 's'})`;\n }\n\n /** Self-heal detection: replay a saved spec's RECORDED grounded steps against\n * the live app and report the first step that no longer locates — the drift\n * point the agent re-grounds. No `playwright test`, no install; the same\n * grounded replay as creation-verification, seeded from the spec's sidecar. */\n async replaySpec(slug: string): Promise<string> {\n if (!this.deps.readSpecSteps) return 'Spec replay unavailable in this server.';\n const sc = await this.deps.readSpecSteps(slug);\n if (!sc) {\n return `No sidecar for \"${slug}\" — only Hover-crystallized specs can be replayed (looked for .hover/sidecars/${slug}.json).`;\n }\n const page = await this.livePage();\n const devUrl = sc.startUrl ?? page.url();\n const res = await replayOnPage(page, devUrl, sc.steps as ReplayStep[]);\n if (res.ok) {\n return `✓ \"${slug}\" still replays clean — ${res.ran}/${res.total} grounded steps located. No drift to heal.`;\n }\n const f = res.failures[0];\n return JSON.stringify(\n {\n spec: slug,\n drifted: true,\n ranBeforeBreak: res.ran,\n total: res.total,\n brokeAtStep: f.index,\n tool: f.tool,\n lookingFor: f.target,\n error: f.error,\n next: 'Re-snapshot at this point, re-locate the control by the intent in \"lookingFor\" (its label/role may have changed), re-drive from here, then crystallize_spec with the SAME name to overwrite the healed spec.',\n },\n null,\n 2,\n );\n }\n\n /** The Hover Cloud heal queue: specs whose CI run drifted, pulled from\n * cloud.gethover.dev. Read-only surfacing — the heal itself is the same\n * local replay_spec → re-ground → crystallize loop, human-reviewed. */\n async cloudFailures(repo?: string): Promise<string> {\n if (!this.deps.cloudFailures) return 'Hover Cloud unavailable in this server.';\n const res = await this.deps.cloudFailures(repo);\n if (!Array.isArray(res)) return `✗ ${res.error}`;\n if (res.length === 0) {\n return `✓ Hover Cloud reports no drifted specs${repo ? ` for ${repo}` : ''} — the CI heal queue is empty.`;\n }\n const lines = res.map((r) => {\n const what = r.hint.failingLocator\n ? `${r.hint.failingAction ?? 'failure'} on \\`${r.hint.failingLocator}\\``\n : r.hint.error;\n const where = [r.project.repo, r.run.branch && `@ ${r.run.branch}`, r.run.ciUrl]\n .filter(Boolean)\n .join(' · ');\n return `- **${healSlug(r.specFile)}** — ${what}\\n ${where}`;\n });\n return [\n `${res.length} spec(s) drifted in CI (Hover Cloud heal queue):`,\n '',\n ...lines,\n '',\n 'Heal locally, one at a time: `/mcp__hover__heal <slug>` (replays the recorded steps, re-grounds the drifted one in the live app; the user reviews the diff). A queue entry closes automatically when CI next sees that spec pass.',\n ].join('\\n');\n }\n\n /** LLM-Wiki P1 Lint: run the deterministic `.hover/` health check (map vs\n * specs vs runs) and return it as a readable report the agent acts on (heal a\n * regressed line, map an orphan spec, drop a dead ref). The LLM-judged checks\n * (contradictory rules, code routes missing from the map) are driven on top of\n * this by the /mcp__hover__lint prompt. */\n async lintWiki(): Promise<string> {\n if (!this.deps.lintWiki) return 'Wiki lint is unavailable in this server.';\n const res = await this.deps.lintWiki();\n if (!res.hasMap) {\n return 'No .hover/hover-map.md yet — nothing to lint. Run /mcp__hover__test_app first to map the app and crystallize specs.';\n }\n const { areas, lines, covered, specs } = res.summary;\n const head = `Wiki lint — ${covered}/${lines} lines covered across ${areas} area${areas === 1 ? '' : 's'}, ${specs} spec file${specs === 1 ? '' : 's'}.`;\n if (res.findings.length === 0) {\n return `${head}\\n✓ No drift: every mapped spec exists, no covered line is failing, no spec is unmapped.`;\n }\n const icon = { error: '✗', warn: '⚠', info: '·' } as const;\n const body = res.findings\n .map((f) => `${icon[f.severity]} [${f.kind}] ${f.message}${f.fix ? `\\n → ${f.fix}` : ''}`)\n .join('\\n');\n return `${head}\\n${res.findings.length} finding${res.findings.length === 1 ? '' : 's'}:\\n${body}`;\n }\n\n /** Report NON-login flows repeated across the saved specs — so the agent can\n * ASK the user whether to lift them into a shared Page Object. Read-only. */\n async detectSharedFlows(): Promise<string> {\n if (!this.deps.detectSharedFlows) return 'Shared-flow detection unavailable in this server.';\n const flows = await this.deps.detectSharedFlows();\n if (!flows.length) {\n return 'No extractable shared flows — no set of specs shares a non-login entry flow (login is already handled by the auth setup). Nothing to lift.';\n }\n return JSON.stringify(\n flows.map((f) => ({ sharedBy: f.specs, steps: f.prose })),\n null,\n 2,\n );\n }\n\n /** Optimize (F7), MCP-first: return the improvement brief for the user's own\n * agent to work from (the agent IS the model — Hover picks none). Never\n * throws; a missing spec / unavailable channel comes back as a plain message\n * the agent reads (this feeds a PROMPT, which isn't wrapped in the ✗ guard). */\n async optimizeBrief(slug: string): Promise<string> {\n if (!this.deps.optimizeBrief) return `Optimize is unavailable in this server (heal/optimize need spec sidecars).`;\n try {\n const res = await this.deps.optimizeBrief(slug);\n if ('error' in res) {\n return `Can't optimize \"${slug}\": ${res.error}. Optimize only works on a Hover-crystallized spec — list __vibe_tests__/*.spec.ts and pass one by slug.`;\n }\n return res.prompt;\n } catch (e) {\n return `Can't build the optimize brief for \"${slug}\": ${e instanceof Error ? e.message.split('\\n')[0] : String(e)}`;\n }\n }\n\n /** File an agent-improved spec as a review candidate. Validation failures throw\n * (the tool guard turns them into a ✗ the agent can fix and retry against). */\n async saveOptimized(slug: string, code: string): Promise<string> {\n if (!this.deps.saveOptimized) return 'Optimize is unavailable in this server.';\n const { candidatePath } = await this.deps.saveOptimized(slug, code);\n return `✓ filed optimized candidate at ${candidatePath} (your spec is untouched). Show the user the diff vs __vibe_tests__/${slug}.spec.ts; if they approve, call promote_optimized_spec(\"${slug}\") to apply it (or they can review + promote in the VS Code cockpit).`;\n }\n\n /** Apply a reviewed candidate over the real spec (user-approved). */\n async promoteOptimized(slug: string): Promise<string> {\n if (!this.deps.promoteOptimized) return 'Optimize is unavailable in this server.';\n const { path } = await this.deps.promoteOptimized(slug);\n return `✓ promoted the candidate → ${path} (draft removed). Run it to confirm: npx playwright test ${path}`;\n }\n\n /** Lift the detected shared flows into `pages/*` + `fixtures.ts` and fold the\n * specs that use them. Call ONLY after the user approves (detectSharedFlows\n * → ask → this). Deterministic; login flows are excluded (auth-fixture owns). */\n async extractPageObjects(): Promise<string> {\n if (!this.deps.extractPageObjects) return 'Page Object extraction unavailable in this server.';\n const res = await this.deps.extractPageObjects();\n if (!res.pages.length) return 'Nothing to extract — no shared non-login flow found.';\n return `✓ lifted ${res.pages.length} Page Object${res.pages.length === 1 ? '' : 's'} (${res.pages\n .map((p) => p.className)\n .join(', ')}) into __vibe_tests__/pages + fixtures.ts; folded ${res.folded.length} spec${\n res.folded.length === 1 ? '' : 's'\n } (${res.folded.join(', ')}) to consume them.`;\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { HoverMcpController } from './controller.js';\n\n/* Wrap the controller as an MCP server. Tool names + the GROUNDED target shape\n * mirror Hover's control MCP so an agent that knows one knows the other. Every\n * handler returns text (✓/✗) and never throws — a failed locate/action becomes\n * a ✗ message the calling agent can react to, not an MCP transport error. */\n\nconst md = (text: string) => ({ content: [{ type: 'text' as const, text }] });\nconst errLine = (e: unknown) => (e instanceof Error ? e.message.split('\\n')[0] : String(e));\n\n/** Common language-code aliases → a name the model reads unambiguously. Anything\n * else passes through verbatim (so HOVER_LANG=Français or =中文 also works). */\nconst LANG_NAMES: Record<string, string> = {\n zh: 'Chinese (简体中文)', 'zh-cn': 'Chinese (简体中文)', 'zh-hans': 'Chinese (简体中文)',\n 'zh-tw': 'Chinese (繁體中文)', 'zh-hant': 'Chinese (繁體中文)',\n ja: 'Japanese', ko: 'Korean', es: 'Spanish', fr: 'French', de: 'German',\n pt: 'Portuguese', it: 'Italian', ru: 'Russian', ar: 'Arabic', hi: 'Hindi',\n};\n\n/** A prompt prefix telling the agent which language to CONVERSE in with the user.\n * Empty for English / unset (the default). Code stays English regardless — we\n * localize the interaction, not the artifacts. */\nexport function languageDirective(lang?: string): string {\n const raw = (lang ?? '').trim();\n if (!raw || /^(en|en-.*|english)$/i.test(raw)) return '';\n const name = LANG_NAMES[raw.toLowerCase()] ?? raw;\n return (\n `IMPORTANT — Communicate with the user in ${name}: every question you ask, ` +\n `status update, summary, and explanation must be in ${name}. Keep code, spec / ` +\n `test names, identifiers, file paths, and \\`slash-commands\\` in English. Only the ` +\n `human-facing prose is translated.\\n\\n`\n );\n}\n\nconst GROUND = {\n role: z.string().optional().describe(\"ARIA role from the snapshot, e.g. 'button', 'textbox', 'link'. Pair with `name`.\"),\n name: z.string().optional().describe('Accessible name from the snapshot, exactly as shown. Pair with `role`.'),\n testId: z.string().optional().describe('A data-testid, if the element has one and no clean role+name.'),\n text: z.string().optional().describe('Real visible text on the element — last resort.'),\n within: z\n .object({ role: z.string(), name: z.string() })\n .optional()\n .describe('Scope the search to a container first (role+name) when a label repeats across groups.'),\n};\n\n/** One asserted API call for crystallize_api_spec — mirrors core's ApiCheck. */\nconst API_CHECK = z.object({\n title: z.string().describe('Short test name, e.g. \"GET /api/cart returns the cart\".'),\n method: z.string(),\n url: z.string().describe('Full URL or same-origin path (same-origin is relativized in the spec).'),\n requestBody: z.any().optional(),\n headers: z.record(z.string(), z.string()).optional(),\n expectStatus: z.number().optional().describe('Expected status — verified with replay_request.'),\n expectBodyKeys: z.array(z.string()).optional().describe('Top-level response keys to assert present.'),\n note: z.string().optional().describe('Emitted as a leading comment, e.g. \"authz: no session → 401\".'),\n});\n\n/** Options for the MCP server. `lang` (from HOVER_LANG) makes the workflow\n * prompts tell the agent which language to CONVERSE in with the user. */\nexport interface HoverServerOptions {\n lang?: string;\n}\n\nexport function createHoverMcpServer(c: HoverMcpController, opts: HoverServerOptions = {}): McpServer {\n const server = new McpServer({ name: 'hover', version: '0.1.0' });\n const guard = (fn: () => Promise<string>) => fn().then(md, (e) => md(`✗ ${errLine(e)}`));\n const lang = languageDirective(opts.lang);\n\n server.registerTool(\n 'browser_navigate',\n { description: 'Open a URL in the app under test (the debug Chrome).', inputSchema: { url: z.string() } },\n ({ url }) => guard(() => c.navigate(url)),\n );\n\n server.registerTool(\n 'browser_snapshot',\n {\n description:\n 'Read the current page as an ARIA snapshot (role + accessible-name tree). Call this BEFORE actuating to get the exact role+name to pass to the *_control tools.',\n inputSchema: {},\n },\n () => guard(() => c.snapshot()),\n );\n\n server.registerTool(\n 'click_control',\n {\n description:\n 'Click a control by a GROUNDED target read off the snapshot (role+name preferred → testId → text). Grounded so the saved spec replays exactly what you did.',\n inputSchema: GROUND,\n },\n (g) => guard(() => c.click(g)),\n );\n\n server.registerTool(\n 'fill_control',\n { description: 'Type a value into a textbox/field by a grounded target.', inputSchema: { ...GROUND, value: z.string() } },\n ({ value, ...g }) => guard(() => c.fill(g, value)),\n );\n\n server.registerTool(\n 'select_control',\n { description: 'Choose an option in a <select> by a grounded target.', inputSchema: { ...GROUND, value: z.string() } },\n ({ value, ...g }) => guard(() => c.select(g, value)),\n );\n\n server.registerTool(\n 'check_control',\n {\n description: 'Check or uncheck a checkbox/radio by a grounded target (handles sr-only / hidden inputs).',\n inputSchema: { ...GROUND, checked: z.boolean().optional().describe('true (default) = check; false = uncheck.') },\n },\n ({ checked, ...g }) => guard(() => c.check(g, checked !== false)),\n );\n\n server.registerTool(\n 'assert_visible',\n {\n description: 'Assert a control/text is visible now — captures an expect(...).toBeVisible() into the saved spec.',\n inputSchema: GROUND,\n },\n (g) => guard(() => c.assertVisible(g)),\n );\n\n server.registerTool(\n 'recall_business_knowledge',\n {\n description:\n 'Recall what earlier Hover runs learned about this app (business rules, expected behaviors, access policies). Call this at the START so you do not re-ask settled questions. Treat what it returns as ground truth. For an app with a lot of remembered rules this returns an INDEX (one line per rule); when a rule is relevant to what you are testing, read its full text with recall_fact.',\n inputSchema: {},\n },\n () => guard(() => c.recall()),\n );\n\n server.registerTool(\n 'recall_fact',\n {\n description:\n \"Read ONE remembered business rule's full text by name (the name shown in the recall_business_knowledge index). Use it when recall returned only the index and you need a specific rule's detail before deciding how to test.\",\n inputSchema: {\n name: z.string().describe('The rule name from the recall index (a slug like \"guests-cannot-checkout\").'),\n },\n },\n ({ name }) => guard(() => c.recallFact(name)),\n );\n\n server.registerTool(\n 'record_fact',\n {\n description:\n \"Remember a durable BUSINESS RULE about this app so neither you nor a future run re-asks it — e.g. an expected behavior, a validation rule, an access policy, or the answer to a 'bug or by-design?' the user just confirmed. State it as a clean self-contained rule. RULES ONLY — never store secrets, passwords, tokens, or personal data.\",\n inputSchema: {\n title: z.string().describe('Short title for the rule (becomes its memory filename + index entry).'),\n rule: z.string().describe('The rule itself, stated clearly and self-contained (no secrets/PII).'),\n type: z\n .enum(['business-rule', 'expected-behavior', 'validation', 'access-policy'])\n .optional()\n .describe('What kind of knowledge this is. Defaults to business-rule.'),\n },\n },\n ({ title, rule, type }) => guard(() => c.recordFact(title, rule, type ?? 'business-rule')),\n );\n\n server.registerTool(\n 'crystallize_spec',\n {\n description:\n 'Save the flow you JUST performed (the grounded click/fill/select/check/assert actions since the last crystallize) as a plain Playwright .spec.ts in __vibe_tests__/. Call it the moment you finish a coherent end-to-end flow. Name it in English, imperative (e.g. \"Log in\").',\n inputSchema: {\n name: z.string().describe('Short imperative flow name in English — becomes the spec filename + test name.'),\n description: z.string().optional().describe('One line on what this flow verifies.'),\n },\n },\n ({ name, description }) => guard(() => c.crystallize(name, description)),\n );\n\n // ── API layer ──────────────────────────────────────────────────────────────\n // While you drive the UI, Hover passively buffers the app's xhr/fetch traffic\n // off the same CDP connection (no MITM). Read it, verify a check, crystallize.\n\n server.registerTool(\n 'capture_requests',\n {\n description:\n \"Return the app's xhr/fetch API calls observed while you drove the UI (method, url, status, content-type, request body, response shape). Call it after exercising a flow to see which endpoints it hit. Optionally filter.\",\n inputSchema: {\n urlContains: z.string().optional().describe('Only calls whose URL contains this substring.'),\n method: z.string().optional().describe('Only calls with this HTTP method.'),\n },\n },\n ({ urlContains, method }) => guard(() => Promise.resolve(c.captureRequests({ urlContains, method }))),\n );\n\n server.registerTool(\n 'replay_request',\n {\n description:\n 'Send a (possibly mutated) request and return the response, to VERIFY an API check before crystallizing it (no confabulated status codes). For an authz check, set authenticated:false (fresh context, no session) and expect 401/403; or drop/alter headers / swap an id for IDOR.',\n inputSchema: {\n method: z.string().describe('HTTP method, e.g. GET / POST.'),\n url: z.string().describe('Full URL or same-origin path.'),\n headers: z.record(z.string(), z.string()).optional().describe('Headers to send (omit/blank the auth header for a \"requires auth\" check).'),\n body: z.any().optional().describe('Request body for POST/PUT/PATCH.'),\n authenticated: z\n .boolean()\n .optional()\n .describe('true (default) = replay with the browser session; false = fresh context with NO session (for \"requires auth\" checks).'),\n },\n },\n ({ method, url, headers, body, authenticated }) =>\n guard(() => c.replayRequest({ method, url, headers, body, authenticated })),\n );\n\n server.registerTool(\n 'crystallize_api_spec',\n {\n description:\n 'Save selected API checks as a plain Playwright `*.api-test.spec.ts` (uses the `request` fixture). Use it ALONGSIDE crystallize_spec when a flow exercised a worthwhile API surface — a real contract, a data mutation, or an authz boundary. Be SELECTIVE: lock checks that matter, not every captured call. Verify each with replay_request first.',\n inputSchema: {\n name: z.string().describe('Short imperative English name — becomes the <name>.api-test.spec.ts filename.'),\n description: z.string().optional().describe('One line on what this API spec verifies.'),\n checks: z.array(API_CHECK).describe('The API checks to lock — each a request + its expected status / shape / authz outcome.'),\n },\n },\n ({ name, description, checks }) => guard(() => c.crystallizeApiSpec(name, description, checks)),\n );\n\n // ── Self-heal ────────────────────────────────────────────────────────────\n server.registerTool(\n 'replay_spec',\n {\n description:\n \"Detect drift in a saved spec: replay its RECORDED grounded steps against the LIVE app and report the first step that no longer locates (its index + what it was looking for). No `playwright test` needed. Use this to find what to heal, then re-ground that step and re-crystallize.\",\n inputSchema: {\n slug: z.string().describe('The spec slug = its filename without .spec.ts (e.g. \"login\" for login.spec.ts).'),\n },\n },\n ({ slug }) => guard(() => c.replaySpec(slug)),\n );\n\n // ── Page Object extraction (detect → ask → extract) ──────────────────────\n server.registerTool(\n 'detect_shared_flows',\n {\n description:\n 'After crystallizing the suite, report NON-login flows repeated across specs (login is already handled by the auth setup). Use it to decide whether to OFFER lifting a shared flow into a Page Object — then ASK the user before extracting.',\n inputSchema: {},\n },\n () => guard(() => c.detectSharedFlows()),\n );\n\n server.registerTool(\n 'extract_page_objects',\n {\n description:\n 'Lift the shared flows into __vibe_tests__/pages/* + fixtures.ts and fold the specs that use them (they call `await xPage.x()` from ./fixtures). Deterministic. Call ONLY after the user approves the offer from detect_shared_flows.',\n inputSchema: {},\n },\n () => guard(() => c.extractPageObjects()),\n );\n\n // ── Wiki lint (LLM-Wiki P1) ──────────────────────────────────────────────\n server.registerTool(\n 'lint_map',\n {\n description:\n \"Health-check the app's test wiki (.hover/): cross-check the business map against the real spec files and the run ledger. Reports deleted specs (a line points at a missing *.spec.ts), regressed coverage (a covered line's spec last ran fail/flaky → heal it), and orphan specs (a spec no line maps). Deterministic; no LLM. Run it to find drift, then fix each finding (heal / re-map / drop the stale ref).\",\n inputSchema: {},\n },\n () => guard(() => c.lintWiki()),\n );\n\n // ── Optimize (F7) ────────────────────────────────────────────────────────\n // The IMPROVEMENT is the agent's (the /mcp__hover__optimize prompt gives it the\n // brief); these tools are Hover's guardrail + write path.\n server.registerTool(\n 'optimize_brief',\n {\n description:\n \"Get the improvement brief for ONE spec (its current code + the outcome its recording session observed + your Page Objects + the improvement rules). Use this when optimizing every spec (`/mcp__hover__optimize` with no arg): call it per spec, follow the brief it returns, then call save_optimized_spec. For a single spec, `/mcp__hover__optimize <slug>` already hands you the brief.\",\n inputSchema: {\n slug: z.string().describe('The spec slug to optimize (its filename without .spec.ts).'),\n },\n },\n ({ slug }) => guard(() => c.optimizeBrief(slug)),\n );\n\n server.registerTool(\n 'save_optimized_spec',\n {\n description:\n \"File an improved spec (produced from the /optimize brief) as a REVIEW CANDIDATE. Hover validates it (semantic selectors, no waitForTimeout/XPath, parses), soft-batches trailing assertions, and writes .hover/cache/optimized/<slug>.spec.ts.draft — it does NOT overwrite your spec. On a ✗ (rejected check), fix it and call again. This is the only way to file an optimization; don't write the .draft yourself.\",\n inputSchema: {\n slug: z.string().describe('The spec slug being optimized (its filename without .spec.ts).'),\n code: z.string().describe('The COMPLETE improved .ts file contents.'),\n },\n },\n ({ slug, code }) => guard(() => c.saveOptimized(slug, code)),\n );\n\n server.registerTool(\n 'promote_optimized_spec',\n {\n description:\n \"Apply a reviewed optimization candidate: overwrite __vibe_tests__/<slug>.spec.ts with its .hover/cache/optimized/<slug>.spec.ts.draft and remove the draft. Call this ONLY after the user has seen the diff and approved — it's the one action that replaces the original spec. Re-validates the draft first.\",\n inputSchema: {\n slug: z.string().describe('The spec slug whose candidate to promote (its filename without .spec.ts).'),\n },\n },\n ({ slug }) => guard(() => c.promoteOptimized(slug)),\n );\n\n server.registerTool(\n 'cloud_failures',\n {\n description:\n \"Hover Cloud's open heal queue: the specs whose CI runs drifted (each with its failing locator + branch + CI link). Heal one locally with `/mcp__hover__heal <slug>`; an entry closes automatically when CI next sees that spec pass. Needs a connected cloud account (HOVER_CLOUD_TOKEN or ~/.hover/credentials.json).\",\n inputSchema: {\n repo: z.string().optional().describe('Limit to one GitHub repo (\"owner/name\"). Omit for all your projects.'),\n },\n },\n ({ repo }) => guard(() => c.cloudFailures(repo)),\n );\n\n // The workflow ships WITH the server as an MCP prompt — Claude Code surfaces\n // it as `/mcp__hover__test_app`, so adding the server brings both the tools\n // AND the command. No project scaffolding needed.\n server.registerPrompt(\n 'test_app',\n {\n title: 'Hover — map & crystallize a test suite',\n description: 'Map this app\\'s business lines and crystallize a Playwright suite (incremental, scales to large apps).',\n argsSchema: { scope: z.string().optional().describe('An area/flow to focus on. Omit to cover the whole app.') },\n },\n ({ scope }) => ({\n messages: [{ role: 'user', content: { type: 'text', text: lang + workflowPrompt(scope) } }],\n }),\n );\n\n // Optimize workflow — surfaced as `/mcp__hover__optimize`. Hover builds the\n // brief (spec + observed session + Page Objects); the agent does the thinking.\n server.registerPrompt(\n 'optimize',\n {\n title: 'Hover — enrich specs with observed assertions',\n description: 'Improve crystallized specs: add assertions for what the session observed, de-literalize volatile values, reuse Page Objects. Pass a spec to optimize one, or omit to optimize every spec. Files review candidates; never overwrites a spec.',\n argsSchema: { spec: z.string().optional().describe('A spec slug to optimize (e.g. \"checkout\"). Omit to optimize EVERY spec.') },\n },\n async ({ spec }) => ({\n messages: [\n {\n role: 'user' as const,\n content: { type: 'text' as const, text: lang + (spec?.trim() ? await c.optimizeBrief(spec.trim()) : optimizeAllPrompt()) },\n },\n ],\n }),\n );\n\n // Wiki-lint workflow — surfaced as `/mcp__hover__lint`. The tool does the\n // mechanical checks; the prompt drives the LLM-judged half on top.\n server.registerPrompt(\n 'lint',\n {\n title: 'Hover — lint the test wiki',\n description: \"Health-check .hover/: deterministic drift (dead spec refs, regressed coverage, unmapped specs) plus LLM-judged checks (contradictory rules, code routes missing from the map), then offer fixes.\",\n argsSchema: {},\n },\n () => ({\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text: lang + lintPrompt() } }],\n }),\n );\n\n // Query workflow (LLM-Wiki P4) — surfaced as `/mcp__hover__ask`. Read the wiki,\n // answer with citations, optionally file a confirmed new rule back.\n server.registerPrompt(\n 'ask',\n {\n title: 'Hover — ask the test wiki',\n description: \"Answer a question about this app from its .hover/ wiki (business map + remembered rules + specs + run log), with citations. Read-only; can file a confirmed new rule back.\",\n argsSchema: { question: z.string().describe('The question to answer, e.g. \"what happens when a guest tries to check out?\"') },\n },\n ({ question }) => ({\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text: lang + askPrompt(question) } }],\n }),\n );\n\n // Self-heal workflow — surfaced as `/mcp__hover__heal`.\n server.registerPrompt(\n 'heal',\n {\n title: 'Hover — heal a drifted spec',\n description: \"Replay a saved spec against the live app; where the UI drifted, re-ground the broken step and re-crystallize. Pass a spec to heal one, or omit to check all.\",\n argsSchema: { spec: z.string().optional().describe('A spec slug to heal (e.g. \"login\"). Omit to check every spec.') },\n },\n ({ spec }) => ({\n messages: [{ role: 'user', content: { type: 'text', text: lang + healPrompt(spec) } }],\n }),\n );\n\n return server;\n}\n\n/** Optimize-ALL workflow body: enrich every spec, one at a time. Each spec's\n * brief comes from the optimize_brief tool (same brief the single-spec prompt\n * delivers inline), so quality is identical; only the loop differs. */\nfunction optimizeAllPrompt(): string {\n return `Optimize EVERY crystallized spec for this app using the **Hover MCP tools** — enrich each with the assertions its recording session observed, without changing what it tests.\n\n1. **List the specs** — the \\`*.spec.ts\\` files under \\`__vibe_tests__/\\` (skip \\`*.api-test.spec.ts\\` and \\`pages/\\`). Use your own file tools.\n2. **For each spec, ONE at a time:**\n - \\`optimize_brief(\"<slug>\")\\` — returns that spec's current code + the outcome its session observed + your Page Objects + the improvement rules.\n - Follow the brief: add assertions for the observed feedback, de-literalize volatile values (a generated id, an order number → a stable anchor), reuse a Page Object where a step sequence matches. Don't invent steps the session didn't perform.\n - \\`save_optimized_spec(\"<slug>\", <the complete improved .ts>)\\` — Hover validates it and files a candidate at \\`.hover/cache/optimized/<slug>.spec.ts.draft\\`. It NEVER overwrites your spec. On a ✗, fix it and call again.\n3. **Be selective per spec** — only add assertions that matter (a real outcome, a stable heading). A spec that's already tight needs no candidate; skip it and say so. Over-asserting a changing value is the failure we're avoiding.\n4. **Report + apply** — list which specs got a candidate. For each, show the user the diff vs \\`__vibe_tests__/<slug>.spec.ts\\`; on their approval call \\`promote_optimized_spec(\"<slug>\")\\` to apply it (overwrites the spec + removes the draft — no manual mv). Nothing lands until they approve.`;\n}\n\n/** Query workflow body (LLM-Wiki P4): read the wiki, answer with citations, and\n * optionally file a confirmed new rule back so the next question is cheaper. */\nfunction askPrompt(question: string): string {\n return `Answer a question about this app using its **test wiki** (\\`.hover/\\`) as the source of truth. The wiki = the business map (\\`.hover/hover-map.md\\`), the remembered business rules (\\`.hover/memory/\\` — via \\`recall_business_knowledge\\` / \\`recall_fact\\`), the crystallized specs under \\`__vibe_tests__/\\`, and the run history (\\`.hover/log.md\\`).\n\nQuestion: ${question}\n\n1. **Gather (read-only — don't drive the browser).** Call \\`recall_business_knowledge\\` for the rule index and pull specifics with \\`recall_fact\\`. Read \\`.hover/hover-map.md\\` for the business lines, coverage, and relationships. Skim the relevant \\`*.spec.ts\\` and \\`.hover/log.md\\` with your own file tools.\n2. **Answer directly, with CITATIONS.** Ground every claim in what you read — name the rule, the business line, or the spec file it came from. If the wiki genuinely doesn't cover it, say so plainly instead of guessing (and suggest running \\`/mcp__hover__test_app\\` to cover that area).\n3. **Optionally file it back.** If answering established a durable business RULE the wiki was missing and the user confirms it, persist it with \\`record_fact\\` (RULES only — never secrets / credentials / PII) so the next run doesn't re-derive it.\n\nStay on what the wiki + code actually say; don't invent behavior.`;\n}\n\n/** Wiki-lint workflow body. `lint_map` does the mechanical checks; the agent\n * layers the LLM-judged checks (contradictions, unmapped code routes) and fixes. */\nfunction lintPrompt(): string {\n return `Lint this app's **test wiki** (\\`.hover/\\`) using the Hover MCP tools, then fix what you find. The wiki = the business map (\\`.hover/hover-map.md\\`), the business-rule memory (\\`.hover/memory/*.md\\`), and the crystallized specs it points at.\n\n1. **Deterministic drift** — call \\`lint_map\\`. It cross-checks the map against the real spec files and the run ledger and reports:\n - **deleted-spec** — a line points at a \\`*.spec.ts\\` that's gone. Re-crystallize the flow, or drop the stale reference from the map.\n - **regressed-coverage** — a covered line whose spec last ran fail/flaky. Heal it with \\`/mcp__hover__heal <slug>\\` (or say it's a real app bug to fix).\n - **orphan-spec** — a spec no line maps. Add its business line to the map (\\`[x]\\` with the spec).\n2. **Contradictory rules (LLM-judged)** — read \\`.hover/memory/*.md\\`. If two facts conflict (e.g. one says guests can check out, another says they can't), surface the pair and ASK the user which holds; correct the wrong one with \\`record_fact\\` (or delete it).\n3. **Unmapped code routes (LLM-judged)** — with your own file tools, grep the app's router for user-facing routes. Any real route absent from \\`.hover/hover-map.md\\` is a coverage gap — add it as an uncovered \\`[ ]\\` line under the right area so the map stays honest.\n4. **Report + fix** — summarize the findings, apply the safe fixes (re-map, add gaps, correct rules), and for anything destructive (dropping a spec, deleting a rule) ASK first. Update \\`.hover/hover-map.md\\` as you go.\n\nStay on the app under test. Don't invent business lines that don't exist in the code.`;\n}\n\n/** Self-heal workflow body. The agent uses `replay_spec` to find the drift,\n * re-grounds the broken step by its recorded intent, and re-crystallizes. */\nfunction healPrompt(spec?: string): string {\n const scope = spec?.trim()\n ? `the spec \\`${spec.trim()}\\``\n : 'every spec under `__vibe_tests__/` (list them first, then heal each that drifted)';\n return `Heal ${scope} for this app using the **Hover MCP tools** — repair specs whose UI drifted, without rewriting them by hand.\n\nA spec \"drifted\" when the app changed so a recorded step no longer locates its control (a renamed button, a moved field). Healing = re-grounding ONLY the broken step against the current UI, keeping everything else, so record==replay still holds.\n\nDrift found in CI (a Hover Cloud heal request)? Call \\`cloud_failures\\` first — it lists each drifted spec's slug, failing locator, and branch, so you know what to heal and why.\n\nWork ONE spec at a time:\n\n1. **Detect** — \\`replay_spec(\"<slug>\")\\`. It replays the spec's recorded grounded steps against the live app and reports the first step that fails to locate: its index, the tool, and what it was \\`lookingFor\\` (role+name/text). If it replays clean, that spec is fine — move on.\n2. **Re-ground the broken step** — \\`browser_navigate\\` to the spec's route, \\`browser_snapshot\\`, and find the control that NOW serves the intent in \\`lookingFor\\` (e.g. the submit button whose label changed \"Sign in\" → \"Log in\"). Re-drive from the break with the grounded \\`*_control\\` tools. Change ONLY what drifted — don't redesign the flow.\n3. **Re-crystallize** — when the flow runs green again, \\`crystallize_spec\\` with the SAME name as the broken spec to overwrite it with the healed version.\n4. **Report** — say which step drifted, what changed (old target → new target), and that it's re-crystallized. The user reviews the old-vs-new diff in the cockpit before keeping it.\n\nRules: heal by the recorded INTENT (re-locate the same control), never invent a new flow or new assertions. If a step is gone because the FEATURE was removed (not just renamed), don't guess — say so and ASK whether to drop that step or the whole spec. Stay on the app under test.`;\n}\n\n/** The phased, scale-aware workflow, delivered as the prompt body. Mirrors the\n * hover-mcp tool surface; the agent's own file tools do the code-reading. */\nfunction workflowPrompt(scope?: string): string {\n const target = scope?.trim() ? scope.trim() : 'the whole app';\n return `Build (or extend) a Playwright test suite for this web app using the **Hover MCP tools**.\nDrive the browser ONLY through these tools — they actuate via grounded selectors\n(role+name → testId → text), so every spec you save replays EXACTLY what you did\n(record==replay). Never write spec files yourself; only \\`crystallize_spec\\` does.\n\nTools: \\`recall_business_knowledge\\` / \\`recall_fact\\` · \\`browser_navigate\\` ·\n\\`browser_snapshot\\` (ARIA tree — read before acting) · \\`click_control\\` /\n\\`fill_control\\` / \\`select_control\\` / \\`check_control\\` (grounded target from the\nsnapshot) · \\`assert_visible\\` · \\`record_fact\\` · \\`crystallize_spec(name, description?)\\`.\nAPI layer: \\`capture_requests\\` · \\`replay_request\\` · \\`crystallize_api_spec\\`. Suite:\n\\`detect_shared_flows\\` · \\`extract_page_objects\\` · \\`replay_spec\\` · \\`lint_map\\`.\n\nTarget: the app at HOVER_TARGET (set in the server's env). Scope: ${target}.\n\n## First: are you bootstrapping or extending? (load only what this run needs)\nCheck whether \\`.hover/hover-map.md\\` already exists.\n- **It exists → you're EXTENDING.** Read it + call \\`recall_business_knowledge\\`, then go straight to Phase 2 and cover the uncovered \\`[ ]\\` lines. Skip the Phase-1 code-mapping — the map already IS the plan. Only re-map if the user says the app changed.\n- **It's absent → you're BOOTSTRAPPING.** Do the full Phase 1 below to build the map first.\nThis keeps a returning run cheap: you don't re-derive a map you already have.\n\n## Ground rules (they protect record==replay AND the user's real app)\n- **Grounded targets only.** Pass role+name EXACTLY as they appear in the LATEST \\`browser_snapshot\\`. If a locate fails, re-snapshot and read the real target — never guess, invent, or reuse a stale name.\n- **It's the user's REAL app.** Avoid irreversible / destructive actions — real payments, deleting data you didn't create, sending real emails or SMS — unless the user confirms this is a safe test environment. When unsure, ASK first.\n- **Assert stable outcomes.** Assert on semantic, durable signals (a success message, a heading, a new row's label) — NEVER volatile instance data (timestamps, generated ids, \"today\", a one-off order number), which makes the saved spec flaky on replay.\n- **Log in first.** If the app needs auth, do that before anything else — ask for credentials if you don't have them — then crystallize it as its own \"Log in\" spec and stay logged in for the rest of the run.\n\nWork in PHASES — this is what lets it scale from a tiny app to a large one.\n\n## Phase 1 — Map the business lines (read the CODE, don't click around)\n- FIRST call \\`recall_business_knowledge\\` — rules earlier runs learned (and read \\`.hover/hover-map.md\\` if it exists, the running map; CONTINUE it, don't start over). Treat both as ground truth; don't re-ask what they already answer. For an app with many remembered rules this returns an INDEX (one line per rule); when a rule is relevant to what you're about to test, pull its full text with \\`recall_fact(\"<name>\")\\`.\n- Use YOUR OWN file tools (read / grep / glob) to find the app's ROUTES + navigation: the router config, route/page files, the nav components. Enumerate the user-facing BUSINESS LINES (a coherent task a user performs), each with its entry route, grouped by area. Reading code is cheaper + more complete than clicking around, and finds areas behind auth / nav you'd otherwise miss.\n- Write/update \\`.hover/hover-map.md\\` as a checklist (4-space indent = a code block):\n\n # Business map — <app>\n ## Auth\n - [ ] Log in — /login\n - [x] Checkout — /checkout — checkout.spec.ts\n ## Relationships\n - Checkout depends-on Log in\n - Cart shares-state Checkout\n\n- Optionally add a \\`## Relationships\\` block recording inter-line edges you notice — \\`<line> depends-on <line>\\`, \\`<line> shares-state <line>\\`, or \\`<line> navigates-to <line>\\` (names must match lines above). These become graph edges in the cockpit's Business Map; they also tell a later run what a flow depends on. Record only real edges; skip the block if none stand out.\n- Don't test yet. For a large app, this map IS the plan.\n\n## Phase 2 — Pick the scope\n- If a scope was given, cover that. Otherwise show the uncovered lines and ask which to cover now (offer \"all uncovered\"). For a small app, just cover them all.\n\n## Phase 3 — Cover each chosen line, ONE AT A TIME\nFor each line: \\`browser_navigate\\` to its route → \\`browser_snapshot\\` → EXERCISE the real flow (click / fill / select / check — you are a tester, never just describe the page) → \\`assert_visible\\` on the OUTCOME (a success message, the new row, the next screen) → \\`crystallize_spec(\"<short imperative English name>\")\\`. Crystallize the MOMENT each flow is done, before the next — the buffer is per-flow. The tools share ONE browser, so cover lines SEQUENTIALLY.\n\n## Phase 3.5 — Lock the API layer too (SELECTIVELY)\nAs you drive each flow, Hover passively captures the app's xhr/fetch traffic. After a flow, call \\`capture_requests\\` to see what it hit. For a line that exercised a **worthwhile API surface — a data mutation (POST/PUT/DELETE), a clear contract, or an authz boundary** — also lock an API spec:\n- Decide the checks. Contract: the call returns its status + key fields. Authz: the same call WITHOUT the session must be refused — call \\`replay_request\\` with \\`authenticated:false\\` and confirm it's 401/403 before asserting it.\n- VERIFY each check with \\`replay_request\\` first (so you never assert a confabulated status), then \\`crystallize_api_spec(name, checks)\\` → a \\`*.api-test.spec.ts\\`.\n- Be SELECTIVE — skip pure-display reads, analytics, third-party pings. Most UI flows need 0–1 API specs. A static/read-only flow needs none. This is judgment, not a per-flow quota.\n\n## Phase 4 — Update coverage\n- Mark each covered line \\`[x]\\` in \\`.hover/hover-map.md\\` with its spec filename. Report covered vs still-open. A LARGE app doesn't have to finish in one go — covering a batch + updating the map is a complete, resumable unit; re-invoke to continue the uncovered lines.\n- Then call \\`lint_map\\` to catch wiki drift you may have introduced (a covered line whose spec now fails, a stale spec reference, a spec no line maps) and fix or report it. \\`/mcp__hover__lint\\` runs the deeper check any time.\n\n## Phase 5 — Lift shared flows into Page Objects (ASK first)\nOnce specs are crystallized, call \\`detect_shared_flows\\`. If it reports a NON-login flow repeated across specs (login is already handled by the auth setup), tell the user which specs share it and ASK whether to lift it into a shared Page Object (so a UI change to that flow is a one-place fix). On yes → \\`extract_page_objects\\` (generates \\`pages/*\\` + \\`fixtures.ts\\` and folds the specs to \\`await xPage.x()\\`). If nothing is shared, skip silently — most small suites have nothing to lift; don't force it.\n\n## Understand the business — ASK, then REMEMBER\nWhen you genuinely can't resolve something on your own — is this a bug or by-design? which flows matter? what does this domain term mean? — ASK the user (don't guess, don't stop). When they confirm a durable business RULE, call \\`record_fact\\` to persist it (RULES ONLY — never credentials/secrets/PII). Also ASK when blocked on something only they can provide (login credentials, a file). Stay on the app under test — never navigate to external origins.`;\n}\n"],"mappings":"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","names":["server","ctx"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hover-dev/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Hover MCP server — grounded browser actuation + crystallize a Playwright suite, for your own coding agent",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Hyperyond",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
37
|
"playwright-core": "^1.50.0",
|
|
38
38
|
"zod": "^4.4.3",
|
|
39
|
-
"@hover-dev/core": "0.
|
|
39
|
+
"@hover-dev/core": "0.26.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^22.0.0",
|