@skyramp/mcp 0.4.4 → 0.5.0-rc.10

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 (144) hide show
  1. package/build/entitlements/entitlementGate.d.ts +8 -0
  2. package/build/entitlements/entitlementGate.js +57 -0
  3. package/build/entitlements/runCodes.d.ts +18 -0
  4. package/build/entitlements/runCodes.js +16 -0
  5. package/build/index.js +32 -6
  6. package/build/playwright/PlaywrightTraceService.d.ts +17 -0
  7. package/build/playwright/PlaywrightTraceService.js +29 -1
  8. package/build/playwright/dataSafetyConfig.d.ts +74 -0
  9. package/build/playwright/dataSafetyConfig.js +109 -0
  10. package/build/playwright/essentialTools.d.ts +1 -0
  11. package/build/playwright/essentialTools.js +44 -0
  12. package/build/playwright/registerPlaywrightTools.d.ts +36 -1
  13. package/build/playwright/registerPlaywrightTools.js +87 -43
  14. package/build/playwright/safeResolveDataSafety.d.ts +24 -0
  15. package/build/playwright/safeResolveDataSafety.js +33 -0
  16. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
  17. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
  18. package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
  19. package/build/prompts/modularization/ui-test-modularization.js +9 -2
  20. package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
  21. package/build/services/FleetExecutionService.d.ts +87 -16
  22. package/build/services/FleetExecutionService.js +487 -48
  23. package/build/services/FleetRunRegistry.d.ts +49 -0
  24. package/build/services/FleetRunRegistry.js +178 -0
  25. package/build/services/containerEnv.js +8 -2
  26. package/build/services/fleetLimitConfirmation.d.ts +19 -0
  27. package/build/services/fleetLimitConfirmation.js +126 -0
  28. package/build/tool-phases.d.ts +14 -8
  29. package/build/tool-phases.js +88 -43
  30. package/build/tools/code-refactor/assertion-state.js +3 -1
  31. package/build/tools/code-refactor/codeReuseTool.js +2 -1
  32. package/build/tools/code-refactor/utils-verify-gates.js +1 -1
  33. package/build/tools/executeFleetTool.d.ts +6 -4
  34. package/build/tools/executeFleetTool.js +67 -23
  35. package/build/tools/fixErrorTool.js +2 -1
  36. package/build/tools/fleetLifecycleTools.d.ts +20 -0
  37. package/build/tools/fleetLifecycleTools.js +124 -0
  38. package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +38 -0
  39. package/build/tools/generate-tests/generateIntegrationRestTool.js +7 -1
  40. package/build/tools/generate-tests/generateUILoadRestTool.d.ts +19 -1
  41. package/build/tools/generate-tests/generateUILoadRestTool.js +316 -50
  42. package/build/tools/submitReportTool.js +4 -122
  43. package/build/tools/test-management/registerTestPlanTool.js +22 -6
  44. package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
  45. package/build/tools/workspace/initScanWorkspaceTool.js +111 -9
  46. package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
  47. package/build/types/AssertionOutcome.d.ts +3 -0
  48. package/build/types/TestbotReport.d.ts +0 -37
  49. package/build/types/TestbotReport.js +0 -22
  50. package/build/types/index.d.ts +1 -1
  51. package/build/types/index.js +1 -1
  52. package/build/utils/assertion-verify/created-add-guard.d.ts +12 -0
  53. package/build/utils/assertion-verify/created-add-guard.js +173 -0
  54. package/build/utils/assertion-verify/index.d.ts +1 -0
  55. package/build/utils/assertion-verify/index.js +1 -0
  56. package/build/utils/assertion-verify/verify.d.ts +7 -3
  57. package/build/utils/assertion-verify/verify.js +9 -3
  58. package/build/utils/featureFlags.js +6 -3
  59. package/build/utils/repositorySlug.d.ts +14 -0
  60. package/build/utils/repositorySlug.js +46 -1
  61. package/build/utils/telemetry.d.ts +2 -0
  62. package/build/utils/telemetry.js +12 -0
  63. package/build/utils/utils-verify/action-key.d.ts +59 -2
  64. package/build/utils/utils-verify/action-key.js +162 -6
  65. package/build/utils/utils-verify/action-sites.d.ts +7 -0
  66. package/build/utils/utils-verify/action-sites.js +277 -35
  67. package/build/utils/utils-verify/call-sites.d.ts +8 -2
  68. package/build/utils/utils-verify/language-spec.js +1 -1
  69. package/build/utils/utils-verify/parse.d.ts +4 -0
  70. package/build/utils/utils-verify/parse.js +2 -1
  71. package/build/utils/versions.d.ts +3 -3
  72. package/build/utils/versions.js +1 -1
  73. package/build/workspace/workspace.d.ts +63 -121
  74. package/build/workspace/workspace.js +45 -48
  75. package/build/workspace/workspaceVerifier.d.ts +22 -0
  76. package/build/workspace/workspaceVerifier.js +65 -0
  77. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +7 -6
  78. package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +5 -4
  79. package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +12 -0
  80. package/node_modules/playwright/lib/mcp/skyramp/dataSafetyGuard.js +822 -0
  81. package/node_modules/playwright/lib/mcp/skyramp/dataSafetySession.js +372 -0
  82. package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +101 -8
  83. package/node_modules/playwright/lib/mcp/skyramp/finishTestSetupTool.js +45 -0
  84. package/node_modules/playwright/lib/mcp/skyramp/index.js +16 -0
  85. package/node_modules/playwright/lib/mcp/skyramp/recordingPhase.js +65 -0
  86. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +1 -0
  87. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +598 -127
  88. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +85 -31
  89. package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +132 -5
  90. package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +2 -0
  91. package/node_modules/playwright/node_modules/playwright-core/lib/client/browserContext.js +4 -2
  92. package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
  93. package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
  94. package/node_modules/playwright/node_modules/playwright-core/lib/protocol/validator.js +2 -1
  95. package/node_modules/playwright/node_modules/playwright-core/lib/server/browserContext.js +17 -1
  96. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  97. package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harRecorder.js +1 -0
  98. package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harTracer.js +1 -1
  99. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +2 -1
  100. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -1
  101. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/clickExpandedState.js +75 -0
  102. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +4 -2
  103. package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +85 -0
  104. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bou8kKzE.js → codeMirrorModule-BuhAdckO.js} +1 -1
  105. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-COAHN430.js +809 -0
  106. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.0MJW0uG7.js → index.7AU8h0PZ.js} +1 -1
  107. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  108. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.GLqsx5cI.js → uiMode.Dl7smBJn.js} +1 -1
  109. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  110. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  111. package/node_modules/playwright/node_modules/playwright-core/src/cli/program.ts +5 -0
  112. package/node_modules/playwright/node_modules/playwright-core/src/client/browserContext.ts +3 -1
  113. package/node_modules/playwright/node_modules/playwright-core/src/client/types.ts +2 -0
  114. package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
  115. package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
  116. package/node_modules/playwright/node_modules/playwright-core/src/protocol/validator.ts +1 -0
  117. package/node_modules/playwright/node_modules/playwright-core/src/server/browserContext.ts +25 -1
  118. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +2 -0
  119. package/node_modules/playwright/node_modules/playwright-core/src/server/har/harRecorder.ts +1 -0
  120. package/node_modules/playwright/node_modules/playwright-core/src/server/har/harTracer.ts +3 -1
  121. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +3 -1
  122. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +1 -0
  123. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/clickExpandedState.ts +82 -0
  124. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +2 -0
  125. package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +137 -1
  126. package/node_modules/playwright/package.json +1 -1
  127. package/package.json +4 -4
  128. package/plugin/prompts/plan-tests.md +2 -2
  129. package/plugin/prompts/testbot-task1.md +1 -1
  130. package/plugin/skills/enhance-assertions/reference/integration.md +1 -0
  131. package/plugin/skills/setup-testbot-target-sut/SKILL.md +60 -0
  132. package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
  133. package/plugin/skills/setup-testbot-target-sut/reference/deployed-environment.md +105 -0
  134. package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
  135. package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
  136. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
  137. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
  138. package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
  139. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
  140. package/build/prompts/sut-setup/shared.d.ts +0 -62
  141. package/build/prompts/sut-setup/shared.js +0 -196
  142. package/build/resources/sutSetupResource.d.ts +0 -2
  143. package/build/resources/sutSetupResource.js +0 -63
  144. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DTp2-8Si.js +0 -809
