@hover-dev/core 0.23.0 → 0.24.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
@@ -20,6 +20,8 @@ export { writeApiSpec } from './specs/writeApiSpec.js';
20
20
  export type { ApiCheck, WriteApiSpecOptions, WriteApiSpecResult } from './specs/writeApiSpec.js';
21
21
  export { buildOptimizeBrief, saveOptimizedCandidate, OptimizeError } from './specs/optimizeSpec.js';
22
22
  export type { OptimizeResult } from './specs/optimizeSpec.js';
23
+ export { appendWikiLog, readWikiLog, wikiLogPath } from './specs/wikiLog.js';
24
+ export type { WikiLogKind, WikiLogEntry } from './specs/wikiLog.js';
23
25
  export { lintWiki, parseRunStatuses } from './specs/lintWiki.js';
24
26
  export type { LintResult, LintFinding, LintKind, LintSeverity } from './specs/lintWiki.js';
25
27
  export { parseBusinessMap } from './specs/businessMap.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,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,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"}
package/dist/engine.js CHANGED
@@ -20,6 +20,8 @@ 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
22
  export { buildOptimizeBrief, saveOptimizedCandidate, OptimizeError } from './specs/optimizeSpec.js';
23
+ // LLM-Wiki P3 log — append-only, machine-parseable run history at .hover/log.md.
24
+ export { appendWikiLog, readWikiLog, wikiLogPath } from './specs/wikiLog.js';
23
25
  // LLM-Wiki P1 Lint — deterministic health check over .hover/ (map vs specs vs runs).
24
26
  export { lintWiki, parseRunStatuses } from './specs/lintWiki.js';
25
27
  export { parseBusinessMap } from './specs/businessMap.js';
@@ -1,5 +1,8 @@
1
1
  export type MapNodeKind = 'app' | 'area' | 'line' | 'spec';
2
2
  export type CoverageStatus = 'covered' | 'uncovered';
