@task-shepherd/agent 1.0.5 → 1.0.7

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 (81) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/meta.json +5 -5
  4. package/package.json +3 -2
  5. package/shared/dist/index.d.ts +15 -0
  6. package/shared/dist/index.js +12 -0
  7. package/shared/dist/mcp-client/client.d.ts +18 -0
  8. package/shared/dist/mcp-client/client.js +49 -0
  9. package/shared/dist/mcp-client/index.d.ts +7 -0
  10. package/shared/dist/mcp-client/index.js +7 -0
  11. package/shared/dist/mcp-client/types.d.ts +822 -0
  12. package/shared/dist/mcp-client/types.js +193 -0
  13. package/shared/dist/schema/index.d.ts +189 -0
  14. package/shared/dist/schema/index.js +142 -0
  15. package/shared/dist/schema/mcp-mappings.d.ts +50 -0
  16. package/shared/dist/schema/mcp-mappings.js +563 -0
  17. package/shared/dist/schema/validation.d.ts +91 -0
  18. package/shared/dist/schema/validation.js +282 -0
  19. package/shared/dist/work-queue/index.d.ts +7 -0
  20. package/shared/dist/work-queue/index.js +7 -0
  21. package/shared/dist/work-queue/types.d.ts +147 -0
  22. package/shared/dist/work-queue/types.js +4 -0
  23. package/shared/dist/work-queue/validation.d.ts +24 -0
  24. package/shared/dist/work-queue/validation.js +160 -0
  25. package/shared/dist/workspace/constants.d.ts +148 -0
  26. package/shared/dist/workspace/constants.js +432 -0
  27. package/shared/dist/workspace/index.d.ts +10 -0
  28. package/shared/dist/workspace/index.js +10 -0
  29. package/shared/dist/workspace/types.d.ts +477 -0
  30. package/shared/dist/workspace/types.js +9 -0
  31. package/shared/dist/workspace/utils.d.ts +79 -0
  32. package/shared/dist/workspace/utils.js +334 -0
  33. package/shared/dist/workspace/validation.d.ts +1312 -0
  34. package/shared/dist/workspace/validation.js +467 -0
  35. package/shared/graphql/generated-internal.ts +3629 -0
  36. package/shared/graphql/generated-public.ts +773 -0
  37. package/shared/graphql/generated.d.ts +7456 -0
  38. package/shared/graphql/generated.js +11799 -0
  39. package/shared/graphql/generated.ts +27569 -0
  40. package/shared/graphql/generated.ts.backup +16531 -0
  41. package/shared/graphql/generated.ts.working +4828 -0
  42. package/shared/graphql/introspection-internal.json +15845 -0
  43. package/shared/graphql/introspection-public.json +9658 -0
  44. package/shared/graphql/introspection.json +44263 -0
  45. package/shared/graphql/operations/ai-service.graphql +131 -0
  46. package/shared/graphql/operations/ai-work-queue.graphql +31 -0
  47. package/shared/graphql/operations/analytics.graphql +283 -0
  48. package/shared/graphql/operations/analytics.ts +3 -0
  49. package/shared/graphql/operations/api-keys.graphql +126 -0
  50. package/shared/graphql/operations/attachments.graphql +53 -0
  51. package/shared/graphql/operations/attachments.ts +39 -0
  52. package/shared/graphql/operations/audit.graphql +46 -0
  53. package/shared/graphql/operations/auth.graphql +83 -0
  54. package/shared/graphql/operations/claude-usage.graphql +178 -0
  55. package/shared/graphql/operations/comments.graphql +4 -0
  56. package/shared/graphql/operations/dashboard.graphql +29 -0
  57. package/shared/graphql/operations/development-plans.graphql +408 -0
  58. package/shared/graphql/operations/early-access.graphql.disabled +21 -0
  59. package/shared/graphql/operations/errors.graphql.disabled +83 -0
  60. package/shared/graphql/operations/internal-api.graphql +931 -0
  61. package/shared/graphql/operations/notifications.graphql +4 -0
  62. package/shared/graphql/operations/organization-invites.graphql.disabled +32 -0
  63. package/shared/graphql/operations/performance.graphql +4 -0
  64. package/shared/graphql/operations/project-reviews.graphql +610 -0
  65. package/shared/graphql/operations/projects.graphql +98 -0
  66. package/shared/graphql/operations/settings.graphql +4 -0
  67. package/shared/graphql/operations/stories.graphql +113 -0
  68. package/shared/graphql/operations/subscriptions.graphql +235 -0
  69. package/shared/graphql/operations/subscriptions.graphql.disabled +96 -0
  70. package/shared/graphql/operations/tasks.graphql +257 -0
  71. package/shared/graphql/operations/team.graphql +111 -0
  72. package/shared/graphql/operations/team.ts +226 -0
  73. package/shared/graphql/operations/time-tracking.graphql.disabled +96 -0
  74. package/shared/graphql/operations/work-queue.graphql +210 -0
  75. package/shared/graphql/operations/work-queue.graphql.disabled +474 -0
  76. package/shared/graphql/operations/workspace.graphql +146 -0
  77. package/shared/graphql/schema-internal.graphql +1085 -0
  78. package/shared/graphql/schema-public.graphql +709 -0
  79. package/shared/graphql/schema.graphql +3473 -0
  80. package/shared/package.json +23 -0
  81. package/web/README.md +68 -0
