@hover-dev/core 0.24.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.
@@ -0,0 +1,56 @@
1
+ import type { RunFailure } from './specs/runFailures.js';
2
+ export declare const DEFAULT_CLOUD_URL = "https://cloud.gethover.dev";
3
+ export interface CloudCredentials {
4
+ /** Personal access token minted at cloud Settings → Access tokens. */
5
+ token: string;
6
+ /** Cloud base URL; DEFAULT_CLOUD_URL unless self-configured. */
7
+ url: string;
8
+ }
9
+ /** One drifted spec queued by the cloud, as /api/v1/heal-requests returns it.
10
+ * `hint` is RunFailure-shaped — feed it straight to the local heal flow. */
11
+ export interface CloudHealRequest {
12
+ id: string;
13
+ status: 'open' | 'routed' | 'healed' | 'dismissed';
14
+ specFile: string;
15
+ hint: RunFailure;
16
+ createdAt: string;
17
+ project: {
18
+ id: string;
19
+ name: string;
20
+ repo: string;
21
+ };
22
+ run: {
23
+ id: string;
24
+ branch: string | null;
25
+ commitSha: string | null;
26
+ ciUrl: string | null;
27
+ createdAt: string;
28
+ };
29
+ }
30
+ export declare function credentialsPath(home?: string): string;
31
+ /** Resolve credentials: env first, then ~/.hover/credentials.json. Null when
32
+ * neither is present (the surfaces show their "connect" hint on null). */
33
+ export declare function readCloudCredentials(env?: NodeJS.ProcessEnv, home?: string): CloudCredentials | null;
34
+ /** Persist credentials for every local surface (0600 — token, not a secret to
35
+ * sync; same trust model as ~/.aws/credentials). Returns the path written. */
36
+ export declare function writeCloudCredentials(creds: {
37
+ token: string;
38
+ url?: string;
39
+ }, home?: string): string;
40
+ /** Raised on a non-2xx cloud response; `status` 401 → token revoked/expired. */
41
+ export declare class CloudApiError extends Error {
42
+ status: number;
43
+ constructor(status: number, message: string);
44
+ }
45
+ /** The open heal queue (optionally one repo's slice, `owner/name`). */
46
+ export declare function fetchHealRequests(creds: CloudCredentials, opts?: {
47
+ status?: 'open' | 'routed' | 'healed' | 'dismissed' | 'all';
48
+ repo?: string;
49
+ }, fetchImpl?: typeof fetch): Promise<CloudHealRequest[]>;
50
+ /** Mark a request routed (a local heal picked it up) or dismissed / reopened.
51
+ * `healed` is not writable — only CI seeing the spec pass closes a request. */
52
+ export declare function updateHealRequest(creds: CloudCredentials, id: string, status: 'routed' | 'dismissed' | 'open', fetchImpl?: typeof fetch): Promise<void>;
53
+ /** The heal slug for a queued request (`checkout.spec.ts` → `checkout`) — what
54
+ * `/mcp__hover__heal <slug>` takes. */
55
+ export declare function healSlug(specFile: string): string;
56
+ //# sourceMappingURL=cloud.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,eAAO,MAAM,iBAAiB,+BAA+B,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAC;CACb;AAED;6EAC6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,GAAG,EAAE;QACH,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,wBAAgB,eAAe,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEhE;AAED;2EAC2E;AAC3E,wBAAgB,oBAAoB,CAClC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,CAAC,EAAE,MAAM,GACZ,gBAAgB,GAAG,IAAI,CAczB;AAED;+EAC+E;AAC/E,wBAAgB,qBAAqB,CACnC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACtC,IAAI,GAAE,MAAkB,GACvB,MAAM,CAOR;AAED,gFAAgF;AAChF,qBAAa,aAAc,SAAQ,KAAK;IAE7B,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM;CAIlB;AAuBD,uEAAuE;AACvE,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,gBAAgB,EACvB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,KAAK,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,EACzF,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAY7B;AAED;gFACgF;AAChF,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,gBAAgB,EACvB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,EACvC,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;wCACwC;AACxC,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGjD"}
package/dist/cloud.js ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Hover Cloud client — the PULL side of the cloud ↔ editor loop.
3
+ *
4
+ * Cloud (cloud.gethover.dev) ingests CI runs and queues a heal request per
5
+ * drifted spec. Nothing in the cloud can reach into an editor: this module is
6
+ * how the local surfaces (the VS Code extension, the MCP server) pull that
7
+ * queue and feed it to the existing local heal flow. The fix stays local +
8
+ * human-reviewed; a request auto-closes only when CI sees the spec pass again.
9
+ *
10
+ * Credentials resolve in a fixed chain so one sign-in covers every surface:
11
+ * 1. HOVER_CLOUD_TOKEN (+ optional HOVER_CLOUD_URL) env vars — explicit, CI
12
+ * 2. ~/.hover/credentials.json — written by "Hover: Connect Hover Cloud" in
13
+ * VS Code (or by hand); 0600, shared by the extension AND the MCP server
14
+ * (VS Code SecretStorage is extension-private, so it can't be the store).
15
+ */
16
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
17
+ import { homedir } from 'node:os';
18
+ import { join } from 'node:path';
19
+ export const DEFAULT_CLOUD_URL = 'https://cloud.gethover.dev';
20
+ export function credentialsPath(home = homedir()) {
21
+ return join(home, '.hover', 'credentials.json');
22
+ }
23
+ /** Resolve credentials: env first, then ~/.hover/credentials.json. Null when
24
+ * neither is present (the surfaces show their "connect" hint on null). */
25
+ export function readCloudCredentials(env = process.env, home) {
26
+ if (env.HOVER_CLOUD_TOKEN) {
27
+ return { token: env.HOVER_CLOUD_TOKEN, url: env.HOVER_CLOUD_URL || DEFAULT_CLOUD_URL };
28
+ }
29
+ try {
30
+ const raw = JSON.parse(readFileSync(credentialsPath(home), 'utf8'));
31
+ if (!raw.token)
32
+ return null;
33
+ return { token: raw.token, url: raw.url || DEFAULT_CLOUD_URL };
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /** Persist credentials for every local surface (0600 — token, not a secret to
40
+ * sync; same trust model as ~/.aws/credentials). Returns the path written. */
41
+ export function writeCloudCredentials(creds, home = homedir()) {
42
+ const p = credentialsPath(home);
43
+ mkdirSync(join(home, '.hover'), { recursive: true });
44
+ writeFileSync(p, `${JSON.stringify({ url: DEFAULT_CLOUD_URL, ...creds }, null, 2)}\n`, {
45
+ mode: 0o600,
46
+ });
47
+ return p;
48
+ }
49
+ /** Raised on a non-2xx cloud response; `status` 401 → token revoked/expired. */
50
+ export class CloudApiError extends Error {
51
+ status;
52
+ constructor(status, message) {
53
+ super(message);
54
+ this.status = status;
55
+ }
56
+ }
57
+ async function cloudJson(creds, path, init = {}, fetchImpl = fetch) {
58
+ const res = await fetchImpl(`${creds.url.replace(/\/$/, '')}${path}`, {
59
+ ...init,
60
+ headers: {
61
+ Authorization: `Bearer ${creds.token}`,
62
+ 'Content-Type': 'application/json',
63
+ ...init.headers,
64
+ },
65
+ });
66
+ if (!res.ok) {
67
+ const body = await res.text().catch(() => '');
68
+ throw new CloudApiError(res.status, `${path} → ${res.status}: ${body.slice(0, 200)}`);
69
+ }
70
+ return (await res.json());
71
+ }
72
+ /** The open heal queue (optionally one repo's slice, `owner/name`). */
73
+ export async function fetchHealRequests(creds, opts = {}, fetchImpl = fetch) {
74
+ const params = new URLSearchParams();
75
+ if (opts.status)
76
+ params.set('status', opts.status);
77
+ if (opts.repo)
78
+ params.set('repo', opts.repo);
79
+ const qs = params.size > 0 ? `?${params}` : '';
80
+ const data = await cloudJson(creds, `/api/v1/heal-requests${qs}`, {}, fetchImpl);
81
+ return data.healRequests;
82
+ }
83
+ /** Mark a request routed (a local heal picked it up) or dismissed / reopened.
84
+ * `healed` is not writable — only CI seeing the spec pass closes a request. */
85
+ export async function updateHealRequest(creds, id, status, fetchImpl = fetch) {
86
+ await cloudJson(creds, `/api/v1/heal-requests/${id}`, { method: 'PATCH', body: JSON.stringify({ status }) }, fetchImpl);
87
+ }
88
+ /** The heal slug for a queued request (`checkout.spec.ts` → `checkout`) — what
89
+ * `/mcp__hover__heal <slug>` takes. */
90
+ export function healSlug(specFile) {
91
+ const base = specFile.split('/').pop() ?? specFile;
92
+ return base.replace(/\.spec\.ts$/, '');
93
+ }
package/dist/engine.d.ts CHANGED
@@ -18,7 +18,7 @@ export type { SkillStep } from './specs/specStep.js';
18
18
  export { reRenderSpec } from './specs/writeSpec.js';
19
19
  export { writeApiSpec } from './specs/writeApiSpec.js';
20
20
  export type { ApiCheck, WriteApiSpecOptions, WriteApiSpecResult } from './specs/writeApiSpec.js';
21
- export { buildOptimizeBrief, saveOptimizedCandidate, OptimizeError } from './specs/optimizeSpec.js';
21
+ export { buildOptimizeBrief, saveOptimizedCandidate, promoteOptimizedCandidate, OptimizeError } from './specs/optimizeSpec.js';
22
22
  export type { OptimizeResult } from './specs/optimizeSpec.js';
23
23
  export { appendWikiLog, readWikiLog, wikiLogPath } from './specs/wikiLog.js';
24
24
  export type { WikiLogKind, WikiLogEntry } from './specs/wikiLog.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;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EAAE,QAAQ,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGjG,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACpG,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC7E,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACjE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjF,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAClF,YAAY,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,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;AAEzG,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrG,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAKhF,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5J,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG/D,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;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EAAE,QAAQ,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGjG,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/H,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC7E,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACjE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjF,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAClF,YAAY,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,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;AAEzG,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrG,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAKhF,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5J,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG/D,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
@@ -19,7 +19,7 @@ export { reRenderSpec } from './specs/writeSpec.js';
19
19
  export { writeApiSpec } from './specs/writeApiSpec.js';
20
20
  // Optimize (F7) — build the improvement brief for the user's own agent, then
21
21
  // file its result as a reviewed candidate. No Hover-owned model runs.
22
- export { buildOptimizeBrief, saveOptimizedCandidate, OptimizeError } from './specs/optimizeSpec.js';
22
+ export { buildOptimizeBrief, saveOptimizedCandidate, promoteOptimizedCandidate, OptimizeError } from './specs/optimizeSpec.js';
23
23
  // LLM-Wiki P3 log — append-only, machine-parseable run history at .hover/log.md.
24
24
  export { appendWikiLog, readWikiLog, wikiLogPath } from './specs/wikiLog.js';
25
25
  // LLM-Wiki P1 Lint — deterministic health check over .hover/ (map vs specs vs runs).
@@ -54,6 +54,16 @@ export declare function saveOptimizedCandidate(devRoot: string, slug: string, ll
54
54
  candidatePath: string;
55
55
  code: string;
56
56
  }>;
57
+ /**
58
+ * Promote a reviewed optimization candidate: overwrite the real spec with the
59
+ * `.hover/cache/optimized/<slug>.spec.ts.draft` and remove the draft. This is the
60
+ * one place a candidate replaces the original — done ONLY on the user's say-so
61
+ * (they reviewed the diff), never automatically as part of optimize. Re-validates
62
+ * the draft (in case it was hand-edited) before applying. Throws if no draft.
63
+ */
64
+ export declare function promoteOptimizedCandidate(devRoot: string, slug: string): Promise<{
65
+ path: string;
66
+ }>;
57
67
  /**
58
68
  * Build the codegen prompt: the current spec + the observed session, plus the
59
69
  * same rules the deterministic path enforces (semantic selectors, no XPath, no
@@ -1 +1 @@
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,CAKzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA8B/C;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBlD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GAAG,IAAI,EAC3B,KAAK,GAAE,QAAQ,EAAO,EACtB,KAAK,GAAE,YAA4B,EACnC,iBAAiB,SAA+F,GAC/G,MAAM,CAiFR;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"}
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,CAKzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA+B/C;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBlD;AA6BD;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAgBxG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,WAAW,GAAG,IAAI,EAC3B,KAAK,GAAE,QAAQ,EAAO,EACtB,KAAK,GAAE,YAA4B,EACnC,iBAAiB,SAA+F,GAC/G,MAAM,CAiFR;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"}
@@ -10,7 +10,7 @@
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, readdir } from 'node:fs/promises';
13
+ import { readFile, mkdir, writeFile, readdir, rm } from 'node:fs/promises';
14
14
  import { join } from 'node:path';
15
15
  import { Project } from 'ts-morph';
16
16
  import { readSidecar } from './sidecar.js';
@@ -85,8 +85,9 @@ export async function buildOptimizeBrief(devRoot, slug) {
85
85
  `.ts file as \`code\`. Hover validates it (semantic selectors, no waitForTimeout/XPath), ` +
86
86
  `soft-batches trailing assertions, and files it as a REVIEW CANDIDATE at ` +
87
87
  `.hover/cache/optimized/${slug}.spec.ts.draft — it does NOT touch your spec. If it comes ` +
88
- `back with a ✗ (a rejected check), fix that and call it again. Then tell the user the ` +
89
- `candidate path so they can diff it against __vibe_tests__/${slug}.spec.ts and promote it.`;
88
+ `back with a ✗ (a rejected check), fix that and call it again. Then show the user the ` +
89
+ `diff vs __vibe_tests__/${slug}.spec.ts; if they approve, call \`promote_optimized_spec("${slug}")\` ` +
90
+ `to apply it (it overwrites the spec + removes the draft — no manual mv). They can also promote in the VS Code cockpit.`;
90
91
  return { prompt: buildOptimizePrompt(draft, sidecar, seeds, suite, outputInstruction), original: draft };
91
92
  }
92
93
  /**
@@ -115,6 +116,61 @@ export async function saveOptimizedCandidate(devRoot, slug, llmCode) {
115
116
  await writeFile(candidatePath, code.endsWith('\n') ? code : `${code}\n`, 'utf-8');
116
117
  return { candidatePath, code };
117
118
  }
119
+ /** Find the on-disk spec whose basename is `<slug>.spec.ts` under __vibe_tests__/
120
+ * (grouped specs live in subfolders), or the flat default path if none exists. */
121
+ async function findSpecPath(devRoot, slug) {
122
+ const root = join(devRoot, '__vibe_tests__');
123
+ const want = `${slug}.spec.ts`;
124
+ const walk = async (d) => {
125
+ let entries;
126
+ try {
127
+ entries = await readdir(d, { withFileTypes: true });
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ for (const e of entries) {
133
+ const p = join(d, e.name);
134
+ if (e.isDirectory()) {
135
+ if (e.name === 'pages' || e.name === '.hover' || e.name === 'node_modules')
136
+ continue;
137
+ const hit = await walk(p);
138
+ if (hit)
139
+ return hit;
140
+ }
141
+ else if (e.name === want) {
142
+ return p;
143
+ }
144
+ }
145
+ return null;
146
+ };
147
+ return (await walk(root)) ?? join(root, want);
148
+ }
149
+ /**
150
+ * Promote a reviewed optimization candidate: overwrite the real spec with the
151
+ * `.hover/cache/optimized/<slug>.spec.ts.draft` and remove the draft. This is the
152
+ * one place a candidate replaces the original — done ONLY on the user's say-so
153
+ * (they reviewed the diff), never automatically as part of optimize. Re-validates
154
+ * the draft (in case it was hand-edited) before applying. Throws if no draft.
155
+ */
156
+ export async function promoteOptimizedCandidate(devRoot, slug) {
157
+ const draftPath = join(devRoot, '.hover', 'cache', 'optimized', `${slug}.spec.ts.draft`);
158
+ let draft;
159
+ try {
160
+ draft = await readFile(draftPath, 'utf-8');
161
+ }
162
+ catch {
163
+ throw new OptimizeError(`no optimization candidate for "${slug}" (looked at ${draftPath}). Run optimize first.`);
164
+ }
165
+ const check = validateSpecCode(draft);
166
+ if (!check.ok) {
167
+ throw new OptimizeError(`candidate for "${slug}" is invalid — ${check.errors.join('; ')}. Not promoting.`);
168
+ }
169
+ const specPath = await findSpecPath(devRoot, slug);
170
+ await writeFile(specPath, draft.endsWith('\n') ? draft : `${draft}\n`, 'utf-8');
171
+ await rm(draftPath, { force: true });
172
+ return { path: specPath };
173
+ }
118
174
  /**
119
175
  * Build the codegen prompt: the current spec + the observed session, plus the
120
176
  * same rules the deterministic path enforces (semantic selectors, no XPath, no
package/dist/wiki.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { lintWiki, parseRunStatuses } from './specs/lintWiki.js';
2
+ export type { LintResult, LintFinding, LintKind, LintSeverity } from './specs/lintWiki.js';
3
+ export { readWikiLog, wikiLogPath } from './specs/wikiLog.js';
4
+ export type { WikiLogEntry, WikiLogKind } from './specs/wikiLog.js';
5
+ export { parseBusinessMap } from './specs/businessMap.js';
6
+ export type { BusinessMapGraph, MapNode, MapEdge, MapRelation, RelationKind, CoverageStatus } from './specs/businessMap.js';
7
+ //# sourceMappingURL=wiki.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiki.d.ts","sourceRoot":"","sources":["../src/wiki.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACjE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC9D,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/wiki.js ADDED
@@ -0,0 +1,14 @@
1
+ /*
2
+ * `@hover-dev/core/wiki` — the pure-read entry over `.hover/` (the app's test
3
+ * wiki): the business-map parser + the Lint health check + the run-history log
4
+ * reader. Everything here transitively imports ONLY `node:fs` / `node:path`
5
+ * (and type-only refs) — no playwright, no ts-morph, no engine.
6
+ *
7
+ * Why a separate entry from `./engine`: a slim consumer (the VS Code cockpit)
8
+ * bundles this to render the wiki, and importing the full engine barrel would
9
+ * drag `playwright-core` + `ts-morph` into its ~220 KB .vsix. This barrel keeps
10
+ * that consumer's bundle tiny while still sharing one source of truth.
11
+ */
12
+ export { lintWiki, parseRunStatuses } from './specs/lintWiki.js';
13
+ export { readWikiLog, wikiLogPath } from './specs/wikiLog.js';
14
+ export { parseBusinessMap } from './specs/businessMap.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hover-dev/core",
3
- "version": "0.24.0",
3
+ "version": "0.26.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",
@@ -30,6 +30,14 @@
30
30
  "./engine": {
31
31
  "types": "./dist/engine.d.ts",
32
32
  "import": "./dist/engine.js"
33
+ },
34
+ "./wiki": {
35
+ "types": "./dist/wiki.d.ts",
36
+ "import": "./dist/wiki.js"
37
+ },
38
+ "./cloud": {
39
+ "types": "./dist/cloud.d.ts",
40
+ "import": "./dist/cloud.js"
33
41
  }
34
42
  },
35
43
  "files": [