@skyramp/mcp 0.4.4-rc.2 → 0.5.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 (36) 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 +15 -0
  5. package/build/index.js +4 -4
  6. package/build/playwright/essentialTools.d.ts +1 -0
  7. package/build/playwright/essentialTools.js +43 -0
  8. package/build/playwright/registerPlaywrightTools.js +1 -43
  9. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
  10. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
  11. package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
  12. package/build/tool-phases.d.ts +14 -8
  13. package/build/tool-phases.js +83 -43
  14. package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
  15. package/build/tools/workspace/initScanWorkspaceTool.js +100 -9
  16. package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
  17. package/build/utils/versions.d.ts +3 -3
  18. package/build/utils/versions.js +1 -1
  19. package/build/workspace/workspace.d.ts +51 -121
  20. package/build/workspace/workspace.js +34 -47
  21. package/build/workspace/workspaceVerifier.d.ts +22 -0
  22. package/build/workspace/workspaceVerifier.js +65 -0
  23. package/package.json +2 -2
  24. package/plugin/prompts/plan-tests.md +2 -2
  25. package/plugin/skills/setup-testbot-target-sut/SKILL.md +54 -0
  26. package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
  27. package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
  28. package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
  29. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
  30. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
  31. package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
  32. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
  33. package/build/prompts/sut-setup/shared.d.ts +0 -62
  34. package/build/prompts/sut-setup/shared.js +0 -196
  35. package/build/resources/sutSetupResource.d.ts +0 -2
  36. package/build/resources/sutSetupResource.js +0 -63
