@hover-dev/core 0.16.0 → 0.18.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/engine.d.ts +28 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +27 -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/playwright/launchChrome.d.ts +18 -0
- package/dist/playwright/launchChrome.d.ts.map +1 -1
- package/dist/playwright/launchChrome.js +46 -3
- 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/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/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/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/pageObjectManifest.d.ts +3 -1
- package/dist/specs/pageObjectManifest.d.ts.map +1 -1
- package/dist/specs/pageObjectManifest.js +24 -19
- 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 +9 -29
- 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/argv.d.ts +0 -11
- package/dist/agents/argv.d.ts.map +0 -1
- package/dist/agents/argv.js +0 -23
- package/dist/agents/claude.d.ts +0 -3
- package/dist/agents/claude.d.ts.map +0 -1
- package/dist/agents/claude.js +0 -195
- package/dist/agents/codex.d.ts +0 -19
- package/dist/agents/codex.d.ts.map +0 -1
- package/dist/agents/codex.js +0 -216
- 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/agents/detect.d.ts +0 -46
- package/dist/agents/detect.d.ts.map +0 -1
- package/dist/agents/detect.js +0 -80
- package/dist/agents/gemini.d.ts +0 -17
- package/dist/agents/gemini.d.ts.map +0 -1
- package/dist/agents/gemini.js +0 -186
- package/dist/agents/index.d.ts +0 -6
- package/dist/agents/index.d.ts.map +0 -1
- package/dist/agents/index.js +0 -5
- package/dist/agents/invoke.d.ts +0 -12
- package/dist/agents/invoke.d.ts.map +0 -1
- package/dist/agents/invoke.js +0 -96
- package/dist/agents/qwen.d.ts +0 -17
- package/dist/agents/qwen.d.ts.map +0 -1
- package/dist/agents/qwen.js +0 -172
- package/dist/agents/registry.d.ts +0 -19
- package/dist/agents/registry.d.ts.map +0 -1
- package/dist/agents/registry.js +0 -34
- package/dist/agents/shared.d.ts +0 -28
- package/dist/agents/shared.d.ts.map +0 -1
- package/dist/agents/shared.js +0 -35
- package/dist/agents/types.d.ts +0 -186
- package/dist/agents/types.d.ts.map +0 -1
- package/dist/agents/types.js +0 -23
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/mcp/sourceFence.d.ts +0 -23
- package/dist/mcp/sourceFence.d.ts.map +0 -1
- package/dist/mcp/sourceFence.js +0 -75
- package/dist/mcp/sourceServer.d.ts +0 -3
- package/dist/mcp/sourceServer.d.ts.map +0 -1
- package/dist/mcp/sourceServer.js +0 -116
- package/dist/playwright/cdpStatus.d.ts +0 -29
- package/dist/playwright/cdpStatus.d.ts.map +0 -1
- package/dist/playwright/cdpStatus.js +0 -119
- package/dist/playwright/preflight.d.ts +0 -31
- package/dist/playwright/preflight.d.ts.map +0 -1
- package/dist/playwright/preflight.js +0 -82
- package/dist/playwright/preflightCache.d.ts +0 -27
- package/dist/playwright/preflightCache.d.ts.map +0 -1
- package/dist/playwright/preflightCache.js +0 -21
- 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/playwright/resolveMcpConfig.d.ts +0 -55
- package/dist/playwright/resolveMcpConfig.d.ts.map +0 -1
- package/dist/playwright/resolveMcpConfig.js +0 -66
- package/dist/plugin-api.d.ts +0 -235
- package/dist/plugin-api.d.ts.map +0 -1
- package/dist/plugin-api.js +0 -52
- package/dist/runSession.d.ts +0 -42
- package/dist/runSession.d.ts.map +0 -1
- package/dist/runSession.js +0 -81
- 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/service/cdpHandlers.d.ts +0 -44
- package/dist/service/cdpHandlers.d.ts.map +0 -1
- package/dist/service/cdpHandlers.js +0 -85
- package/dist/service/cdpHint.d.ts +0 -48
- package/dist/service/cdpHint.d.ts.map +0 -1
- package/dist/service/cdpHint.js +0 -216
- package/dist/service/conventions.d.ts +0 -8
- package/dist/service/conventions.d.ts.map +0 -1
- package/dist/service/conventions.js +0 -42
- package/dist/service/saveHandlers.d.ts +0 -52
- package/dist/service/saveHandlers.d.ts.map +0 -1
- package/dist/service/saveHandlers.js +0 -75
- package/dist/service/types.d.ts +0 -58
- package/dist/service/types.d.ts.map +0 -1
- package/dist/service/types.js +0 -26
- package/dist/service.d.ts +0 -50
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -1065
- 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/extractPageObjects.d.ts +0 -18
- package/dist/specs/extractPageObjects.d.ts.map +0 -1
- package/dist/specs/extractPageObjects.js +0 -98
- 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/optimizeSpecWithAgent.d.ts +0 -11
- package/dist/specs/optimizeSpecWithAgent.d.ts.map +0 -1
- package/dist/specs/optimizeSpecWithAgent.js +0 -40
- package/dist/specs/writeCaseCsv.d.ts +0 -28
- package/dist/specs/writeCaseCsv.d.ts.map +0 -1
- package/dist/specs/writeCaseCsv.js +0 -134
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session ledger — one summary JSON per completed agent run, appended under
|
|
3
|
+
* `.hover/sessions/`. The local console (S3) reads these for run history +
|
|
4
|
+
* spend; Hover Cloud sync (S4) uploads them as-is.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately summary-only: full `SkillStep[]` lives in the spec sidecar for
|
|
7
|
+
* saved sessions and is dropped for unsaved ones (persisting unsaved
|
|
8
|
+
* transcripts is a privacy decision deferred to a future opt-in).
|
|
9
|
+
*
|
|
10
|
+
* Writes are best-effort: a ledger failure must never break a run or a save.
|
|
11
|
+
*/
|
|
12
|
+
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { runDir, conversationsDir } from '../specs/sidecar.js';
|
|
15
|
+
export const SESSION_RECORD_VERSION = 2;
|
|
16
|
+
/** Unescape literal "\n" / "\r\n" / "\t" sequences (e.g. an agent double-escaped
|
|
17
|
+
* its newlines) into real whitespace so markdown renders properly. */
|
|
18
|
+
function deEsc(s) {
|
|
19
|
+
return s.replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n').replace(/\\t/g, ' ');
|
|
20
|
+
}
|
|
21
|
+
/** Defensive leak-guard, NOT a parse path. The agent is directed to emit a
|
|
22
|
+
* plain-markdown report (REPORTING_DIRECTIVE) — no JSON. A non-compliant agent
|
|
23
|
+
* that still wraps its report in a ```json block would otherwise leak raw JSON
|
|
24
|
+
* to the UI, so strip it: recover the `summary` field as prose when present
|
|
25
|
+
* (tolerating unescaped quotes by matching up to `","findings"`), else drop the
|
|
26
|
+
* block. Findings are NEVER extracted from JSON — they come only from the
|
|
27
|
+
* markdown `## Findings` section below. */
|
|
28
|
+
function stripJsonArtifact(summary) {
|
|
29
|
+
const block = summary.match(/```json\s*([\s\S]*?)```/i);
|
|
30
|
+
if (!block)
|
|
31
|
+
return summary;
|
|
32
|
+
const sm = block[1].match(/"summary"\s*:\s*"([\s\S]*?)"\s*,\s*"findings"/i);
|
|
33
|
+
if (sm)
|
|
34
|
+
return deEsc(sm[1].replace(/\\"/g, '"')).trim();
|
|
35
|
+
return summary.replace(block[0], '').replace(/\n{3,}/g, '\n\n').trim();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Strip leaked function-call syntax a model sometimes emits as TEXT instead of
|
|
39
|
+
* actually invoking the tool — e.g. a final summary that ends with
|
|
40
|
+
* `call\n<invoke name="mcp__playwright__browser_wait_for">…</invoke>`. The model
|
|
41
|
+
* "writes out" the call (a known tool-calling glitch, common at end-of-turn /
|
|
42
|
+
* budget cap) and the parser renders it verbatim into the report + Done card.
|
|
43
|
+
* This keeps user-facing prose about the APP, not Hover's tooling
|
|
44
|
+
* (REPORTING_DIRECTIVE). Defensive + total: any agent can trip this.
|
|
45
|
+
*/
|
|
46
|
+
export function stripToolCallNoise(text) {
|
|
47
|
+
return text
|
|
48
|
+
.replace(/<function_calls>[\s\S]*?<\/function_calls>/gi, '') // wrapper form
|
|
49
|
+
.replace(/<invoke\b[\s\S]*?<\/invoke>/gi, '') // closed call block
|
|
50
|
+
.replace(/<invoke\b[\s\S]*$/gi, '') // dangling (truncated) call
|
|
51
|
+
.replace(/<parameter\b[\s\S]*?<\/parameter>/gi, '') // stray parameter
|
|
52
|
+
.replace(/^[ \t]*call[ \t]*$/gim, '') // lone "call" lead-in line
|
|
53
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
54
|
+
.trim();
|
|
55
|
+
}
|
|
56
|
+
/** Markdown-forced: the agent emits a plain-markdown report (REPORTING_DIRECTIVE)
|
|
57
|
+
* — ONE outcome line, `- ` bullets, and an optional `## Findings` section with
|
|
58
|
+
* `- **severity** — text` items. Parse the summary + findings from that markdown
|
|
59
|
+
* only; a stray ```json block (a non-compliant agent) is stripped, never parsed
|
|
60
|
+
* for findings and never leaked. Pure + total — no Findings block yields none. */
|
|
61
|
+
export function parseFindings(summary) {
|
|
62
|
+
const cleaned = stripToolCallNoise(stripJsonArtifact(summary));
|
|
63
|
+
const lines = cleaned.split('\n');
|
|
64
|
+
let hi = -1;
|
|
65
|
+
for (let i = 0; i < lines.length; i++) {
|
|
66
|
+
const t = lines[i].trim();
|
|
67
|
+
if (/^#{1,6}\s*(findings|bugs|issues)\b/i.test(t) || /^findings\s*:/i.test(t)) {
|
|
68
|
+
hi = i;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (hi < 0)
|
|
73
|
+
return { summary: cleaned.trim(), findings: [] };
|
|
74
|
+
let j = hi + 1;
|
|
75
|
+
while (j < lines.length && lines[j].trim() === '')
|
|
76
|
+
j++;
|
|
77
|
+
const start = j;
|
|
78
|
+
while (j < lines.length && /^\s*[-*]\s+/.test(lines[j]))
|
|
79
|
+
j++;
|
|
80
|
+
const bullets = lines.slice(start, j);
|
|
81
|
+
const findings = [];
|
|
82
|
+
for (const line of bullets) {
|
|
83
|
+
const m = line.match(/^\s*[-*]\s+(?:\*\*\s*([^*]+?)\s*\*\*\s*[—–:-]?\s*)?([\s\S]+)$/);
|
|
84
|
+
if (!m)
|
|
85
|
+
continue;
|
|
86
|
+
const text = (m[2] || '').trim();
|
|
87
|
+
if (!text)
|
|
88
|
+
continue;
|
|
89
|
+
findings.push({ severity: (m[1] || 'note').trim(), text });
|
|
90
|
+
}
|
|
91
|
+
const main = lines.slice(0, hi).concat(lines.slice(j)).join('\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
92
|
+
return { summary: main, findings };
|
|
93
|
+
}
|
|
94
|
+
/** Count tool_use steps by tool name for the `toolCounts` field. */
|
|
95
|
+
export function tallyTools(steps) {
|
|
96
|
+
const counts = {};
|
|
97
|
+
for (const s of steps) {
|
|
98
|
+
if (s.kind !== 'step' || !s.tool)
|
|
99
|
+
continue;
|
|
100
|
+
counts[s.tool] = (counts[s.tool] ?? 0) + 1;
|
|
101
|
+
}
|
|
102
|
+
return counts;
|
|
103
|
+
}
|
|
104
|
+
/** Write one session record as `<runDir>/meta.json`. The id (runId) + the
|
|
105
|
+
* conversation are decided by the caller at run start (so screenshots + report
|
|
106
|
+
* share the folder). NEVER throws; returns the path or an error string. */
|
|
107
|
+
export async function writeSessionRecord(devRoot, conversationId, runId, rec) {
|
|
108
|
+
try {
|
|
109
|
+
const dir = runDir(devRoot, conversationId, runId);
|
|
110
|
+
await mkdir(dir, { recursive: true });
|
|
111
|
+
const record = { version: SESSION_RECORD_VERSION, id: runId, conversationId, ...rec };
|
|
112
|
+
const path = join(dir, 'meta.json');
|
|
113
|
+
await writeFile(path, JSON.stringify(record, null, 2) + '\n', 'utf-8');
|
|
114
|
+
return { path, id: runId };
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** List every run's meta.json across all conversations: `.hover/runs/<conv>/<run>/meta.json`.
|
|
121
|
+
* Best-effort; returns [] if no runs yet. */
|
|
122
|
+
export async function listSessionRecords(devRoot) {
|
|
123
|
+
const out = [];
|
|
124
|
+
const root = conversationsDir(devRoot);
|
|
125
|
+
let convs;
|
|
126
|
+
try {
|
|
127
|
+
convs = await readdir(root);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
for (const conv of convs) {
|
|
133
|
+
let runIds;
|
|
134
|
+
try {
|
|
135
|
+
runIds = await readdir(join(root, conv));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
for (const rid of runIds) {
|
|
141
|
+
const path = join(root, conv, rid, 'meta.json');
|
|
142
|
+
try {
|
|
143
|
+
out.push({ path, rec: JSON.parse(await readFile(path, 'utf-8')) });
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
/* not a run dir / unreadable — skip */
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Mark the session that produced `promptText` as crystallized: find the most
|
|
154
|
+
* recent record matching the prompt that has no `specSlug` yet, set
|
|
155
|
+
* `outcome: 'saved'` + the slug. Save-as-spec arrives as a separate WS message
|
|
156
|
+
* after the run record was already written, so this is a patch, keyed on the
|
|
157
|
+
* prompt (the `user` seed step) — tolerant by design; a miss is a no-op.
|
|
158
|
+
* NEVER throws.
|
|
159
|
+
*/
|
|
160
|
+
export async function markSessionSaved(devRoot, promptText, specSlug) {
|
|
161
|
+
try {
|
|
162
|
+
const records = (await listSessionRecords(devRoot)).sort((a, b) => String(b.rec.startedAt).localeCompare(String(a.rec.startedAt)));
|
|
163
|
+
for (const { path, rec } of records) {
|
|
164
|
+
if (rec.specSlug || rec.prompt !== promptText)
|
|
165
|
+
continue;
|
|
166
|
+
rec.outcome = 'saved';
|
|
167
|
+
rec.specSlug = specSlug;
|
|
168
|
+
await writeFile(path, JSON.stringify(rec, null, 2) + '\n', 'utf-8');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
/* no ledger yet / unreadable — fine */
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SkillStep } from './specStep.js';
|
|
2
|
+
/**
|
|
3
|
+
* Length of the leading login prefix among `actions` (a spec's tool steps,
|
|
4
|
+
* POST-redaction). The login flow = the steps up to AND INCLUDING the submit
|
|
5
|
+
* click that follows the LAST credential fill (e.g. navigate → type email →
|
|
6
|
+
* type password → click "Sign in"). `envVars` are the redaction env-var names.
|
|
7
|
+
*
|
|
8
|
+
* Returns 0 when there are no redacted credentials, or none are filled in the
|
|
9
|
+
* steps — so a spec with no login keeps today's inline behavior unchanged (no
|
|
10
|
+
* regression). The caller slices `actions[0..N)` as the auth prefix and
|
|
11
|
+
* `actions[N..]` as the business flow.
|
|
12
|
+
*/
|
|
13
|
+
export declare function authPrefixLength(actions: SkillStep[], envVars: readonly string[]): number;
|
|
14
|
+
/**
|
|
15
|
+
* Stage 4a — propose the playwright.config edit that registers the auth-fixture
|
|
16
|
+
* setup project. AST-based (ts-morph) so it only reprints what it touches and
|
|
17
|
+
* preserves the user's formatting. Adds:
|
|
18
|
+
*
|
|
19
|
+
* projects: [
|
|
20
|
+
* { name: 'setup', testMatch: /.*\.setup\.ts$/ },
|
|
21
|
+
* { name: 'chromium', dependencies: ['setup'] },
|
|
22
|
+
* ]
|
|
23
|
+
*
|
|
24
|
+
* Returns the edited source, or null when it can't safely edit — no config
|
|
25
|
+
* object found, or `projects` ALREADY exists (merging into a user's project
|
|
26
|
+
* matrix is risky; the caller degrades to the static paste hint instead). The
|
|
27
|
+
* edit is never applied here; the caller shows it for approval first.
|
|
28
|
+
*/
|
|
29
|
+
export declare function addSetupProjectToConfig(source: string): string | null;
|
|
30
|
+
//# sourceMappingURL=authFixture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authFixture.d.ts","sourceRoot":"","sources":["../../src/specs/authFixture.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAiC/C;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAazF;AA0BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoBrE"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth-as-fixture (crystallization debt 3) — login-prefix detection.
|
|
3
|
+
*
|
|
4
|
+
* Today a recorded login is crystallized INLINE into every spec and re-run
|
|
5
|
+
* through the UI each test. The fix is to lift the login into a Playwright setup
|
|
6
|
+
* project that authenticates ONCE, save `storageState`, and have specs start
|
|
7
|
+
* already authenticated. The first, pure step is detecting which leading steps
|
|
8
|
+
* ARE the login flow — done here so it can be unit-tested in isolation, with no
|
|
9
|
+
* codegen changes (those land in later stages).
|
|
10
|
+
*
|
|
11
|
+
* Signal: `redactSteps` (writeSpec.ts) already rewrites credential values to
|
|
12
|
+
* `process.env.<envVar> ?? ''`, so the credential-bearing steps are exactly the
|
|
13
|
+
* fills whose value references one of the run's redaction env vars. The login
|
|
14
|
+
* prefix is the run of steps up to AND INCLUDING the submit click that follows
|
|
15
|
+
* the last credential fill.
|
|
16
|
+
*
|
|
17
|
+
* See docs/superpowers/specs/2026-06-24-auth-as-fixture.md.
|
|
18
|
+
*/
|
|
19
|
+
import { Project, SyntaxKind, Node } from 'ts-morph';
|
|
20
|
+
const CLICK_TOOLS = new Set(['browser_click', 'click_control']);
|
|
21
|
+
/** Bare tool name — grounded steps arrive as `mcp__hover-control__click_control`,
|
|
22
|
+
* playwright ones as bare `browser_click`. */
|
|
23
|
+
const bareTool = (t) => (t ?? '').replace(/^mcp__[a-z0-9_-]+?__/, '');
|
|
24
|
+
/** The string values a fill-type action writes, across the tool variants
|
|
25
|
+
* (browser_type / fill_control / select_control / browser_fill_form). */
|
|
26
|
+
function fillValues(step) {
|
|
27
|
+
if (step.kind !== 'step' || !step.input)
|
|
28
|
+
return [];
|
|
29
|
+
const input = step.input;
|
|
30
|
+
const out = [];
|
|
31
|
+
if (typeof input.text === 'string')
|
|
32
|
+
out.push(input.text); // browser_type
|
|
33
|
+
if (typeof input.value === 'string')
|
|
34
|
+
out.push(input.value); // fill_control / select_control
|
|
35
|
+
if (Array.isArray(input.fields)) {
|
|
36
|
+
// browser_fill_form
|
|
37
|
+
for (const f of input.fields) {
|
|
38
|
+
if (f && typeof f.value === 'string')
|
|
39
|
+
out.push(f.value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/** True when an action fills one of the redacted credential env refs. `actions`
|
|
45
|
+
* are POST-redaction, so a credential value reads `process.env.<envVar> ?? ''`. */
|
|
46
|
+
function fillsCredential(step, envVars) {
|
|
47
|
+
if (!envVars.length)
|
|
48
|
+
return false;
|
|
49
|
+
const values = fillValues(step);
|
|
50
|
+
return values.some((v) => envVars.some((name) => v.includes(`process.env.${name}`)));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Length of the leading login prefix among `actions` (a spec's tool steps,
|
|
54
|
+
* POST-redaction). The login flow = the steps up to AND INCLUDING the submit
|
|
55
|
+
* click that follows the LAST credential fill (e.g. navigate → type email →
|
|
56
|
+
* type password → click "Sign in"). `envVars` are the redaction env-var names.
|
|
57
|
+
*
|
|
58
|
+
* Returns 0 when there are no redacted credentials, or none are filled in the
|
|
59
|
+
* steps — so a spec with no login keeps today's inline behavior unchanged (no
|
|
60
|
+
* regression). The caller slices `actions[0..N)` as the auth prefix and
|
|
61
|
+
* `actions[N..]` as the business flow.
|
|
62
|
+
*/
|
|
63
|
+
export function authPrefixLength(actions, envVars) {
|
|
64
|
+
if (!envVars.length)
|
|
65
|
+
return 0;
|
|
66
|
+
let lastCred = -1;
|
|
67
|
+
for (let i = 0; i < actions.length; i++) {
|
|
68
|
+
if (fillsCredential(actions[i], envVars))
|
|
69
|
+
lastCred = i;
|
|
70
|
+
}
|
|
71
|
+
if (lastCred < 0)
|
|
72
|
+
return 0;
|
|
73
|
+
// Extend through the submit click immediately after the last credential fill
|
|
74
|
+
// (the "Sign in" button). A non-click next step means login auto-submitted (or
|
|
75
|
+
// we've already moved into the app), so stop at the fill — don't over-capture.
|
|
76
|
+
const next = actions[lastCred + 1];
|
|
77
|
+
if (next && CLICK_TOOLS.has(bareTool(next.tool)))
|
|
78
|
+
return lastCred + 2;
|
|
79
|
+
return lastCred + 1;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Locate the Playwright config object literal — the argument of `defineConfig({…})`
|
|
83
|
+
* or a bare `export default {…}` — so the setup project can be inserted into it.
|
|
84
|
+
*/
|
|
85
|
+
function findConfigObject(sf) {
|
|
86
|
+
const def = sf.getExportAssignment((d) => !d.isExportEquals());
|
|
87
|
+
const expr = def?.getExpression();
|
|
88
|
+
if (expr) {
|
|
89
|
+
if (Node.isObjectLiteralExpression(expr))
|
|
90
|
+
return expr;
|
|
91
|
+
if (Node.isCallExpression(expr)) {
|
|
92
|
+
const arg = expr.getArguments()[0];
|
|
93
|
+
if (arg && Node.isObjectLiteralExpression(arg))
|
|
94
|
+
return arg;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Fallback: a defineConfig(...) call anywhere in the file.
|
|
98
|
+
for (const call of sf.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
99
|
+
if (call.getExpression().getText() === 'defineConfig') {
|
|
100
|
+
const arg = call.getArguments()[0];
|
|
101
|
+
if (arg && Node.isObjectLiteralExpression(arg))
|
|
102
|
+
return arg;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Stage 4a — propose the playwright.config edit that registers the auth-fixture
|
|
109
|
+
* setup project. AST-based (ts-morph) so it only reprints what it touches and
|
|
110
|
+
* preserves the user's formatting. Adds:
|
|
111
|
+
*
|
|
112
|
+
* projects: [
|
|
113
|
+
* { name: 'setup', testMatch: /.*\.setup\.ts$/ },
|
|
114
|
+
* { name: 'chromium', dependencies: ['setup'] },
|
|
115
|
+
* ]
|
|
116
|
+
*
|
|
117
|
+
* Returns the edited source, or null when it can't safely edit — no config
|
|
118
|
+
* object found, or `projects` ALREADY exists (merging into a user's project
|
|
119
|
+
* matrix is risky; the caller degrades to the static paste hint instead). The
|
|
120
|
+
* edit is never applied here; the caller shows it for approval first.
|
|
121
|
+
*/
|
|
122
|
+
export function addSetupProjectToConfig(source) {
|
|
123
|
+
try {
|
|
124
|
+
const project = new Project({ useInMemoryFileSystem: true, compilerOptions: { allowJs: true } });
|
|
125
|
+
const sf = project.createSourceFile('__pwconfig.ts', source, { overwrite: true });
|
|
126
|
+
const obj = findConfigObject(sf);
|
|
127
|
+
if (!obj)
|
|
128
|
+
return null;
|
|
129
|
+
if (obj.getProperty('projects'))
|
|
130
|
+
return null; // user already manages projects — don't risk it
|
|
131
|
+
obj.addPropertyAssignment({
|
|
132
|
+
name: 'projects',
|
|
133
|
+
initializer: [
|
|
134
|
+
'[',
|
|
135
|
+
" { name: 'setup', testMatch: /.*\\.setup\\.ts$/ },",
|
|
136
|
+
" { name: 'chromium', dependencies: ['setup'] },",
|
|
137
|
+
' ]',
|
|
138
|
+
].join('\n'),
|
|
139
|
+
});
|
|
140
|
+
return sf.getFullText();
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detectSharedFlows.d.ts","sourceRoot":"","sources":["../../src/specs/detectSharedFlows.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"detectSharedFlows.d.ts","sourceRoot":"","sources":["../../src/specs/detectSharedFlows.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;2CACuC;IACvC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yDAAyD;IACzD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;wDACoD;IACpD,WAAW,EAAE,SAAS,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B;2EACuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;sDACkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgC5E;AAwDD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,UAAU,EAAE,CAAC,CAiCvB"}
|
|
@@ -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
|
|
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
|
|
64
|
-
*
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 '../
|
|
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,
|
|
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
|
-
*
|
|
6
|
-
*
|
|
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 '../
|
|
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
|
|
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"}
|
package/dist/specs/humanSteps.js
CHANGED
|
@@ -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,
|
|
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"}
|