3
+ /** Inter-line relationship kinds recorded in the map's `## Relationships` block
4
+ * (LLM-Wiki P2) — the graph edges that aren't the app→area→line→spec hierarchy. */
5
+ export type RelationKind = 'depends-on' | 'shares-state' | 'navigates-to';
3
6
  export interface MapNode {
4
7
  id: string;
5
8
  label: string;
@@ -12,10 +15,18 @@ export interface MapEdge {
12
15
  source: string;
13
16
  target: string;
14
17
  }
18
+ /** A resolved inter-line edge: source/target are `line:` node ids. */
19
+ export interface MapRelation {
20
+ source: string;
21
+ target: string;
22
+ kind: RelationKind;
23
+ }
15
24
  export interface BusinessMapGraph {
16
25
  app: string;
17
26
  nodes: MapNode[];
18
27
  edges: MapEdge[];
28
+ /** Inter-line relationships from the `## Relationships` block (may be empty). */
29
+ relations: MapRelation[];
19
30
  stats: {
20
31
  lines: number;
21
32
  covered: number;
@@ -1 +1 @@
1
- {"version":3,"file":"businessMap.d.ts","sourceRoot":"","sources":["../../src/specs/businessMap.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,WAAW,CAAC;AAErD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AACD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D;AA4BD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,SAAQ,GAAG,gBAAgB,CAuDlF"}
1
+ {"version":3,"file":"businessMap.d.ts","sourceRoot":"","sources":["../../src/specs/businessMap.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,WAAW,CAAC;AACrD;oFACoF;AACpF,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,cAAc,GAAG,cAAc,CAAC;AAE1E,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AACD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,YAAY,CAAC;CACpB;AACD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,iFAAiF;IACjF,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D;AA+BD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,SAAQ,GAAG,gBAAgB,CAmFlF"}
@@ -8,6 +8,8 @@
8
8
  * copy (packages/vscode-ext/src/businessMap.ts) on purpose — a read-only view
9
9
  * must not depend on the engine — so keep the two in sync if the format changes.
10
10
  */
11
+ const RELATION_KINDS = ['depends-on', 'shares-state', 'navigates-to'];
12
+ const RELATION_RE = new RegExp(`^\\s*-\\s+(.+?)\\s+(${RELATION_KINDS.join('|')})\\s+(.+?)\\s*$`);
11
13
  function slug(s) {
12
14
  return (s
13
15
  .toLowerCase()
@@ -50,14 +52,27 @@ export function parseBusinessMap(md, fallbackApp = 'app') {
50
52
  }
51
53
  add({ id: 'app', label: app, kind: 'app' });
52
54
  let area = null;
55
+ let inRelationships = false;
53
56
  let covered = 0;
54
57
  let lineCount = 0;
55
58
  let areaCount = 0;
59
+ // name-slug → line node id, so the `## Relationships` block can resolve a line
60
+ // by its label regardless of which area it sits under. First-defined wins.
61
+ const lineBySlug = new Map();
62
+ const rawRelations = [];
56
63
  for (const raw of md.split('\n')) {
57
64
  const line = raw.trimEnd();
58
65
  const areaM = line.match(/^##\s+(.+)$/);
59
66
  if (areaM) {
60
67
  const label = areaM[1].trim();
68
+ // The Relationships block is metadata, not an area — don't node it; its
69
+ // items are edges (parsed below), not business lines.
70
+ if (slug(label) === 'relationships') {
71
+ inRelationships = true;
72
+ area = null;
73
+ continue;
74
+ }
75
+ inRelationships = false;
61
76
  const id = `area:${slug(label)}`;
62
77
  area = { id };
63
78
  add({ id, label, kind: 'area' });
@@ -65,6 +80,12 @@ export function parseBusinessMap(md, fallbackApp = 'app') {
65
80
  areaCount++;
66
81
  continue;
67
82
  }
83
+ if (inRelationships) {
84
+ const relM = line.match(RELATION_RE);
85
+ if (relM)
86
+ rawRelations.push({ source: relM[1].trim(), kind: relM[2], target: relM[3].trim() });
87
+ continue;
88
+ }
68
89
  const itemM = line.match(/^\s*-\s*\[([ xX])\]\s+(.+)$/);
69
90
  if (itemM) {
70
91
  const status = itemM[1].toLowerCase() === 'x' ? 'covered' : 'uncovered';
@@ -72,6 +93,8 @@ export function parseBusinessMap(md, fallbackApp = 'app') {
72
93
  const parentId = area?.id ?? 'app';
73
94
  const lineId = `line:${slug(area ? area.id.slice(5) : 'top')}/${slug(name)}`;
74
95
  add({ id: lineId, label: name, kind: 'line', status, route, spec });
96
+ if (!lineBySlug.has(slug(name)))
97
+ lineBySlug.set(slug(name), lineId);
75
98
  edges.push({ source: parentId, target: lineId });
76
99
  lineCount++;
77
100
  if (status === 'covered')
@@ -83,5 +106,14 @@ export function parseBusinessMap(md, fallbackApp = 'app') {
83
106
  }
84
107
  }
85
108
  }
86
- return { app, nodes, edges, stats: { lines: lineCount, covered, areas: areaCount } };
109
+ // Resolve relationships against the lines now that all are known; an edge whose
110
+ // endpoints don't both name a known line is dropped (a stale/typo'd reference).
111
+ const relations = [];
112
+ for (const r of rawRelations) {
113
+ const source = lineBySlug.get(slug(r.source));
114
+ const target = lineBySlug.get(slug(r.target));
115
+ if (source && target && source !== target)
116
+ relations.push({ source, target, kind: r.kind });
117
+ }
118
+ return { app, nodes, edges, relations, stats: { lines: lineCount, covered, areas: areaCount } };
87
119
  }
@@ -0,0 +1,14 @@
1
+ export type WikiLogKind = 'crystallize' | 'api' | 'extract' | 'heal' | 'note';
2
+ export interface WikiLogEntry {
3
+ /** ISO-8601 timestamp. */
4
+ iso: string;
5
+ kind: string;
6
+ summary: string;
7
+ }
8
+ export declare function wikiLogPath(devRoot: string): string;
9
+ /** Append one event line. Best-effort — never throws. */
10
+ export declare function appendWikiLog(devRoot: string, kind: WikiLogKind, summary: string): Promise<void>;
11
+ /** Read the log's most recent entries (oldest→newest), parsed. Total: a missing
12
+ * or malformed file yields []. */
13
+ export declare function readWikiLog(devRoot: string, limit?: number): Promise<WikiLogEntry[]>;
14
+ //# sourceMappingURL=wikiLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wikiLog.d.ts","sourceRoot":"","sources":["../../src/specs/wikiLog.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,yDAAyD;AACzD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBtG;AAED;mCACmC;AACnC,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAYvF"}
@@ -0,0 +1,58 @@
1
+ /*
2
+ * LLM-Wiki P3 — `.hover/log.md`: an append-only, machine-parseable run history
3
+ * for the app's test wiki. One event per line:
4
+ *
5
+ * - <ISO timestamp> · <kind> · <summary>
6
+ *
7
+ * Hover writes it deterministically as it MUTATES the wiki (a spec crystallized,
8
+ * an API spec locked, Page Objects extracted) — no dependence on the agent
9
+ * remembering to log, and no prompt churn. It powers an auditable timeline (and
10
+ * a future cockpit history view). Best-effort by contract: a log failure must
11
+ * NEVER break a crystallize / extract — same rule as the memory + run ledger.
12
+ */
13
+ import { appendFile, mkdir, readFile } from 'node:fs/promises';
14
+ import { join } from 'node:path';
15
+ import { hoverDir } from './sidecar.js';
16
+ const HEADER = '# Hover log\n\n' +
17
+ "Append-only run history for this app's test wiki. One event per line: " +
18
+ '`- <ISO> · <kind> · <summary>`.\n\n';
19
+ export function wikiLogPath(devRoot) {
20
+ return join(hoverDir(devRoot), 'log.md');
21
+ }
22
+ /** Append one event line. Best-effort — never throws. */
23
+ export async function appendWikiLog(devRoot, kind, summary) {
24
+ try {
25
+ await mkdir(hoverDir(devRoot), { recursive: true });
26
+ const path = wikiLogPath(devRoot);
27
+ let existing = '';
28
+ try {
29
+ existing = await readFile(path, 'utf-8');
30
+ }
31
+ catch {
32
+ /* new file → write the header first */
33
+ }
34
+ const iso = new Date().toISOString();
35
+ const line = `- ${iso} · ${kind} · ${summary.replace(/\s+/g, ' ').trim()}\n`;
36
+ await appendFile(path, `${existing ? '' : HEADER}${line}`, 'utf-8');
37
+ }
38
+ catch {
39
+ /* best-effort: a wiki-log failure must not break the write that triggered it */
40
+ }
41
+ }
42
+ /** Read the log's most recent entries (oldest→newest), parsed. Total: a missing
43
+ * or malformed file yields []. */
44
+ export async function readWikiLog(devRoot, limit = 200) {
45
+ try {
46
+ const raw = await readFile(wikiLogPath(devRoot), 'utf-8');
47
+ const entries = [];
48
+ for (const l of raw.split('\n')) {
49
+ const m = l.match(/^-\s+(\S+)\s+·\s+(\w+)\s+·\s+(.+)$/);
50
+ if (m)
51
+ entries.push({ iso: m[1], kind: m[2], summary: m[3].trim() });
52
+ }
53
+ return entries.slice(-limit);
54
+ }
55
+ catch {
56
+ return [];
57
+ }
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hover-dev/core",
3
- "version": "0.23.0",
3
+ "version": "0.24.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",