@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.
Files changed (154) hide show
  1. package/README.md +26 -55
  2. package/dist/agentDirectives.d.ts +55 -0
  3. package/dist/agentDirectives.d.ts.map +1 -0
  4. package/dist/agentDirectives.js +276 -0
  5. package/dist/agents/claude.d.ts.map +1 -1
  6. package/dist/agents/claude.js +28 -3
  7. package/dist/agents/codex.d.ts.map +1 -1
  8. package/dist/agents/codex.js +29 -14
  9. package/dist/agents/invoke.d.ts.map +1 -1
  10. package/dist/agents/invoke.js +3 -6
  11. package/dist/agents/registry.d.ts.map +1 -1
  12. package/dist/agents/registry.js +0 -4
  13. package/dist/agents/types.d.ts +19 -11
  14. package/dist/agents/types.d.ts.map +1 -1
  15. package/dist/engine.d.ts +53 -0
  16. package/dist/engine.d.ts.map +1 -0
  17. package/dist/engine.js +78 -0
  18. package/dist/mcp/actuateServer.d.ts +3 -0
  19. package/dist/mcp/actuateServer.d.ts.map +1 -0
  20. package/dist/mcp/actuateServer.js +594 -0
  21. package/dist/mcp/sourceFence.d.ts.map +1 -1
  22. package/dist/mcp/sourceFence.js +4 -0
  23. package/dist/mcp/sourceServer.js +75 -0
  24. package/dist/memory/businessMemory.d.ts +29 -0
  25. package/dist/memory/businessMemory.d.ts.map +1 -0
  26. package/dist/memory/businessMemory.js +125 -0
  27. package/dist/modes.d.ts +39 -0
  28. package/dist/modes.d.ts.map +1 -0
  29. package/dist/modes.js +34 -0
  30. package/dist/playwright/cdpStatus.d.ts +0 -15
  31. package/dist/playwright/cdpStatus.d.ts.map +1 -1
  32. package/dist/playwright/cdpStatus.js +0 -67
  33. package/dist/playwright/launchChrome.d.ts +18 -0
  34. package/dist/playwright/launchChrome.d.ts.map +1 -1
  35. package/dist/playwright/launchChrome.js +46 -3
  36. package/dist/playwright/resolveMcpConfig.d.ts +7 -1
  37. package/dist/playwright/resolveMcpConfig.d.ts.map +1 -1
  38. package/dist/playwright/resolveMcpConfig.js +22 -4
  39. package/dist/plugin-api.d.ts +28 -26
  40. package/dist/plugin-api.d.ts.map +1 -1
  41. package/dist/plugin-api.js +2 -2
  42. package/dist/qa/candidates.d.ts +32 -0
  43. package/dist/qa/candidates.d.ts.map +1 -0
  44. package/dist/qa/candidates.js +20 -0
  45. package/dist/qa/classify.d.ts +38 -0
  46. package/dist/qa/classify.d.ts.map +1 -0
  47. package/dist/qa/classify.js +138 -0
  48. package/dist/qa/intensity.d.ts +33 -0
  49. package/dist/qa/intensity.d.ts.map +1 -0
  50. package/dist/qa/intensity.js +25 -0
  51. package/dist/qa/qaReport.d.ts +19 -0
  52. package/dist/qa/qaReport.d.ts.map +1 -0
  53. package/dist/qa/qaReport.js +50 -0
  54. package/dist/runSession.d.ts +14 -3
  55. package/dist/runSession.d.ts.map +1 -1
  56. package/dist/runSession.js +26 -11
  57. package/dist/service/cdpHandlers.d.ts +1 -21
  58. package/dist/service/cdpHandlers.d.ts.map +1 -1
  59. package/dist/service/cdpHandlers.js +4 -39
  60. package/dist/service/cdpHint.d.ts +21 -28
  61. package/dist/service/cdpHint.d.ts.map +1 -1
  62. package/dist/service/cdpHint.js +106 -164
  63. package/dist/service/relayHandlers.d.ts +28 -0
  64. package/dist/service/relayHandlers.d.ts.map +1 -0
  65. package/dist/service/relayHandlers.js +105 -0
  66. package/dist/service/saveHandlers.d.ts +1 -3
  67. package/dist/service/saveHandlers.d.ts.map +1 -1
  68. package/dist/service/saveHandlers.js +17 -15
  69. package/dist/service/types.d.ts +108 -8
  70. package/dist/service/types.d.ts.map +1 -1
  71. package/dist/service.d.ts +7 -3
  72. package/dist/service.d.ts.map +1 -1
  73. package/dist/service.js +907 -200
  74. package/dist/sessions/sessions.d.ts +125 -0
  75. package/dist/sessions/sessions.d.ts.map +1 -0
  76. package/dist/sessions/sessions.js +175 -0
  77. package/dist/specs/authFixture.d.ts +30 -0
  78. package/dist/specs/authFixture.d.ts.map +1 -0
  79. package/dist/specs/authFixture.js +145 -0
  80. package/dist/specs/businessMap.d.ts +29 -0
  81. package/dist/specs/businessMap.d.ts.map +1 -0
  82. package/dist/specs/businessMap.js +95 -0
  83. package/dist/specs/detectSharedFlows.d.ts +1 -1
  84. package/dist/specs/detectSharedFlows.d.ts.map +1 -1
  85. package/dist/specs/detectSharedFlows.js +20 -21
  86. package/dist/specs/generatePageObject.d.ts +1 -1
  87. package/dist/specs/generatePageObject.d.ts.map +1 -1
  88. package/dist/specs/healPrompt.d.ts +19 -0
  89. package/dist/specs/healPrompt.d.ts.map +1 -0
  90. package/dist/specs/healPrompt.js +48 -0
  91. package/dist/specs/humanSteps.d.ts +4 -8
  92. package/dist/specs/humanSteps.d.ts.map +1 -1
  93. package/dist/specs/humanSteps.js +6 -1
  94. package/dist/specs/optimizeSpec.d.ts +15 -8
  95. package/dist/specs/optimizeSpec.d.ts.map +1 -1
  96. package/dist/specs/optimizeSpec.js +71 -41
  97. package/dist/specs/optimizeSpecWithAgent.d.ts +0 -2
  98. package/dist/specs/optimizeSpecWithAgent.d.ts.map +1 -1
  99. package/dist/specs/optimizeSpecWithAgent.js +0 -1
  100. package/dist/specs/pageObjectManifest.d.ts +3 -1
  101. package/dist/specs/pageObjectManifest.d.ts.map +1 -1
  102. package/dist/specs/pageObjectManifest.js +13 -9
  103. package/dist/specs/replayGrounded.d.ts +45 -0
  104. package/dist/specs/replayGrounded.d.ts.map +1 -0
  105. package/dist/specs/replayGrounded.js +155 -0
  106. package/dist/specs/runFailures.d.ts +34 -0
  107. package/dist/specs/runFailures.d.ts.map +1 -0
  108. package/dist/specs/runFailures.js +93 -0
  109. package/dist/specs/seeds.d.ts +16 -15
  110. package/dist/specs/seeds.d.ts.map +1 -1
  111. package/dist/specs/seeds.js +86 -54
  112. package/dist/specs/sidecar.d.ts +34 -6
  113. package/dist/specs/sidecar.d.ts.map +1 -1
  114. package/dist/specs/sidecar.js +79 -9
  115. package/dist/specs/specStep.d.ts +21 -0
  116. package/dist/specs/specStep.d.ts.map +1 -0
  117. package/dist/specs/specStep.js +1 -0
  118. package/dist/specs/text.d.ts +8 -6
  119. package/dist/specs/text.d.ts.map +1 -1
  120. package/dist/specs/text.js +10 -7
  121. package/dist/specs/writeSpec.d.ts +62 -1
  122. package/dist/specs/writeSpec.d.ts.map +1 -1
  123. package/dist/specs/writeSpec.js +596 -21
  124. package/package.json +6 -9
  125. package/dist/agents/aider.d.ts +0 -16
  126. package/dist/agents/aider.d.ts.map +0 -1
  127. package/dist/agents/aider.js +0 -161
  128. package/dist/agents/cursor.d.ts +0 -18
  129. package/dist/agents/cursor.d.ts.map +0 -1
  130. package/dist/agents/cursor.js +0 -220
  131. package/dist/playwright/raiseWindow.d.ts +0 -10
  132. package/dist/playwright/raiseWindow.d.ts.map +0 -1
  133. package/dist/playwright/raiseWindow.js +0 -158
  134. package/dist/scripts/bench-multi-tab.d.ts +0 -2
  135. package/dist/scripts/bench-multi-tab.d.ts.map +0 -1
  136. package/dist/scripts/bench-multi-tab.js +0 -192
  137. package/dist/scripts/bench-ttfb.d.ts +0 -2
  138. package/dist/scripts/bench-ttfb.d.ts.map +0 -1
  139. package/dist/scripts/bench-ttfb.js +0 -127
  140. package/dist/scripts/start-chrome.d.ts +0 -3
  141. package/dist/scripts/start-chrome.d.ts.map +0 -1
  142. package/dist/scripts/start-chrome.js +0 -23
  143. package/dist/skills/writeSkill.d.ts +0 -27
  144. package/dist/skills/writeSkill.d.ts.map +0 -1
  145. package/dist/skills/writeSkill.js +0 -13
  146. package/dist/specs/listSpecs.d.ts +0 -52
  147. package/dist/specs/listSpecs.d.ts.map +0 -1
  148. package/dist/specs/listSpecs.js +0 -139
  149. package/dist/specs/optimizationSuggestion.d.ts +0 -26
  150. package/dist/specs/optimizationSuggestion.d.ts.map +0 -1
  151. package/dist/specs/optimizationSuggestion.js +0 -28
  152. package/dist/specs/writeCaseCsv.d.ts +0 -28
  153. package/dist/specs/writeCaseCsv.d.ts.map +0 -1
  154. package/dist/specs/writeCaseCsv.js +0 -134