@@ -0,0 +1,467 @@
1
+ /**
2
+ * Zod Validation Schemas for Workspace Configuration
3
+ *
4
+ * Provides runtime type safety and validation for all workspace configuration types.
5
+ */
6
+ import { z } from 'zod';
7
+ /**
8
+ * Base validation schemas
9
+ */
10
+ export const WorkspacePatternSchema = z.enum(['monorepo', 'multi-repo', 'hybrid']);
11
+ export const ServiceTechnologySchema = z.enum([
12
+ 'nodejs', 'python', 'go', 'rust', 'java', 'dotnet', 'php', 'ruby',
13
+ 'react', 'vue', 'angular', 'static', 'database', 'cache', 'queue', 'other'
14
+ ]);
15
+ export const EnvironmentSchema = z.enum(['development', 'staging', 'production', 'test']);
16
+ export const ProtocolSchema = z.enum([
17
+ 'http', 'https', 'ws', 'wss', 'tcp', 'udp', 'grpc',
18
+ 'postgresql', 'redis', 'mongodb'
19
+ ]);
20
+ export const AuthTypeSchema = z.enum([
21
+ 'none', 'basic', 'bearer', 'apikey', 'oauth2', 'custom'
22
+ ]);
23
+ export const PackageManagerSchema = z.enum([
24
+ 'npm', 'yarn', 'pnpm', 'pip', 'go mod', 'cargo', 'maven', 'gradle'
25
+ ]);
26
+ export const AIProviderSchema = z.enum([
27
+ 'claude', 'openai', 'gemini', 'azure', 'custom'
28
+ ]);
29
+ export const IsolationLevelSchema = z.enum(['strict', 'moderate', 'relaxed']);
30
+ export const SeverityLevelSchema = z.enum(['error', 'warning', 'info']);
31
+ /**
32
+ * Service dependency validation schema
33
+ */
34
+ export const ServiceDependencySchema = z.object({
35
+ service: z.string().min(1, 'Service name is required'),
36
+ type: z.enum(['required', 'optional', 'development']),
37
+ version: z.string().optional(),
38
+ description: z.string().optional()
39
+ });
40
+ /**
41
+ * Service authentication validation schema
42
+ */
43
+ export const ServiceAuthenticationSchema = z.object({
44
+ type: AuthTypeSchema,
45
+ credentials: z.record(z.string(), z.string()).optional(),
46
+ headers: z.record(z.string(), z.string()).optional()
47
+ });
48
+ /**
49
+ * Service endpoint validation schema
50
+ */
51
+ export const ServiceEndpointSchema = z.object({
52
+ id: z.string().min(1, 'Service ID is required'),
53
+ name: z.string().min(1, 'Service name is required'),
54
+ description: z.string().optional(),
55
+ url: z.string().url('Invalid service URL'),
56
+ port: z.number().int().min(1).max(65535, 'Port must be between 1 and 65535'),
57
+ protocol: ProtocolSchema,
58
+ technology: ServiceTechnologySchema,
59
+ environment: EnvironmentSchema,
60
+ healthCheck: z.string().optional(),
61
+ version: z.string().optional(),
62
+ dependencies: z.array(ServiceDependencySchema).default([]),
63
+ auth: ServiceAuthenticationSchema.optional(),
64
+ metadata: z.record(z.string(), z.any()).optional()
65
+ });
66
+ /**
67
+ * Repository configuration validation schema
68
+ */
69
+ export const RepositoryConfigSchema = z.object({
70
+ id: z.string().min(1, 'Repository ID is required'),
71
+ name: z.string().min(1, 'Repository name is required'),
72
+ url: z.string().url('Invalid repository URL'),
73
+ path: z.string().min(1, 'Repository path is required'),
74
+ branch: z.string().min(1, 'Default branch is required').default('main'),
75
+ type: z.enum(['primary', 'service', 'library', 'config']),
76
+ services: z.array(z.string()).default([]),
77
+ config: z.object({
78
+ packageManager: PackageManagerSchema.optional(),
79
+ buildCommands: z.array(z.string()).optional(),
80
+ testCommands: z.array(z.string()).optional(),
81
+ lintCommands: z.array(z.string()).optional()
82
+ }).optional()
83
+ });
84
+ /**
85
+ * AI model configuration validation schema
86
+ */
87
+ export const AIModelConfigSchema = z.object({
88
+ provider: AIProviderSchema,
89
+ model: z.string().min(1, 'Model name is required'),
90
+ apiKey: z.string().min(1, 'API key is required'),
91
+ config: z.record(z.string(), z.any()).optional(),
92
+ rateLimit: z.object({
93
+ requestsPerMinute: z.number().int().positive().optional(),
94
+ tokensPerMinute: z.number().int().positive().optional(),
95
+ requestsPerDay: z.number().int().positive().optional()
96
+ }).optional()
97
+ });
98
+ /**
99
+ * AI capabilities validation schema
100
+ */
101
+ export const AICapabilitiesSchema = z.object({
102
+ maxConcurrentOperations: z.number().int().positive().default(3),
103
+ supportedAnalysisTypes: z.array(z.string()).min(1, 'At least one analysis type is required'),
104
+ isolationLevel: IsolationLevelSchema.default('moderate'),
105
+ serviceCapabilities: z.record(z.string(), z.array(z.string())).optional(),
106
+ customCapabilities: z.record(z.string(), z.any()).optional()
107
+ });
108
+ /**
109
+ * Workspace paths validation schema
110
+ */
111
+ export const WorkspacePathsSchema = z.object({
112
+ root: z.string().min(1, 'Root path is required').default('.'),
113
+ src: z.array(z.string()).default(['src']),
114
+ tests: z.array(z.string()).default(['test', 'tests']),
115
+ docs: z.array(z.string()).default(['docs']),
116
+ config: z.array(z.string()).default(['config']),
117
+ build: z.array(z.string()).default(['build', 'dist']),
118
+ temp: z.array(z.string()).default(['tmp', 'temp']),
119
+ logs: z.array(z.string()).default(['logs']),
120
+ cache: z.array(z.string()).default(['cache', '.cache']),
121
+ assets: z.array(z.string()).default(['assets', 'public']),
122
+ excluded: z.array(z.string()).default(['node_modules', '.git', '.env']),
123
+ servicePaths: z.record(z.string(), z.array(z.string())).optional()
124
+ });
125
+ /**
126
+ * Security configuration validation schema
127
+ */
128
+ export const SecurityConfigSchema = z.object({
129
+ filePermissions: z.object({
130
+ read: z.boolean().default(true),
131
+ write: z.boolean().default(true),
132
+ execute: z.boolean().default(false),
133
+ restricted: z.array(z.string()).default(['.env', '.env.local', '.env.production']),
134
+ readOnly: z.array(z.string()).default([])
135
+ }),
136
+ networkPermissions: z.object({
137
+ http: z.boolean().default(true),
138
+ https: z.boolean().default(true),
139
+ allowedDomains: z.array(z.string()).default(['api.anthropic.com', 'api.openai.com']),
140
+ blockedDomains: z.array(z.string()).default([]),
141
+ allowedPorts: z.array(z.number().int().min(1).max(65535)).optional()
142
+ }),
143
+ apiAccess: z.object({
144
+ allowedEndpoints: z.array(z.string()),
145
+ rateLimits: z.record(z.string(), z.number().positive()).optional()
146
+ }).optional()
147
+ });
148
+ /**
149
+ * Development configuration validation schema
150
+ */
151
+ export const DevelopmentConfigSchema = z.object({
152
+ packageManager: PackageManagerSchema.default('npm'),
153
+ runtimeVersions: z.object({
154
+ node: z.string().optional(),
155
+ python: z.string().optional(),
156
+ go: z.string().optional(),
157
+ rust: z.string().optional(),
158
+ java: z.string().optional(),
159
+ dotnet: z.string().optional()
160
+ }).optional(),
161
+ env: z.record(z.string(), z.string()).optional(),
162
+ scripts: z.object({
163
+ start: z.string().optional(),
164
+ build: z.string().optional(),
165
+ test: z.string().optional(),
166
+ lint: z.string().optional(),
167
+ typeCheck: z.string().optional(),
168
+ dbSetup: z.string().optional(),
169
+ clean: z.string().optional()
170
+ }).optional(),
171
+ tools: z.object({
172
+ formatter: z.enum(['prettier', 'black', 'gofmt', 'rustfmt', 'custom']).optional(),
173
+ linter: z.enum(['eslint', 'pylint', 'golangci-lint', 'clippy', 'custom']).optional(),
174
+ testFramework: z.enum(['jest', 'vitest', 'pytest', 'go test', 'cargo test', 'custom']).optional()
175
+ }).optional()
176
+ });
177
+ /**
178
+ * Integrations configuration validation schema
179
+ */
180
+ export const IntegrationsConfigSchema = z.object({
181
+ tasqhub: z.object({
182
+ apiUrl: z.string().url('Invalid TasqHub API URL'),
183
+ apiKey: z.string().min(1, 'TasqHub API key is required'),
184
+ projectId: z.string().optional(),
185
+ workspaceId: z.string().optional()
186
+ }).optional(),
187
+ git: z.object({
188
+ remotes: z.record(z.string(), z.string().url('Invalid repository URL')),
189
+ defaultBranch: z.string().default('main'),
190
+ hooks: z.object({
191
+ preCommit: z.array(z.string()).optional(),
192
+ postCommit: z.array(z.string()).optional(),
193
+ prePush: z.array(z.string()).optional(),
194
+ postMerge: z.array(z.string()).optional()
195
+ }).optional()
196
+ }).optional(),
197
+ cicd: z.object({
198
+ provider: z.enum(['github-actions', 'gitlab-ci', 'jenkins', 'azure-devops', 'custom']),
199
+ configPath: z.string().optional(),
200
+ pipelines: z.record(z.string(), z.any()).optional()
201
+ }).optional(),
202
+ containers: z.object({
203
+ runtime: z.enum(['docker', 'podman', 'containerd']),
204
+ registry: z.object({
205
+ url: z.string().url(),
206
+ credentials: z.record(z.string(), z.string()).optional()
207
+ }).optional(),
208
+ composeFiles: z.array(z.string()).optional()
209
+ }).optional(),
210
+ cloud: z.object({
211
+ provider: z.enum(['aws', 'gcp', 'azure', 'custom']),
212
+ config: z.record(z.string(), z.any()).optional()
213
+ }).optional()
214
+ });
215
+ /**
216
+ * Workspace metadata validation schema
217
+ */
218
+ export const WorkspaceMetadataSchema = z.object({
219
+ id: z.string().uuid('Invalid workspace ID format'),
220
+ name: z.string().min(1, 'Workspace name is required').max(100, 'Workspace name too long'),
221
+ description: z.string().max(500, 'Description too long').optional(),
222
+ pattern: WorkspacePatternSchema,
223
+ version: z.string().regex(/^\d+\.\d+\.\d+$/, 'Invalid version format (use semver)'),
224
+ createdAt: z.string().datetime('Invalid creation timestamp'),
225
+ updatedAt: z.string().datetime('Invalid update timestamp'),
226
+ tags: z.array(z.string()).optional(),
227
+ owner: z.object({
228
+ name: z.string().min(1),
229
+ email: z.string().email('Invalid owner email')
230
+ }).optional(),
231
+ team: z.array(z.object({
232
+ name: z.string().min(1),
233
+ email: z.string().email('Invalid team member email'),
234
+ role: z.string().min(1)
235
+ })).optional()
236
+ });
237
+ /**
238
+ * Complete workspace configuration validation schema
239
+ */
240
+ export const WorkspaceConfigSchema = z.object({
241
+ workspace: WorkspaceMetadataSchema,
242
+ services: z.record(z.string(), ServiceEndpointSchema).refine((services) => Object.keys(services).length > 0, 'At least one service must be defined'),
243
+ repositories: z.record(z.string(), RepositoryConfigSchema).optional(),
244
+ ai: z.object({
245
+ models: z.record(z.string(), AIModelConfigSchema).refine((models) => Object.keys(models).length > 0, 'At least one AI model must be configured'),
246
+ defaultModel: z.string().min(1, 'Default AI model is required'),
247
+ capabilities: AICapabilitiesSchema
248
+ }),
249
+ paths: WorkspacePathsSchema,
250
+ development: DevelopmentConfigSchema,
251
+ security: SecurityConfigSchema,
252
+ integrations: IntegrationsConfigSchema.optional(),
253
+ extensions: z.record(z.string(), z.any()).optional()
254
+ }).refine((config) => {
255
+ // Validate that default AI model exists in models
256
+ return config.ai.defaultModel in config.ai.models;
257
+ }, {
258
+ message: 'Default AI model must be defined in models configuration',
259
+ path: ['ai', 'defaultModel']
260
+ }).refine((config) => {
261
+ // Validate service dependencies reference existing services
262
+ const serviceIds = Object.keys(config.services);
263
+ for (const [serviceId, service] of Object.entries(config.services)) {
264
+ for (const dep of service.dependencies) {
265
+ if (!serviceIds.includes(dep.service)) {
266
+ return false;
267
+ }
268
+ }
269
+ }
270
+ return true;
271
+ }, {
272
+ message: 'Service dependencies must reference existing services',
273
+ path: ['services']
274
+ });
275
+ /**
276
+ * Service discovery configuration validation schema
277
+ */
278
+ export const ServiceDiscoveryConfigSchema = z.object({
279
+ enabled: z.boolean().default(true),
280
+ methods: z.array(z.enum(['filesystem', 'docker-compose', 'kubernetes', 'consul', 'custom'])).min(1),
281
+ patterns: z.object({
282
+ files: z.array(z.string()).default(['package.json', 'Dockerfile', 'docker-compose.yml']),
283
+ directories: z.array(z.string()).default(['src', 'app', 'services']),
284
+ portRanges: z.array(z.object({
285
+ start: z.number().int().min(1).max(65535),
286
+ end: z.number().int().min(1).max(65535)
287
+ })).optional()
288
+ }),
289
+ overrides: z.record(z.string(), ServiceEndpointSchema).optional()
290
+ });
291
+ /**
292
+ * Workspace validation configuration schema
293
+ */
294
+ export const WorkspaceValidationConfigSchema = z.object({
295
+ rules: z.object({
296
+ requiredServices: z.array(z.string()).optional(),
297
+ validateDependencies: z.boolean().default(true),
298
+ validatePaths: z.boolean().default(true),
299
+ detectPortConflicts: z.boolean().default(true),
300
+ customRules: z.array(z.object({
301
+ name: z.string().min(1),
302
+ description: z.string().min(1),
303
+ validator: z.string().min(1)
304
+ })).optional()
305
+ }),
306
+ severity: z.object({
307
+ missingServices: SeverityLevelSchema.default('error'),
308
+ circularDependencies: SeverityLevelSchema.default('error'),
309
+ portConflicts: SeverityLevelSchema.default('warning'),
310
+ invalidPaths: SeverityLevelSchema.default('warning')
311
+ })
312
+ });
313
+ /**
314
+ * Workspace initialization options validation schema
315
+ */
316
+ export const WorkspaceInitOptionsSchema = z.object({
317
+ directory: z.string().optional(),
318
+ name: z.string().min(1).max(100).optional(),
319
+ description: z.string().max(500).optional(),
320
+ pattern: WorkspacePatternSchema.optional(),
321
+ template: z.string().optional(),
322
+ interactive: z.boolean().default(false),
323
+ force: z.boolean().default(false),
324
+ services: z.array(z.string()).optional(),
325
+ aiProvider: z.string().optional(),
326
+ repositories: z.array(z.string().url()).optional(),
327
+ discovery: ServiceDiscoveryConfigSchema.partial().optional()
328
+ });
329
+ /**
330
+ * Workspace template validation schema
331
+ */
332
+ export const WorkspaceTemplateSchema = z.object({
333
+ id: z.string().min(1, 'Template ID is required'),
334
+ name: z.string().min(1, 'Template name is required'),
335
+ description: z.string().min(1, 'Template description is required'),
336
+ patterns: z.array(WorkspacePatternSchema).min(1, 'At least one pattern must be supported'),
337
+ config: WorkspaceConfigSchema.partial(),
338
+ requiredServices: z.array(z.string()).default([]),
339
+ optionalServices: z.array(z.string()).default([]),
340
+ setupSteps: z.array(z.string()).optional(),
341
+ variables: z.record(z.string(), z.object({
342
+ description: z.string().min(1),
343
+ type: z.enum(['string', 'number', 'boolean', 'array']),
344
+ default: z.any().optional(),
345
+ required: z.boolean().optional()
346
+ })).optional()
347
+ });
348
+ /**
349
+ * Validation helper functions
350
+ */
351
+ export class WorkspaceValidator {
352
+ /**
353
+ * Validate workspace configuration
354
+ */
355
+ static validateWorkspaceConfig(config) {
356
+ const result = WorkspaceConfigSchema.safeParse(config);
357
+ if (result.success) {
358
+ return {
359
+ success: true,
360
+ data: result.data,
361
+ errors: []
362
+ };
363
+ }
364
+ return {
365
+ success: false,
366
+ errors: result.error.issues.map(err => ({
367
+ path: err.path.map(String),
368
+ message: err.message,
369
+ code: err.code
370
+ }))
371
+ };
372
+ }
373
+ /**
374
+ * Validate service endpoint configuration
375
+ */
376
+ static validateServiceEndpoint(endpoint) {
377
+ const result = ServiceEndpointSchema.safeParse(endpoint);
378
+ if (result.success) {
379
+ return {
380
+ success: true,
381
+ data: result.data,
382
+ errors: []
383
+ };
384
+ }
385
+ return {
386
+ success: false,
387
+ errors: result.error.issues.map(err => ({
388
+ path: err.path.map(String),
389
+ message: err.message,
390
+ code: err.code
391
+ }))
392
+ };
393
+ }
394
+ /**
395
+ * Validate workspace initialization options
396
+ */
397
+ static validateInitOptions(options) {
398
+ const result = WorkspaceInitOptionsSchema.safeParse(options);
399
+ if (result.success) {
400
+ return {
401
+ success: true,
402
+ data: result.data,
403
+ errors: []
404
+ };
405
+ }
406
+ return {
407
+ success: false,
408
+ errors: result.error.issues.map(err => ({
409
+ path: err.path.map(String),
410
+ message: err.message,
411
+ code: err.code
412
+ }))
413
+ };
414
+ }
415
+ /**
416
+ * Check for circular dependencies in services
417
+ */
418
+ static checkCircularDependencies(services) {
419
+ const visited = new Set();
420
+ const recursionStack = new Set();
421
+ const cycles = [];
422
+ const dfs = (serviceId, path = []) => {
423
+ if (recursionStack.has(serviceId)) {
424
+ const cycleStart = path.indexOf(serviceId);
425
+ const cycle = [...path.slice(cycleStart), serviceId];
426
+ cycles.push(cycle.join(' -> '));
427
+ return;
428
+ }
429
+ if (visited.has(serviceId)) {
430
+ return;
431
+ }
432
+ visited.add(serviceId);
433
+ recursionStack.add(serviceId);
434
+ const service = services[serviceId];
435
+ if (service?.dependencies) {
436
+ for (const dep of service.dependencies) {
437
+ if (dep.type === 'required') {
438
+ dfs(dep.service, [...path, serviceId]);
439
+ }
440
+ }
441
+ }
442
+ recursionStack.delete(serviceId);
443
+ };
444
+ for (const serviceId of Object.keys(services)) {
445
+ if (!visited.has(serviceId)) {
446
+ dfs(serviceId);
447
+ }
448
+ }
449
+ return cycles;
450
+ }
451
+ /**
452
+ * Check for port conflicts
453
+ */
454
+ static checkPortConflicts(services) {
455
+ const portMap = new Map();
456
+ for (const [serviceId, service] of Object.entries(services)) {
457
+ const port = service.port;
458
+ if (!portMap.has(port)) {
459
+ portMap.set(port, []);
460
+ }
461
+ portMap.get(port).push(serviceId);
462
+ }
463
+ return Array.from(portMap.entries())
464
+ .filter(([_, serviceIds]) => serviceIds.length > 1)
465
+ .map(([port, serviceIds]) => ({ port, services: serviceIds }));
466
+ }
467
+ }