@@ -1,25 +1,87 @@
1
- export const TOOL_PHASE_MAP = {
2
- skyramp_recommend_tests: "analyzing",
3
- skyramp_analyze_changes: "analyzing",
4
- skyramp_register_test_plan: "analyzing",
5
- skyramp_smoke_test_generation: "generating",
6
- skyramp_fuzz_test_generation: "generating",
7
- skyramp_contract_test_generation: "generating",
8
- skyramp_load_test_generation: "generating",
9
- skyramp_integration_test_generation: "generating",
10
- skyramp_e2e_test_generation: "generating",
11
- skyramp_ui_test_generation: "generating",
12
- skyramp_ui_load_test_generation: "generating",
13
- skyramp_batch_scenario_test_generation: "generating",
14
- skyramp_mock_generation: "generating",
15
- skyramp_batch_mock_generation: "generating",
16
- skyramp_generate_enriched_integration_test: "generating",
17
- skyramp_enrich_test_with_mocks: "generating",
18
- skyramp_execute_test: { before: "maintaining", after: "executing" },
19
- skyramp_execute_fleet: { before: "maintaining", after: "executing" },
20
- skyramp_analyze_test_health: "maintaining",
21
- skyramp_submit_report: "reporting",
1
+ import { ESSENTIAL_TOOLS } from "./playwright/essentialTools.js";
2
+ export const NOT_GATED = "not gated";
3
+ const EXECUTION_PHASE = { before: "maintaining", after: "executing" };
4
+ export const TOOLS = {
5
+ skyramp_analyze_changes: { phase: "analyzing", entitlement: "analyze" },
6
+ skyramp_analyze_test_health: {
7
+ phase: "maintaining",
8
+ entitlement: "analyze",
9
+ },
10
+ // A prompt, not a tool; listed for its phase.
11
+ skyramp_recommend_tests: { phase: "analyzing", entitlement: "recommend" },
12
+ skyramp_register_test_plan: { phase: "analyzing", entitlement: "recommend" },
13
+ skyramp_resolve_screen: { entitlement: "recommend" },
14
+ skyramp_smoke_test_generation: {
15
+ phase: "generating",
16
+ entitlement: "generate",
17
+ },
18
+ skyramp_fuzz_test_generation: {
19
+ phase: "generating",
20
+ entitlement: "generate",
21
+ },
22
+ skyramp_contract_test_generation: {
23
+ phase: "generating",
24
+ entitlement: "generate",
25
+ },
26
+ skyramp_load_test_generation: {
27
+ phase: "generating",
28
+ entitlement: "generate",
29
+ },
30
+ skyramp_integration_test_generation: {
31
+ phase: "generating",
32
+ entitlement: "generate",
33
+ },
34
+ skyramp_e2e_test_generation: { phase: "generating", entitlement: "generate" },
35
+ skyramp_ui_test_generation: { phase: "generating", entitlement: "generate" },
36
+ skyramp_ui_load_test_generation: {
37
+ phase: "generating",
38
+ entitlement: "generate",
39
+ },
40
+ skyramp_batch_scenario_test_generation: {
41
+ phase: "generating",
42
+ entitlement: "generate",
43
+ },
44
+ skyramp_mock_generation: { phase: "generating", entitlement: "generate" },
45
+ skyramp_batch_mock_generation: {
46
+ phase: "generating",
47
+ entitlement: "generate",
48
+ },
49
+ skyramp_generate_enriched_integration_test: {
50
+ phase: "generating",
51
+ entitlement: "generate",
52
+ },
53
+ skyramp_enrich_test_with_mocks: {
54
+ phase: "generating",
55
+ entitlement: "generate",
56
+ },
57
+ skyramp_reuse_code: { entitlement: "generate" },
58
+ skyramp_modularization: { entitlement: "generate" },
59
+ skyramp_enhance_assertions: { entitlement: "generate" },
60
+ skyramp_enhance_assertions_snapshot: { entitlement: "generate" },
61
+ skyramp_verify_enhance_assertions: { entitlement: "generate" },
62
+ skyramp_align_assertions: { entitlement: "generate" },
63
+ skyramp_verify_test_dependencies: { entitlement: "generate" },
64
+ skyramp_fix_errors: { entitlement: "generate" },
65
+ skyramp_actions: { entitlement: "generate" },
66
+ skyramp_one_click_tool: { entitlement: "generate" },
67
+ skyramp_preflight_mock_check: { entitlement: "generate" },
68
+ skyramp_setup_local_dev_worker: { entitlement: "generate" },
69
+ skyramp_query_proxy_mocks: { entitlement: "generate" },
70
+ ...Object.fromEntries([...ESSENTIAL_TOOLS].map((name) => [
71
+ name,
72
+ { entitlement: "generate" },
73
+ ])),
74
+ skyramp_execute_test: { phase: EXECUTION_PHASE, entitlement: "execute" },
75
+ skyramp_execute_fleet: { phase: EXECUTION_PHASE, entitlement: "execute" },
76
+ skyramp_start_trace_collection: { entitlement: "execute" },
77
+ skyramp_stop_trace_collection: { entitlement: "execute" },
78
+ skyramp_submit_report: { phase: "reporting", entitlement: NOT_GATED },
79
+ skyramp_login: { entitlement: NOT_GATED },
80
+ skyramp_logout: { entitlement: NOT_GATED },
81
+ skyramp_init_scan: { entitlement: NOT_GATED },
82
+ skyramp_init_workspace: { entitlement: NOT_GATED },
22
83
  };
84
+ export const TOOL_PHASE_MAP = Object.fromEntries(Object.entries(TOOLS).flatMap(([name, entry]) => entry.phase === undefined ? [] : [[name, entry.phase]]));
23
85
  /** Tools whose phase depends on context — listed here for consumer discovery. */
24
86
  export const CONTEXT_DEPENDENT_TOOLS = new Set(Object.entries(TOOL_PHASE_MAP)
25
87
  .filter((entry) => typeof entry[1] === "object")
@@ -28,25 +90,3 @@ export const CONTEXT_DEPENDENT_TOOLS = new Set(Object.entries(TOOL_PHASE_MAP)
28
90
  export const GENERATING_TOOLS = new Set(Object.entries(TOOL_PHASE_MAP)
29
91
  .filter((entry) => typeof entry[1] === "string" && entry[1] === "generating")
30
92
  .map((entry) => entry[0]));
31
- /** Tools that intentionally have no progress phase (infrastructure/utility). */
32
- export const TOOLS_WITHOUT_PHASE = new Set([
33
- "skyramp_login",
34
- "skyramp_logout",
35
- "skyramp_init_scan",
36
- "skyramp_init_workspace",
37
- "skyramp_one_click_tool",
38
- "skyramp_setup_local_dev_worker",
39
- "skyramp_actions",
40
- "skyramp_start_trace_collection",
41
- "skyramp_stop_trace_collection",
42
- "skyramp_fix_errors",
43
- "skyramp_modularization",
44
- "skyramp_reuse_code",
45
- "skyramp_enhance_assertions",
46
- "skyramp_enhance_assertions_snapshot",
47
- "skyramp_verify_enhance_assertions",
48
- "skyramp_align_assertions",
49
- "skyramp_preflight_mock_check",
50
- "skyramp_query_proxy_mocks",
51
- "skyramp_verify_test_dependencies",
52
- ]);
@@ -1,4 +1,14 @@
1
1
  import { McpServer } from "@modelcontextprotocol/server";
2
- export declare function issueScanToken(): string;
2
+ import { type ScanRelatedRepository } from "../../prompts/initialize-workspace/initializeWorkspacePrompt.js";
3
+ /** What a scan token was issued for: when it expires, and the related
4
+ * repositories its scan named, which skyramp_init_workspace verifies the
5
+ * services against. */
6
+ export interface ScanGrant {
7
+ expiry: number;
8
+ relatedRepositories: ScanRelatedRepository[];
9
+ }
10
+ export declare function issueScanToken(relatedRepositories?: ScanRelatedRepository[]): string;
11
+ /** The grant of a valid, unexpired token, without consuming it. */
12
+ export declare function scanTokenGrant(token: string): ScanGrant | undefined;
3
13
  export declare function validateAndConsumeScanToken(token: string): boolean;
4
14
  export declare function registerInitScanWorkspaceTool(server: McpServer): void;
@@ -1,24 +1,33 @@
1
1
  import { z } from "zod";
2
2
  import { randomUUID } from "crypto";
3
+ import fs from "fs";
3
4
  import { WorkspaceConfigManager } from "../../workspace/workspace.js";
5
+ import { resolveRealPath, tryRepositorySlug, } from "../../utils/repositorySlug.js";
4
6
  import { logger } from "../../utils/logger.js";
5
7
  import { AnalyticsService } from "../../services/AnalyticsService.js";
6
- import { INIT_WORKSPACE_INSTRUCTIONS } from "../../prompts/initialize-workspace/initializeWorkspacePrompt.js";
8
+ import { buildInitWorkspaceInstructions, } from "../../prompts/initialize-workspace/initializeWorkspacePrompt.js";
7
9
  const TOOL_NAME = "skyramp_init_scan";
8
- // Token store: token -> expiry timestamp (ms)
9
10
  const scanTokens = new Map();
10
11
  const TOKEN_TTL_MS = 10 * 60 * 1000; // 10 minutes
11
- export function issueScanToken() {
12
+ export function issueScanToken(relatedRepositories = []) {
12
13
  const token = randomUUID();
13
- scanTokens.set(token, Date.now() + TOKEN_TTL_MS);
14
+ scanTokens.set(token, {
15
+ expiry: Date.now() + TOKEN_TTL_MS,
16
+ relatedRepositories,
17
+ });
14
18
  return token;
15
19
  }
20
+ /** The grant of a valid, unexpired token, without consuming it. */
21
+ export function scanTokenGrant(token) {
22
+ const grant = scanTokens.get(token);
23
+ return grant && Date.now() < grant.expiry ? grant : undefined;
24
+ }
16
25
  export function validateAndConsumeScanToken(token) {
17
- const expiry = scanTokens.get(token);
18
- if (!expiry)
26
+ const grant = scanTokens.get(token);
27
+ if (!grant)
19
28
  return false;
20
29
  scanTokens.delete(token);
21
- return Date.now() < expiry;
30
+ return Date.now() < grant.expiry;
22
31
  }
23
32
  const initScanWorkspaceSchema = z.object({
24
33
  workspacePath: z.string().describe("Path to the workspace/repository root"),
@@ -26,12 +35,66 @@ const initScanWorkspaceSchema = z.object({
26
35
  .boolean()
27
36
  .default(false)
28
37
  .describe("Set to true to force a re-scan even if .skyramp/workspace.yml already exists. Default is false."),
38
+ relatedRepositories: z
39
+ .array(z.object({
40
+ repositoryPath: z
41
+ .string()
42
+ .describe("Path to the related repository's checkout"),
43
+ repository: z
44
+ .string()
45
+ .optional()
46
+ .describe("The related repository's owner/repo. Defaults to the one its checkout's origin remote names."),
47
+ }))
48
+ .optional()
49
+ .describe("The other repositories of a multi-repository Target. Each is scanned into the one workspace.yml at workspacePath, its services carrying its owner/repo as `repository`; none gets a workspace.yml of its own. Omit for a single repository."),
50
+ testRepositoryPath: z
51
+ .string()
52
+ .optional()
53
+ .describe("Path to the separate test repository generated tests are delivered into, when there is one. Each service's testDirectory is then read from, and relative to, that checkout."),
29
54
  });
55
+ const OWNER_REPO = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
56
+ function isDirectory(p) {
57
+ try {
58
+ return fs.statSync(p).isDirectory();
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Each related repository with its owner/repo, or the first problem with them:
66
+ * a checkout that is not a directory, is the workspace itself, or names no
67
+ * owner/repo, and a repository listed twice. Paths compare with symlinks
68
+ * resolved, and owner/repo case-insensitively, as GitHub does.
69
+ */
70
+ async function resolveRelatedRepositories(workspacePath, entries) {
71
+ const repos = [];
72
+ const workspaceReal = await resolveRealPath(workspacePath);
73
+ for (const entry of entries) {
74
+ const at = entry.repositoryPath;
75
+ if (!isDirectory(at))
76
+ return { error: `relatedRepositories: ${at} is not a directory` };
77
+ if ((await resolveRealPath(at)) === workspaceReal)
78
+ return {
79
+ error: `relatedRepositories: ${at} is workspacePath itself; list only the other repositories`,
80
+ };
81
+ const repository = entry.repository?.trim() || (await tryRepositorySlug(at));
82
+ if (!repository || !OWNER_REPO.test(repository))
83
+ return {
84
+ error: `relatedRepositories: no owner/repo for ${at}${repository ? ` ("${repository}" is not owner/repo)` : " (its checkout has no origin remote)"}; pass its repository`,
85
+ };
86
+ if (repos.some((r) => r.repository.toLowerCase() === repository.toLowerCase()))
87
+ return { error: `relatedRepositories: ${repository} is listed twice` };
88
+ repos.push({ repository, repositoryPath: at });
89
+ }
90
+ return { repos };
91
+ }
30
92
  export function registerInitScanWorkspaceTool(server) {
31
93
  server.registerTool(TOOL_NAME, {
32
94
  description: "Call this FIRST, before any other Skyramp tool (test generation, execution, analysis), whenever workspacePath is a git repository and .skyramp/workspace.yml does not exist at its root. " +
33
95
  "PREREQUISITE for skyramp_init_workspace. " +
34
96
  "Scans a repository and returns instructions for discovering all services, along with a scanToken. " +
97
+ "For a Target spanning several repositories, call it ONCE with the others in relatedRepositories, so all their services go into one workspace.yml. " +
35
98
  "Follow the instructions to build the services array, then pass both to skyramp_init_workspace to create the workspace config.",
36
99
  inputSchema: initScanWorkspaceSchema,
37
100
  }, async (params) => {
@@ -39,6 +102,8 @@ export function registerInitScanWorkspaceTool(server) {
39
102
  const recordParams = {
40
103
  workspacePath: params.workspacePath,
41
104
  force: String(params.force || false),
105
+ relatedRepositories: String(params.relatedRepositories?.length ?? 0),
106
+ testRepository: String(Boolean(params.testRepositoryPath)),
42
107
  };
43
108
  try {
44
109
  const manager = new WorkspaceConfigManager(params.workspacePath);
@@ -53,12 +118,38 @@ export function registerInitScanWorkspaceTool(server) {
53
118
  isError: false,
54
119
  };
55
120
  }
56
- const token = issueScanToken();
121
+ const related = await resolveRelatedRepositories(params.workspacePath, params.relatedRepositories ?? []);
122
+ if ("error" in related) {
123
+ errorResult = {
124
+ content: [{ type: "text", text: related.error }],
125
+ isError: true,
126
+ };
127
+ return errorResult;
128
+ }
129
+ if (params.testRepositoryPath &&
130
+ !isDirectory(params.testRepositoryPath)) {
131
+ errorResult = {
132
+ content: [
133
+ {
134
+ type: "text",
135
+ text: `testRepositoryPath: ${params.testRepositoryPath} is not a directory`,
136
+ },
137
+ ],
138
+ isError: true,
139
+ };
140
+ return errorResult;
141
+ }
142
+ const token = issueScanToken(related.repos);
143
+ const instructions = buildInitWorkspaceInstructions({
144
+ workspacePath: params.workspacePath,
145
+ relatedRepositories: related.repos,
146
+ testRepositoryPath: params.testRepositoryPath,
147
+ });
57
148
  return {
58
149
  content: [
59
150
  {
60
151
  type: "text",
61
- text: `SCAN TOKEN: ${token}\n\nSave this token — you MUST pass it as scanToken when calling skyramp_init_workspace.\n\n${INIT_WORKSPACE_INSTRUCTIONS}`,
152
+ text: `SCAN TOKEN: ${token}\n\nSave this token — you MUST pass it as scanToken when calling skyramp_init_workspace.\n\n${instructions}`,
62
153
  },
63
154
  ],
64
155
  isError: false,
@@ -7,7 +7,8 @@ import yaml from "js-yaml";
7
7
  import { logger } from "../../utils/logger.js";
8
8
  import { AnalyticsService } from "../../services/AnalyticsService.js";
9
9
  import { SKYRAMP_IMAGE_VERSION } from "../../utils/versions.js";
10
- import { validateAndConsumeScanToken } from "./initScanWorkspaceTool.js";
10
+ import { scanTokenGrant, validateAndConsumeScanToken, } from "./initScanWorkspaceTool.js";
11
+ import { verifyWorkspaceServices } from "../../workspace/workspaceVerifier.js";
11
12
  import { upsertServicesByRepo } from "./serviceUpsert.js";
12
13
  import { isInsideDir } from "../../utils/gitStaging.js";
13
14
  import { EXECUTOR_VIDEOS_DIR, EXECUTOR_WORK_DIR, isInsideExecutorWorkDir, } from "../../utils/executorWorkDir.js";
@@ -110,7 +111,7 @@ export function registerInitializeWorkspaceTool(server) {
110
111
  "1. Fresh init: PREREQUISITE: Call skyramp_init_scan first to get a scanToken and discover services. " +
111
112
  "Then call this tool to write .skyramp/workspace.yml with the discovered services and scanToken. " +
112
113
  "2. Edit existing workspace.yml: omit scanToken, set force: true, and pass the FULL updated services array (overwrite, not merge — fields not in the array are dropped). " +
113
- "The services schema is strict: any field not in the schema (e.g. inventing `serverStopCommand` on `runtimeDetails`) fails validation and the call is rejected.",
114
+ "The services schema is strict: any field not in the schema (e.g. inventing `healthUrl` on `runtimeDetails`) fails validation and the call is rejected.",
114
115
  inputSchema: initializeWorkspaceSchema,
115
116
  _meta: {
116
117
  keywords: [
@@ -182,11 +183,46 @@ export function registerInitializeWorkspaceTool(server) {
182
183
  if (badMerged)
183
184
  return badTestDirectoryResult(badMerged);
184
185
  }
186
+ // The verifier, before the token is spent: a refusal asks for a
187
+ // corrected retry, which must be able to reuse the same token. Only the
188
+ // incoming services: a merge must not be blocked by an older entry.
189
+ const grant = params.scanToken
190
+ ? scanTokenGrant(params.scanToken)
191
+ : undefined;
192
+ const issues = verifyWorkspaceServices(params.services, {
193
+ workspacePath,
194
+ relatedRepositories: grant?.relatedRepositories,
195
+ });
196
+ if (issues.length > 0) {
197
+ return {
198
+ content: [
199
+ {
200
+ type: "text",
201
+ text: `${manager.getConfigPath()} was not written: ${issues.length} problem(s) with the services.\n${issues.map((i) => `- ${i}`).join("\n")}\nFix each one and call skyramp_init_workspace again with the full corrected services array${grant ? " and the same scanToken, which is still valid" : ""}.`,
202
+ },
203
+ ],
204
+ isError: true,
205
+ };
206
+ }
185
207
  // scanToken is required for fresh init; for edits (force:true on an
186
208
  // existing workspace.yml) the caller supplies the full services array
187
- // directly and we skip the token check.
209
+ // directly and no token is needed. A token passed with an edit is still
210
+ // validated and spent, so a successful write never leaves it reusable.
188
211
  const isEdit = alreadyExists && params.force;
189
- if (!isEdit) {
212
+ if (isEdit && params.scanToken) {
213
+ if (!validateAndConsumeScanToken(params.scanToken)) {
214
+ return {
215
+ content: [
216
+ {
217
+ type: "text",
218
+ text: "Invalid or expired scanToken. An edit of an existing workspace.yml needs none: call again with force: true and no scanToken.",
219
+ },
220
+ ],
221
+ isError: true,
222
+ };
223
+ }
224
+ }
225
+ else if (!isEdit) {
190
226
  if (!params.scanToken) {
191
227
  return {
192
228
  content: [
@@ -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.51";
2
+ export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.51";
3
+ export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.51";
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.51";
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<{
@@ -105,24 +70,8 @@ export declare const workspaceConfigSchema: z.ZodObject<{
105
70
  services: z.ZodOptional<z.ZodArray<z.ZodObject<{
106
71
  serviceName: z.ZodString;
107
72
  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
- }>>;
73
+ language: z.ZodOptional<z.ZodString>;
74
+ framework: z.ZodOptional<z.ZodString>;
126
75
  testDirectory: z.ZodOptional<z.ZodString>;
127
76
  api: z.ZodOptional<z.ZodObject<{
128
77
  schemaPath: z.ZodOptional<z.ZodString>;
@@ -146,55 +95,35 @@ export declare const workspaceConfigSchema: z.ZodObject<{
146
95
  baseUrl: z.ZodOptional<z.ZodString>;
147
96
  skipTLSVerify: z.ZodOptional<z.ZodBoolean>;
148
97
  }, z.core.$strict>>;
149
- runtimeDetails: z.ZodOptional<z.ZodObject<{
98
+ runtimeDetails: z.ZodOptional<z.ZodPipe<z.ZodObject<{
150
99
  serverStartCommand: z.ZodOptional<z.ZodString>;
151
100
  serverStopCommand: z.ZodOptional<z.ZodString>;
152
101
  runtime: z.ZodPreprocess<z.ZodEnum<typeof import("../types/TestTypes.js").RuntimeEnvironment>, unknown>;
153
102
  dockerNetwork: z.ZodOptional<z.ZodString>;
154
103
  k8sNamespace: z.ZodOptional<z.ZodString>;
155
104
  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>>;
105
+ testSuites: z.ZodOptional<z.ZodUnknown>;
106
+ }, z.core.$strict>, z.ZodTransform<{
107
+ runtime: import("../types/TestTypes.js").RuntimeEnvironment;
108
+ serverStartCommand?: string | undefined;
109
+ serverStopCommand?: string | undefined;
110
+ dockerNetwork?: string | undefined;
111
+ k8sNamespace?: string | undefined;
112
+ k8sContext?: string | undefined;
113
+ }, {
114
+ runtime: import("../types/TestTypes.js").RuntimeEnvironment;
115
+ serverStartCommand?: string | undefined;
116
+ serverStopCommand?: string | undefined;
117
+ dockerNetwork?: string | undefined;
118
+ k8sNamespace?: string | undefined;
119
+ k8sContext?: string | undefined;
120
+ testSuites?: unknown;
121
+ }>>>;
192
122
  }, z.core.$strict>>>;
193
123
  }, z.core.$strict>;
194
124
  export type Service = z.infer<typeof serviceSchema>;
195
125
  export type ServiceApi = NonNullable<Service["api"]>;
196
126
  export type ServiceRuntimeDetails = NonNullable<Service["runtimeDetails"]>;
197
- export type TestSuite = NonNullable<ServiceRuntimeDetails["testSuites"]>[number];
198
127
  export type WorkspaceConfig = z.infer<typeof workspaceConfigSchema>;
199
128
  export type WorkspaceSection = NonNullable<WorkspaceConfig["workspace"]>;
200
129
  export type MetadataSection = NonNullable<WorkspaceConfig["metadata"]>;
@@ -214,8 +143,8 @@ export declare function validateWorkspaceConfig(config: unknown): z.ZodSafeParse
214
143
  services?: {
215
144
  serviceName: string;
216
145
  repository?: string | undefined;
217
- language?: "python" | "typescript" | "javascript" | "java" | undefined;
218
- framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
146
+ language?: string | undefined;
147
+ framework?: string | undefined;
219
148
  testDirectory?: string | undefined;
220
149
  api?: {
221
150
  schemaPath?: string | undefined;
@@ -237,13 +166,6 @@ export declare function validateWorkspaceConfig(config: unknown): z.ZodSafeParse
237
166
  dockerNetwork?: string | undefined;
238
167
  k8sNamespace?: string | undefined;
239
168
  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
169
  } | undefined;
248
170
  }[] | undefined;
249
171
  }>;
@@ -273,9 +195,17 @@ export declare class WorkspaceConfigManager {
273
195
  /**
274
196
  * Initialize workspace — creates .skyramp/workspace.yml with repo info.
275
197
  * Auto-detects repoName and repoUrl from git when not provided.
276
- * Produces an empty services array and default metadata timestamps.
198
+ * Produces an empty services array and default metadata timestamps. Over an
199
+ * existing file it keeps that file's createdAt: the workspace was created
200
+ * then, however often it is rewritten.
277
201
  */
278
202
  initialize(workspaceInfo?: WorkspaceSection): Promise<WorkspaceConfig>;
203
+ /**
204
+ * The existing file's metadata.createdAt, read leniently: a rewrite is often
205
+ * the repair of a file that no longer validates, and its createdAt still
206
+ * stands. Undefined when there is no file or it records none.
207
+ */
208
+ private existingCreatedAt;
279
209
  /**
280
210
  * Update the metadata section of an existing workspace config.
281
211
  * Only the provided fields are merged; metadata.updatedAt is refreshed