@hover-dev/core 0.18.0 → 0.19.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/engine.d.ts CHANGED
@@ -15,6 +15,8 @@
15
15
  export { writeSpec } from './specs/writeSpec.js';
16
16
  export type { WriteSpecOptions, WriteSpecResult, Redaction } from './specs/writeSpec.js';
17
17
  export type { SkillStep } from './specs/specStep.js';
18
+ export { writeApiSpec } from './specs/writeApiSpec.js';
19
+ export type { ApiCheck, WriteApiSpecOptions, WriteApiSpecResult } from './specs/writeApiSpec.js';
18
20
  export { replayGroundedSteps, replayOnPage, applyGroundedStep, groundedLocate } from './specs/replayGrounded.js';
19
21
  export type { ReplayResult, ReplayFailure, ReplayStep, GroundedTarget } from './specs/replayGrounded.js';
20
22
  export { launchDebugChrome, closeDebugChrome, findChromeBinary } from './playwright/launchChrome.js';
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACzF,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACjH,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGzG,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrG,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGhF,OAAO,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AAG7F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACrG,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG/D,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzG,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACzF,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EAAE,QAAQ,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEjG,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACjH,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGzG,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrG,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGhF,OAAO,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AAG7F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACrG,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG/D,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzG,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/engine.js CHANGED
@@ -14,6 +14,8 @@
14
14
  */
15
15
  // ── crystallization + grounded replay ────────────────────────────────────────
16
16
  export { writeSpec } from './specs/writeSpec.js';
17
+ // API-layer crystallizer — observed/replayed requests → *.api-test.spec.ts.
18
+ export { writeApiSpec } from './specs/writeApiSpec.js';
17
19
  // Creation-verification: replay a flow's grounded steps over CDP (no playwright test).
18
20
  export { replayGroundedSteps, replayOnPage, applyGroundedStep, groundedLocate } from './specs/replayGrounded.js';
19
21
  // ── debug-Chrome lifecycle ───────────────────────────────────────────────────
