@skyramp/mcp 0.1.8 → 0.2.0-rc.2

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 (122) hide show
  1. package/build/index.js +4 -2
  2. package/build/playwright/registerPlaywrightTools.js +12 -0
  3. package/build/playwright/traceRecordingPrompt.js +15 -0
  4. package/build/prompts/code-reuse.js +106 -7
  5. package/build/prompts/pom-aware-code-reuse.js +106 -7
  6. package/build/prompts/startTraceCollectionPrompts.js +37 -15
  7. package/build/prompts/test-maintenance/drift-analysis-prompt.js +26 -31
  8. package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +40 -1
  9. package/build/prompts/test-maintenance/driftAnalysisSections.js +90 -86
  10. package/build/prompts/test-recommendation/analysisOutputPrompt.js +286 -163
  11. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +154 -45
  12. package/build/prompts/test-recommendation/diffExecutionPlan.js +246 -117
  13. package/build/prompts/test-recommendation/promptPlan.js +290 -0
  14. package/build/prompts/test-recommendation/promptPlan.test.js +336 -0
  15. package/build/prompts/test-recommendation/recommendationSections.js +4 -3
  16. package/build/prompts/test-recommendation/recommendationShared.js +23 -1
  17. package/build/prompts/test-recommendation/scopeAssessment.js +65 -14
  18. package/build/prompts/test-recommendation/scopeAssessment.test.js +93 -2
  19. package/build/prompts/test-recommendation/test-recommendation-prompt.js +36 -12
  20. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +316 -1
  21. package/build/prompts/testbot/testbot-prompts.js +73 -13
  22. package/build/prompts/testbot/testbot-prompts.test.js +114 -1
  23. package/build/resources/testbotResource.js +1 -1
  24. package/build/services/ScenarioGenerationService.integration.test.js +158 -0
  25. package/build/services/ScenarioGenerationService.js +47 -4
  26. package/build/services/ScenarioGenerationService.test.js +158 -22
  27. package/build/services/TestExecutionService.js +73 -15
  28. package/build/services/TestExecutionService.test.js +105 -0
  29. package/build/services/TestGenerationService.js +11 -1
  30. package/build/tools/executeSkyrampTestTool.js +1 -10
  31. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +16 -4
  32. package/build/tools/generate-tests/generateIntegrationRestTool.js +2 -0
  33. package/build/tools/generate-tests/generateUIRestTool.js +2 -0
  34. package/build/tools/test-management/actionsTool.js +152 -63
  35. package/build/tools/test-management/analyzeChangesTool.js +178 -64
  36. package/build/tools/test-management/analyzeChangesTool.test.js +103 -16
  37. package/build/tools/test-management/analyzeTestHealthTool.js +30 -81
  38. package/build/tools/test-management/index.js +1 -0
  39. package/build/tools/test-management/uiAnalyzeChangesTool.js +149 -0
  40. package/build/tools/test-management/uiAnalyzeChangesTool.test.js +100 -0
  41. package/build/tools/trace/resolveSaveStoragePath.js +16 -0
  42. package/build/tools/trace/resolveSaveStoragePath.test.js +17 -0
  43. package/build/tools/trace/resolveSessionPaths.js +39 -0
  44. package/build/tools/trace/resolveSessionPaths.test.js +103 -0
  45. package/build/tools/trace/sessionState.js +14 -0
  46. package/build/tools/trace/sessionState.test.js +17 -0
  47. package/build/tools/trace/startTraceCollectionTool.js +84 -14
  48. package/build/tools/trace/stopTraceCollectionTool.js +9 -2
  49. package/build/types/TestAnalysis.js +50 -0
  50. package/build/types/TestRecommendation.js +6 -58
  51. package/build/types/TestTypes.js +1 -1
  52. package/build/utils/AnalysisStateManager.js +22 -11
  53. package/build/utils/branchDiff.js +11 -2
  54. package/build/utils/docker.test.js +1 -1
  55. package/build/utils/gitStaging.js +52 -3
  56. package/build/utils/gitStaging.test.js +19 -1
  57. package/build/utils/repoScanner.js +18 -10
  58. package/build/utils/repoScanner.test.js +92 -0
  59. package/build/utils/routeParsers.js +180 -25
  60. package/build/utils/routeParsers.test.js +180 -1
  61. package/build/utils/scenarioDrafting.js +220 -17
  62. package/build/utils/scenarioDrafting.test.js +182 -9
  63. package/build/utils/sourceRouteExtractor.js +806 -0
  64. package/build/utils/sourceRouteExtractor.test.js +565 -0
  65. package/build/utils/uiPageEnumerator.js +319 -0
  66. package/build/utils/uiPageEnumerator.test.js +422 -0
  67. package/build/utils/utils.js +27 -0
  68. package/build/utils/versions.js +1 -1
  69. package/build/utils/workspaceAuth.js +33 -4
  70. package/node_modules/playwright/ThirdPartyNotices.txt +6 -6
  71. package/node_modules/playwright/lib/dom-analyzer/analyze.js +111 -0
  72. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1210 -0
  73. package/node_modules/playwright/lib/dom-analyzer/blueprint.test.js +396 -0
  74. package/node_modules/playwright/lib/dom-analyzer/blueprintCache.js +57 -0
  75. package/node_modules/playwright/lib/dom-analyzer/blueprintCache.test.js +57 -0
  76. package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.js +254 -0
  77. package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.test.js +304 -0
  78. package/node_modules/playwright/lib/dom-analyzer/crawler.js +384 -0
  79. package/node_modules/playwright/lib/dom-analyzer/curatedWidgets.js +73 -0
  80. package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +43 -0
  81. package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +85 -0
  82. package/node_modules/playwright/lib/dom-analyzer/fingerprint.js +90 -0
  83. package/node_modules/playwright/lib/dom-analyzer/fingerprint.test.js +231 -0
  84. package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.fixtures.js +145 -0
  85. package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.test.js +41 -0
  86. package/node_modules/playwright/lib/dom-analyzer/graph.js +36 -0
  87. package/node_modules/playwright/lib/dom-analyzer/liveFingerprints.js +43 -0
  88. package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.js +72 -0
  89. package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.test.js +182 -0
  90. package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.js +150 -0
  91. package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.test.js +470 -0
  92. package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.js +169 -0
  93. package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.test.js +269 -0
  94. package/node_modules/playwright/lib/dom-analyzer/serialization.js +75 -0
  95. package/node_modules/playwright/lib/dom-analyzer/slug.js +30 -0
  96. package/node_modules/playwright/lib/dom-analyzer/slug.test.js +84 -0
  97. package/node_modules/playwright/lib/dom-analyzer/widgetContract.js +127 -0
  98. package/node_modules/playwright/lib/dom-analyzer/widgetContract.test.js +212 -0
  99. package/node_modules/playwright/lib/mcp/browser/browserContextFactory.js +3 -1
  100. package/node_modules/playwright/lib/mcp/browser/config.js +1 -1
  101. package/node_modules/playwright/lib/mcp/browser/context.js +17 -1
  102. package/node_modules/playwright/lib/mcp/browser/tab.js +38 -0
  103. package/node_modules/playwright/lib/mcp/browser/tools/domAnalyzer.js +261 -0
  104. package/node_modules/playwright/lib/mcp/browser/tools/keyboard.js +3 -3
  105. package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +146 -0
  106. package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.test.js +140 -0
  107. package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +226 -0
  108. package/node_modules/playwright/lib/mcp/browser/tools/snapshot.js +2 -2
  109. package/node_modules/playwright/lib/mcp/browser/tools/widgetContract.js +168 -0
  110. package/node_modules/playwright/lib/mcp/browser/tools.js +6 -0
  111. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +52 -12
  112. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +64 -13
  113. package/node_modules/playwright/package.json +1 -1
  114. package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.3.tgz +0 -0
  115. package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.4.tgz +0 -0
  116. package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.5.tgz +0 -0
  117. package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.6.tgz +0 -0
  118. package/package.json +3 -3
  119. package/build/services/TestHealthService.js +0 -694
  120. package/build/services/TestHealthService.test.js +0 -241
  121. package/build/types/TestDriftAnalysis.js +0 -1
  122. package/build/types/TestHealth.js +0 -4
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ var import_sectionGrouper = require("./sectionGrouper");
3
+ const cases = [];
4
+ function test(name, run) {
5
+ cases.push({ name, run });
6
+ }
7
+ function assertEqual(actual, expected, msg) {
8
+ const a = JSON.stringify(actual);
9
+ const e = JSON.stringify(expected);
10
+ if (a !== e) throw new Error(`${msg ?? "assertEqual"} \u2014 expected ${e}, got ${a}`);
11
+ }
12
+ function el(overrides) {
13
+ return {
14
+ id: "test-el-" + Math.random(),
15
+ ancestry: { landmarkAncestor: null, sectioningAncestor: null, headingAncestry: [] },
16
+ ...overrides
17
+ };
18
+ }
19
+ test("resolveSections: landmark beats sectioning beats heading", () => {
20
+ const inputs = [
21
+ el({
22
+ id: "a",
23
+ ancestry: {
24
+ landmarkAncestor: { landmark: "main", nameHint: "main", anchorKey: "ak1" },
25
+ sectioningAncestor: { tagName: "section", accessibleName: "Inner", anchorKey: "ak2" },
26
+ headingAncestry: [{ level: 2, text: "Deepest", anchorKey: "ak3" }]
27
+ }
28
+ })
29
+ ];
30
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
31
+ assertEqual(out.assignments, { a: "main" });
32
+ });
33
+ test("resolveSections: sectioning without name falls through to implicit heading", () => {
34
+ const inputs = [
35
+ el({
36
+ id: "a",
37
+ ancestry: {
38
+ landmarkAncestor: null,
39
+ sectioningAncestor: { tagName: "section", accessibleName: null, anchorKey: "ak1" },
40
+ headingAncestry: [{ level: 2, text: "Orders", anchorKey: "ak2" }]
41
+ }
42
+ })
43
+ ];
44
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
45
+ assertEqual(out.assignments, { a: "orders" });
46
+ });
47
+ test("resolveSections: G1 drops empty-interactive implicit heading", () => {
48
+ const inputs = [
49
+ el({
50
+ id: "a",
51
+ ancestry: {
52
+ landmarkAncestor: { landmark: "main", nameHint: "main", anchorKey: "main-ak" },
53
+ sectioningAncestor: null,
54
+ headingAncestry: [{ level: 2, text: "Decorative", anchorKey: "ak1" }]
55
+ }
56
+ })
57
+ ];
58
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
59
+ assertEqual(out.assignments, { a: "main" });
60
+ assertEqual(out.sections.find((s) => s.name === "decorative"), void 0);
61
+ });
62
+ test("resolveSections: G2 (leaf-only) drops ancestor heading when descendant has elements", () => {
63
+ const inputs = [
64
+ el({
65
+ id: "a",
66
+ ancestry: {
67
+ landmarkAncestor: null,
68
+ sectioningAncestor: null,
69
+ headingAncestry: [
70
+ { level: 1, text: "Dashboard", anchorKey: "dash-ak" },
71
+ { level: 2, text: "Orders", anchorKey: "orders-ak" }
72
+ ]
73
+ }
74
+ })
75
+ ];
76
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
77
+ assertEqual(out.assignments, { a: "orders" });
78
+ });
79
+ test("resolveSections: G2 keeps two sibling headings at same level", () => {
80
+ const inputs = [
81
+ el({
82
+ id: "a",
83
+ ancestry: {
84
+ landmarkAncestor: null,
85
+ sectioningAncestor: null,
86
+ headingAncestry: [{ level: 2, text: "Orders", anchorKey: "orders-ak" }]
87
+ }
88
+ }),
89
+ el({
90
+ id: "b",
91
+ ancestry: {
92
+ landmarkAncestor: null,
93
+ sectioningAncestor: null,
94
+ headingAncestry: [{ level: 2, text: "Products", anchorKey: "products-ak" }]
95
+ }
96
+ })
97
+ ];
98
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
99
+ assertEqual(out.assignments, { a: "orders", b: "products" });
100
+ });
101
+ test("resolveSections: G3 rejects junk slug \u2014 fall through to implicit heading", () => {
102
+ const inputs = [
103
+ el({
104
+ id: "a",
105
+ ancestry: {
106
+ landmarkAncestor: null,
107
+ sectioningAncestor: { tagName: "section", accessibleName: "Untitled", anchorKey: "sec-ak" },
108
+ headingAncestry: [{ level: 2, text: "Orders", anchorKey: "orders-ak" }]
109
+ }
110
+ })
111
+ ];
112
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
113
+ assertEqual(out.assignments, { a: "orders" });
114
+ });
115
+ test("resolveSections: G3 rejects all signals \u2192 page fallback", () => {
116
+ const inputs = [
117
+ el({
118
+ id: "a",
119
+ ancestry: {
120
+ landmarkAncestor: null,
121
+ sectioningAncestor: { tagName: "section", accessibleName: "Content", anchorKey: "sec-ak" },
122
+ headingAncestry: [{ level: 2, text: "Overview", anchorKey: "ov-ak" }]
123
+ }
124
+ })
125
+ ];
126
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
127
+ assertEqual(out.assignments, { a: "page" });
128
+ });
129
+ test("resolveSections: sectioning null-name and junk-name behave identically (fall through)", () => {
130
+ const inputsNull = [
131
+ el({
132
+ id: "null-a",
133
+ ancestry: {
134
+ landmarkAncestor: null,
135
+ sectioningAncestor: { tagName: "section", accessibleName: null, anchorKey: "s1" },
136
+ headingAncestry: [{ level: 2, text: "Orders", anchorKey: "o1" }]
137
+ }
138
+ })
139
+ ];
140
+ const inputsJunk = [
141
+ el({
142
+ id: "junk-a",
143
+ ancestry: {
144
+ landmarkAncestor: null,
145
+ sectioningAncestor: { tagName: "section", accessibleName: "Untitled", anchorKey: "s2" },
146
+ headingAncestry: [{ level: 2, text: "Orders", anchorKey: "o2" }]
147
+ }
148
+ })
149
+ ];
150
+ const outNull = (0, import_sectionGrouper.resolveSections)(inputsNull);
151
+ const outJunk = (0, import_sectionGrouper.resolveSections)(inputsJunk);
152
+ assertEqual(outNull.assignments["null-a"], "orders");
153
+ assertEqual(outJunk.assignments["junk-a"], "orders");
154
+ });
155
+ test("resolveSections: two headings same text different anchorKeys \u2192 separate sections", () => {
156
+ const inputs = [
157
+ el({
158
+ id: "a",
159
+ ancestry: {
160
+ landmarkAncestor: null,
161
+ sectioningAncestor: null,
162
+ headingAncestry: [{ level: 2, text: "Orders", anchorKey: "orders-1" }]
163
+ }
164
+ }),
165
+ el({
166
+ id: "b",
167
+ ancestry: {
168
+ landmarkAncestor: null,
169
+ sectioningAncestor: null,
170
+ headingAncestry: [{ level: 2, text: "Orders", anchorKey: "orders-2" }]
171
+ }
172
+ })
173
+ ];
174
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
175
+ assertEqual(out.assignments, { a: "orders", b: "orders_2" });
176
+ });
177
+ test("resolveSections: element in landmark with inner heading \u2014 landmark wins", () => {
178
+ const inputs = [
179
+ el({
180
+ id: "a",
181
+ ancestry: {
182
+ landmarkAncestor: { landmark: "main", nameHint: "main", anchorKey: "main-ak" },
183
+ sectioningAncestor: null,
184
+ headingAncestry: [{ level: 3, text: "Filters", anchorKey: "h3-ak" }]
185
+ }
186
+ })
187
+ ];
188
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
189
+ assertEqual(out.assignments, { a: "main" });
190
+ });
191
+ test("resolveSections: element in landmark+sectioning+heading \u2014 landmark wins (strongest)", () => {
192
+ const inputs = [
193
+ el({
194
+ id: "a",
195
+ ancestry: {
196
+ landmarkAncestor: { landmark: "main", nameHint: "main", anchorKey: "main-ak" },
197
+ sectioningAncestor: { tagName: "section", accessibleName: "Orders", anchorKey: "ord-ak" },
198
+ headingAncestry: [{ level: 3, text: "Filters", anchorKey: "h3-ak" }]
199
+ }
200
+ })
201
+ ];
202
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
203
+ assertEqual(out.assignments, { a: "main" });
204
+ });
205
+ test("resolveSections: G2 nested drop cascade \u2014 A > B > C, only C has elements", () => {
206
+ const inputs = [
207
+ el({
208
+ id: "a",
209
+ ancestry: {
210
+ landmarkAncestor: null,
211
+ sectioningAncestor: null,
212
+ headingAncestry: [
213
+ { level: 1, text: "Dashboard", anchorKey: "dash-ak" },
214
+ { level: 2, text: "Orders", anchorKey: "orders-ak" },
215
+ { level: 3, text: "Recent", anchorKey: "recent-ak" }
216
+ ]
217
+ }
218
+ })
219
+ ];
220
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
221
+ assertEqual(out.assignments, { a: "recent" });
222
+ assertEqual(out.sections.find((s) => s.name === "dashboard"), void 0);
223
+ assertEqual(out.sections.find((s) => s.name === "orders"), void 0);
224
+ });
225
+ test("resolveSections: G2 keeps ancestor heading that has its own elements", () => {
226
+ const inputs = [
227
+ el({
228
+ id: "a",
229
+ ancestry: {
230
+ landmarkAncestor: null,
231
+ sectioningAncestor: null,
232
+ headingAncestry: [
233
+ { level: 1, text: "Dashboard", anchorKey: "dash-ak" }
234
+ ]
235
+ }
236
+ }),
237
+ el({
238
+ id: "b",
239
+ ancestry: {
240
+ landmarkAncestor: null,
241
+ sectioningAncestor: null,
242
+ headingAncestry: [
243
+ { level: 1, text: "Dashboard", anchorKey: "dash-ak" },
244
+ { level: 2, text: "Orders", anchorKey: "orders-ak" }
245
+ ]
246
+ }
247
+ })
248
+ ];
249
+ const out = (0, import_sectionGrouper.resolveSections)(inputs);
250
+ assertEqual(out.assignments, { a: "page", b: "orders" });
251
+ });
252
+ let failed = 0;
253
+ for (const { name, run } of cases) {
254
+ try {
255
+ run();
256
+ console.log(" \u2713", name);
257
+ } catch (e) {
258
+ failed++;
259
+ console.log(" \u2717", name);
260
+ console.log(" ", e.message);
261
+ }
262
+ }
263
+ if (failed > 0) {
264
+ console.log(`
265
+ ${failed}/${cases.length} failed`);
266
+ process.exit(1);
267
+ }
268
+ console.log(`
269
+ ${cases.length} passed`);
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var serialization_exports = {};
20
+ __export(serialization_exports, {
21
+ CRAWL_TTL_MS: () => CRAWL_TTL_MS,
22
+ SCHEMA_VERSION: () => SCHEMA_VERSION,
23
+ assertSchemaVersion: () => assertSchemaVersion,
24
+ normalizeUrl: () => normalizeUrl,
25
+ nowTimestamp: () => nowTimestamp,
26
+ validateTimestamp: () => validateTimestamp
27
+ });
28
+ module.exports = __toCommonJS(serialization_exports);
29
+ const SCHEMA_VERSION = 1;
30
+ const CRAWL_TTL_MS = 30 * 60 * 1e3;
31
+ function normalizeUrl(raw) {
32
+ const url = new URL(raw);
33
+ url.hash = "";
34
+ if (url.protocol === "http:" && url.port === "80" || url.protocol === "https:" && url.port === "443")
35
+ url.port = "";
36
+ if (url.search) {
37
+ const params = Array.from(url.searchParams.entries());
38
+ params.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
39
+ url.search = "";
40
+ for (const [k, v] of params)
41
+ url.searchParams.append(k, v);
42
+ }
43
+ let pathname = url.pathname;
44
+ if (pathname.length > 1 && pathname.endsWith("/"))
45
+ pathname = pathname.replace(/\/+$/, "");
46
+ const normalizedPath = pathname.replace(/%[0-9a-f]{2}/g, (m) => m.toUpperCase());
47
+ const normalizedSearch = url.search.replace(/%[0-9a-f]{2}/g, (m) => m.toUpperCase());
48
+ const origin = `${url.protocol}//${url.host}`;
49
+ return `${origin}${normalizedPath}${normalizedSearch}`;
50
+ }
51
+ function validateTimestamp(value) {
52
+ const parsed = new Date(value);
53
+ if (Number.isNaN(parsed.getTime()))
54
+ throw new Error(`Invalid ISO-8601 timestamp: ${JSON.stringify(value)}`);
55
+ const roundTripped = parsed.toISOString();
56
+ if (roundTripped !== value)
57
+ throw new Error(`Timestamp not in canonical ISO-8601 UTC form (got ${JSON.stringify(value)}, expected ${JSON.stringify(roundTripped)})`);
58
+ return value;
59
+ }
60
+ function nowTimestamp() {
61
+ return (/* @__PURE__ */ new Date()).toISOString();
62
+ }
63
+ function assertSchemaVersion(version) {
64
+ if (version !== SCHEMA_VERSION)
65
+ throw new Error(`Unsupported schemaVersion: got ${version}, expected ${SCHEMA_VERSION}`);
66
+ }
67
+ // Annotate the CommonJS export names for ESM import in node:
68
+ 0 && (module.exports = {
69
+ CRAWL_TTL_MS,
70
+ SCHEMA_VERSION,
71
+ assertSchemaVersion,
72
+ normalizeUrl,
73
+ nowTimestamp,
74
+ validateTimestamp
75
+ });
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var slug_exports = {};
20
+ __export(slug_exports, {
21
+ slug: () => slug
22
+ });
23
+ module.exports = __toCommonJS(slug_exports);
24
+ function slug(input) {
25
+ return input.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "_").replace(/^_+|_+$/g, "");
26
+ }
27
+ // Annotate the CommonJS export names for ESM import in node:
28
+ 0 && (module.exports = {
29
+ slug
30
+ });
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var import_slug = require("./slug");
3
+ const cases = [];
4
+ function test(name, run) {
5
+ cases.push({ name, run });
6
+ }
7
+ function assertEqual(actual, expected, msg) {
8
+ const a = JSON.stringify(actual);
9
+ const e = JSON.stringify(expected);
10
+ if (a !== e) throw new Error(`${msg ?? "assertEqual"} \u2014 expected ${e}, got ${a}`);
11
+ }
12
+ test("slug: lowercases and replaces spaces with underscores", () => {
13
+ assertEqual((0, import_slug.slug)("Add Product"), "add_product");
14
+ });
15
+ test("slug: replaces punctuation with underscores", () => {
16
+ assertEqual((0, import_slug.slug)("Next: Select Country"), "next_select_country");
17
+ });
18
+ test("slug: collapses runs of separators", () => {
19
+ assertEqual((0, import_slug.slug)("Foo --- Bar"), "foo_bar");
20
+ });
21
+ test("slug: strips leading and trailing underscores", () => {
22
+ assertEqual((0, import_slug.slug)(" Hello "), "hello");
23
+ assertEqual((0, import_slug.slug)("_leading"), "leading");
24
+ assertEqual((0, import_slug.slug)("trailing_"), "trailing");
25
+ });
26
+ test("slug: empty input returns empty string", () => {
27
+ assertEqual((0, import_slug.slug)(""), "");
28
+ });
29
+ test("slug: all-non-alphanumeric input returns empty string", () => {
30
+ assertEqual((0, import_slug.slug)("---"), "");
31
+ assertEqual((0, import_slug.slug)(" "), "");
32
+ });
33
+ test("slug: preserves digits", () => {
34
+ assertEqual((0, import_slug.slug)("Order 12"), "order_12");
35
+ });
36
+ test("slug: is idempotent on already-slug-like input", () => {
37
+ assertEqual((0, import_slug.slug)("already_slug"), "already_slug");
38
+ });
39
+ test("slug: preserves CJK (Japanese)", () => {
40
+ assertEqual((0, import_slug.slug)("\u8CC7\u7523\u7BA1\u7406"), "\u8CC7\u7523\u7BA1\u7406");
41
+ assertEqual((0, import_slug.slug)("\u8CC7\u7523\u60C5\u5831"), "\u8CC7\u7523\u60C5\u5831");
42
+ assertEqual((0, import_slug.slug)("\u4E00\u62EC\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9"), "\u4E00\u62EC\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9");
43
+ });
44
+ test("slug: preserves Cyrillic and lowercases it", () => {
45
+ assertEqual((0, import_slug.slug)("\u0420\u0443\u0441\u0441\u043A\u0438\u0439"), "\u0440\u0443\u0441\u0441\u043A\u0438\u0439");
46
+ });
47
+ test("slug: preserves Arabic", () => {
48
+ assertEqual((0, import_slug.slug)("\u0627\u0644\u0639\u0631\u0628\u064A\u0629"), "\u0627\u0644\u0639\u0631\u0628\u064A\u0629");
49
+ });
50
+ test("slug: preserves Korean", () => {
51
+ assertEqual((0, import_slug.slug)("\uD55C\uAD6D\uC5B4"), "\uD55C\uAD6D\uC5B4");
52
+ });
53
+ test("slug: preserves accented Latin", () => {
54
+ assertEqual((0, import_slug.slug)("r\xE9sum\xE9 caf\xE9"), "r\xE9sum\xE9_caf\xE9");
55
+ });
56
+ test("slug: mixed CJK + ASCII digits and whitespace", () => {
57
+ assertEqual((0, import_slug.slug)("\u901A\u77E5 (1063)"), "\u901A\u77E5_1063");
58
+ });
59
+ test("slug: emoji and symbols are still separators", () => {
60
+ assertEqual((0, import_slug.slug)("\u{1F600} emoji test"), "emoji_test");
61
+ assertEqual((0, import_slug.slug)("foo \u2192 bar"), "foo_bar");
62
+ });
63
+ test("slug: output cannot contain the :: separator used by collision groups", () => {
64
+ assertEqual((0, import_slug.slug)("a::b"), "a_b");
65
+ assertEqual((0, import_slug.slug)("foo:bar:baz"), "foo_bar_baz");
66
+ });
67
+ let failed = 0;
68
+ for (const { name, run } of cases) {
69
+ try {
70
+ run();
71
+ console.log(" \u2713", name);
72
+ } catch (e) {
73
+ failed++;
74
+ console.log(" \u2717", name);
75
+ console.log(" ", e.message);
76
+ }
77
+ }
78
+ if (failed > 0) {
79
+ console.log(`
80
+ ${failed}/${cases.length} failed`);
81
+ process.exit(1);
82
+ }
83
+ console.log(`
84
+ ${cases.length} passed`);
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var widgetContract_exports = {};
20
+ __export(widgetContract_exports, {
21
+ InferredWidgetContractSchema: () => InferredWidgetContractSchema,
22
+ WidgetContractSchema: () => WidgetContractSchema,
23
+ cacheInferredContract: () => cacheInferredContract,
24
+ lookupContract: () => lookupContract,
25
+ validateContract: () => validateContract
26
+ });
27
+ module.exports = __toCommonJS(widgetContract_exports);
28
+ var import_mcpBundle = require("playwright-core/lib/mcpBundle");
29
+ var import_curatedWidgets = require("./curatedWidgets");
30
+ const ClickStepSchema = import_mcpBundle.z.object({
31
+ kind: import_mcpBundle.z.literal("click"),
32
+ target: import_mcpBundle.z.enum(["trigger", "option"]),
33
+ matchBy: import_mcpBundle.z.enum(["accessibleName", "role"]).optional(),
34
+ param: import_mcpBundle.z.string().optional()
35
+ });
36
+ const FillStepSchema = import_mcpBundle.z.discriminatedUnion("target", [
37
+ import_mcpBundle.z.object({ kind: import_mcpBundle.z.literal("fill"), target: import_mcpBundle.z.literal("activeInput"), param: import_mcpBundle.z.string() }),
38
+ import_mcpBundle.z.object({ kind: import_mcpBundle.z.literal("fill"), target: import_mcpBundle.z.literal("inputByLabel"), label: import_mcpBundle.z.string(), param: import_mcpBundle.z.string() })
39
+ ]);
40
+ const WaitForStepSchema = import_mcpBundle.z.object({
41
+ kind: import_mcpBundle.z.literal("waitFor"),
42
+ location: import_mcpBundle.z.enum(["inDom", "portal", "frame"]),
43
+ signal: import_mcpBundle.z.enum(["visible", "present"])
44
+ });
45
+ const SelectOptionStepSchema = import_mcpBundle.z.object({
46
+ kind: import_mcpBundle.z.literal("selectOption"),
47
+ by: import_mcpBundle.z.enum(["accessibleName", "index"]),
48
+ param: import_mcpBundle.z.string()
49
+ });
50
+ const EnterFrameStepSchema = import_mcpBundle.z.object({
51
+ kind: import_mcpBundle.z.literal("enterFrame"),
52
+ framePath: import_mcpBundle.z.array(import_mcpBundle.z.string())
53
+ });
54
+ const ExitFrameStepSchema = import_mcpBundle.z.object({ kind: import_mcpBundle.z.literal("exitFrame") });
55
+ const HandoffStepSchema = import_mcpBundle.z.object({ kind: import_mcpBundle.z.literal("handoff") });
56
+ const ContractStepSchema = import_mcpBundle.z.union([
57
+ ClickStepSchema,
58
+ FillStepSchema,
59
+ WaitForStepSchema,
60
+ SelectOptionStepSchema,
61
+ EnterFrameStepSchema,
62
+ ExitFrameStepSchema,
63
+ HandoffStepSchema
64
+ ]);
65
+ const WidgetContractSchema = import_mcpBundle.z.object({
66
+ source: import_mcpBundle.z.enum(["curated", "inferred", "unknown"]),
67
+ confidence: import_mcpBundle.z.enum(["high", "medium", "low"]),
68
+ widgetIdentification: import_mcpBundle.z.string().max(100),
69
+ parameters: import_mcpBundle.z.array(import_mcpBundle.z.string()),
70
+ steps: import_mcpBundle.z.array(ContractStepSchema)
71
+ });
72
+ const InferredWidgetContractSchema = import_mcpBundle.z.object({
73
+ source: import_mcpBundle.z.enum(["inferred", "unknown"]),
74
+ confidence: import_mcpBundle.z.enum(["high", "medium", "low"]),
75
+ widgetIdentification: import_mcpBundle.z.string().max(100),
76
+ parameters: import_mcpBundle.z.array(import_mcpBundle.z.string()),
77
+ steps: import_mcpBundle.z.array(ContractStepSchema)
78
+ });
79
+ function validateContract(contract) {
80
+ const params = new Set(contract.parameters);
81
+ let enterFrameCount = 0;
82
+ let sawWaitFor = false;
83
+ for (const step of contract.steps) {
84
+ const stepParam = step.param;
85
+ if (stepParam !== void 0 && !params.has(stepParam))
86
+ throw new Error(`step param '${stepParam}' not declared in parameters[]`);
87
+ if (step.kind === "enterFrame") {
88
+ if (step.framePath.length === 0)
89
+ throw new Error("EnterFrameStep.framePath must be non-empty");
90
+ enterFrameCount++;
91
+ if (enterFrameCount > 1)
92
+ throw new Error("at most one EnterFrameStep per contract (provisional limit)");
93
+ }
94
+ if (step.kind === "waitFor") sawWaitFor = true;
95
+ if (step.kind === "handoff" && !sawWaitFor) {
96
+ console.warn("widgetContract: HandoffStep not preceded by WaitForStep \u2014 agent may act on pre-widget-open DOM state");
97
+ }
98
+ }
99
+ }
100
+ function lookupContract(fingerprint, inferredCache) {
101
+ const curated = import_curatedWidgets.CURATED[fingerprint];
102
+ if (curated) return { status: "found", contract: curated };
103
+ const inferred = inferredCache.get(fingerprint);
104
+ if (inferred) return { status: "found", contract: inferred };
105
+ return { status: "needs_inference_marker" };
106
+ }
107
+ function cacheInferredContract(fingerprint, contract, inferredCache) {
108
+ if (Object.prototype.hasOwnProperty.call(import_curatedWidgets.CURATED, fingerprint))
109
+ return { ok: false, error: "fingerprint collides with curated entry; cannot overwrite" };
110
+ if (contract.source === "curated")
111
+ return { ok: false, error: "source: 'curated' is reserved for the curated library; use 'inferred' or 'unknown'" };
112
+ try {
113
+ validateContract(contract);
114
+ } catch (e) {
115
+ return { ok: false, error: e.message };
116
+ }
117
+ inferredCache.set(fingerprint, contract);
118
+ return { ok: true };
119
+ }
120
+ // Annotate the CommonJS export names for ESM import in node:
121
+ 0 && (module.exports = {
122
+ InferredWidgetContractSchema,
123
+ WidgetContractSchema,
124
+ cacheInferredContract,
125
+ lookupContract,
126
+ validateContract
127
+ });