@hover-dev/core 0.16.0 → 0.17.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.
Files changed (154) hide show
  1. package/README.md +26 -55
  2. package/dist/agentDirectives.d.ts +55 -0
  3. package/dist/agentDirectives.d.ts.map +1 -0
  4. package/dist/agentDirectives.js +276 -0
  5. package/dist/agents/claude.d.ts.map +1 -1
  6. package/dist/agents/claude.js +28 -3
  7. package/dist/agents/codex.d.ts.map +1 -1
  8. package/dist/agents/codex.js +29 -14
  9. package/dist/agents/invoke.d.ts.map +1 -1
  10. package/dist/agents/invoke.js +3 -6
  11. package/dist/agents/registry.d.ts.map +1 -1
  12. package/dist/agents/registry.js +0 -4
  13. package/dist/agents/types.d.ts +19 -11
  14. package/dist/agents/types.d.ts.map +1 -1
  15. package/dist/engine.d.ts +53 -0
  16. package/dist/engine.d.ts.map +1 -0
  17. package/dist/engine.js +78 -0
  18. package/dist/mcp/actuateServer.d.ts +3 -0
  19. package/dist/mcp/actuateServer.d.ts.map +1 -0
  20. package/dist/mcp/actuateServer.js +594 -0
  21. package/dist/mcp/sourceFence.d.ts.map +1 -1
  22. package/dist/mcp/sourceFence.js +4 -0
  23. package/dist/mcp/sourceServer.js +75 -0
  24. package/dist/memory/businessMemory.d.ts +29 -0
  25. package/dist/memory/businessMemory.d.ts.map +1 -0
  26. package/dist/memory/businessMemory.js +125 -0
  27. package/dist/modes.d.ts +39 -0
  28. package/dist/modes.d.ts.map +1 -0
  29. package/dist/modes.js +34 -0
  30. package/dist/playwright/cdpStatus.d.ts +0 -15
  31. package/dist/playwright/cdpStatus.d.ts.map +1 -1
  32. package/dist/playwright/cdpStatus.js +0 -67
  33. package/dist/playwright/launchChrome.d.ts +18 -0
  34. package/dist/playwright/launchChrome.d.ts.map +1 -1
  35. package/dist/playwright/launchChrome.js +46 -3
  36. package/dist/playwright/resolveMcpConfig.d.ts +7 -1
  37. package/dist/playwright/resolveMcpConfig.d.ts.map +1 -1
  38. package/dist/playwright/resolveMcpConfig.js +22 -4
  39. package/dist/plugin-api.d.ts +28 -26
  40. package/dist/plugin-api.d.ts.map +1 -1
  41. package/dist/plugin-api.js +2 -2
  42. package/dist/qa/candidates.d.ts +32 -0
  43. package/dist/qa/candidates.d.ts.map +1 -0
  44. package/dist/qa/candidates.js +20 -0
  45. package/dist/qa/classify.d.ts +38 -0
  46. package/dist/qa/classify.d.ts.map +1 -0
  47. package/dist/qa/classify.js +138 -0
  48. package/dist/qa/intensity.d.ts +33 -0
  49. package/dist/qa/intensity.d.ts.map +1 -0
  50. package/dist/qa/intensity.js +25 -0
  51. package/dist/qa/qaReport.d.ts +19 -0
  52. package/dist/qa/qaReport.d.ts.map +1 -0
  53. package/dist/qa/qaReport.js +50 -0
  54. package/dist/runSession.d.ts +14 -3
  55. package/dist/runSession.d.ts.map +1 -1
  56. package/dist/runSession.js +26 -11
  57. package/dist/service/cdpHandlers.d.ts +1 -21
  58. package/dist/service/cdpHandlers.d.ts.map +1 -1
  59. package/dist/service/cdpHandlers.js +4 -39
  60. package/dist/service/cdpHint.d.ts +21 -28
  61. package/dist/service/cdpHint.d.ts.map +1 -1
  62. package/dist/service/cdpHint.js +106 -164
  63. package/dist/service/relayHandlers.d.ts +28 -0
  64. package/dist/service/relayHandlers.d.ts.map +1 -0
  65. package/dist/service/relayHandlers.js +105 -0
  66. package/dist/service/saveHandlers.d.ts +1 -3
  67. package/dist/service/saveHandlers.d.ts.map +1 -1
  68. package/dist/service/saveHandlers.js +17 -15
  69. package/dist/service/types.d.ts +108 -8
  70. package/dist/service/types.d.ts.map +1 -1
  71. package/dist/service.d.ts +7 -3
  72. package/dist/service.d.ts.map +1 -1
  73. package/dist/service.js +907 -200
  74. package/dist/sessions/sessions.d.ts +125 -0
  75. package/dist/sessions/sessions.d.ts.map +1 -0
  76. package/dist/sessions/sessions.js +175 -0
  77. package/dist/specs/authFixture.d.ts +30 -0
  78. package/dist/specs/authFixture.d.ts.map +1 -0
  79. package/dist/specs/authFixture.js +145 -0
  80. package/dist/specs/businessMap.d.ts +29 -0
  81. package/dist/specs/businessMap.d.ts.map +1 -0
  82. package/dist/specs/businessMap.js +95 -0
  83. package/dist/specs/detectSharedFlows.d.ts +1 -1
  84. package/dist/specs/detectSharedFlows.d.ts.map +1 -1
  85. package/dist/specs/detectSharedFlows.js +20 -21
  86. package/dist/specs/generatePageObject.d.ts +1 -1
  87. package/dist/specs/generatePageObject.d.ts.map +1 -1
  88. package/dist/specs/healPrompt.d.ts +19 -0
  89. package/dist/specs/healPrompt.d.ts.map +1 -0
  90. package/dist/specs/healPrompt.js +48 -0
  91. package/dist/specs/humanSteps.d.ts +4 -8
  92. package/dist/specs/humanSteps.d.ts.map +1 -1
  93. package/dist/specs/humanSteps.js +6 -1
  94. package/dist/specs/optimizeSpec.d.ts +15 -8
  95. package/dist/specs/optimizeSpec.d.ts.map +1 -1
  96. package/dist/specs/optimizeSpec.js +71 -41
  97. package/dist/specs/optimizeSpecWithAgent.d.ts +0 -2
  98. package/dist/specs/optimizeSpecWithAgent.d.ts.map +1 -1
  99. package/dist/specs/optimizeSpecWithAgent.js +0 -1
  100. package/dist/specs/pageObjectManifest.d.ts +3 -1
  101. package/dist/specs/pageObjectManifest.d.ts.map +1 -1
  102. package/dist/specs/pageObjectManifest.js +13 -9
  103. package/dist/specs/replayGrounded.d.ts +45 -0
  104. package/dist/specs/replayGrounded.d.ts.map +1 -0
  105. package/dist/specs/replayGrounded.js +155 -0
  106. package/dist/specs/runFailures.d.ts +34 -0
  107. package/dist/specs/runFailures.d.ts.map +1 -0
  108. package/dist/specs/runFailures.js +93 -0
  109. package/dist/specs/seeds.d.ts +16 -15
  110. package/dist/specs/seeds.d.ts.map +1 -1
  111. package/dist/specs/seeds.js +86 -54
  112. package/dist/specs/sidecar.d.ts +34 -6
  113. package/dist/specs/sidecar.d.ts.map +1 -1
  114. package/dist/specs/sidecar.js +79 -9
  115. package/dist/specs/specStep.d.ts +21 -0
  116. package/dist/specs/specStep.d.ts.map +1 -0
  117. package/dist/specs/specStep.js +1 -0
  118. package/dist/specs/text.d.ts +8 -6
  119. package/dist/specs/text.d.ts.map +1 -1
  120. package/dist/specs/text.js +10 -7
  121. package/dist/specs/writeSpec.d.ts +62 -1
  122. package/dist/specs/writeSpec.d.ts.map +1 -1
  123. package/dist/specs/writeSpec.js +596 -21
  124. package/package.json +6 -9
  125. package/dist/agents/aider.d.ts +0 -16
  126. package/dist/agents/aider.d.ts.map +0 -1
  127. package/dist/agents/aider.js +0 -161
  128. package/dist/agents/cursor.d.ts +0 -18
  129. package/dist/agents/cursor.d.ts.map +0 -1
  130. package/dist/agents/cursor.js +0 -220
  131. package/dist/playwright/raiseWindow.d.ts +0 -10
  132. package/dist/playwright/raiseWindow.d.ts.map +0 -1
  133. package/dist/playwright/raiseWindow.js +0 -158
  134. package/dist/scripts/bench-multi-tab.d.ts +0 -2
  135. package/dist/scripts/bench-multi-tab.d.ts.map +0 -1
  136. package/dist/scripts/bench-multi-tab.js +0 -192
  137. package/dist/scripts/bench-ttfb.d.ts +0 -2
  138. package/dist/scripts/bench-ttfb.d.ts.map +0 -1
  139. package/dist/scripts/bench-ttfb.js +0 -127
  140. package/dist/scripts/start-chrome.d.ts +0 -3
  141. package/dist/scripts/start-chrome.d.ts.map +0 -1
  142. package/dist/scripts/start-chrome.js +0 -23
  143. package/dist/skills/writeSkill.d.ts +0 -27
  144. package/dist/skills/writeSkill.d.ts.map +0 -1
  145. package/dist/skills/writeSkill.js +0 -13
  146. package/dist/specs/listSpecs.d.ts +0 -52
  147. package/dist/specs/listSpecs.d.ts.map +0 -1
  148. package/dist/specs/listSpecs.js +0 -139
  149. package/dist/specs/optimizationSuggestion.d.ts +0 -26
  150. package/dist/specs/optimizationSuggestion.d.ts.map +0 -1
  151. package/dist/specs/optimizationSuggestion.js +0 -28
  152. package/dist/specs/writeCaseCsv.d.ts +0 -28
  153. package/dist/specs/writeCaseCsv.d.ts.map +0 -1
  154. package/dist/specs/writeCaseCsv.js +0 -134