@@ -0,0 +1,36 @@
1
+ /** One asserted API call — a contract, shape, or authz check. */
2
+ export interface ApiCheck {
3
+ /** Short imperative title → the `test()` name (e.g. "GET /api/cart returns the cart"). */
4
+ title: string;
5
+ /** HTTP method. */
6
+ method: string;
7
+ /** URL as captured. Same-origin URLs are relativized to a path so the spec
8
+ * rides the config's baseURL; cross-origin URLs are emitted in full. */
9
+ url: string;
10
+ /** Request body to send (objects are emitted as a JSON literal via `data`). */
11
+ requestBody?: unknown;
12
+ /** Headers to send. For an authz check the agent omits/alters the auth header. */
13
+ headers?: Record<string, string>;
14
+ /** Expected status — observed for a contract check, or expected-after-mutation
15
+ * for an authz check (verified with replay_request before crystallizing). */
16
+ expectStatus?: number;
17
+ /** Top-level response keys to assert present (a light shape contract). */
18
+ expectBodyKeys?: string[];
19
+ /** Optional human note → emitted as a leading comment (e.g. "authz: no session → 401"). */
20
+ note?: string;
21
+ }
22
+ export interface WriteApiSpecOptions {
23
+ devRoot: string;
24
+ name: string;
25
+ description?: string;
26
+ checks: ApiCheck[];
27
+ /** Run target origin — same-origin URLs are relativized against it. */
28
+ startUrl?: string;
29
+ overwrite?: boolean;
30
+ }
31
+ export interface WriteApiSpecResult {
32
+ path: string;
33
+ slug: string;
34
+ }
35
+ export declare function writeApiSpec(opts: WriteApiSpecOptions): Promise<WriteApiSpecResult>;
36
+ //# sourceMappingURL=writeApiSpec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeApiSpec.d.ts","sourceRoot":"","sources":["../../src/specs/writeApiSpec.ts"],"names":[],"mappings":"AAgBA,iEAAiE;AACjE,MAAM,WAAW,QAAQ;IACvB,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;IACf;6EACyE;IACzE,GAAG,EAAE,MAAM,CAAC;IACZ,+EAA+E;IAC/E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;kFAC8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AA0DD,wBAAsB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAyBzF"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Deterministic API-spec crystallizer — the API-layer sibling of writeSpec.
3
+ *
4
+ * Writes a `<devRoot>/__vibe_tests__/<slug>.api-test.spec.ts` from a list of
5
+ * `ApiCheck`s the agent assembled (observed via CDP capture, optionally with a
6
+ * mutation it verified with replay_request). Pure `@playwright/test`
7
+ * `APIRequestContext` — `request.get/post(...)` + status / shape / authz
8
+ * assertions. No LLM in the codegen path: a 1:1 string-template translation, so
9
+ * record == replay holds for the API layer too (the call that was captured is
10
+ * the call that's asserted).
11
+ */
12
+ import { mkdir, writeFile } from 'node:fs/promises';
13
+ import { existsSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { slugify, firstSentence } from './text.js';
16
+ const KNOWN_METHODS = new Set(['get', 'post', 'put', 'delete', 'patch', 'head']);
17
+ function q(s) {
18
+ return `'${String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
19
+ }
20
+ /** Same-origin → path (rides baseURL); cross-origin → full URL. */
21
+ function toRequestUrl(url, startUrl) {
22
+ try {
23
+ const u = new URL(url);
24
+ if (startUrl) {
25
+ const base = new URL(startUrl);
26
+ if (u.origin === base.origin)
27
+ return (u.pathname + u.search) || '/';
28
+ }
29
+ return url;
30
+ }
31
+ catch {
32
+ return url; // already a path, or unparseable — emit as-is
33
+ }
34
+ }
35
+ function renderCheck(check, startUrl) {
36
+ const m = check.method.toLowerCase();
37
+ const useFetch = !KNOWN_METHODS.has(m);
38
+ const fn = useFetch ? 'fetch' : m;
39
+ const url = toRequestUrl(check.url, startUrl);
40
+ // Build the options object (data / headers / method-for-fetch).
41
+ const opts = [];
42
+ if (useFetch)
43
+ opts.push(`method: ${q(check.method.toUpperCase())}`);
44
+ if (check.requestBody !== undefined && m !== 'get' && m !== 'head') {
45
+ opts.push(`data: ${JSON.stringify(check.requestBody)}`);
46
+ }
47
+ if (check.headers && Object.keys(check.headers).length) {
48
+ opts.push(`headers: ${JSON.stringify(check.headers)}`);
49
+ }
50
+ const callArgs = opts.length ? `${q(url)}, { ${opts.join(', ')} }` : q(url);
51
+ const body = [];
52
+ if (check.note)
53
+ body.push(` // ${check.note}`);
54
+ body.push(` const res = await request.${fn}(${callArgs});`);
55
+ if (typeof check.expectStatus === 'number') {
56
+ body.push(` expect(res.status()).toBe(${check.expectStatus});`);
57
+ }
58
+ else {
59
+ body.push(` expect(res.ok()).toBeTruthy();`);
60
+ }
61
+ if (check.expectBodyKeys && check.expectBodyKeys.length) {
62
+ body.push(` const body = await res.json();`);
63
+ for (const k of check.expectBodyKeys) {
64
+ body.push(` expect(body).toHaveProperty(${q(k)});`);
65
+ }
66
+ }
67
+ const title = check.title.replace(/'/g, "\\'");
68
+ return [` test('${title}', async ({ request }) => {`, ...body, ` });`].join('\n');
69
+ }
70
+ export async function writeApiSpec(opts) {
71
+ const slug = slugify(opts.name);
72
+ if (!slug)
73
+ throw new Error('api spec name must contain at least one alphanumeric character');
74
+ if (!opts.checks.length)
75
+ throw new Error('api spec needs at least one check');
76
+ const dir = join(opts.devRoot, '__vibe_tests__');
77
+ const path = join(dir, `${slug}.api-test.spec.ts`);
78
+ if (existsSync(path) && !opts.overwrite) {
79
+ throw new Error(`${path} already exists (pass overwrite to replace)`);
80
+ }
81
+ const header = opts.description ? `// ${firstSentence(opts.description)}` : null;
82
+ const lines = [
83
+ ...(header ? [header] : []),
84
+ `import { test, expect } from '@playwright/test';`,
85
+ ``,
86
+ `test.describe(${q(opts.name)}, () => {`,
87
+ opts.checks.map(c => renderCheck(c, opts.startUrl)).join('\n\n'),
88
+ `});`,
89
+ ``,
90
+ ];
91
+ await mkdir(dir, { recursive: true });
92
+ await writeFile(path, lines.join('\n'), 'utf-8');
93
+ return { path, slug };
94
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hover-dev/core",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Hover's local Node service: agent invocation, Playwright CDP preflight, WebSocket bridge.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hyperyond",