@@ -1,4 +1,5 @@
1
1
  import { type UtilsLanguageSpec } from "./language-spec.js";
2
+ import { type ActionPattern } from "./action-key.js";
2
3
  import { type HelperSleep } from "./fixed-sleep.js";
3
4
  import { type SourceSpan } from "../pom-scope/strip.js";
4
5
  export interface HelperAssertion {
@@ -56,6 +57,9 @@ export interface UtilsHelper {
56
57
  * step per entry, for the sibling scan that matches step by step. */
57
58
  actionKey?: string;
58
59
  actionSteps?: string[];
60
+ /** The sequence as the sibling scan matches it, a parameter-fed locator as a
61
+ * wildcard (SKYR-4537). See actionPatternOf. */
62
+ actionPattern?: ActionPattern;
59
63
  }
60
64
  /** The declared parameters of a signature, raw text trimmed, empty entries dropped
61
65
  * (a trailing comma). Reads the signature from the definition line to its body
@@ -1,5 +1,5 @@
1
1
  import { normalizePath } from "./language-spec.js";
2
- import { actionStepKeys, actionStepsOf, } from "./action-key.js";
2
+ import { actionPatternOf, actionStepKeys, actionStepsOf, } from "./action-key.js";
3
3
  /** A helper's action steps and their joined key, read once from its body. */
4
4
  function actionSequence(body, language) {
5
5
  const steps = actionStepsOf(body, language);
@@ -282,6 +282,7 @@ export function parseUtilsFile(content, spec) {
282
282
  // Comments blanked, strings kept: a commented-out click is not a step, and
283
283
  // the selector literals are the key.
284
284
  ...actionSequence(blankComments(body.join("\n"), spec), spec.language),
285
+ actionPattern: actionPatternOf(blankComments(body.join("\n"), spec), spec.language, params ?? []),
285
286
  };
286
287
  let requests = 0;
287
288
  for (let k = 0; k < body.length; k++) {
@@ -1,4 +1,4 @@
1
- export declare const SKYRAMP_IMAGE_VERSION = "v1.3.50";
2
- export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.50";
3
- export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.50";
1
+ export declare const SKYRAMP_IMAGE_VERSION = "v1.3.53";
2
+ export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.53";
3
+ export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.53";
4
4
  export declare const WORKER_CONTROL_PORT = 35142;
@@ -1,4 +1,4 @@
1
- export const SKYRAMP_IMAGE_VERSION = "v1.3.50";
1
+ export const SKYRAMP_IMAGE_VERSION = "v1.3.53";
2
2
  export const EXECUTOR_DOCKER_IMAGE = `skyramp/executor:${SKYRAMP_IMAGE_VERSION}`;
3
3
  export const WORKER_DOCKER_IMAGE = `skyramp/worker:${SKYRAMP_IMAGE_VERSION}`;
4
4
  // Control port the Skyramp worker listens on (SDK `CONTAINER_PORT`).
@@ -5,24 +5,8 @@ export { SUPPORTED_FRAMEWORKS } from "./frameworks.js";
5
5
  export declare const serviceSchema: z.ZodObject<{
6
6
  serviceName: z.ZodString;
7
7
  repository: z.ZodOptional<z.ZodString>;
8
- language: z.ZodOptional<z.ZodEnum<{
9
- python: "python";
10
- typescript: "typescript";
11
- javascript: "javascript";
12
- java: "java";
13
- }>>;
14
- framework: z.ZodOptional<z.ZodEnum<{
15
- playwright: "playwright";
16
- pytest: "pytest";
17
- robot: "robot";
18
- junit: "junit";
19
- vitest: "vitest";
20
- jest: "jest";
21
- mocha: "mocha";
22
- go: "go";
23
- rspec: "rspec";
24
- jasmine: "jasmine";
25
- }>>;
8
+ language: z.ZodOptional<z.ZodString>;
9
+ framework: z.ZodOptional<z.ZodString>;
26
10
  testDirectory: z.ZodOptional<z.ZodString>;
27
11
  api: z.ZodOptional<z.ZodObject<{
28
12
  schemaPath: z.ZodOptional<z.ZodString>;
@@ -46,49 +30,30 @@ export declare const serviceSchema: z.ZodObject<{
46
30
  baseUrl: z.ZodOptional<z.ZodString>;
47
31
  skipTLSVerify: z.ZodOptional<z.ZodBoolean>;
48
32
  }, z.core.$strict>>;
49
- runtimeDetails: z.ZodOptional<z.ZodObject<{
33
+ runtimeDetails: z.ZodOptional<z.ZodPipe<z.ZodObject<{
50
34
  serverStartCommand: z.ZodOptional<z.ZodString>;
51
35
  serverStopCommand: z.ZodOptional<z.ZodString>;
52
36
  runtime: z.ZodPreprocess<z.ZodEnum<typeof import("../types/TestTypes.js").RuntimeEnvironment>, unknown>;
53
37
  dockerNetwork: z.ZodOptional<z.ZodString>;
54
38
  k8sNamespace: z.ZodOptional<z.ZodString>;
55
39
  k8sContext: z.ZodOptional<z.ZodString>;
56
- testSuites: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodObject<{
57
- framework: z.ZodOptional<z.ZodEnum<{
58
- playwright: "playwright";
59
- pytest: "pytest";
60
- robot: "robot";
61
- junit: "junit";
62
- vitest: "vitest";
63
- jest: "jest";
64
- mocha: "mocha";
65
- go: "go";
66
- rspec: "rspec";
67
- jasmine: "jasmine";
68
- }>>;
69
- testRunCommand: z.ZodString;
70
- pathGlobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
71
- canaryStatus: z.ZodOptional<z.ZodEnum<{
72
- proven: "proven";
73
- unproven: "unproven";
74
- }>>;
75
- canaryDetail: z.ZodOptional<z.ZodString>;
76
- testEnv: z.ZodOptional<z.ZodUnknown>;
77
- }, z.core.$strict>, z.ZodTransform<{
78
- testRunCommand: string;
79
- framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
80
- pathGlobs?: string[] | undefined;
81
- canaryStatus?: "proven" | "unproven" | undefined;
82
- canaryDetail?: string | undefined;
83
- }, {
84
- testRunCommand: string;
85
- framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
86
- pathGlobs?: string[] | undefined;
87
- canaryStatus?: "proven" | "unproven" | undefined;
88
- canaryDetail?: string | undefined;
89
- testEnv?: unknown;
90
- }>>>>;
91
- }, z.core.$strict>>;
40
+ testSuites: z.ZodOptional<z.ZodUnknown>;
41
+ }, z.core.$strict>, z.ZodTransform<{
42
+ runtime: import("../types/TestTypes.js").RuntimeEnvironment;
43
+ serverStartCommand?: string | undefined;
44
+ serverStopCommand?: string | undefined;
45
+ dockerNetwork?: string | undefined;
46
+ k8sNamespace?: string | undefined;
47
+ k8sContext?: string | undefined;
48
+ }, {
49
+ runtime: import("../types/TestTypes.js").RuntimeEnvironment;
50
+ serverStartCommand?: string | undefined;
51
+ serverStopCommand?: string | undefined;
52
+ dockerNetwork?: string | undefined;
53
+ k8sNamespace?: string | undefined;
54
+ k8sContext?: string | undefined;
55
+ testSuites?: unknown;
56
+ }>>>;
92
57
  }, z.core.$strict>;
93
58
  export declare const workspaceConfigSchema: z.ZodObject<{
94
59
  workspace: z.ZodOptional<z.ZodObject<{
@@ -102,27 +67,19 @@ export declare const workspaceConfigSchema: z.ZodObject<{
102
67
  createdAt: z.ZodOptional<z.ZodString>;
103
68
  updatedAt: z.ZodOptional<z.ZodString>;
104
69
  }, z.core.$strict>>;
70
+ dataSafety: z.ZodOptional<z.ZodObject<{
71
+ mode: z.ZodOptional<z.ZodEnum<{
72
+ readonly: "readonly";
73
+ strict: "strict";
74
+ off: "off";
75
+ }>>;
76
+ offLimits: z.ZodOptional<z.ZodArray<z.ZodString>>;
77
+ }, z.core.$strict>>;
105
78
  services: z.ZodOptional<z.ZodArray<z.ZodObject<{
106
79
  serviceName: z.ZodString;
107
80
  repository: z.ZodOptional<z.ZodString>;
108
- language: z.ZodOptional<z.ZodEnum<{
109
- python: "python";
110
- typescript: "typescript";
111
- javascript: "javascript";
112
- java: "java";
113
- }>>;
114
- framework: z.ZodOptional<z.ZodEnum<{
115
- playwright: "playwright";
116
- pytest: "pytest";
117
- robot: "robot";
118
- junit: "junit";
119
- vitest: "vitest";
120
- jest: "jest";
121
- mocha: "mocha";
122
- go: "go";
123
- rspec: "rspec";
124
- jasmine: "jasmine";
125
- }>>;
81
+ language: z.ZodOptional<z.ZodString>;
82
+ framework: z.ZodOptional<z.ZodString>;
126
83
  testDirectory: z.ZodOptional<z.ZodString>;
127
84
  api: z.ZodOptional<z.ZodObject<{
128
85
  schemaPath: z.ZodOptional<z.ZodString>;
@@ -146,55 +103,35 @@ export declare const workspaceConfigSchema: z.ZodObject<{
146
103
  baseUrl: z.ZodOptional<z.ZodString>;
147
104
  skipTLSVerify: z.ZodOptional<z.ZodBoolean>;
148
105
  }, z.core.$strict>>;
149
- runtimeDetails: z.ZodOptional<z.ZodObject<{
106
+ runtimeDetails: z.ZodOptional<z.ZodPipe<z.ZodObject<{
150
107
  serverStartCommand: z.ZodOptional<z.ZodString>;
151
108
  serverStopCommand: z.ZodOptional<z.ZodString>;
152
109
  runtime: z.ZodPreprocess<z.ZodEnum<typeof import("../types/TestTypes.js").RuntimeEnvironment>, unknown>;
153
110
  dockerNetwork: z.ZodOptional<z.ZodString>;
154
111
  k8sNamespace: z.ZodOptional<z.ZodString>;
155
112
  k8sContext: z.ZodOptional<z.ZodString>;
156
- testSuites: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodObject<{
157
- framework: z.ZodOptional<z.ZodEnum<{
158
- playwright: "playwright";
159
- pytest: "pytest";
160
- robot: "robot";
161
- junit: "junit";
162
- vitest: "vitest";
163
- jest: "jest";
164
- mocha: "mocha";
165
- go: "go";
166
- rspec: "rspec";
167
- jasmine: "jasmine";
168
- }>>;
169
- testRunCommand: z.ZodString;
170
- pathGlobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
171
- canaryStatus: z.ZodOptional<z.ZodEnum<{
172
- proven: "proven";
173
- unproven: "unproven";
174
- }>>;
175
- canaryDetail: z.ZodOptional<z.ZodString>;
176
- testEnv: z.ZodOptional<z.ZodUnknown>;
177
- }, z.core.$strict>, z.ZodTransform<{
178
- testRunCommand: string;
179
- framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
180
- pathGlobs?: string[] | undefined;
181
- canaryStatus?: "proven" | "unproven" | undefined;
182
- canaryDetail?: string | undefined;
183
- }, {
184
- testRunCommand: string;
185
- framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
186
- pathGlobs?: string[] | undefined;
187
- canaryStatus?: "proven" | "unproven" | undefined;
188
- canaryDetail?: string | undefined;
189
- testEnv?: unknown;
190
- }>>>>;
191
- }, z.core.$strict>>;
113
+ testSuites: z.ZodOptional<z.ZodUnknown>;
114
+ }, z.core.$strict>, z.ZodTransform<{
115
+ runtime: import("../types/TestTypes.js").RuntimeEnvironment;
116
+ serverStartCommand?: string | undefined;
117
+ serverStopCommand?: string | undefined;
118
+ dockerNetwork?: string | undefined;
119
+ k8sNamespace?: string | undefined;
120
+ k8sContext?: string | undefined;
121
+ }, {
122
+ runtime: import("../types/TestTypes.js").RuntimeEnvironment;
123
+ serverStartCommand?: string | undefined;
124
+ serverStopCommand?: string | undefined;
125
+ dockerNetwork?: string | undefined;
126
+ k8sNamespace?: string | undefined;
127
+ k8sContext?: string | undefined;
128
+ testSuites?: unknown;
129
+ }>>>;
192
130
  }, z.core.$strict>>>;
193
131
  }, z.core.$strict>;
194
132
  export type Service = z.infer<typeof serviceSchema>;
195
133
  export type ServiceApi = NonNullable<Service["api"]>;
196
134
  export type ServiceRuntimeDetails = NonNullable<Service["runtimeDetails"]>;
197
- export type TestSuite = NonNullable<ServiceRuntimeDetails["testSuites"]>[number];
198
135
  export type WorkspaceConfig = z.infer<typeof workspaceConfigSchema>;
199
136
  export type WorkspaceSection = NonNullable<WorkspaceConfig["workspace"]>;
200
137
  export type MetadataSection = NonNullable<WorkspaceConfig["metadata"]>;
@@ -211,11 +148,15 @@ export declare function validateWorkspaceConfig(config: unknown): z.ZodSafeParse
211
148
  createdAt?: string | undefined;
212
149
  updatedAt?: string | undefined;
213
150
  } | undefined;
151
+ dataSafety?: {
152
+ mode?: "readonly" | "strict" | "off" | undefined;
153
+ offLimits?: string[] | undefined;
154
+ } | undefined;
214
155
  services?: {
215
156
  serviceName: string;
216
157
  repository?: string | undefined;
217
- language?: "python" | "typescript" | "javascript" | "java" | undefined;
218
- framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
158
+ language?: string | undefined;
159
+ framework?: string | undefined;
219
160
  testDirectory?: string | undefined;
220
161
  api?: {
221
162
  schemaPath?: string | undefined;
@@ -237,13 +178,6 @@ export declare function validateWorkspaceConfig(config: unknown): z.ZodSafeParse
237
178
  dockerNetwork?: string | undefined;
238
179
  k8sNamespace?: string | undefined;
239
180
  k8sContext?: string | undefined;
240
- testSuites?: {
241
- testRunCommand: string;
242
- framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
243
- pathGlobs?: string[] | undefined;
244
- canaryStatus?: "proven" | "unproven" | undefined;
245
- canaryDetail?: string | undefined;
246
- }[] | undefined;
247
181
  } | undefined;
248
182
  }[] | undefined;
249
183
  }>;
@@ -273,9 +207,17 @@ export declare class WorkspaceConfigManager {
273
207
  /**
274
208
  * Initialize workspace — creates .skyramp/workspace.yml with repo info.
275
209
  * Auto-detects repoName and repoUrl from git when not provided.
276
- * Produces an empty services array and default metadata timestamps.
210
+ * Produces an empty services array and default metadata timestamps. Over an
211
+ * existing file it keeps that file's createdAt: the workspace was created
212
+ * then, however often it is rewritten.
277
213
  */
278
214
  initialize(workspaceInfo?: WorkspaceSection): Promise<WorkspaceConfig>;
215
+ /**
216
+ * The existing file's metadata.createdAt, read leniently: a rewrite is often
217
+ * the repair of a file that no longer validates, and its createdAt still
218
+ * stands. Undefined when there is no file or it records none.
219
+ */
220
+ private existingCreatedAt;
279
221
  /**
280
222
  * Update the metadata section of an existing workspace config.
281
223
  * Only the provided fields are merged; metadata.updatedAt is refreshed
@@ -14,7 +14,6 @@ import { execFile } from "child_process";
14
14
  import { promisify } from "util";
15
15
  import yaml from "js-yaml";
16
16
  import { z } from "zod";
17
- import { SUPPORTED_FRAMEWORKS } from "./frameworks.js";
18
17
  import { runtimeEnvironmentInputSchema } from "../types/TestTypes.js";
19
18
  import { parseOwnerRepo } from "../utils/repositorySlug.js";
20
19
  const execFileAsync = promisify(execFile);
@@ -23,8 +22,8 @@ const execFileAsync = promisify(execFile);
23
22
  // ---------------------------------------------------------------------------
24
23
  export const WORKSPACE_DIR = ".skyramp";
25
24
  export const WORKSPACE_FILENAME = "workspace.yml";
26
- // SUPPORTED_FRAMEWORKS lives in the dependency-free ./frameworks module (imported
27
- // above for the schema below) and is re-exported here for existing importers.
25
+ // SUPPORTED_FRAMEWORKS lives in the dependency-free ./frameworks module and is
26
+ // re-exported here for existing importers.
28
27
  export { SUPPORTED_FRAMEWORKS } from "./frameworks.js";
29
28
  // ---------------------------------------------------------------------------
30
29
  // Zod schemas (source of truth — types are inferred from these below)
@@ -32,12 +31,14 @@ export { SUPPORTED_FRAMEWORKS } from "./frameworks.js";
32
31
  export const serviceSchema = z
33
32
  .object({
34
33
  serviceName: z.string(),
35
- // owner/repo this service belongs to (multi-repo workspaces). Absent → the
34
+ // owner/repo (org/project/repo on Azure DevOps) this service belongs to (multi-repo workspaces). Absent → the
36
35
  // primary repo. Service identity is the composite (repository, serviceName),
37
36
  // so the same serviceName can appear once per repo without colliding.
38
37
  repository: z.string().optional(),
39
- language: z.enum(["python", "typescript", "javascript", "java"]).optional(),
40
- framework: z.enum(SUPPORTED_FRAMEWORKS).optional(),
38
+ // Any language and test framework, as the repository names them: the scan is
39
+ // language- and framework-agnostic.
40
+ language: z.string().optional(),
41
+ framework: z.string().optional(),
41
42
  testDirectory: z.string().optional(),
42
43
  api: z
43
44
  .object({
@@ -105,49 +106,13 @@ export const serviceSchema = z
105
106
  dockerNetwork: z.string().optional(),
106
107
  k8sNamespace: z.string().optional(),
107
108
  k8sContext: z.string().optional(),
108
- // External-suite test contract (SKYR-4002). A service hosts one or more
109
- // runnable suites — record each under testSuites, even a single suite
110
- // (there is no flat single-suite form). Written by the SUT adapt_workflow
111
- // bootstrap (skyramp_testbot_sut). Each suite carries its own framework
112
- // (falling back to the service's), testRunCommand, and pathGlobs
113
- // — the repo-relative paths that suite owns, which the agent reads to find
114
- // the suite of a test file. One service can host multiple frameworks.
115
- testSuites: z
116
- .array(
117
- // A workspace.yml written before testEnv was removed still loads: the
118
- // key is accepted and then dropped, so the next write leaves it out.
119
- z
120
- .object({
121
- framework: z.enum(SUPPORTED_FRAMEWORKS).optional(),
122
- testRunCommand: z.string().min(1),
123
- pathGlobs: z.array(z.string()).optional(),
124
- // Did SUT setup actually run one test from this suite?
125
- //
126
- // "Is the environment proven" and "should maintenance work on this
127
- // suite" are independent, and collapsing them costs one way or the
128
- // other. The prompt used to make recording free, so a suite could
129
- // read as covered while never having executed. Requiring proof
130
- // instead would drop exactly the suite a user most wants
131
- // maintained: a self-provisioning e2e suite whose substrate the
132
- // setup cannot stand up. Run 30402703836 shows why that trade is
133
- // bad — with its Playwright environment dead, static analysis
134
- // still found the stale POM testid and fixed it correctly.
135
- //
136
- // So an unproven suite STAYS and is still maintained; this records
137
- // that its environment was never validated, and canaryDetail says
138
- // what it needs. Absent means unproven (older artifacts predate
139
- // this field), which is the safe reading.
140
- canaryStatus: z.enum(["proven", "unproven"]).optional(),
141
- /** Why it could not be proven — required by the prompt when unproven. */
142
- canaryDetail: z.string().optional(),
143
- // Accepted only so an older file parses; the transform removes it.
144
- testEnv: z.unknown().optional(),
145
- })
146
- .strict()
147
- .transform(({ testEnv: _testEnv, ...rest }) => rest))
148
- .optional(),
109
+ // Deprecated: testSuites (with its pathGlobs) is no longer recorded or read.
110
+ // A workspace.yml that still has it loads; the key is dropped, so the next
111
+ // write leaves it out.
112
+ testSuites: z.unknown().optional(),
149
113
  })