@@ -0,0 +1,155 @@
1
+ import { chromium } from 'playwright-core';
2
+ const ACTUATION_TOOLS = new Set(['click_control', 'fill_control', 'select_control', 'check_control', 'assert_visible']);
3
+ const ACTION_TIMEOUT = 8000;
4
+ function bare(tool) {
5
+ if (!tool)
6
+ return '';
7
+ const p = tool.split('__');
8
+ return p[0] === 'mcp' && p.length >= 3 ? p.slice(2).join('__') : tool;
9
+ }
10
+ function errLine(e) {
11
+ return e instanceof Error ? e.message.split('\n')[0] : String(e);
12
+ }
13
+ function originOf(u) {
14
+ try {
15
+ return new URL(u).origin;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ function describe(g) {
22
+ if (g.role && g.name)
23
+ return `${g.role} "${g.name}"`;
24
+ if (g.testId)
25
+ return `testId "${g.testId}"`;
26
+ if (g.text)
27
+ return `text "${g.text}"`;
28
+ return '(no target)';
29
+ }
30
+ // Mirrors mcp/actuateServer.ts `locate` — role+name → testId → text, optionally
31
+ // scoped to a `within` container. See file header. Exported so non-extension
32
+ // consumers (the replayer here, the standalone `hover-mcp`) share ONE grounded
33
+ // resolver without touching the load-bearing actuateServer.
34
+ export function groundedLocate(page, g) {
35
+ const base = g.within?.role && g.within?.name
36
+ ? page.getByRole(g.within.role, { name: g.within.name, exact: true })
37
+ : page;
38
+ if (g.role && g.name)
39
+ return base.getByRole(g.role, { name: g.name, exact: true });
40
+ if (g.testId)
41
+ return base.getByTestId(g.testId);
42
+ if (g.text)
43
+ return base.getByText(g.text).first();
44
+ return null;
45
+ }
46
+ /** Replay ONE grounded step on a page. Returns 'skipped' for non-actuation
47
+ * steps; throws on a failed locate / action (the caller records it). */
48
+ export async function applyGroundedStep(page, step) {
49
+ const tool = bare(step.tool);
50
+ if (!ACTUATION_TOOLS.has(tool))
51
+ return 'skipped';
52
+ const input = (step.input ?? {});
53
+ const loc = groundedLocate(page, input);
54
+ if (!loc)
55
+ throw new Error(`could not locate ${describe(input)}`);
56
+ switch (tool) {
57
+ case 'click_control':
58
+ await loc.click({ timeout: ACTION_TIMEOUT });
59
+ return 'ok';
60
+ case 'fill_control':
61
+ await loc.fill(String(input.value ?? ''), { timeout: ACTION_TIMEOUT });
62
+ return 'ok';
63
+ case 'select_control':
64
+ await loc.selectOption(String(input.value ?? ''), { timeout: ACTION_TIMEOUT });
65
+ return 'ok';
66
+ case 'check_control':
67
+ if (input.checked === false)
68
+ await loc.uncheck({ timeout: ACTION_TIMEOUT });
69
+ else
70
+ await loc.check({ timeout: ACTION_TIMEOUT });
71
+ return 'ok';
72
+ case 'assert_visible': {
73
+ const visible = await loc.first().isVisible();
74
+ if (!visible)
75
+ throw new Error(`${describe(input)} not visible`);
76
+ return 'ok';
77
+ }
78
+ default:
79
+ return 'skipped';
80
+ }
81
+ }
82
+ /** Replay a flow's grounded steps on a given page (injected, so it's testable
83
+ * without a real browser). Navigates to `devUrl` first for a consistent start,
84
+ * then runs each grounded action, stopping at the first failure. */
85
+ export async function replayOnPage(page, devUrl, steps) {
86
+ const failures = [];
87
+ let ran = 0;
88
+ const total = steps.filter((s) => ACTUATION_TOOLS.has(bare(s.tool))).length;
89
+ try {
90
+ await page.goto(devUrl, { waitUntil: 'domcontentloaded', timeout: 15000 });
91
+ }
92
+ catch {
93
+ // SPA / already on the origin — replay anyway.
94
+ }
95
+ for (let i = 0; i < steps.length; i++) {
96
+ if (!ACTUATION_TOOLS.has(bare(steps[i].tool)))
97
+ continue;
98
+ try {
99
+ if ((await applyGroundedStep(page, steps[i])) === 'ok')
100
+ ran++;
101
+ }
102
+ catch (e) {
103
+ failures.push({ index: i, tool: bare(steps[i].tool), target: describe((steps[i].input ?? {})), error: errLine(e) });
104
+ break; // a broken step breaks the flow — stop here
105
+ }
106
+ }
107
+ return { ok: failures.length === 0, ran, total, failures };
108
+ }
109
+ /** Pick the page on the dev origin, else the foreground page. */
110
+ async function pickPage(browser, devUrl) {
111
+ const wantOrigin = originOf(devUrl);
112
+ const pages = browser.contexts().flatMap((c) => c.pages());
113
+ if (pages.length === 0)
114
+ return null;
115
+ const matches = wantOrigin ? pages.filter((p) => originOf(p.url()) === wantOrigin) : [];
116
+ const candidates = matches.length ? matches : pages;
117
+ let chosen = candidates[candidates.length - 1];
118
+ for (const p of candidates) {
119
+ try {
120
+ if (await p.evaluate(() => document.visibilityState === 'visible')) {
121
+ chosen = p;
122
+ break;
123
+ }
124
+ }
125
+ catch {
126
+ /* busy/closed */
127
+ }
128
+ }
129
+ return chosen;
130
+ }
131
+ /** Connect to the debug Chrome over CDP and replay a flow's grounded steps. */
132
+ export async function replayGroundedSteps(opts) {
133
+ let browser;
134
+ try {
135
+ browser = await chromium.connectOverCDP(opts.cdpUrl, { timeout: 5000 });
136
+ }
137
+ catch (e) {
138
+ return { ok: false, ran: 0, total: 0, failures: [{ index: -1, tool: 'connect', target: opts.cdpUrl, error: errLine(e) }] };
139
+ }
140
+ try {
141
+ const page = await pickPage(browser, opts.devUrl);
142
+ if (!page) {
143
+ return { ok: false, ran: 0, total: 0, failures: [{ index: -1, tool: 'page', target: opts.devUrl, error: 'no page on the dev origin' }] };
144
+ }
145
+ return await replayOnPage(page, opts.devUrl, opts.steps);
146
+ }
147
+ finally {
148
+ try {
149
+ await browser.close();
150
+ }
151
+ catch {
152
+ /* disconnect only — never kill the user's debug Chrome */
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Self-heal Stage 1 — the failure → heal-hint bridge.
3
+ *
4
+ * Playwright's JSON reporter records pass/fail but not *which locator* failed.
5
+ * Self-healing needs that to know what to re-locate. The locator IS in the error
6
+ * message (e.g. `waiting for getByRole('button', { name: 'Submit' })`), so this
7
+ * module parses the run JSON's failed tests and pulls out, per failure, the
8
+ * `getBy…` expression + the action that failed — the hint the heal session
9
+ * (Stage 2) drives the agent with.
10
+ *
11
+ * Pure + total: bad/partial JSON yields []. No FS, no agent — just parsing.
12
+ */
13
+ export interface RunFailure {
14
+ /** Spec file as Playwright reports it (path or basename). */
15
+ specFile: string;
16
+ /** Test title. */
17
+ title: string;
18
+ /** First line of the error message (the human-readable failure). */
19
+ error: string;
20
+ /** The `getBy…` locator expression parsed from the error — the thing to
21
+ * re-locate — or undefined if the failure wasn't a locator miss. */
22
+ failingLocator?: string;
23
+ /** The Playwright action that failed: 'click' / 'fill' / 'assert' / … */
24
+ failingAction?: string;
25
+ }
26
+ /** The `getBy…` expression in an error message, if any. */
27
+ export declare function extractLocator(message: string): string | undefined;
28
+ /** The failing action (click / fill / assert / …) inferred from the message. */
29
+ export declare function extractAction(message: string): string | undefined;
30
+ /** Parse a Playwright JSON-reporter run into its failures (with the failing
31
+ * locator + action pulled from each error). Accepts the parsed object or a
32
+ * JSON string; anything malformed yields []. */
33
+ export declare function parseRunFailures(json: unknown): RunFailure[];
34
+ //# sourceMappingURL=runFailures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runFailures.d.ts","sourceRoot":"","sources":["../../src/specs/runFailures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,UAAU;IACzB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd;yEACqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAgBD,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGlE;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOjE;AAoCD;;iDAEiD;AACjD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,EAAE,CAY5D"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Self-heal Stage 1 — the failure → heal-hint bridge.
3
+ *
4
+ * Playwright's JSON reporter records pass/fail but not *which locator* failed.
5
+ * Self-healing needs that to know what to re-locate. The locator IS in the error
6
+ * message (e.g. `waiting for getByRole('button', { name: 'Submit' })`), so this
7
+ * module parses the run JSON's failed tests and pulls out, per failure, the
8
+ * `getBy…` expression + the action that failed — the hint the heal session
9
+ * (Stage 2) drives the agent with.
10
+ *
11
+ * Pure + total: bad/partial JSON yields []. No FS, no agent — just parsing.
12
+ */
13
+ /** A `getBy…(...)` call with an optional `.first()/.last()/.nth()` tail. The
14
+ * inner `(?:[^()]|\([^()]*\))*` tolerates one level of nested parens so
15
+ * `getByRole('button', { name: 'x' })` matches whole. */
16
+ const LOCATOR_RE = /getBy\w+\((?:[^()]|\([^()]*\))*\)(?:\.(?:first|last|nth)\([^)]*\))?/;
17
+ /** A KNOWN Playwright interaction at the head of an error (`locator.click: …`),
18
+ * restricted to real actions so a generic `Error:` / `Some:` prefix is NOT
19
+ * mistaken for one. */
20
+ const ACTION_RE = /^(?:locator\.)?(click|dblclick|fill|type|press|check|uncheck|selectOption|setInputFiles|hover|tap|focus|clear)\b/;
21
+ const ASSERT_RE = /\b(?:toBeVisible|toHaveText|toContainText|toHaveCount|toHaveValue|toBeChecked|toBeEnabled)\b|^expect\(/;
22
+ function firstLine(s) {
23
+ return (s.split('\n').find(l => l.trim()) ?? '').trim();
24
+ }
25
+ /** The `getBy…` expression in an error message, if any. */
26
+ export function extractLocator(message) {
27
+ const m = message.match(LOCATOR_RE);
28
+ return m ? m[0] : undefined;
29
+ }
30
+ /** The failing action (click / fill / assert / …) inferred from the message. */
31
+ export function extractAction(message) {
32
+ const head = firstLine(message);
33
+ // Assert first — an `Error: expect(...)` line leads with "Error:", which a
34
+ // generic action match would wrongly read as the action.
35
+ if (ASSERT_RE.test(head) || ASSERT_RE.test(message))
36
+ return 'assert';
37
+ const m = head.match(ACTION_RE);
38
+ return m ? m[1].toLowerCase() : undefined;
39
+ }
40
+ /** The error message of the first failed/timed-out result on a spec, if any. */
41
+ function failedMessage(spec) {
42
+ for (const t of spec.tests ?? []) {
43
+ for (const r of t.results ?? []) {
44
+ if (r.status === 'failed' || r.status === 'timedOut' || r.status === 'interrupted') {
45
+ return r.error?.message || r.errors?.find(e => e.message)?.message || 'Test failed (no error message).';
46
+ }
47
+ }
48
+ }
49
+ return undefined;
50
+ }
51
+ function walk(suite, fileFallback, out) {
52
+ const file = suite.file || fileFallback;
53
+ for (const spec of suite.specs ?? []) {
54
+ if (spec.ok === true)
55
+ continue;
56
+ const message = failedMessage(spec);
57
+ if (!message)
58
+ continue; // not a failure (ok may be undefined but no failed result)
59
+ out.push({
60
+ specFile: spec.file || file,
61
+ title: spec.title || '(untitled)',
62
+ error: firstLine(message),
63
+ failingLocator: extractLocator(message),
64
+ failingAction: extractAction(message),
65
+ });
66
+ }
67
+ for (const child of suite.suites ?? [])
68
+ walk(child, file, out);
69
+ }
70
+ /** Parse a Playwright JSON-reporter run into its failures (with the failing
71
+ * locator + action pulled from each error). Accepts the parsed object or a
72
+ * JSON string; anything malformed yields []. */
73
+ export function parseRunFailures(json) {
74
+ let root;
75
+ if (typeof json === 'string') {
76
+ try {
77
+ root = JSON.parse(json);
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ }
83
+ else if (json && typeof json === 'object') {
84
+ root = json;
85
+ }
86
+ else {
87
+ return [];
88
+ }
89
+ const out = [];
90
+ for (const suite of root.suites ?? [])
91
+ walk(suite, suite.file || '', out);
92
+ return out;
93
+ }
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Translation seeds: human-written worked examples that teach the optimization
3
+ * pass (F7) a multi-step Playwright pattern by few-shot — NOT by deterministic
4
+ * match+template. A seed is a rough `signature` (tool names, used only to pick
5
+ * relevant seeds) + a concrete `example` (input steps → output code) the LLM
6
+ * generalizes from.
7
+ *
8
+ * These ship inlined as the `BUILTIN_SEEDS` constant below. They used to be
9
+ * JSON files under `packages/core/seeds/optimization/` plus a `.hover/rules/`
10
+ * "author your own seed" mechanism and a `.hover/seeds.json` opt-out — all
11
+ * removed: that user-facing surface added burden for a small curated catalogue
12
+ * that feeds an optional, manually-invoked pass. To add a pattern, append a
13
+ * `SeedRule` here.
14
+ */
1
15
  export interface SeedRule {
2
16
  /** Identifier, e.g. `download`. */
3
17
  name: string;
@@ -13,23 +27,10 @@ export interface SeedRule {
13
27
  };
14
28
  }
15
29
  /**
16
- * Built-in seeds ship with core and feed EVERY project's optimization pass, so
17
- * the bar is high: a pattern qualifies as built-in ONLY if it's a *highly
18
- * certain* optimization — a fixed, app-agnostic translation whose output is
19
- * deterministic and can't mislead (e.g. download → waitForEvent pairing).
20
- *
21
- * Deliberately NOT built-in:
22
- * - Semantic / judgement-based optimizations (e.g. WHICH feedback text to
23
- * assert) — those are already standing instructions in buildOptimizePrompt,
24
- * and a bad generalization would pollute every user's spec.
25
- * - Popup/new-tab — hardcoded in the translator (writeSpec), not a seed.
26
- * Project-specific or speculative patterns live in <root>/.hover/rules/, where
27
- * the bar is the user's own call.
30
+ * Built-in optimization seeds, inlined. They feed EVERY project's optimization
31
+ * pass (the prompt builder and the relevance filter consume this directly).
28
32
  */
29
33
  export declare const BUILTIN_SEEDS: SeedRule[];
30
- /** Built-in seeds + any in <projectRoot>/.hover/rules/*.json. Malformed files
31
- * are skipped rather than failing the whole read. */
32
- export declare function readSeeds(projectRoot: string): Promise<SeedRule[]>;
33
34
  /** Pick seeds whose signature's base tool appears in the spec — a cheap
34
35
  * relevance filter so the prompt only carries plausibly-applicable examples. */
35
36
  export declare function relevantSeeds(seeds: SeedRule[], specTools: Set<string>, cap?: number): SeedRule[];
@@ -1 +1 @@
1
- {"version":3,"file":"seeds.d.ts","sourceRoot":"","sources":["../../src/specs/seeds.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,QAAQ;IACvB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb;qEACiE;IACjE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,EAenC,CAAC;AAEF;sDACsD;AACtD,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAiBxE;AAED;iFACiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,SAAI,GAAG,QAAQ,EAAE,CAG5F"}
1
+ {"version":3,"file":"seeds.d.ts","sourceRoot":"","sources":["../../src/specs/seeds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,QAAQ;IACvB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb;qEACiE;IACjE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,EAiFnC,CAAC;AAEF;iFACiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,SAAI,GAAG,QAAQ,EAAE,CAG5F"}
@@ -1,71 +1,103 @@
1
1
  /**
2
- * Community translation seeds (Stage 6, approach A): human-written worked
3
- * examples that teach the optimization pass (F7) new patterns by few-shot,
4
- * NOT by deterministic match+template. A seed is a rough `signature` (tool
5
- * names, used only to pick relevant seeds) + a concrete `example`
6
- * (input steps → output code) the LLM generalizes from.
2
+ * Translation seeds: human-written worked examples that teach the optimization
3
+ * pass (F7) a multi-step Playwright pattern by few-shot — NOT by deterministic
4
+ * match+template. A seed is a rough `signature` (tool names, used only to pick
5
+ * relevant seeds) + a concrete `example` (input steps → output code) the LLM
6
+ * generalizes from.
7
7
  *
8
- * Sources: a small built-in set + the project's <projectRoot>/.hover/rules/.
9
- * Adding a pattern = dropping an example JSON in .hover/rules/ — no core change.
10
- *
11
- * Built-in seeds deliberately cover patterns the deterministic translator does
12
- * NOT hardcode (popup is already hardcoded in writeSpec, so it's not here).
8
+ * These ship inlined as the `BUILTIN_SEEDS` constant below. They used to be
9
+ * JSON files under `packages/core/seeds/optimization/` plus a `.hover/rules/`
10
+ * "author your own seed" mechanism and a `.hover/seeds.json` opt-out — all
11
+ * removed: that user-facing surface added burden for a small curated catalogue
12
+ * that feeds an optional, manually-invoked pass. To add a pattern, append a
13
+ * `SeedRule` here.
13
14
  */
14
- import { readFile, readdir } from 'node:fs/promises';
15
- import { join } from 'node:path';
16
15
  /**
17
- * Built-in seeds ship with core and feed EVERY project's optimization pass, so
18
- * the bar is high: a pattern qualifies as built-in ONLY if it's a *highly
19
- * certain* optimization — a fixed, app-agnostic translation whose output is
20
- * deterministic and can't mislead (e.g. download → waitForEvent pairing).
21
- *
22
- * Deliberately NOT built-in:
23
- * - Semantic / judgement-based optimizations (e.g. WHICH feedback text to
24
- * assert) — those are already standing instructions in buildOptimizePrompt,
25
- * and a bad generalization would pollute every user's spec.
26
- * - Popup/new-tab — hardcoded in the translator (writeSpec), not a seed.
27
- * Project-specific or speculative patterns live in <root>/.hover/rules/, where
28
- * the bar is the user's own call.
16
+ * Built-in optimization seeds, inlined. They feed EVERY project's optimization
17
+ * pass (the prompt builder and the relevance filter consume this directly).
29
18
  */
30
19
  export const BUILTIN_SEEDS = [
31
20
  {
32
21
  name: 'download',
33
22
  signature: ['browser_click'],
34
- note: 'a click that triggers a file download pair with waitForEvent("download")',
23
+ note: 'A click that triggers a file download pair it with waitForEvent(\'download\') so the listener is registered before the click fires.',
35
24
  example: {
36
25
  steps: [{ tool: 'browser_click', element: 'Export CSV button' }],
37
- code: "const [download] = await Promise.all([\n" +
38
- " page.waitForEvent('download'),\n" +
39
- " page.getByRole('button', { name: 'Export CSV' }).click(),\n" +
40
- "]);\n" +
41
- "expect(await download.suggestedFilename()).toContain('.csv');",
26
+ code: `const [download] = await Promise.all([
27
+ page.waitForEvent('download'),
28
+ page.getByRole('button', { name: 'Export CSV' }).click(),
29
+ ]);
30
+ expect(await download.suggestedFilename()).toContain('.csv');`,
31
+ },
32
+ },
33
+ {
34
+ name: 'file-upload',
35
+ signature: ['browser_file_upload'],
36
+ note: 'Set a file on a (often hidden) <input type=file>. The file chooser opens synchronously on click, so register waitForEvent(\'filechooser\') before the click — same race as download.',
37
+ example: {
38
+ steps: [
39
+ { tool: 'browser_click', element: 'Upload avatar button' },
40
+ { tool: 'browser_file_upload', paths: ['avatar.png'] },
41
+ ],
42
+ code: `const [chooser] = await Promise.all([
43
+ page.waitForEvent('filechooser'),
44
+ page.getByRole('button', { name: 'Upload avatar' }).click(),
45
+ ]);
46
+ await chooser.setFiles('tests/fixtures/avatar.png');
47
+ await expect(page.getByText('avatar.png')).toBeVisible();`,
48
+ },
49
+ },
50
+ {
51
+ name: 'dialog',
52
+ signature: ['browser_handle_dialog'],
53
+ note: 'A click that triggers a native dialog (alert/confirm/prompt). Register the page \'dialog\' handler BEFORE the click that fires it — otherwise Playwright auto-dismisses it and the assertion is wrong.',
54
+ example: {
55
+ steps: [
56
+ { tool: 'browser_click', element: 'Delete account button' },
57
+ { tool: 'browser_handle_dialog', action: 'accept' },
58
+ ],
59
+ code: `page.once('dialog', dialog => dialog.accept());
60
+ await page.getByRole('button', { name: 'Delete account' }).click();
61
+ await expect(page.getByText('Account deleted')).toBeVisible();`,
62
+ },
63
+ },
64
+ {
65
+ name: 'oauth-popup',
66
+ signature: ['browser_click', 'browser_tabs:select'],
67
+ note: 'Sign in through a provider popup that opens a new tab. Pair the opener click with context.waitForEvent(\'page\'), then drive the returned popup page.',
68
+ example: {
69
+ steps: [
70
+ { tool: 'browser_click', element: 'Sign in with Google button' },
71
+ { tool: 'browser_tabs', action: 'select', idx: 1 },
72
+ ],
73
+ code: `const [popup] = await Promise.all([
74
+ context.waitForEvent('page'),
75
+ page.getByRole('button', { name: 'Sign in with Google' }).click(),
76
+ ]);
77
+ await popup.getByLabel('Email').fill('user@example.com');
78
+ await popup.getByRole('button', { name: 'Next' }).click();
79
+ await popup.waitForEvent('close');
80
+ await expect(page.getByText('Signed in')).toBeVisible();`,
81
+ },
82
+ },
83
+ {
84
+ name: 'network-gated-assertion',
85
+ signature: ['browser_click', 'browser_wait_for'],
86
+ note: 'A click fires an XHR/fetch and the result is asserted. Pair the click with page.waitForResponse so the test waits for the real request to settle, instead of a guessed timeout or a race.',
87
+ example: {
88
+ steps: [
89
+ { tool: 'browser_click', element: 'Place order button' },
90
+ { tool: 'browser_wait_for', text: 'Order confirmed' },
91
+ ],
92
+ code: `const [res] = await Promise.all([
93
+ page.waitForResponse(r => r.url().includes('/api/orders') && r.request().method() === 'POST'),
94
+ page.getByRole('button', { name: 'Place order' }).click(),
95
+ ]);
96
+ expect(res.ok()).toBeTruthy();
97
+ await expect(page.getByText('Order confirmed')).toBeVisible();`,
42
98
  },
43
99
  },
44
100
  ];
45
- /** Built-in seeds + any in <projectRoot>/.hover/rules/*.json. Malformed files
46
- * are skipped rather than failing the whole read. */
47
- export async function readSeeds(projectRoot) {
48
- const out = [...BUILTIN_SEEDS];
49
- try {
50
- const dir = join(projectRoot, '.hover', 'rules');
51
- for (const f of await readdir(dir)) {
52
- if (!f.endsWith('.json'))
53
- continue;
54
- try {
55
- const s = JSON.parse(await readFile(join(dir, f), 'utf-8'));
56
- if (s && s.name && Array.isArray(s.signature) && s.example?.code)
57
- out.push(s);
58
- }
59
- catch {
60
- /* skip malformed seed file */
61
- }
62
- }
63
- }
64
- catch {
65
- /* no .hover/rules/ directory */
66
- }
67
- return out;
68
- }
69
101
  /** Pick seeds whose signature's base tool appears in the spec — a cheap
70
102
  * relevance filter so the prompt only carries plausibly-applicable examples. */
71
103
  export function relevantSeeds(seeds, specTools, cap = 6) {
@@ -1,4 +1,4 @@
1
- import type { SkillStep } from '../skills/writeSkill.js';
1
+ import type { SkillStep } from '../specs/specStep.js';
2
2
  import type { SpecAssertion } from './writeSpec.js';
3
3
  /** Current sidecar schema version. Bump when the shape changes so readers
4
4
  * (Stage 2 detection, Stage 7 optimization) can migrate or skip cleanly. */
@@ -15,11 +15,39 @@ export interface SpecSidecar {
15
15
  /** Alt-click assertions captured alongside the session. */
16
16
  assertions: SpecAssertion[];
17
17
  }
18
- /** Sidecar directory under the spec output dir. Dot-prefixed on purpose:
19
- * Playwright's default `*.spec.ts` glob never reaches into `.hover/`. */
18
+ /** Project-root `.hover/` directory the single home for Hover-derived data
19
+ * (sidecars, runs, rules, conventions). */
20
+ export declare function hoverDir(devRoot: string): string;
21
+ /** Sanitize an id segment for use as a directory name (conversation / run id). */
22
+ export declare function safeSeg(s: string): string;
23
+ /** Per-run home: `<devRoot>/.hover/conversations/<conversationId>/<runId>/`.
24
+ * Everything a single agent run produces — meta.json (the ledger record),
25
+ * report.md (QA), screenshots/ — lives here, grouped under its conversation so
26
+ * deleting a conversation is one `rm -rf .hover/conversations/<conversationId>`.
27
+ * (Distinct from `.hover/runs/`, which is the Playwright spec-run-results
28
+ * ledger written by ▶ Run.) */
29
+ export declare function conversationsDir(devRoot: string): string;
30
+ export declare function conversationDir(devRoot: string, conversationId: string): string;
31
+ export declare function runDir(devRoot: string, conversationId: string, runId: string): string;
32
+ /** Sidecar directory: `<devRoot>/.hover/sidecars`. Outside `__vibe_tests__/`,
33
+ * so Playwright's default `*.spec.ts` glob trivially never reaches it. */
20
34
  export declare function sidecarDir(devRoot: string): string;
21
- /** Write the structured-session sidecar at `.hover/<slug>.json`. Caller passes
22
- * the data minus the stamped fields (`version`, `createdAt`), which this
23
- * function fills. Returns the absolute path written. */
35
+ /** Pre-relocation sidecar home (`__vibe_tests__/.hover/`). Read-only fallback;
36
+ * nothing writes here anymore. */
37
+ export declare function legacySidecarDir(devRoot: string): string;
38
+ /** Write the structured-session sidecar at `.hover/sidecars/<slug>.json`.
39
+ * Caller passes the data minus the stamped fields (`version`, `createdAt`),
40
+ * which this function fills. Returns the absolute path written. */
24
41
  export declare function writeSidecar(devRoot: string, data: Omit<SpecSidecar, 'version' | 'createdAt'>): Promise<string>;
42
+ /**
43
+ * Read one sidecar by slug, with legacy fallback + lazy copy-forward: when a
44
+ * sidecar only exists at the pre-relocation `__vibe_tests__/.hover/` path it
45
+ * is parsed from there and best-effort re-written into `.hover/sidecars/` so
46
+ * the next read hits the new home. Returns `null` when absent or malformed.
47
+ */
48
+ export declare function readSidecar(devRoot: string, slug: string): Promise<SpecSidecar | null>;
49
+ /** Parse one sidecar file, or `null` when absent / not JSON. Deliberately
50
+ * lenient on shape (an empty `{}` still counts as "a sidecar exists") —
51
+ * consumers that need `steps`/`slug` filter for themselves. */
52
+ export declare function parseSidecarFile(path: string): Promise<SpecSidecar | null>;
25
53
  //# sourceMappingURL=sidecar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../../src/specs/sidecar.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;6EAC6E;AAC7E,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB;yCACqC;IACrC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,2DAA2D;IAC3D,UAAU,EAAE,aAAa,EAAE,CAAC;CAC7B;AAED;0EAC0E;AAC1E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;yDAEyD;AACzD,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,WAAW,CAAC,GAC/C,OAAO,CAAC,MAAM,CAAC,CAWjB"}
1
+ {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../../src/specs/sidecar.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;6EAC6E;AAC7E,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB;yCACqC;IACrC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,2DAA2D;IAC3D,UAAU,EAAE,aAAa,EAAE,CAAC;CAC7B;AAED;4CAC4C;AAC5C,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,kFAAkF;AAClF,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED;;;;;gCAKgC;AAChC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AACD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAE/E;AACD,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAErF;AAED;2EAC2E;AAC3E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;mCACmC;AACnC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;oEAEoE;AACpE,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,WAAW,CAAC,GAC/C,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAoB5F;AAED;;gEAEgE;AAChE,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAOhF"}