@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
@@ -17,9 +17,9 @@
17
17
  * same signature even though their typed values differ — value vs. structure
18
18
  * separation is mechanical (D4).
19
19
  */
20
- import { readdir, readFile } from 'node:fs/promises';
20
+ import { readdir } from 'node:fs/promises';
21
21
  import { join } from 'node:path';
22
- import { sidecarDir } from './sidecar.js';
22
+ import { sidecarDir, legacySidecarDir, parseSidecarFile } from './sidecar.js';
23
23
  import { humanStep } from './humanSteps.js';
24
24
  /**
25
25
  * Reduce one captured step to a signature string: the tool plus its structural
@@ -60,31 +60,30 @@ export function stepSignature(tool, rawInput) {
60
60
  return null;
61
61
  }
62
62
  }
63
- /** Read and parse every sidecar under `.hover/`. Malformed files are skipped
64
- * (better to detect across the valid ones than fail because one is broken). */
63
+ /** Read and parse every sidecar under `.hover/sidecars/`, unioned with any
64
+ * still in the legacy `__vibe_tests__/.hover/` home (current home wins on a
65
+ * slug collision). Malformed files are skipped (better to detect across the
66
+ * valid ones than fail because one is broken). */
65
67
  async function readSidecars(devRoot) {
66
- const dir = sidecarDir(devRoot);
67
- let entries;
68
- try {
69
- entries = await readdir(dir);
70
- }
71
- catch {
72
- return [];
73
- }
74
- const out = [];
75
- for (const entry of entries) {
76
- if (!entry.endsWith('.json'))
77
- continue;
68
+ const bySlug = new Map();
69
+ for (const dir of [legacySidecarDir(devRoot), sidecarDir(devRoot)]) {
70
+ let entries;
78
71
  try {
79
- const sc = JSON.parse(await readFile(join(dir, entry), 'utf-8'));
80
- if (Array.isArray(sc.steps) && typeof sc.slug === 'string')
81
- out.push(sc);
72
+ entries = await readdir(dir);
82
73
  }
83
74
  catch {
84
- // skip malformed sidecar
75
+ continue;
76
+ }
77
+ for (const entry of entries) {
78
+ if (!entry.endsWith('.json'))
79
+ continue;
80
+ const sc = await parseSidecarFile(join(dir, entry));
81
+ if (sc && Array.isArray(sc.steps) && typeof sc.slug === 'string') {
82
+ bySlug.set(sc.slug, sc); // later dir (current home) overwrites
83
+ }
85
84
  }
86
85
  }
87
- return out;
86
+ return [...bySlug.values()];
88
87
  }
89
88
  /** Project a sidecar's steps to (signature, prose) lists, dropping
90
89
  * non-flow steps. */
@@ -11,7 +11,7 @@
11
11
  * emitters with a `this.page` page variable, so a Page Object's selectors match
12
12
  * the crystallized specs exactly.
13
13
  */
14
- import type { SkillStep } from '../skills/writeSkill.js';
14
+ import type { SkillStep } from '../specs/specStep.js';
15
15
  export interface PageObjectResult {
16
16
  /** PascalCase class name, e.g. `LoginPage`. */
17
17
  className: string;
@@ -1 +1 @@
1
- {"version":3,"file":"generatePageObject.d.ts","sourceRoot":"","sources":["../../src/specs/generatePageObject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAUzD,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,EAAE,EAClB,QAAQ,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACzD,gBAAgB,CAoElB"}
1
+ {"version":3,"file":"generatePageObject.d.ts","sourceRoot":"","sources":["../../src/specs/generatePageObject.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAUtD,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,EAAE,EAClB,QAAQ,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACzD,gBAAgB,CAoElB"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Self-heal Stage 2 — the heal prompt.
3
+ *
4
+ * When a saved spec fails on replay (the app changed → a locator no longer
5
+ * matches), healing re-performs the flow against the LIVE app and fixes the
6
+ * broken step(s). This builds the instruction that drives that run: the agent
7
+ * gets the spec's intended flow (its source) + exactly what broke (the parsed
8
+ * failures), and re-locates via the grounded control tools + source reader.
9
+ *
10
+ * The heal then crystallizes through the normal candidate flow (record_candidate
11
+ * / fallback) — deterministic re-render, human-reviewed; the agent re-locates,
12
+ * it does not author the spec. Pure: prompt-building only.
13
+ */
14
+ import type { RunFailure } from './runFailures.js';
15
+ /** A short, chat-friendly label for the heal run (the user bubble), vs the full
16
+ * prompt the agent receives. */
17
+ export declare function healLabel(slug: string): string;
18
+ export declare function buildHealPrompt(slug: string, specSource: string, failures: RunFailure[]): string;
19
+ //# sourceMappingURL=healPrompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healPrompt.d.ts","sourceRoot":"","sources":["../../src/specs/healPrompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;iCACiC;AACjC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM,CA2ChG"}
@@ -0,0 +1,48 @@
1
+ /** A short, chat-friendly label for the heal run (the user bubble), vs the full
2
+ * prompt the agent receives. */
3
+ export function healLabel(slug) {
4
+ return `🏥 Heal "${slug}" — re-running the flow to fix what changed`;
5
+ }
6
+ export function buildHealPrompt(slug, specSource, failures) {
7
+ const failureLines = failures.length > 0
8
+ ? failures.map(f => {
9
+ const what = f.failingLocator
10
+ ? `${f.failingAction ?? 'a step'} on \`${f.failingLocator}\` no longer matches`
11
+ : (f.error || 'a step failed');
12
+ return ` - ${what}`;
13
+ }).join('\n')
14
+ : ' - (no structured failure captured — re-run the whole flow and fix whatever no longer works)';
15
+ return [
16
+ `You are REPAIRING a saved Playwright test that no longer passes because the`,
17
+ `app under test changed. Re-perform its flow against the LIVE app and fix only`,
18
+ `the step(s) that broke — do not invent new behavior or add unrelated steps.`,
19
+ ``,
20
+ `Test: "${slug}"`,
21
+ ``,
22
+ `What it does (the saved spec — this is the intended flow to reproduce):`,
23
+ `\`\`\`ts`,
24
+ specSource.trim(),
25
+ `\`\`\``,
26
+ ``,
27
+ `What broke on replay:`,
28
+ failureLines,
29
+ ``,
30
+ `How to repair it:`,
31
+ ` - Open the app and walk the SAME flow, interacting through the grounded`,
32
+ ` control tools (click_control / fill_control / select_control / …) so the`,
33
+ ` repaired selectors stay replayable.`,
34
+ ` - Where a step's old selector no longer matches, find the element that step`,
35
+ ` INTENDED and operate that instead. Read the component source if you are`,
36
+ ` unsure why it moved or what replaced it.`,
37
+ ` - JUDGE broke-vs-changed: if a failure is because the app INTENTIONALLY`,
38
+ ` changed (the feature now works differently — not a regression), adapt the`,
39
+ ` flow to the new correct behavior and say so in your summary. If it looks`,
40
+ ` like a real regression (the app is wrong), report it as a finding and heal`,
41
+ ` to what the test originally intended.`,
42
+ ` - Keep dynamic content dynamic: if a step grounds on data that varies`,
43
+ ` run-to-run, flag it dynamic — don't freeze this run's value.`,
44
+ ``,
45
+ `When the flow works end to end, call record_candidate with the test's name so`,
46
+ `the repaired version can be saved. Do not write any file yourself.`,
47
+ ].join('\n');
48
+ }
@@ -1,18 +1,14 @@
1
1
  /**
2
2
  * Translate the captured `browser_*` tool calls into plain English.
3
3
  *
4
- * Used by:
5
- * - writeSpec.ts to enrich the generated `.spec.ts` JSDoc with a
6
- * numbered "Steps:" block that QA / PMs can read without grokking
7
- * `getByRole(...)`.
8
- * - writeCaseCsv.ts — to populate the Step column of an
9
- * Xray-compatible test case CSV, so the same prose travels into
10
- * Jira / Xray / Zephyr.
4
+ * Used by writeSpec.ts to enrich the generated `.spec.ts` JSDoc with a
5
+ * numbered "Steps:" block that QA / PMs can read without grokking
6
+ * `getByRole(...)`.
11
7
  *
12
8
  * Mirrors the tool dispatch table in writeSpec.ts:translateStep — when
13
9
  * a new replayable browser action is added there, add it here too.
14
10
  */
15
- import type { SkillStep } from '../skills/writeSkill.js';
11
+ import type { SkillStep } from '../specs/specStep.js';
16
12
  /** A single human-readable line for one tool call, or null to skip. */
17
13
  export declare function humanStep(tool: string, rawInput: unknown): string | null;
18
14
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"humanSteps.d.ts","sourceRoot":"","sources":["../../src/specs/humanSteps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,uEAAuE;AACvE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAwDxE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAmBvD"}
1
+ {"version":3,"file":"humanSteps.d.ts","sourceRoot":"","sources":["../../src/specs/humanSteps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD,uEAAuE;AACvE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAwDxE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAmBvD"}
@@ -91,7 +91,12 @@ function describe(raw) {
91
91
  const s = String(raw ?? '').trim();
92
92
  return s.length > 0 ? s : 'the target element';
93
93
  }
94
- /** Wrap in double-quotes for prose; escape internal quotes. */
94
+ /** Wrap in double-quotes for prose; escape internal quotes. A redacted
95
+ * credential (stored as a `process.env.X …` expression) shows as the masked
96
+ * `$X` instead — the prose, like the code, never reveals the secret. */
95
97
  function quote(s) {
98
+ const env = /^process\.env\.([A-Za-z0-9_]+)/.exec(s);
99
+ if (env)
100
+ return `$${env[1]}`;
96
101
  return `"${s.replace(/"/g, '\\"')}"`;
97
102
  }
@@ -5,6 +5,20 @@ export declare class OptimizeError extends Error {
5
5
  }
6
6
  /** Runs the codegen LLM on a prompt and returns its raw text output. */
7
7
  export type RunCodegen = (prompt: string) => Promise<string>;
8
+ /** Project context fed to the refinement pass so the candidate FITS the existing
9
+ * suite: the team's conventions + the reusable Page Objects to prefer over raw
10
+ * locators. Relevant files only (POMs + conventions), NOT the whole suite — the
11
+ * refinement is a cheap pass, keep the context bounded. */
12
+ export interface SuiteContext {
13
+ conventions?: string;
14
+ pages: {
15
+ name: string;
16
+ source: string;
17
+ }[];
18
+ }
19
+ /** Best-effort gather of the suite context (conventions.md + __vibe_tests__/pages
20
+ * Page Objects). Missing files → empty; never throws. */
21
+ export declare function gatherSuiteContext(devRoot: string): Promise<SuiteContext>;
8
22
  export interface OptimizeResult {
9
23
  /** Absolute path of the written candidate (never the original spec). */
10
24
  candidatePath: string;
@@ -20,7 +34,7 @@ export declare function optimizeSpec(devRoot: string, slug: string, runCodegen:
20
34
  * same rules the deterministic path enforces (semantic selectors, no XPath, no
21
35
  * waitForTimeout, keep the test.step shape).
22
36
  */
23
- export declare function buildOptimizePrompt(draft: string, sidecar: SpecSidecar | null, seeds?: SeedRule[]): string;
37
+ export declare function buildOptimizePrompt(draft: string, sidecar: SpecSidecar | null, seeds?: SeedRule[], suite?: SuiteContext): string;
24
38
  /** Strip a ```ts fence if the model wrapped its output in one. */
25
39
  export declare function extractCode(raw: string): string;
26
40
  /**
@@ -32,11 +46,4 @@ export declare function validateSpecCode(code: string): {
32
46
  ok: boolean;
33
47
  errors: string[];
34
48
  };
35
- /** Promote an optimization candidate to the real spec (overwriting it) and
36
- * remove the candidate. Returns the written spec path. The human's "Use
37
- * optimized" / `mv` action. */
38
- export declare function promoteOptimized(devRoot: string, slug: string): Promise<string>;
39
- /** Discard an optimization candidate (delete the .draft, leave the spec). The
40
- * human's "Keep original". */
41
- export declare function discardOptimized(devRoot: string, slug: string): Promise<void>;
42
49
  //# sourceMappingURL=optimizeSpec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"optimizeSpec.d.ts","sourceRoot":"","sources":["../../src/specs/optimizeSpec.ts"],"names":[],"mappings":"AAeA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrE,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AAED,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb;2EACuE;IACvE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,cAAc,CAAC,CA2CzB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GAAG,IAAI,EAC3B,KAAK,GAAE,QAAQ,EAAO,GACrB,MAAM,CAmDR;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAWhF;AAyBD;;gCAEgC;AAChC,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYrF;AAED;+BAC+B;AAC/B,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF"}
1
+ {"version":3,"file":"optimizeSpec.d.ts","sourceRoot":"","sources":["../../src/specs/optimizeSpec.ts"],"names":[],"mappings":"AAeA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAgC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGzE,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AAED,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE7D;;;4DAG4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAOD;0DAC0D;AAC1D,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAgB/E;AAED,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb;2EACuE;IACvE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,cAAc,CAAC,CAwCzB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GAAG,IAAI,EAC3B,KAAK,GAAE,QAAQ,EAAO,EACtB,KAAK,GAAE,YAA4B,GAClC,MAAM,CAkFR;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAWhF"}
@@ -4,17 +4,17 @@
4
4
  * Reads a deterministic draft spec + its sidecar, asks an LLM (the codegen
5
5
  * mode — no browser, no MCP) to improve it (chiefly: add assertions for the
6
6
  * feedback the session observed), validates the result, and writes it as a
7
- * CANDIDATE at `.hover/optimized/<slug>.spec.ts.draft` — never overwriting the
7
+ * CANDIDATE at `.hover/cache/optimized/<slug>.spec.ts.draft` — never overwriting the
8
8
  * original (D10). A human promotes or discards it via diff.
9
9
  *
10
10
  * The LLM call is injected (`runCodegen`) so callers wire their own agent and
11
11
  * tests run deterministically without spawning anything.
12
12
  */
13
- import { readFile, mkdir, writeFile, rm } from 'node:fs/promises';
13
+ import { readFile, mkdir, writeFile, readdir } from 'node:fs/promises';
14
14
  import { join } from 'node:path';
15
15
  import { Project } from 'ts-morph';
16
- import { sidecarDir } from './sidecar.js';
17
- import { readSeeds, relevantSeeds } from './seeds.js';
16
+ import { readSidecar } from './sidecar.js';
17
+ import { BUILTIN_SEEDS, relevantSeeds } from './seeds.js';
18
18
  import { softBatch } from './softBatch.js';
19
19
  export class OptimizeError extends Error {
20
20
  constructor(message) {
@@ -22,6 +22,32 @@ export class OptimizeError extends Error {
22
22
  this.name = 'OptimizeError';
23
23
  }
24
24
  }
25
+ /** Total Page-Object source budget injected into the prompt (chars). Keeps the
26
+ * refinement context bounded on a large suite; extra POMs are dropped (logged in
27
+ * the prompt) rather than blowing the window. */
28
+ const POM_CONTEXT_BUDGET = 16_000;
29
+ /** Best-effort gather of the suite context (conventions.md + __vibe_tests__/pages
30
+ * Page Objects). Missing files → empty; never throws. */
31
+ export async function gatherSuiteContext(devRoot) {
32
+ const conventions = (await readFile(join(devRoot, '.hover', 'conventions.md'), 'utf-8').catch(() => ''))
33
+ .trim() || undefined;
34
+ const pages = [];
35
+ let used = 0;
36
+ try {
37
+ const dir = join(devRoot, '__vibe_tests__', 'pages');
38
+ for (const f of (await readdir(dir)).sort()) {
39
+ if (!f.endsWith('.ts'))
40
+ continue;
41
+ const source = (await readFile(join(dir, f), 'utf-8').catch(() => '')).trim();
42
+ if (!source || used + source.length > POM_CONTEXT_BUDGET)
43
+ continue;
44
+ used += source.length;
45
+ pages.push({ name: f, source });
46
+ }
47
+ }
48
+ catch { /* no pages dir — plain spec, no POMs to reuse */ }
49
+ return { conventions, pages };
50
+ }
25
51
  export async function optimizeSpec(devRoot, slug, runCodegen) {
26
52
  const specPath = join(devRoot, '__vibe_tests__', `${slug}.spec.ts`);
27
53
  let draft;
@@ -31,18 +57,14 @@ export async function optimizeSpec(devRoot, slug, runCodegen) {
31
57
  catch {
32
58
  throw new OptimizeError(`spec not found: ${slug} (looked at ${specPath})`);
33
59
  }
34
- let sidecar = null;
35
- try {
36
- sidecar = JSON.parse(await readFile(join(sidecarDir(devRoot), `${slug}.json`), 'utf-8'));
37
- }
38
- catch {
39
- /* no sidecar — optimize from the draft alone */
40
- }
60
+ // Legacy-aware read; null → no sidecar, optimize from the draft alone.
61
+ const sidecar = await readSidecar(devRoot, slug);
41
62
  const specTools = new Set((sidecar?.steps ?? [])
42
63
  .filter(s => s.kind === 'step' && s.tool)
43
64
  .map(s => s.tool));
44
- const seeds = relevantSeeds(await readSeeds(devRoot), specTools);
45
- const raw = await runCodegen(buildOptimizePrompt(draft, sidecar, seeds));
65
+ const seeds = relevantSeeds(BUILTIN_SEEDS, specTools);
66
+ const suite = await gatherSuiteContext(devRoot);
67
+ const raw = await runCodegen(buildOptimizePrompt(draft, sidecar, seeds, suite));
46
68
  const llmCode = extractCode(raw);
47
69
  const check = validateSpecCode(llmCode);
48
70
  if (!check.ok) {
@@ -52,7 +74,9 @@ export async function optimizeSpec(devRoot, slug, runCodegen) {
52
74
  // applies the safe mechanical rewrite (trailing run of independent assertions
53
75
  // → expect.soft) surgically on its output. See softBatch.ts for the guard.
54
76
  const code = softBatch(llmCode).code;
55
- const dir = join(devRoot, '__vibe_tests__', '.hover', 'optimized');
77
+ // Candidates are disposable derived artifacts → `.hover/cache/` (always
78
+ // gitignored). Losing one costs a re-run of the optimization, nothing more.
79
+ const dir = join(devRoot, '.hover', 'cache', 'optimized');
56
80
  await mkdir(dir, { recursive: true });
57
81
  // `.spec.ts.draft`, never `*.spec.ts` — Playwright's glob must not collect a
58
82
  // candidate before a human reviews it.
@@ -65,7 +89,7 @@ export async function optimizeSpec(devRoot, slug, runCodegen) {
65
89
  * same rules the deterministic path enforces (semantic selectors, no XPath, no
66
90
  * waitForTimeout, keep the test.step shape).
67
91
  */
68
- export function buildOptimizePrompt(draft, sidecar, seeds = []) {
92
+ export function buildOptimizePrompt(draft, sidecar, seeds = [], suite = { pages: [] }) {
69
93
  const done = sidecar?.steps.find(s => s.kind === 'done');
70
94
  const stepsJson = sidecar
71
95
  ? JSON.stringify(sidecar.steps.filter(s => s.kind === 'step'), null, 2)
@@ -78,8 +102,29 @@ export function buildOptimizePrompt(draft, sidecar, seeds = []) {
78
102
  `Improve it WITHOUT changing what it tests:`,
79
103
  ` - Add assertions for the success/error feedback the session OBSERVED —`,
80
104
  ` e.g. await expect(page.getByText('Invalid email')).toBeVisible(), a`,
81
- ` success toast, a counter value. Use the captured steps + the outcome`,
105
+ ` success toast, a confirmation. Use the captured steps + the outcome`,
82
106
  ` summary below to know what to assert.`,
107
+ ` - ASSERT THE INVARIANT, NOT THIS RUN'S VALUE. When the asserted content`,
108
+ ` varies run-to-run (a generated id, an order number, a date, a counter, a`,
109
+ ` drawn word — and ANY step whose captured input has "dynamic": true),`,
110
+ ` assert the contract with a pattern, never the literal: expect(loc).not`,
111
+ ` .toHaveText('') or .toContainText(/…/), or .toHaveCount(n). Reserve an`,
112
+ ` exact-literal assertion for genuinely fixed text (a heading, a fixed`,
113
+ ` confirmation). Any captured assert_visible step already encodes this —`,
114
+ ` preserve its matcher/intent; don't tighten a dynamic one back to a literal.`,
115
+ ` - DE-LITERALIZE VOLATILE VALUES — even ones NOT pre-flagged. Scan every`,
116
+ ` selector and assertion in the spec for values that are this run's DATA, not`,
117
+ ` stable UI: a word/title/name/id/order number/date/price/count drawn from`,
118
+ ` app content. Judge from what this app IS (the conventions + Page Objects`,
119
+ ` below tell you) and the captured values. For each volatile one:`,
120
+ ` getByRole(role, { name: "<value>" }) -> a stable anchor (getByTestId, or`,
121
+ ` getByRole(role).first(), or a Page Object method); toHaveText("<value>") ->`,
122
+ ` .not.toHaveText('') or .toContainText(/.../). When unsure if a value is`,
123
+ ` volatile, PREFER the invariant — over-asserting a changing value is the`,
124
+ ` failure we are fixing. But NEVER newly hard-code a value that wasn't there.`,
125
+ ` - REUSE the project's Page Objects + conventions (below). If a step sequence`,
126
+ ` matches a Page Object method, CALL it instead of re-emitting raw locators,`,
127
+ ` and follow the naming / structure the existing suite uses.`,
83
128
  ` - Keep semantic selectors: getByRole / getByLabel / getByText. NEVER emit`,
84
129
  ` XPath or CSS-id selectors. NEVER use waitForTimeout (Playwright`,
85
130
  ` auto-waits).`,
@@ -104,6 +149,16 @@ export function buildOptimizePrompt(draft, sidecar, seeds = []) {
104
149
  ``,
105
150
  `=== CAPTURED STEPS ===`,
106
151
  stepsJson,
152
+ ...(suite.conventions
153
+ ? [``, `=== PROJECT CONVENTIONS (follow these) ===`, suite.conventions]
154
+ : []),
155
+ ...(suite.pages.length > 0
156
+ ? [
157
+ ``,
158
+ `=== REUSABLE PAGE OBJECTS (prefer calling these over raw locators) ===`,
159
+ ...suite.pages.map(p => `// ${p.name}\n${p.source}`),
160
+ ]
161
+ : []),
107
162
  ...(seeds.length > 0
108
163
  ? [
109
164
  ``,
@@ -161,28 +216,3 @@ function hasSyntaxError(code) {
161
216
  const sf = project.createSourceFile('__candidate.ts', code, { overwrite: true });
162
217
  return project.getProgram().getSyntacticDiagnostics(sf).length > 0;
163
218
  }
164
- function candidatePathFor(devRoot, slug) {
165
- return join(devRoot, '__vibe_tests__', '.hover', 'optimized', `${slug}.spec.ts.draft`);
166
- }
167
- /** Promote an optimization candidate to the real spec (overwriting it) and
168
- * remove the candidate. Returns the written spec path. The human's "Use
169
- * optimized" / `mv` action. */
170
- export async function promoteOptimized(devRoot, slug) {
171
- const candidate = candidatePathFor(devRoot, slug);
172
- const specPath = join(devRoot, '__vibe_tests__', `${slug}.spec.ts`);
173
- let code;
174
- try {
175
- code = await readFile(candidate, 'utf-8');
176
- }
177
- catch {
178
- throw new OptimizeError(`no optimization candidate to promote for "${slug}"`);
179
- }
180
- await writeFile(specPath, code, 'utf-8');
181
- await rm(candidate, { force: true });
182
- return specPath;
183
- }
184
- /** Discard an optimization candidate (delete the .draft, leave the spec). The
185
- * human's "Keep original". */
186
- export async function discardOptimized(devRoot, slug) {
187
- await rm(candidatePathFor(devRoot, slug), { force: true });
188
- }
@@ -3,8 +3,6 @@ export interface OptimizeAgentOptions {
3
3
  agentId: string;
4
4
  model?: string;
5
5
  maxBudgetUsd?: number;
6
- /** Optional model API key, injected into the spawned CLI's env. */
7
- apiKey?: string;
8
6
  signal?: AbortSignal;
9
7
  }
10
8
  export declare function optimizeSpecWithAgent(devRoot: string, slug: string, opts: OptimizeAgentOptions): Promise<OptimizeResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"optimizeSpecWithAgent.d.ts","sourceRoot":"","sources":["../../src/specs/optimizeSpecWithAgent.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgB,KAAK,cAAc,EAAmB,MAAM,mBAAmB,CAAC;AAEvF,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,cAAc,CAAC,CA4BzB"}
1
+ {"version":3,"file":"optimizeSpecWithAgent.d.ts","sourceRoot":"","sources":["../../src/specs/optimizeSpecWithAgent.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgB,KAAK,cAAc,EAAmB,MAAM,mBAAmB,CAAC;AAEvF,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,cAAc,CAAC,CA2BzB"}
@@ -24,7 +24,6 @@ export async function optimizeSpecWithAgent(devRoot, slug, opts) {
24
24
  prompt,
25
25
  model: opts.model,
26
26
  maxBudgetUsd: opts.maxBudgetUsd,
27
- apiKey: opts.apiKey,
28
27
  signal: opts.signal,
29
28
  disallowedTools,
30
29
  })) {
@@ -15,6 +15,8 @@ export interface PageObjectManifest {
15
15
  pages: PageObjectEntry[];
16
16
  }
17
17
  export declare function writePageObjectManifest(devRoot: string, pages: PageObjectEntry[]): Promise<string>;
18
- /** Read the manifest, or null when none exists (no extraction has run). */
18
+ /** Read the manifest, or null when none exists (no extraction has run).
19
+ * Falls back to the legacy `__vibe_tests__/.hover/` home for manifests
20
+ * written before the `.hover/sidecars/` relocation. */
19
21
  export declare function readPageObjectManifest(devRoot: string): Promise<PageObjectManifest | null>;
20
22
  //# sourceMappingURL=pageObjectManifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pageObjectManifest.d.ts","sourceRoot":"","sources":["../../src/specs/pageObjectManifest.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAMD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,EAAE,GACvB,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED,2EAA2E;AAC3E,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAQhG"}
1
+ {"version":3,"file":"pageObjectManifest.d.ts","sourceRoot":"","sources":["../../src/specs/pageObjectManifest.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAMD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,EAAE,GACvB,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;wDAEwD;AACxD,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAUhG"}
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
15
15
  import { join } from 'node:path';
16
- import { sidecarDir } from './sidecar.js';
16
+ import { sidecarDir, legacySidecarDir } from './sidecar.js';
17
17
  export const MANIFEST_VERSION = 1;
18
18
  function manifestPath(devRoot) {
19
19
  return join(sidecarDir(devRoot), 'page-objects.json');
@@ -26,15 +26,19 @@ export async function writePageObjectManifest(devRoot, pages) {
26
26
  await writeFile(path, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
27
27
  return path;
28
28
  }
29
- /** Read the manifest, or null when none exists (no extraction has run). */
29
+ /** Read the manifest, or null when none exists (no extraction has run).
30
+ * Falls back to the legacy `__vibe_tests__/.hover/` home for manifests
31
+ * written before the `.hover/sidecars/` relocation. */
30
32
  export async function readPageObjectManifest(devRoot) {
31
- try {
32
- const m = JSON.parse(await readFile(manifestPath(devRoot), 'utf-8'));
33
- if (Array.isArray(m.pages))
34
- return m;
35
- }
36
- catch {
37
- /* no manifest / malformed — treat as none */
33
+ for (const path of [manifestPath(devRoot), join(legacySidecarDir(devRoot), 'page-objects.json')]) {
34
+ try {
35
+ const m = JSON.parse(await readFile(path, 'utf-8'));
36
+ if (Array.isArray(m.pages))
37
+ return m;
38
+ }
39
+ catch {
40
+ /* no manifest / malformed — try next */
41
+ }
38
42
  }
39
43
  return null;
40
44
  }
@@ -0,0 +1,45 @@
1
+ import { type Locator, type Page } from 'playwright-core';
2
+ export interface GroundedTarget {
3
+ role?: string;
4
+ name?: string;
5
+ text?: string;
6
+ testId?: string;
7
+ within?: {
8
+ role?: string;
9
+ name?: string;
10
+ };
11
+ }
12
+ export interface ReplayStep {
13
+ kind?: string;
14
+ tool?: string;
15
+ input?: unknown;
16
+ }
17
+ export interface ReplayFailure {
18
+ index: number;
19
+ tool: string;
20
+ target: string;
21
+ error: string;
22
+ }
23
+ export interface ReplayResult {
24
+ ok: boolean;
25
+ /** Grounded actions that ran successfully. */
26
+ ran: number;
27
+ /** Total grounded actions in the flow. */
28
+ total: number;
29
+ failures: ReplayFailure[];
30
+ }
31
+ export declare function groundedLocate(page: Page, g: GroundedTarget): Locator | null;
32
+ /** Replay ONE grounded step on a page. Returns 'skipped' for non-actuation
33
+ * steps; throws on a failed locate / action (the caller records it). */
34
+ export declare function applyGroundedStep(page: Page, step: ReplayStep): Promise<'ok' | 'skipped'>;
35
+ /** Replay a flow's grounded steps on a given page (injected, so it's testable
36
+ * without a real browser). Navigates to `devUrl` first for a consistent start,
37
+ * then runs each grounded action, stopping at the first failure. */
38
+ export declare function replayOnPage(page: Page, devUrl: string, steps: ReplayStep[]): Promise<ReplayResult>;
39
+ /** Connect to the debug Chrome over CDP and replay a flow's grounded steps. */
40
+ export declare function replayGroundedSteps(opts: {
41
+ cdpUrl: string;
42
+ devUrl: string;
43
+ steps: ReplayStep[];
44
+ }): Promise<ReplayResult>;
45
+ //# sourceMappingURL=replayGrounded.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replayGrounded.d.ts","sourceRoot":"","sources":["../../src/specs/replayGrounded.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAkBlF,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAGD,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AACD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AA+BD,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,cAAc,GAAG,OAAO,GAAG,IAAI,CAS5E;AAED;yEACyE;AACzE,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CA4B/F;AAED;;qEAEqE;AACrE,wBAAsB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAqBzG;AAuBD,+EAA+E;AAC/E,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAoB9H"}