@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.
- package/README.md +26 -55
- package/dist/agentDirectives.d.ts +55 -0
- package/dist/agentDirectives.d.ts.map +1 -0
- package/dist/agentDirectives.js +276 -0
- package/dist/agents/claude.d.ts.map +1 -1
- package/dist/agents/claude.js +28 -3
- package/dist/agents/codex.d.ts.map +1 -1
- package/dist/agents/codex.js +29 -14
- package/dist/agents/invoke.d.ts.map +1 -1
- package/dist/agents/invoke.js +3 -6
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +0 -4
- package/dist/agents/types.d.ts +19 -11
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/engine.d.ts +53 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +78 -0
- package/dist/mcp/actuateServer.d.ts +3 -0
- package/dist/mcp/actuateServer.d.ts.map +1 -0
- package/dist/mcp/actuateServer.js +594 -0
- package/dist/mcp/sourceFence.d.ts.map +1 -1
- package/dist/mcp/sourceFence.js +4 -0
- package/dist/mcp/sourceServer.js +75 -0
- package/dist/memory/businessMemory.d.ts +29 -0
- package/dist/memory/businessMemory.d.ts.map +1 -0
- package/dist/memory/businessMemory.js +125 -0
- package/dist/modes.d.ts +39 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/modes.js +34 -0
- package/dist/playwright/cdpStatus.d.ts +0 -15
- package/dist/playwright/cdpStatus.d.ts.map +1 -1
- package/dist/playwright/cdpStatus.js +0 -67
- package/dist/playwright/launchChrome.d.ts +18 -0
- package/dist/playwright/launchChrome.d.ts.map +1 -1
- package/dist/playwright/launchChrome.js +46 -3
- package/dist/playwright/resolveMcpConfig.d.ts +7 -1
- package/dist/playwright/resolveMcpConfig.d.ts.map +1 -1
- package/dist/playwright/resolveMcpConfig.js +22 -4
- package/dist/plugin-api.d.ts +28 -26
- package/dist/plugin-api.d.ts.map +1 -1
- package/dist/plugin-api.js +2 -2
- package/dist/qa/candidates.d.ts +32 -0
- package/dist/qa/candidates.d.ts.map +1 -0
- package/dist/qa/candidates.js +20 -0
- package/dist/qa/classify.d.ts +38 -0
- package/dist/qa/classify.d.ts.map +1 -0
- package/dist/qa/classify.js +138 -0
- package/dist/qa/intensity.d.ts +33 -0
- package/dist/qa/intensity.d.ts.map +1 -0
- package/dist/qa/intensity.js +25 -0
- package/dist/qa/qaReport.d.ts +19 -0
- package/dist/qa/qaReport.d.ts.map +1 -0
- package/dist/qa/qaReport.js +50 -0
- package/dist/runSession.d.ts +14 -3
- package/dist/runSession.d.ts.map +1 -1
- package/dist/runSession.js +26 -11
- package/dist/service/cdpHandlers.d.ts +1 -21
- package/dist/service/cdpHandlers.d.ts.map +1 -1
- package/dist/service/cdpHandlers.js +4 -39
- package/dist/service/cdpHint.d.ts +21 -28
- package/dist/service/cdpHint.d.ts.map +1 -1
- package/dist/service/cdpHint.js +106 -164
- package/dist/service/relayHandlers.d.ts +28 -0
- package/dist/service/relayHandlers.d.ts.map +1 -0
- package/dist/service/relayHandlers.js +105 -0
- package/dist/service/saveHandlers.d.ts +1 -3
- package/dist/service/saveHandlers.d.ts.map +1 -1
- package/dist/service/saveHandlers.js +17 -15
- package/dist/service/types.d.ts +108 -8
- package/dist/service/types.d.ts.map +1 -1
- package/dist/service.d.ts +7 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +907 -200
- package/dist/sessions/sessions.d.ts +125 -0
- package/dist/sessions/sessions.d.ts.map +1 -0
- package/dist/sessions/sessions.js +175 -0
- package/dist/specs/authFixture.d.ts +30 -0
- package/dist/specs/authFixture.d.ts.map +1 -0
- package/dist/specs/authFixture.js +145 -0
- package/dist/specs/businessMap.d.ts +29 -0
- package/dist/specs/businessMap.d.ts.map +1 -0
- package/dist/specs/businessMap.js +95 -0
- package/dist/specs/detectSharedFlows.d.ts +1 -1
- package/dist/specs/detectSharedFlows.d.ts.map +1 -1
- package/dist/specs/detectSharedFlows.js +20 -21
- package/dist/specs/generatePageObject.d.ts +1 -1
- package/dist/specs/generatePageObject.d.ts.map +1 -1
- package/dist/specs/healPrompt.d.ts +19 -0
- package/dist/specs/healPrompt.d.ts.map +1 -0
- package/dist/specs/healPrompt.js +48 -0
- package/dist/specs/humanSteps.d.ts +4 -8
- package/dist/specs/humanSteps.d.ts.map +1 -1
- package/dist/specs/humanSteps.js +6 -1
- package/dist/specs/optimizeSpec.d.ts +15 -8
- package/dist/specs/optimizeSpec.d.ts.map +1 -1
- package/dist/specs/optimizeSpec.js +71 -41
- package/dist/specs/optimizeSpecWithAgent.d.ts +0 -2
- package/dist/specs/optimizeSpecWithAgent.d.ts.map +1 -1
- package/dist/specs/optimizeSpecWithAgent.js +0 -1
- package/dist/specs/pageObjectManifest.d.ts +3 -1
- package/dist/specs/pageObjectManifest.d.ts.map +1 -1
- package/dist/specs/pageObjectManifest.js +13 -9
- package/dist/specs/replayGrounded.d.ts +45 -0
- package/dist/specs/replayGrounded.d.ts.map +1 -0
- package/dist/specs/replayGrounded.js +155 -0
- package/dist/specs/runFailures.d.ts +34 -0
- package/dist/specs/runFailures.d.ts.map +1 -0
- package/dist/specs/runFailures.js +93 -0
- package/dist/specs/seeds.d.ts +16 -15
- package/dist/specs/seeds.d.ts.map +1 -1
- package/dist/specs/seeds.js +86 -54
- package/dist/specs/sidecar.d.ts +34 -6
- package/dist/specs/sidecar.d.ts.map +1 -1
- package/dist/specs/sidecar.js +79 -9
- package/dist/specs/specStep.d.ts +21 -0
- package/dist/specs/specStep.d.ts.map +1 -0
- package/dist/specs/specStep.js +1 -0
- package/dist/specs/text.d.ts +8 -6
- package/dist/specs/text.d.ts.map +1 -1
- package/dist/specs/text.js +10 -7
- package/dist/specs/writeSpec.d.ts +62 -1
- package/dist/specs/writeSpec.d.ts.map +1 -1
- package/dist/specs/writeSpec.js +596 -21
- package/package.json +6 -9
- package/dist/agents/aider.d.ts +0 -16
- package/dist/agents/aider.d.ts.map +0 -1
- package/dist/agents/aider.js +0 -161
- package/dist/agents/cursor.d.ts +0 -18
- package/dist/agents/cursor.d.ts.map +0 -1
- package/dist/agents/cursor.js +0 -220
- package/dist/playwright/raiseWindow.d.ts +0 -10
- package/dist/playwright/raiseWindow.d.ts.map +0 -1
- package/dist/playwright/raiseWindow.js +0 -158
- package/dist/scripts/bench-multi-tab.d.ts +0 -2
- package/dist/scripts/bench-multi-tab.d.ts.map +0 -1
- package/dist/scripts/bench-multi-tab.js +0 -192
- package/dist/scripts/bench-ttfb.d.ts +0 -2
- package/dist/scripts/bench-ttfb.d.ts.map +0 -1
- package/dist/scripts/bench-ttfb.js +0 -127
- package/dist/scripts/start-chrome.d.ts +0 -3
- package/dist/scripts/start-chrome.d.ts.map +0 -1
- package/dist/scripts/start-chrome.js +0 -23
- package/dist/skills/writeSkill.d.ts +0 -27
- package/dist/skills/writeSkill.d.ts.map +0 -1
- package/dist/skills/writeSkill.js +0 -13
- package/dist/specs/listSpecs.d.ts +0 -52
- package/dist/specs/listSpecs.d.ts.map +0 -1
- package/dist/specs/listSpecs.js +0 -139
- package/dist/specs/optimizationSuggestion.d.ts +0 -26
- package/dist/specs/optimizationSuggestion.d.ts.map +0 -1
- package/dist/specs/optimizationSuggestion.js +0 -28
- package/dist/specs/writeCaseCsv.d.ts +0 -28
- package/dist/specs/writeCaseCsv.d.ts.map +0 -1
- package/dist/specs/writeCaseCsv.js +0 -134
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Benchmark agent success rate on the multi-tab "Pay with PayHover" flow.
|
|
3
|
-
*
|
|
4
|
-
* Why this exists — v0.10's central theme is "agent can drive cross-tab
|
|
5
|
-
* flows in the wild." The system-prompt addendum (cdpHint.ts rule 5/6/7)
|
|
6
|
-
* is the lever we're tuning. This script gives us a number to tune
|
|
7
|
-
* against: across N iterations, how often does the agent get from
|
|
8
|
-
* "browse the store" to "Order placed"?
|
|
9
|
-
*
|
|
10
|
-
* Per iteration the agent has to:
|
|
11
|
-
* 1. Browse the e-commerce store, add 1+ items to cart, go to checkout.
|
|
12
|
-
* 2. Fill the shipping form.
|
|
13
|
-
* 3. Pick "Pay with PayHover" (opens a new tab at localhost:5177).
|
|
14
|
-
* 4. Switch to the new tab, fill card number + CVV, click Continue.
|
|
15
|
-
* 5. Wait ~600ms for the simulated 3DS pre-check.
|
|
16
|
-
* 6. Fill the 6-digit OTP (always 123456 in the sandbox).
|
|
17
|
-
* 7. Click Confirm. The provider tab closes itself.
|
|
18
|
-
* 8. Switch back to the original tab, observe the "Order placed" view.
|
|
19
|
-
*
|
|
20
|
-
* Steps 3, 4, 7, 8 are the failure-prone ones.
|
|
21
|
-
*
|
|
22
|
-
* Assumes:
|
|
23
|
-
* - Debug Chrome on :9222 (run `pnpm smoke:chrome`).
|
|
24
|
-
* - e-commerce on :5174 AND payment-provider on :5177 both running
|
|
25
|
-
* (run `pnpm dev:example:e-commerce` and `pnpm dev:example:payment-provider`
|
|
26
|
-
* in two terminals before invoking this).
|
|
27
|
-
*
|
|
28
|
-
* Usage:
|
|
29
|
-
* pnpm --filter @hover-dev/core exec tsx src/scripts/bench-multi-tab.ts [n]
|
|
30
|
-
* pnpm bench-multi-tab [n]
|
|
31
|
-
*
|
|
32
|
-
* `n` defaults to 5. Per-iteration timeout is 5 minutes — multi-tab flows
|
|
33
|
-
* are slow because the agent does a lot of browser_snapshot calls.
|
|
34
|
-
*
|
|
35
|
-
* Output: per-run pass/fail + final summary (success rate, median wall
|
|
36
|
-
* time, median turns, median cost in $). A/B prompt changes by running
|
|
37
|
-
* once on each branch and comparing.
|
|
38
|
-
*/
|
|
39
|
-
import { WebSocket } from 'ws';
|
|
40
|
-
import { startService } from '../service.js';
|
|
41
|
-
const PROMPT = process.env.HOVER_BENCH_PROMPT ??
|
|
42
|
-
[
|
|
43
|
-
'Open http://localhost:5174 (Hover Store).',
|
|
44
|
-
'Add any item to the cart, go to checkout, fill the shipping form with',
|
|
45
|
-
'realistic values, then choose "Pay with PayHover". A new tab opens at',
|
|
46
|
-
'the payment provider — switch to it, fill in card 4242 4242 4242 4242',
|
|
47
|
-
'with CVV 123, click Continue, wait for the OTP step, enter 123456,',
|
|
48
|
-
'click Confirm. The popup will close. Switch back to the original tab',
|
|
49
|
-
'and verify the order shows as placed.',
|
|
50
|
-
].join(' ');
|
|
51
|
-
const ITERATIONS = Number(process.argv[2] ?? 5);
|
|
52
|
-
const PER_RUN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
53
|
-
async function singleRun(idx) {
|
|
54
|
-
process.stderr.write(`\n[bench-multi-tab] run ${idx + 1}/${ITERATIONS}\n`);
|
|
55
|
-
const service = await startService({
|
|
56
|
-
port: 0,
|
|
57
|
-
agentId: 'claude',
|
|
58
|
-
model: 'sonnet',
|
|
59
|
-
cdpUrl: 'http://localhost:9222',
|
|
60
|
-
devRoot: process.cwd(),
|
|
61
|
-
});
|
|
62
|
-
return new Promise((resolve) => {
|
|
63
|
-
const ws = new WebSocket(`ws://127.0.0.1:${service.port}`);
|
|
64
|
-
const t0 = performance.now();
|
|
65
|
-
let turns = 0;
|
|
66
|
-
let costUsd = null;
|
|
67
|
-
let resolved = false;
|
|
68
|
-
const finish = (result) => {
|
|
69
|
-
if (resolved)
|
|
70
|
-
return;
|
|
71
|
-
resolved = true;
|
|
72
|
-
try {
|
|
73
|
-
ws.close(1000);
|
|
74
|
-
}
|
|
75
|
-
catch { /* already closed */ }
|
|
76
|
-
service.close().finally(() => resolve(result));
|
|
77
|
-
};
|
|
78
|
-
const timeout = setTimeout(() => {
|
|
79
|
-
finish({
|
|
80
|
-
ok: false,
|
|
81
|
-
wallMs: performance.now() - t0,
|
|
82
|
-
turns,
|
|
83
|
-
costUsd,
|
|
84
|
-
reason: `timed out after ${PER_RUN_TIMEOUT_MS / 1000}s`,
|
|
85
|
-
});
|
|
86
|
-
}, PER_RUN_TIMEOUT_MS);
|
|
87
|
-
ws.on('open', () => {
|
|
88
|
-
ws.send(JSON.stringify({ type: 'command', payload: { text: PROMPT } }));
|
|
89
|
-
});
|
|
90
|
-
ws.on('message', (raw) => {
|
|
91
|
-
let msg;
|
|
92
|
-
try {
|
|
93
|
-
msg = JSON.parse(raw.toString());
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (msg.type !== 'event')
|
|
99
|
-
return;
|
|
100
|
-
const ev = msg.payload;
|
|
101
|
-
if (ev.kind === 'tool_use') {
|
|
102
|
-
turns += 1;
|
|
103
|
-
if (process.env.HOVER_BENCH_VERBOSE === '1') {
|
|
104
|
-
const ev2 = ev;
|
|
105
|
-
process.stderr.write(` [turn ${turns}] ${ev2.name ?? '<tool>'}\n`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (ev.kind === 'session_end') {
|
|
109
|
-
clearTimeout(timeout);
|
|
110
|
-
const evAny = ev;
|
|
111
|
-
if (typeof evAny.costUsd === 'number')
|
|
112
|
-
costUsd = evAny.costUsd;
|
|
113
|
-
finish({
|
|
114
|
-
ok: !evAny.isError,
|
|
115
|
-
wallMs: performance.now() - t0,
|
|
116
|
-
turns,
|
|
117
|
-
costUsd,
|
|
118
|
-
reason: evAny.isError ? 'agent reported error' : undefined,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
ws.on('error', (err) => {
|
|
123
|
-
clearTimeout(timeout);
|
|
124
|
-
finish({
|
|
125
|
-
ok: false,
|
|
126
|
-
wallMs: performance.now() - t0,
|
|
127
|
-
turns,
|
|
128
|
-
costUsd,
|
|
129
|
-
reason: `WS error: ${err.message}`,
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
function median(xs) {
|
|
135
|
-
if (xs.length === 0)
|
|
136
|
-
return 0;
|
|
137
|
-
const sorted = [...xs].sort((a, b) => a - b);
|
|
138
|
-
const mid = Math.floor(sorted.length / 2);
|
|
139
|
-
return sorted.length % 2 === 0
|
|
140
|
-
? (sorted[mid - 1] + sorted[mid]) / 2
|
|
141
|
-
: sorted[mid];
|
|
142
|
-
}
|
|
143
|
-
function fmtMs(ms) {
|
|
144
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
145
|
-
}
|
|
146
|
-
function fmtUsd(usd) {
|
|
147
|
-
return usd == null ? '–' : `$${usd.toFixed(4)}`;
|
|
148
|
-
}
|
|
149
|
-
async function main() {
|
|
150
|
-
process.stderr.write(`[bench-multi-tab] ${ITERATIONS} iterations, per-run timeout ${PER_RUN_TIMEOUT_MS / 1000}s\n`);
|
|
151
|
-
process.stderr.write(`[bench-multi-tab] prompt: ${PROMPT.slice(0, 80)}…\n`);
|
|
152
|
-
const results = [];
|
|
153
|
-
for (let i = 0; i < ITERATIONS; i++) {
|
|
154
|
-
try {
|
|
155
|
-
const r = await singleRun(i);
|
|
156
|
-
results.push(r);
|
|
157
|
-
const status = r.ok ? '✓ PASS' : '✗ FAIL';
|
|
158
|
-
process.stderr.write(`[bench-multi-tab] run ${i + 1}: ${status} · ${fmtMs(r.wallMs)} · ${r.turns} turns · ${fmtUsd(r.costUsd)}${r.reason ? ` · ${r.reason}` : ''}\n`);
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
162
|
-
results.push({ ok: false, wallMs: 0, turns: 0, costUsd: null, reason: msg });
|
|
163
|
-
process.stderr.write(`[bench-multi-tab] run ${i + 1}: ✗ FAIL · setup error · ${msg}\n`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const passes = results.filter((r) => r.ok);
|
|
167
|
-
const successRate = passes.length / results.length;
|
|
168
|
-
process.stderr.write('\n[bench-multi-tab] summary\n');
|
|
169
|
-
process.stderr.write(` success rate: ${(successRate * 100).toFixed(0)}% (${passes.length}/${results.length})\n`);
|
|
170
|
-
if (passes.length > 0) {
|
|
171
|
-
process.stderr.write(` median wall: ${fmtMs(median(passes.map((r) => r.wallMs)))}\n`);
|
|
172
|
-
process.stderr.write(` median turns: ${median(passes.map((r) => r.turns)).toFixed(0)}\n`);
|
|
173
|
-
const costs = passes.map((r) => r.costUsd).filter((c) => c != null);
|
|
174
|
-
if (costs.length > 0) {
|
|
175
|
-
process.stderr.write(` median cost: ${fmtUsd(median(costs))}\n`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (passes.length < results.length) {
|
|
179
|
-
process.stderr.write(`\n failures:\n`);
|
|
180
|
-
results.forEach((r, i) => {
|
|
181
|
-
if (!r.ok)
|
|
182
|
-
process.stderr.write(` run ${i + 1}: ${r.reason ?? 'unknown'}\n`);
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
// Exit non-zero if EVERY run failed — useful for CI plumbing later. A
|
|
186
|
-
// partial-pass run still exits 0 so we collect signal across branches.
|
|
187
|
-
process.exit(passes.length === 0 ? 1 : 0);
|
|
188
|
-
}
|
|
189
|
-
main().catch((err) => {
|
|
190
|
-
process.stderr.write(`[bench-multi-tab] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
191
|
-
process.exit(1);
|
|
192
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bench-ttfb.d.ts","sourceRoot":"","sources":["../../src/scripts/bench-ttfb.ts"],"names":[],"mappings":""}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Benchmark "time to first tool_use" for the LLM-driven loop.
|
|
3
|
-
*
|
|
4
|
-
* Assumes:
|
|
5
|
-
* - A debug Chrome is running on :9222 (start with `pnpm smoke:chrome`).
|
|
6
|
-
* - A dev server is running so the agent has something to drive
|
|
7
|
-
* (`pnpm dev:example:basic-app`).
|
|
8
|
-
*
|
|
9
|
-
* Per iteration:
|
|
10
|
-
* - Start a fresh Hover service (cold — kills any prior service to avoid
|
|
11
|
-
* cached MCP process state across iterations).
|
|
12
|
-
* - WS-connect, send a fixed command, mark t0 right before send().
|
|
13
|
-
* - Mark t1 on the first tool_use event from the agent.
|
|
14
|
-
* - Report (t1 - t0) in milliseconds. Close service + WS.
|
|
15
|
-
*
|
|
16
|
-
* pnpm --filter @hover-dev/core exec tsx src/scripts/bench-ttfb.ts <n>
|
|
17
|
-
*
|
|
18
|
-
* `n` defaults to 5. Prints individual timings + median + min/max.
|
|
19
|
-
*/
|
|
20
|
-
import { WebSocket } from 'ws';
|
|
21
|
-
import { startService } from '../service.js';
|
|
22
|
-
const PROMPT = process.env.HOVER_BENCH_PROMPT ?? 'Take a snapshot of the page.';
|
|
23
|
-
const ITERATIONS = Number(process.argv[2] ?? 5);
|
|
24
|
-
async function singleRun() {
|
|
25
|
-
const service = await startService({
|
|
26
|
-
// Use 0 to let the kernel pick — avoids cross-iter EADDRINUSE races.
|
|
27
|
-
port: 0,
|
|
28
|
-
agentId: 'claude',
|
|
29
|
-
model: 'sonnet',
|
|
30
|
-
cdpUrl: 'http://localhost:9222',
|
|
31
|
-
devRoot: process.cwd(),
|
|
32
|
-
});
|
|
33
|
-
return new Promise((resolve, reject) => {
|
|
34
|
-
const ws = new WebSocket(`ws://127.0.0.1:${service.port}`);
|
|
35
|
-
let t0 = 0;
|
|
36
|
-
let resolved = false;
|
|
37
|
-
const timeout = setTimeout(() => {
|
|
38
|
-
if (!resolved) {
|
|
39
|
-
ws.close(1000);
|
|
40
|
-
service.close();
|
|
41
|
-
reject(new Error('timed out waiting for first tool_use after 60s'));
|
|
42
|
-
}
|
|
43
|
-
}, 60_000);
|
|
44
|
-
ws.on('open', () => {
|
|
45
|
-
t0 = performance.now();
|
|
46
|
-
ws.send(JSON.stringify({ type: 'command', payload: { text: PROMPT } }));
|
|
47
|
-
});
|
|
48
|
-
ws.on('message', raw => {
|
|
49
|
-
let msg;
|
|
50
|
-
try {
|
|
51
|
-
msg = JSON.parse(raw.toString());
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (process.env.HOVER_BENCH_VERBOSE === '1') {
|
|
57
|
-
process.stderr.write(` [event] ${raw.toString().slice(0, 200)}\n`);
|
|
58
|
-
}
|
|
59
|
-
if (msg.type !== 'event')
|
|
60
|
-
return;
|
|
61
|
-
const ev = msg.payload;
|
|
62
|
-
if (ev.kind === 'tool_use' && !resolved) {
|
|
63
|
-
const t1 = performance.now();
|
|
64
|
-
const ms = t1 - t0;
|
|
65
|
-
resolved = true;
|
|
66
|
-
clearTimeout(timeout);
|
|
67
|
-
ws.close(1000);
|
|
68
|
-
service.close().finally(() => resolve(ms));
|
|
69
|
-
}
|
|
70
|
-
if (ev.kind === 'session_end' && !resolved) {
|
|
71
|
-
// Ran without any tool_use — agent went text-only or errored.
|
|
72
|
-
// Reject so the bench surfaces the issue instead of recording
|
|
73
|
-
// a misleadingly tiny "first tool_use" timing.
|
|
74
|
-
resolved = true;
|
|
75
|
-
clearTimeout(timeout);
|
|
76
|
-
const evAny = ev;
|
|
77
|
-
const reason = evAny.isError ? 'session_end (error)' : 'session_end without tool_use';
|
|
78
|
-
ws.close(1000);
|
|
79
|
-
service.close().finally(() => reject(new Error(reason)));
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
ws.on('error', err => {
|
|
83
|
-
if (resolved)
|
|
84
|
-
return;
|
|
85
|
-
resolved = true;
|
|
86
|
-
clearTimeout(timeout);
|
|
87
|
-
service.close().finally(() => reject(err));
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
function median(xs) {
|
|
92
|
-
const sorted = [...xs].sort((a, b) => a - b);
|
|
93
|
-
const mid = Math.floor(sorted.length / 2);
|
|
94
|
-
return sorted.length % 2 === 0
|
|
95
|
-
? (sorted[mid - 1] + sorted[mid]) / 2
|
|
96
|
-
: sorted[mid];
|
|
97
|
-
}
|
|
98
|
-
async function main() {
|
|
99
|
-
console.log(`prompt: ${JSON.stringify(PROMPT)}`);
|
|
100
|
-
console.log(`iterations: ${ITERATIONS}`);
|
|
101
|
-
console.log('');
|
|
102
|
-
const results = [];
|
|
103
|
-
for (let i = 1; i <= ITERATIONS; i++) {
|
|
104
|
-
try {
|
|
105
|
-
const ms = await singleRun();
|
|
106
|
-
results.push(ms);
|
|
107
|
-
console.log(` run ${i}: ${ms.toFixed(0).padStart(5)} ms`);
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
console.error(` run ${i}: FAILED — ${err instanceof Error ? err.message : String(err)}`);
|
|
111
|
-
}
|
|
112
|
-
// Small gap between runs so any process-cleanup tail can flush.
|
|
113
|
-
await new Promise(r => setTimeout(r, 500));
|
|
114
|
-
}
|
|
115
|
-
if (results.length === 0) {
|
|
116
|
-
console.error('\nNo successful runs.');
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
console.log('');
|
|
120
|
-
console.log(`min: ${Math.min(...results).toFixed(0)} ms`);
|
|
121
|
-
console.log(`median: ${median(results).toFixed(0)} ms`);
|
|
122
|
-
console.log(`max: ${Math.max(...results).toFixed(0)} ms`);
|
|
123
|
-
}
|
|
124
|
-
main().catch(err => {
|
|
125
|
-
console.error(err);
|
|
126
|
-
process.exit(1);
|
|
127
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"start-chrome.d.ts","sourceRoot":"","sources":["../../src/scripts/start-chrome.ts"],"names":[],"mappings":""}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* "Start debug Chrome on port 9222" CLI.
|
|
4
|
-
*
|
|
5
|
-
* Two entry points:
|
|
6
|
-
* - Repo dev: `pnpm smoke:chrome` → tsx src/scripts/start-chrome.ts
|
|
7
|
-
* - npm consumer: `pnpm exec hover-chrome` → dist/scripts/start-chrome.js
|
|
8
|
-
* (or `npx hover-chrome`, bin exposed by vite-plugin-hover)
|
|
9
|
-
*
|
|
10
|
-
* All actual launch logic lives in ../playwright/launchChrome.ts.
|
|
11
|
-
*/
|
|
12
|
-
import { launchDebugChrome } from '../playwright/launchChrome.js';
|
|
13
|
-
const result = await launchDebugChrome();
|
|
14
|
-
if (!result.ok) {
|
|
15
|
-
console.error(`[hover:chrome] ${result.reason}`);
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
if (result.alreadyRunning) {
|
|
19
|
-
console.log(`[hover:chrome] already listening on ${result.port}`);
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
console.log(`[hover:chrome] ready on ${result.port} (data-dir=${result.userDataDir})`);
|
|
23
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Captured-step type.
|
|
3
|
-
*
|
|
4
|
-
* NOTE: Save-as-Skill (writing `.claude/skills/<slug>/SKILL.md` for agent
|
|
5
|
-
* replay) was retired — `spec` + ⟳ Re-record covers intent-driven replay, and
|
|
6
|
-
* "skill" collided with Claude Code's own skills concept. All that remains
|
|
7
|
-
* here is `SkillStep`: the serialized message shape from the widget's
|
|
8
|
-
* localStorage, which the whole spec pipeline (writeSpec, sidecar, listSpecs,
|
|
9
|
-
* Page-Object extraction) consumes as `SpecStep`. The file keeps its path so
|
|
10
|
-
* the many `import { SkillStep } from '../skills/writeSkill.js'` call sites
|
|
11
|
-
* don't churn; renaming to a neutral module is a separate mechanical pass.
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* Serialized message shape from the widget's localStorage. Matches the
|
|
15
|
-
* `state.messages` schema in packages/widget-bootstrap/src/widget/client.js.
|
|
16
|
-
*/
|
|
17
|
-
export interface SkillStep {
|
|
18
|
-
kind: 'user' | 'system' | 'step' | 'ai' | 'done';
|
|
19
|
-
text?: string;
|
|
20
|
-
tool?: string;
|
|
21
|
-
input?: unknown;
|
|
22
|
-
isError?: boolean;
|
|
23
|
-
turns?: number;
|
|
24
|
-
costUsd?: number;
|
|
25
|
-
summary?: string;
|
|
26
|
-
}
|
|
27
|
-
//# sourceMappingURL=writeSkill.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"writeSkill.d.ts","sourceRoot":"","sources":["../../src/skills/writeSkill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Captured-step type.
|
|
3
|
-
*
|
|
4
|
-
* NOTE: Save-as-Skill (writing `.claude/skills/<slug>/SKILL.md` for agent
|
|
5
|
-
* replay) was retired — `spec` + ⟳ Re-record covers intent-driven replay, and
|
|
6
|
-
* "skill" collided with Claude Code's own skills concept. All that remains
|
|
7
|
-
* here is `SkillStep`: the serialized message shape from the widget's
|
|
8
|
-
* localStorage, which the whole spec pipeline (writeSpec, sidecar, listSpecs,
|
|
9
|
-
* Page-Object extraction) consumes as `SpecStep`. The file keeps its path so
|
|
10
|
-
* the many `import { SkillStep } from '../skills/writeSkill.js'` call sites
|
|
11
|
-
* don't churn; renaming to a neutral module is a separate mechanical pass.
|
|
12
|
-
*/
|
|
13
|
-
export {};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { type OptimizationSuggestion } from './optimizationSuggestion.js';
|
|
2
|
-
export interface SpecSummary {
|
|
3
|
-
/** Path-relative slug, e.g. `login-and-counter`. Identifies the spec. */
|
|
4
|
-
slug: string;
|
|
5
|
-
/** Absolute path to the .spec.ts file. */
|
|
6
|
-
path: string;
|
|
7
|
-
/** `Original prompt:` parsed from the JSDoc header. `null` for
|
|
8
|
-
* hand-authored specs that have no header — they list but can't be
|
|
9
|
-
* re-recorded automatically. */
|
|
10
|
-
originalPrompt: string | null;
|
|
11
|
-
/** First line of `Outcome:` from the JSDoc header, if present. */
|
|
12
|
-
outcome: string | null;
|
|
13
|
-
/** Number of `Steps:` lines parsed (informational only). */
|
|
14
|
-
stepCount: number;
|
|
15
|
-
/** File mtime in ms — used to show "saved 2 hours ago" in the UI. */
|
|
16
|
-
mtimeMs: number;
|
|
17
|
-
/** Whether a structured `.hover/<slug>.json` sidecar exists. The widget
|
|
18
|
-
* gates the optimization pass on this — without a captured session there's
|
|
19
|
-
* no observed feedback for the LLM to add assertions from. */
|
|
20
|
-
hasSidecar: boolean;
|
|
21
|
-
/** Count of `// hover:optimizable` markers the deterministic translator left
|
|
22
|
-
* — interactions it couldn't fully translate single-step. >0 is a strong
|
|
23
|
-
* signal to run the optimization pass (or add a seed). */
|
|
24
|
-
optimizableCount: number;
|
|
25
|
-
/** The default-off "review optimization?" nudge (F7/D10): suggested + reasons,
|
|
26
|
-
* derived from optimizable markers + relevant seeds. */
|
|
27
|
-
optimization: OptimizationSuggestion;
|
|
28
|
-
}
|
|
29
|
-
export interface SpecHeader {
|
|
30
|
-
/** Raw text of `Original prompt:` line, or null when absent. */
|
|
31
|
-
originalPrompt: string | null;
|
|
32
|
-
/** First line of `Outcome:`. */
|
|
33
|
-
outcome: string | null;
|
|
34
|
-
/** Step lines from the `Steps:` block, in order. */
|
|
35
|
-
steps: string[];
|
|
36
|
-
/** Lines from the `Expected:` block, in order. */
|
|
37
|
-
expected: string[];
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Parse the JSDoc header that `writeSpec.ts` emits. Tolerant of:
|
|
41
|
-
* - Specs without any JSDoc (returns all-null).
|
|
42
|
-
* - Hand-edited specs where users reordered or trimmed sections.
|
|
43
|
-
* - Long prompts that wrap across lines (we take only the first line).
|
|
44
|
-
*/
|
|
45
|
-
export declare function parseSpecHeader(source: string): SpecHeader;
|
|
46
|
-
/**
|
|
47
|
-
* List every `*.spec.ts` file under `<devRoot>/__vibe_tests__/` with its
|
|
48
|
-
* parsed header. Returns newest-first by mtime so the widget overlay shows
|
|
49
|
-
* recently-saved specs at the top.
|
|
50
|
-
*/
|
|
51
|
-
export declare function listSpecs(devRoot: string): Promise<SpecSummary[]>;
|
|
52
|
-
//# sourceMappingURL=listSpecs.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"listSpecs.d.ts","sourceRoot":"","sources":["../../src/specs/listSpecs.ts"],"names":[],"mappings":"AAoBA,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAGlG,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb;;qCAEiC;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kEAAkE;IAClE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB;;mEAE+D;IAC/D,UAAU,EAAE,OAAO,CAAC;IACpB;;+DAE2D;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB;6DACyD;IACzD,YAAY,EAAE,sBAAsB,CAAC;CACtC;AAED,MAAM,WAAW,UAAU;IACzB,gEAAgE;IAChE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gCAAgC;IAChC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,oDAAoD;IACpD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,kDAAkD;IAClD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAsB1D;AA4BD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA2DvE"}
|
package/dist/specs/listSpecs.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* List + parse Hover-generated Playwright specs under `<devRoot>/__vibe_tests__/`.
|
|
3
|
-
*
|
|
4
|
-
* Used by:
|
|
5
|
-
* - The widget's "Specs" overlay tab (server pushes a SpecSummary[] list).
|
|
6
|
-
* - The CLI's `hover re-record <spec>` subcommand (parses one spec for its
|
|
7
|
-
* `Original prompt:` JSDoc header).
|
|
8
|
-
*
|
|
9
|
-
* Hand-authored specs (no Hover JSDoc header) are listed but reported with
|
|
10
|
-
* `originalPrompt: null` — the UI / CLI surfaces that "this spec can't be
|
|
11
|
-
* re-recorded automatically; the natural-language intent isn't recorded."
|
|
12
|
-
*
|
|
13
|
-
* Shares the SpecSummary row shape the widget's Specs tab renders.
|
|
14
|
-
*/
|
|
15
|
-
import { readdir, readFile } from 'node:fs/promises';
|
|
16
|
-
import { stat } from 'node:fs/promises';
|
|
17
|
-
import { existsSync } from 'node:fs';
|
|
18
|
-
import { join } from 'node:path';
|
|
19
|
-
import { countOptimizableMarkers } from './writeSpec.js';
|
|
20
|
-
import { readSeeds, relevantSeeds } from './seeds.js';
|
|
21
|
-
import { optimizationSuggestion } from './optimizationSuggestion.js';
|
|
22
|
-
/**
|
|
23
|
-
* Parse the JSDoc header that `writeSpec.ts` emits. Tolerant of:
|
|
24
|
-
* - Specs without any JSDoc (returns all-null).
|
|
25
|
-
* - Hand-edited specs where users reordered or trimmed sections.
|
|
26
|
-
* - Long prompts that wrap across lines (we take only the first line).
|
|
27
|
-
*/
|
|
28
|
-
export function parseSpecHeader(source) {
|
|
29
|
-
// JSDoc block right after the @playwright/test import (or at file top).
|
|
30
|
-
// We don't require it to be the very first JSDoc — there could be a
|
|
31
|
-
// banner comment from a linter. We DO require it to appear before the
|
|
32
|
-
// first `test(` / `test.describe(` so that long file footers can't
|
|
33
|
-
// confuse the parser.
|
|
34
|
-
const beforeFirstTest = source.split(/^\s*(?:test|test\.describe)\s*\(/m)[0] ?? source;
|
|
35
|
-
const blockMatch = beforeFirstTest.match(/\/\*\*([\s\S]*?)\*\//);
|
|
36
|
-
if (!blockMatch) {
|
|
37
|
-
return { originalPrompt: null, outcome: null, steps: [], expected: [] };
|
|
38
|
-
}
|
|
39
|
-
const block = blockMatch[1];
|
|
40
|
-
const originalPrompt = extractScalar(block, /^\s*\*\s*Original prompt:\s*(.+?)\s*$/m);
|
|
41
|
-
const outcome = extractScalar(block, /^\s*\*\s*Outcome:\s*(.+?)\s*$/m);
|
|
42
|
-
return {
|
|
43
|
-
originalPrompt,
|
|
44
|
-
outcome,
|
|
45
|
-
steps: extractList(block, /^\s*\*\s*Steps:\s*$/m),
|
|
46
|
-
expected: extractList(block, /^\s*\*\s*Expected:\s*$/m),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function extractScalar(block, re) {
|
|
50
|
-
const m = block.match(re);
|
|
51
|
-
return m ? m[1].trim() : null;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Extract a JSDoc list-style block. Given a header regex matching "Steps:"
|
|
55
|
-
* or "Expected:", read subsequent ` * <indented line>` lines until the next
|
|
56
|
-
* top-level marker (blank ` *` line or another `Section:` header).
|
|
57
|
-
*/
|
|
58
|
-
function extractList(block, headerRe) {
|
|
59
|
-
const match = block.match(headerRe);
|
|
60
|
-
if (!match)
|
|
61
|
-
return [];
|
|
62
|
-
const start = (match.index ?? 0) + match[0].length;
|
|
63
|
-
const tail = block.slice(start);
|
|
64
|
-
const lines = [];
|
|
65
|
-
for (const raw of tail.split('\n')) {
|
|
66
|
-
// Stop at a blank JSDoc line (` *` only) or another `Section:` header.
|
|
67
|
-
if (/^\s*\*\s*$/.test(raw))
|
|
68
|
-
break;
|
|
69
|
-
if (/^\s*\*\s*\w[\w ]*:\s*$/.test(raw) || /^\s*\*\s*\w[\w ]*:\s/.test(raw))
|
|
70
|
-
break;
|
|
71
|
-
const m = raw.match(/^\s*\*\s*(?:[•\-\*\d.]\s*)*(.+?)\s*$/);
|
|
72
|
-
if (m && m[1])
|
|
73
|
-
lines.push(m[1]);
|
|
74
|
-
}
|
|
75
|
-
return lines;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* List every `*.spec.ts` file under `<devRoot>/__vibe_tests__/` with its
|
|
79
|
-
* parsed header. Returns newest-first by mtime so the widget overlay shows
|
|
80
|
-
* recently-saved specs at the top.
|
|
81
|
-
*/
|
|
82
|
-
export async function listSpecs(devRoot) {
|
|
83
|
-
const root = join(devRoot, '__vibe_tests__');
|
|
84
|
-
let entries;
|
|
85
|
-
try {
|
|
86
|
-
entries = await readdir(root);
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return [];
|
|
90
|
-
}
|
|
91
|
-
// Seeds are devRoot-wide; read once and reuse for every spec's suggestion.
|
|
92
|
-
const seeds = await readSeeds(devRoot);
|
|
93
|
-
const summaries = [];
|
|
94
|
-
for (const entry of entries) {
|
|
95
|
-
if (!entry.endsWith('.spec.ts'))
|
|
96
|
-
continue;
|
|
97
|
-
const path = join(root, entry);
|
|
98
|
-
let content;
|
|
99
|
-
let mtimeMs = 0;
|
|
100
|
-
try {
|
|
101
|
-
content = await readFile(path, 'utf-8');
|
|
102
|
-
const st = await stat(path);
|
|
103
|
-
mtimeMs = st.mtimeMs;
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
const header = parseSpecHeader(content);
|
|
109
|
-
const slug = entry.replace(/\.spec\.ts$/, '');
|
|
110
|
-
const sidecarPath = join(root, '.hover', `${slug}.json`);
|
|
111
|
-
const hasSidecar = existsSync(sidecarPath);
|
|
112
|
-
const optimizableCount = countOptimizableMarkers(content);
|
|
113
|
-
// Which seeds could plausibly apply, from the sidecar's captured tools.
|
|
114
|
-
let relevantSeedNames = [];
|
|
115
|
-
if (hasSidecar && seeds.length > 0) {
|
|
116
|
-
try {
|
|
117
|
-
const sc = JSON.parse(await readFile(sidecarPath, 'utf-8'));
|
|
118
|
-
const tools = new Set((sc.steps ?? []).filter(s => s.kind === 'step' && s.tool).map(s => s.tool));
|
|
119
|
-
relevantSeedNames = relevantSeeds(seeds, tools).map(s => s.name);
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
/* malformed sidecar — treat as no relevant seeds */
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
summaries.push({
|
|
126
|
-
slug,
|
|
127
|
-
path,
|
|
128
|
-
originalPrompt: header.originalPrompt,
|
|
129
|
-
outcome: header.outcome,
|
|
130
|
-
stepCount: header.steps.length,
|
|
131
|
-
mtimeMs,
|
|
132
|
-
hasSidecar,
|
|
133
|
-
optimizableCount,
|
|
134
|
-
optimization: optimizationSuggestion({ hasSidecar, optimizableCount, relevantSeedNames }),
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
summaries.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
138
|
-
return summaries;
|
|
139
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The default-off "should we nudge the user to optimize this spec?" signal
|
|
3
|
-
* (F7 / D10). Optimization never runs automatically; instead, when a saved spec
|
|
4
|
-
* has a clear improvable shape, the widget surfaces a "review optimization?"
|
|
5
|
-
* prompt. This computes that decision + human-readable reasons.
|
|
6
|
-
*
|
|
7
|
-
* Pure function so it's trivially testable; `listSpecs` gathers the inputs
|
|
8
|
-
* (optimizable-marker count, sidecar presence, relevant seed names) and attaches
|
|
9
|
-
* the result to each SpecSummary.
|
|
10
|
-
*/
|
|
11
|
-
export interface OptimizationSuggestion {
|
|
12
|
-
/** Whether to nudge the user to run the optimization pass on this spec. */
|
|
13
|
-
suggested: boolean;
|
|
14
|
-
/** Human-readable reasons, for the widget tooltip / prompt. Empty when not
|
|
15
|
-
* suggested. */
|
|
16
|
-
reasons: string[];
|
|
17
|
-
}
|
|
18
|
-
export declare function optimizationSuggestion(args: {
|
|
19
|
-
/** Whether a `.hover/<slug>.json` sidecar exists. */
|
|
20
|
-
hasSidecar: boolean;
|
|
21
|
-
/** Count of `// hover:optimizable` markers in the spec. */
|
|
22
|
-
optimizableCount: number;
|
|
23
|
-
/** Names of seeds whose signature is relevant to this spec's tools. */
|
|
24
|
-
relevantSeedNames: string[];
|
|
25
|
-
}): OptimizationSuggestion;
|
|
26
|
-
//# sourceMappingURL=optimizationSuggestion.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"optimizationSuggestion.d.ts","sourceRoot":"","sources":["../../src/specs/optimizationSuggestion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,sBAAsB;IACrC,2EAA2E;IAC3E,SAAS,EAAE,OAAO,CAAC;IACnB;qBACiB;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,qDAAqD;IACrD,UAAU,EAAE,OAAO,CAAC;IACpB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B,GAAG,sBAAsB,CAqBzB"}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The default-off "should we nudge the user to optimize this spec?" signal
|
|
3
|
-
* (F7 / D10). Optimization never runs automatically; instead, when a saved spec
|
|
4
|
-
* has a clear improvable shape, the widget surfaces a "review optimization?"
|
|
5
|
-
* prompt. This computes that decision + human-readable reasons.
|
|
6
|
-
*
|
|
7
|
-
* Pure function so it's trivially testable; `listSpecs` gathers the inputs
|
|
8
|
-
* (optimizable-marker count, sidecar presence, relevant seed names) and attaches
|
|
9
|
-
* the result to each SpecSummary.
|
|
10
|
-
*/
|
|
11
|
-
export function optimizationSuggestion(args) {
|
|
12
|
-
const { hasSidecar, optimizableCount, relevantSeedNames } = args;
|
|
13
|
-
const reasons = [];
|
|
14
|
-
// The optimization pass reads the sidecar (observed feedback, captured steps);
|
|
15
|
-
// without one there's nothing to optimize from. Matches the widget's Optimize
|
|
16
|
-
// gate, so we never suggest what can't be acted on.
|
|
17
|
-
if (!hasSidecar)
|
|
18
|
-
return { suggested: false, reasons };
|
|
19
|
-
if (optimizableCount > 0) {
|
|
20
|
-
const n = optimizableCount;
|
|
21
|
-
reasons.push(`${n} interaction${n === 1 ? '' : 's'} couldn't be fully translated — the optimization pass can complete ${n === 1 ? 'it' : 'them'}`);
|
|
22
|
-
}
|
|
23
|
-
if (relevantSeedNames.length > 0) {
|
|
24
|
-
const k = relevantSeedNames.length;
|
|
25
|
-
reasons.push(`${k} seed${k === 1 ? '' : 's'} may apply: ${relevantSeedNames.join(', ')}`);
|
|
26
|
-
}
|
|
27
|
-
return { suggested: reasons.length > 0, reasons };
|
|
28
|
-
}
|