@@ -0,0 +1,125 @@
1
+ export declare const SESSION_RECORD_VERSION = 2;
2
+ /** One agent-reported finding (the ## Findings block), persisted so the
3
+ * ledger becomes a reusable findings log — not just a run-history list.
4
+ * Severity is the raw marker the agent emitted (Bug / Minor / Info / …);
5
+ * readers normalise it for display. */
6
+ export interface SessionFinding {
7
+ severity: string;
8
+ text: string;
9
+ /** Optional short headline (from the structured JSON findings block). */
10
+ title?: string;
11
+ /** Endpoint / method when the finding is about an API call — used to
12
+ * crystallize a request-based regression later. */
13
+ endpoint?: string;
14
+ method?: string;
15
+ }
16
+ export interface SessionRecord {
17
+ /** Bumped to 2 when the reproducibility + outcome fields below were added.
18
+ * Readers must tolerate v1 records (every new field is optional). */
19
+ version: number;
20
+ /** `<sanitized-startedAt>-<rand>` — the runId, generated at run start; names
21
+ * the run folder `.hover/runs/<conversationId>/<id>/` and is the meta.json id. */
22
+ id: string;
23
+ /** The chat conversation this run belongs to — the folder it's grouped under,
24
+ * so deleting a conversation removes all its runs. */
25
+ conversationId?: string;
26
+ startedAt: string;
27
+ endedAt: string;
28
+ /** Real wall-clock of the agent run (endedAt − startedAt in ms). The bare
29
+ * timestamps can collapse to ~0 for an instant failure; this is explicit. */
30
+ durationMs?: number;
31
+ agent: string;
32
+ model?: string;
33
+ /** Active mode: null/absent = normal authoring, else 'api-test' / 'pentest'.
34
+ * A pentest record is a different artifact from a normal one. */
35
+ mode?: string | null;
36
+ prompt: string;
37
+ outcome: 'saved' | 'completed' | 'error' | 'aborted';
38
+ /** Why an error/aborted run ended — engine message, preflight failure,
39
+ * budget cutoff, user cancel. Makes a failed record diagnostic. */
40
+ errorReason?: string;
41
+ /** The agent's final verification prose (the Result card body), minus the
42
+ * Findings block. Searchable history + context, not just the prompt. */
43
+ summary?: string;
44
+ /** Parsed ## Findings — the run's actual product output. */
45
+ findings?: SessionFinding[];
46
+ /** Per-tool call counts (browser_snapshot → 12, browser_click → 8). Explains
47
+ * cost and feeds optimization targeting. */
48
+ toolCounts?: Record<string, number>;
49
+ /** What this run drove. envId/envName come from the editor's environment
50
+ * store (Local vs a remote target); url is the active dev tab. The Cloud
51
+ * run layer keys flakiness + scheduling off these. */
52
+ target?: {
53
+ url?: string;
54
+ envId?: string;
55
+ envName?: string;
56
+ };
57
+ /** @account labels this run logged in with — LABELS ONLY, never the
58
+ * username/password (same contract as spec redaction). */
59
+ accountLabels?: string[];
60
+ /** Tag of the `.hover/screenshots/<tag>` dir this run wrote to, so the UI
61
+ * can open the run's artifacts. (Distinct from `id` because the screenshot
62
+ * dir is named at MCP-launch time, before the record id exists.) */
63
+ screenshotTag?: string;
64
+ /** Chaining hook (reserved for Cloud): the prior turn's session id when this
65
+ * was a `--resume` follow-up, so a multi-turn conversation links as one. */
66
+ resumeOf?: string;
67
+ /** Set when the session was crystallized into a spec. */
68
+ specSlug?: string;
69
+ turns?: number;
70
+ costUsd?: number;
71
+ /** Total tokens consumed (input + output + cache) — the raw-usage counterpart
72
+ * to costUsd, surfaced by the widget/dashboard for users who track tokens
73
+ * rather than dollars. */
74
+ tokensUsed?: number;
75
+ stepCount: number;
76
+ }
77
+ /**
78
+ * Strip leaked function-call syntax a model sometimes emits as TEXT instead of
79
+ * actually invoking the tool — e.g. a final summary that ends with
80
+ * `call\n<invoke name="mcp__playwright__browser_wait_for">…</invoke>`. The model
81
+ * "writes out" the call (a known tool-calling glitch, common at end-of-turn /
82
+ * budget cap) and the parser renders it verbatim into the report + Done card.
83
+ * This keeps user-facing prose about the APP, not Hover's tooling
84
+ * (REPORTING_DIRECTIVE). Defensive + total: any agent can trip this.
85
+ */
86
+ export declare function stripToolCallNoise(text: string): string;
87
+ /** Markdown-forced: the agent emits a plain-markdown report (REPORTING_DIRECTIVE)
88
+ * — ONE outcome line, `- ` bullets, and an optional `## Findings` section with
89
+ * `- **severity** — text` items. Parse the summary + findings from that markdown
90
+ * only; a stray ```json block (a non-compliant agent) is stripped, never parsed
91
+ * for findings and never leaked. Pure + total — no Findings block yields none. */
92
+ export declare function parseFindings(summary: string): {
93
+ summary: string;
94
+ findings: SessionFinding[];
95
+ };
96
+ /** Count tool_use steps by tool name for the `toolCounts` field. */
97
+ export declare function tallyTools(steps: {
98
+ kind: string;
99
+ tool?: string;
100
+ }[]): Record<string, number>;
101
+ /** Write one session record as `<runDir>/meta.json`. The id (runId) + the
102
+ * conversation are decided by the caller at run start (so screenshots + report
103
+ * share the folder). NEVER throws; returns the path or an error string. */
104
+ export declare function writeSessionRecord(devRoot: string, conversationId: string, runId: string, rec: Omit<SessionRecord, 'version' | 'id' | 'conversationId'>): Promise<{
105
+ path: string;
106
+ id: string;
107
+ } | {
108
+ error: string;
109
+ }>;
110
+ /** List every run's meta.json across all conversations: `.hover/runs/<conv>/<run>/meta.json`.
111
+ * Best-effort; returns [] if no runs yet. */
112
+ export declare function listSessionRecords(devRoot: string): Promise<{
113
+ path: string;
114
+ rec: SessionRecord;
115
+ }[]>;
116
+ /**
117
+ * Mark the session that produced `promptText` as crystallized: find the most
118
+ * recent record matching the prompt that has no `specSlug` yet, set
119
+ * `outcome: 'saved'` + the slug. Save-as-spec arrives as a separate WS message
120
+ * after the run record was already written, so this is a patch, keyed on the
121
+ * prompt (the `user` seed step) — tolerant by design; a miss is a no-op.
122
+ * NEVER throws.
123
+ */
124
+ export declare function markSessionSaved(devRoot: string, promptText: string, specSlug: string): Promise<void>;
125
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/sessions/sessions.ts"],"names":[],"mappings":"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"}
@@ -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
+ }
@@ -0,0 +1,29 @@
1
+ export type MapNodeKind = 'app' | 'area' | 'line' | 'spec';
2
+ export type CoverageStatus = 'covered' | 'uncovered';
3
+ export interface MapNode {
4
+ id: string;
5
+ label: string;
6
+ kind: MapNodeKind;
7
+ /** Coverage of a business line (only on `line` nodes). */
8
+ status?: CoverageStatus;
9
+ /** Entry route of a business line, if given. */
10
+ route?: string;
11
+ /** Spec filename a line is covered by (on `line` and `spec` nodes). */
12
+ spec?: string;
13
+ }
14
+ export interface MapEdge {
15
+ source: string;
16
+ target: string;
17
+ }
18
+ export interface BusinessMapGraph {
19
+ app: string;
20
+ nodes: MapNode[];
21
+ edges: MapEdge[];
22
+ stats: {
23
+ lines: number;
24
+ covered: number;
25
+ areas: number;
26
+ };
27
+ }
28
+ export declare function parseBusinessMap(md: string, fallbackApp?: string): BusinessMapGraph;
29
+ //# sourceMappingURL=businessMap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"businessMap.d.ts","sourceRoot":"","sources":["../../src/specs/businessMap.ts"],"names":[],"mappings":"AAeA,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,0DAA0D;IAC1D,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,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;AA8BD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,SAAQ,GAAG,gBAAgB,CAwDlF"}
@@ -0,0 +1,95 @@
1
+ /*
2
+ * Business-map parser: turn the `.hover/hover-map.md` wiki the agent maintains
3
+ * into a graph model the cockpit renders (areas → business lines → specs, with
4
+ * coverage). The map is a human-curated markdown checklist:
5
+ *
6
+ * # Business map — myapp
7
+ * ## Auth
8
+ * - [ ] Log in — /login
9
+ * - [x] Checkout — /checkout — checkout.spec.ts
10
+ *
11
+ * Pure + total: malformed lines are skipped, never thrown. The graph is
12
+ * hierarchical (app → area → line → spec); richer relationship edges
13
+ * (depends-on / shares-state / navigates-to) are a later format extension.
14
+ */
15
+ function slug(s) {
16
+ return (s
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9]+/g, '-')
19
+ .replace(/^-+|-+$/g, '') || 'x');
20
+ }
21
+ const SPEC_RE = /\.spec\.tsx?$/;
22
+ /** Split a business-line item on " — " / " – " / " - " (em/en/hyphen, spaced). */
23
+ function splitItem(rest) {
24
+ const parts = rest
25
+ .split(/\s+[—–-]\s+/)
26
+ .map((p) => p.trim())
27
+ .filter(Boolean);
28
+ const name = parts.shift() ?? rest.trim();
29
+ let route;
30
+ let spec;
31
+ for (const p of parts) {
32
+ if (SPEC_RE.test(p))
33
+ spec = p;
34
+ else if (p.startsWith('/'))
35
+ route = p;
36
+ else if (!spec && SPEC_RE.test(p))
37
+ spec = p;
38
+ }
39
+ return { name, route, spec };
40
+ }
41
+ export function parseBusinessMap(md, fallbackApp = 'app') {
42
+ const nodes = [];
43
+ const edges = [];
44
+ const seen = new Set();
45
+ const add = (n) => {
46
+ if (seen.has(n.id))
47
+ return;
48
+ seen.add(n.id);
49
+ nodes.push(n);
50
+ };
51
+ let app = fallbackApp;
52
+ // Title: `# Business map — <app>` (or any `# <title>`).
53
+ const title = md.match(/^#\s+(.+)$/m);
54
+ if (title) {
55
+ const t = title[1].trim();
56
+ const m = t.match(/business\s*map\s*[—–-]\s*(.+)$/i);
57
+ app = (m ? m[1] : t).trim() || fallbackApp;
58
+ }
59
+ add({ id: 'app', label: app, kind: 'app' });
60
+ let area = null;
61
+ let covered = 0;
62
+ let lineCount = 0;
63
+ let areaCount = 0;
64
+ for (const raw of md.split('\n')) {
65
+ const line = raw.trimEnd();
66
+ const areaM = line.match(/^##\s+(.+)$/);
67
+ if (areaM) {
68
+ const label = areaM[1].trim();
69
+ const id = `area:${slug(label)}`;
70
+ area = { id };
71
+ add({ id, label, kind: 'area' });
72
+ edges.push({ source: 'app', target: id });
73
+ areaCount++;
74
+ continue;
75
+ }
76
+ const itemM = line.match(/^\s*-\s*\[([ xX])\]\s+(.+)$/);
77
+ if (itemM) {
78
+ const status = itemM[1].toLowerCase() === 'x' ? 'covered' : 'uncovered';
79
+ const { name, route, spec } = splitItem(itemM[2]);
80
+ const parentId = area?.id ?? 'app';
81
+ const lineId = `line:${slug(area ? area.id.slice(5) : 'top')}/${slug(name)}`;
82
+ add({ id: lineId, label: name, kind: 'line', status, route, spec });
83
+ edges.push({ source: parentId, target: lineId });
84
+ lineCount++;
85
+ if (status === 'covered')
86
+ covered++;
87
+ if (spec) {
88
+ const specId = `spec:${spec}`;
89
+ add({ id: specId, label: spec, kind: 'spec', spec });
90
+ edges.push({ source: lineId, target: specId });
91
+ }
92
+ }
93
+ }
94
+ return { app, nodes, edges, stats: { lines: lineCount, covered, areas: areaCount } };
95
+ }
@@ -1,4 +1,4 @@
1
- import type { SkillStep } from '../skills/writeSkill.js';
1
+ import type { SkillStep } from '../specs/specStep.js';
2
2
  export interface SharedFlow {
3
3
  /** The shared signature prefix, one entry per step. */
4
4
  signatures: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"detectSharedFlows.d.ts","sourceRoot":"","sources":["../../src/specs/detectSharedFlows.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,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;AAuDD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,UAAU,EAAE,CAAC,CAiCvB"}
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"}