150
114
  .strict()
115
+ .transform(({ testSuites: _testSuites, ...rest }) => rest)
151
116
  .optional(),
152
117
  })
153
118
  .strict();
@@ -174,6 +139,16 @@ export const workspaceConfigSchema = z
174
139
  })
175
140
  .strict()
176
141
  .optional(),
142
+ /** Data safety for UI recording: scratch-entity discipline mode and
143
+ * customer off-limits URL patterns. Consumed by the Playwright trace
144
+ * backend and by testbot's config resolution. */
145
+ dataSafety: z
146
+ .object({
147
+ mode: z.enum(["strict", "readonly", "off"]).optional(),
148
+ offLimits: z.array(z.string()).optional(),
149
+ })
150
+ .strict()
151
+ .optional(),
177
152
  services: z.array(serviceSchema).optional(),
178
153
  })
179
154
  .strict();
@@ -346,13 +321,18 @@ export class WorkspaceConfigManager {
346
321
  /**
347
322
  * Initialize workspace — creates .skyramp/workspace.yml with repo info.
348
323
  * Auto-detects repoName and repoUrl from git when not provided.
349
- * Produces an empty services array and default metadata timestamps.
324
+ * Produces an empty services array and default metadata timestamps. Over an
325
+ * existing file it keeps that file's createdAt: the workspace was created
326
+ * then, however often it is rewritten.
350
327
  */
351
328
  async initialize(workspaceInfo = {}) {
352
329
  await fs.mkdir(this.skyrampDir, { recursive: true });
353
330
  // Auto-detect repo info from workspace root when not explicitly provided
354
331
  const detected = await detectRepoInfo(this.workspacePath);
355
332
  const config = createDefaultConfig();
333
+ const createdAt = await this.existingCreatedAt();
334
+ if (createdAt && config.metadata)
335
+ config.metadata.createdAt = createdAt;
356
336
  config.workspace = {
357
337
  repoName: workspaceInfo.repoName || detected.repoName || undefined,
358
338
  repoUrl: workspaceInfo.repoUrl || detected.repoUrl || undefined,
@@ -361,6 +341,23 @@ export class WorkspaceConfigManager {
361
341
  await writeWorkspaceFile(this.configPath, validated);
362
342
  return validated;
363
343
  }
344
+ /**
345
+ * The existing file's metadata.createdAt, read leniently: a rewrite is often
346
+ * the repair of a file that no longer validates, and its createdAt still
347
+ * stands. Undefined when there is no file or it records none.
348
+ */
349
+ async existingCreatedAt() {
350
+ try {
351
+ const doc = yaml.load(await fs.readFile(this.configPath, "utf8"));
352
+ const createdAt = doc?.metadata?.createdAt;
353
+ return typeof createdAt === "string" && createdAt.trim()
354
+ ? createdAt
355
+ : undefined;
356
+ }
357
+ catch {
358
+ return undefined;
359
+ }
360
+ }
364
361
  /**
365
362
  * Update the metadata section of an existing workspace config.
366
363
  * Only the provided fields are merged; metadata.updatedAt is refreshed
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Verifies the services written to .skyramp/workspace.yml against the rules
3
+ * the scan instructions state and that a file can show deterministically:
4
+ * service identity, runtime fields and API addressing and auth.
5
+ * skyramp_init_workspace refuses a write that breaks one, and the SUT eval runs
6
+ * it on the file a run produced. It reports only what is certainly wrong.
7
+ */
8
+ import type { Service } from "./workspace.js";
9
+ /** A repository of the Target other than the workspace's own. */
10
+ export interface VerifierRepository {
11
+ repository: string;
12
+ repositoryPath: string;
13
+ }
14
+ export interface WorkspaceVerifierOptions {
15
+ /** The workspace's own repository root, where a service with no `repository` lives. */
16
+ workspacePath: string;
17
+ /** The other repositories the scan named. When given, every service's
18
+ * `repository` is one of them. */
19
+ relatedRepositories?: VerifierRepository[];
20
+ }
21
+ /** Every problem with `services`, one line each, naming the service and the fix. */
22
+ export declare function verifyWorkspaceServices(services: readonly Service[], opts: WorkspaceVerifierOptions): string[];
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Verifies the services written to .skyramp/workspace.yml against the rules
3
+ * the scan instructions state and that a file can show deterministically:
4
+ * service identity, runtime fields and API addressing and auth.
5
+ * skyramp_init_workspace refuses a write that breaks one, and the SUT eval runs
6
+ * it on the file a run produced. It reports only what is certainly wrong.
7
+ */
8
+ import { isRepositorySlug } from "../utils/repositorySlug.js";
9
+ /** Every problem with `services`, one line each, naming the service and the fix. */
10
+ export function verifyWorkspaceServices(services, opts) {
11
+ const issues = [];
12
+ const related = opts.relatedRepositories;
13
+ const seen = new Set();
14
+ for (const service of services) {
15
+ const name = service.serviceName;
16
+ const where = service.repository ? `${service.repository}/${name}` : name;
17
+ const key = `${service.repository ?? ""}\u0000${name}`;
18
+ if (seen.has(key))
19
+ issues.push(`${where}: listed twice. Each (repository, serviceName) pair is one service; merge the entries.`);
20
+ seen.add(key);
21
+ if (service.repository !== undefined) {
22
+ if (!isRepositorySlug(service.repository))
23
+ issues.push(`${where}: repository "${service.repository}" is not owner/repo, or org/project/repo on Azure DevOps.`);
24
+ else if (related?.length &&
25
+ !related.some((r) => r.repository.toLowerCase() === service.repository.toLowerCase()))
26
+ issues.push(`${where}: repository "${service.repository}" is not one the scan named (${related.map((r) => r.repository).join(", ")}). Set it to the repository the service was found in, or leave it out for the workspace's own repository.`);
27
+ }
28
+ issues.push(...runtimeIssues(service, where));
29
+ issues.push(...apiIssues(service, where));
30
+ }
31
+ return issues;
32
+ }
33
+ function runtimeIssues(service, where) {
34
+ const issues = [];
35
+ const rt = service.runtimeDetails;
36
+ if (!rt)
37
+ return issues;
38
+ const runtime = rt.runtime;
39
+ if (runtime !== "docker" && rt.dockerNetwork)
40
+ issues.push(`${where}: dockerNetwork is set only for runtime docker; remove it.`);
41
+ if (runtime !== "k8s" && (rt.k8sNamespace || rt.k8sContext))
42
+ issues.push(`${where}: k8sNamespace and k8sContext are set only for runtime k8s; remove them.`);
43
+ return issues;
44
+ }
45
+ function apiIssues(service, where) {
46
+ const issues = [];
47
+ const api = service.api;
48
+ if (!api)
49
+ return issues;
50
+ if (api.baseUrl !== undefined) {
51
+ let ok = false;
52
+ try {
53
+ const url = new URL(api.baseUrl);
54
+ ok = url.protocol === "http:" || url.protocol === "https:";
55
+ }
56
+ catch {
57
+ ok = false;
58
+ }
59
+ if (!ok)
60
+ issues.push(`${where}: api.baseUrl "${api.baseUrl}" is not an absolute http(s) URL.`);
61
+ }
62
+ if (api.authType === "apiKey" && !api.authHeader?.trim())
63
+ issues.push(`${where}: authType apiKey needs authHeader, the header that carries the key. If it cannot be found, use authType none.`);
64
+ return issues;
65
+ }
@@ -595,7 +595,7 @@ async function domEvaluationScript(options) {
595
595
  }
596
596
  return key;
597
597
  }
598
- function isDynamicIdClient(id) {
598
+ function isDynamicIdClient(id, kinds) {
599
599
  if (/^react-aria\d+/.test(id)) return true;
600
600
  if (/^mui-\d+/.test(id)) return true;
601
601
  if (/^(mat|cdk)-[a-z]+-\d+$/.test(id)) return true;
@@ -604,9 +604,10 @@ async function domEvaluationScript(options) {
604
604
  if (/^\d+$/.test(id)) return true;
605
605
  if (/\d{4,}$/.test(id)) return true;
606
606
  if (/[-_][0-9a-f]{6,}$/i.test(id)) return true;
607
- const m = id.match(/[-_]([0-9a-f]{3,5})$/i);
608
- if (m && /[0-9]/.test(m[1])) return true;
609
- if (/[a-zA-Z][0-9]{3,}$/.test(id)) return true;
607
+ const m = id.match(/^(.*?)[-_]([0-9a-f]{3,5})$/i);
608
+ if (m && (/[0-9]/.test(m[2]) || m[2].length >= 4 && kinds?.includes(m[1].toLowerCase()))) return true;
609
+ const counter = id.match(/^(.*?[a-zA-Z])([0-9]+)$/);
610
+ if (counter && (counter[2].length >= 3 || kinds?.includes(counter[1].toLowerCase()))) return true;
610
611
  if (id.includes(":")) return true;
611
612
  if (/«r[0-9a-z]*»|(^|[-_])_?r_[0-9a-z]+_/.test(id)) return true;
612
613
  if (/^.+__search_[a-zA-Z0-9]{4,}$/.test(id)) return true;
@@ -627,7 +628,7 @@ async function domEvaluationScript(options) {
627
628
  const ancestorTestId = resolveTestId(cur);
628
629
  if (ancestorTestId) return ancestorTestId.value;
629
630
  const id = cur.getAttribute("id");
630
- if (id && !isDynamicIdClient(id)) return id;
631
+ if (id && !isDynamicIdClient(id, [cur.localName, getRole(cur).role])) return id;
631
632
  }
632
633
  cur = cur.parentElement;
633
634
  depth++;
@@ -845,7 +846,7 @@ async function domEvaluationScript(options) {
845
846
  const idAttr = el.getAttribute("id");
846
847
  const resolvedTestId = resolveTestId(el);
847
848
  const fingerprint = widgetType === "native" ? null : await computeElementFingerprint(el);
848
- const stableIdValue = idAttr && idUnique(idAttr) && !isDynamicIdClient(idAttr) ? idAttr : null;
849
+ const stableIdValue = idAttr && idUnique(idAttr) && !isDynamicIdClient(idAttr, [el.localName, role]) ? idAttr : null;
849
850
  return {
850
851
  role,
851
852
  accessibleName,
@@ -21,7 +21,7 @@ __export(dynamicId_exports, {
21
21
  isDynamicId: () => isDynamicId
22
22
  });
23
23
  module.exports = __toCommonJS(dynamicId_exports);
24
- function isDynamicId(id) {
24
+ function isDynamicId(id, kinds) {
25
25
  if (/^react-aria\d+/.test(id)) return true;
26
26
  if (/^mui-\d+/.test(id)) return true;
27
27
  if (/^(mat|cdk)-[a-z]+-\d+$/.test(id)) return true;
@@ -30,9 +30,10 @@ function isDynamicId(id) {
30
30
  if (/^\d+$/.test(id)) return true;
31
31
  if (/\d{4,}$/.test(id)) return true;
32
32
  if (/[-_][0-9a-f]{6,}$/i.test(id)) return true;
33
- const shortHexMatch = id.match(/[-_]([0-9a-f]{3,5})$/i);
34
- if (shortHexMatch && /[0-9]/.test(shortHexMatch[1])) return true;
35
- if (/[a-zA-Z][0-9]{3,}$/.test(id)) return true;
33
+ const shortHexMatch = id.match(/^(.*?)[-_]([0-9a-f]{3,5})$/i);
34
+ if (shortHexMatch && (/[0-9]/.test(shortHexMatch[2]) || shortHexMatch[2].length >= 4 && kinds?.includes(shortHexMatch[1].toLowerCase()))) return true;
35
+ const counterMatch = id.match(/^(.*?[a-zA-Z])([0-9]+)$/);
36
+ if (counterMatch && (counterMatch[2].length >= 3 || kinds?.includes(counterMatch[1].toLowerCase()))) return true;
36
37
  if (id.includes(":")) return true;
37
38
  if (/«r[0-9a-z]*»|(^|[-_])_?r_[0-9a-z]+_/.test(id)) return true;
38
39
  if (/^.+__search_[a-zA-Z0-9]{4,}$/.test(id)) return true;
@@ -71,6 +71,18 @@ test("isDynamicId: React 19 useId forms", () => {
71
71
  assertTrue((0, import_dynamicId.isDynamicId)("_r_1k_"), "_r_1k_ (bare React 19.1)");
72
72
  assertFalse((0, import_dynamicId.isDynamicId)("user_r_name"), "user_r_name (word, not a useId)");
73
73
  });
74
+ test("isDynamicId: the element's own kind plus a counter or short hash", () => {
75
+ assertTrue((0, import_dynamicId.isDynamicId)("input-cbcc", ["input", "textbox"]), "input-cbcc on an input");
76
+ assertTrue((0, import_dynamicId.isDynamicId)("checkbox7", ["input", "checkbox"]), "checkbox7 on a checkbox");
77
+ assertTrue((0, import_dynamicId.isDynamicId)("textbox-fdda", ["input", "textbox"]), "textbox-fdda on a textbox");
78
+ assertFalse((0, import_dynamicId.isDynamicId)("input-cbcc"), "input-cbcc without the element: a word-like suffix");
79
+ assertFalse((0, import_dynamicId.isDynamicId)("checkbox7"), "checkbox7 without the element: below the 3-digit threshold");
80
+ assertFalse((0, import_dynamicId.isDynamicId)("news-feed", ["input", "textbox"]), "news-feed: authored stem");
81
+ assertFalse((0, import_dynamicId.isDynamicId)("option2", ["input", "checkbox"]), "option2: stem is not the element kind");
82
+ assertFalse((0, import_dynamicId.isDynamicId)("IDToken2", ["input", "textbox"]), "IDToken2: authored stem");
83
+ assertFalse((0, import_dynamicId.isDynamicId)("password-login", ["input", "textbox"]), "password-login");
84
+ assertFalse((0, import_dynamicId.isDynamicId)("button-add", ["button"]), "button-add: 3 letters stay a word");
85
+ });
74
86
  let failed = 0;
75
87
  for (const { name, run } of cases) {
76
88
  try {