@supatest/cli 0.0.43 → 0.0.45

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 (2) hide show
  1. package/dist/index.js +533 -95
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -32,6 +32,34 @@ If no: Run discovery once, then write findings to .supatest/SUPATEST.md:
32
32
  This ensures discovery happens once and persists across sessions.
33
33
  </context>
34
34
 
35
+ <test_tagging>
36
+ Tag tests with metadata for organization and filtering on the Supatest platform:
37
+
38
+ **Platform Tags** (indexed, fast filtering):
39
+ - @feature:name - Feature area (e.g., auth, checkout, dashboard)
40
+ - @owner:email - Test owner/maintainer
41
+ - @priority:critical|high|medium|low - Test priority
42
+ - @test_type:smoke|e2e|regression|integration|unit - Test category
43
+ - @ticket:PROJ-123 - Related ticket/issue
44
+ - @slow - Flag for long-running tests
45
+ - @flaky - Flag for known flaky tests
46
+
47
+ **Custom Tags** (flexible metadata):
48
+ - @key:value - Any custom metadata (e.g., @browser:chrome, @viewport:mobile)
49
+
50
+ **Playwright - Use native tag property (preferred):**
51
+ test("User can complete purchase", {
52
+ tag: ['@feature:checkout', '@priority:high', '@test_type:e2e', '@owner:qa@example.com']
53
+ }, async ({ page }) => {
54
+ // test code
55
+ });
56
+
57
+ **WebdriverIO/Other frameworks - Use title tags:**
58
+ it("@feature:checkout @priority:high @test_type:e2e User can complete purchase", async () => {
59
+ // test code
60
+ });
61
+ </test_tagging>
62
+
35
63
  <workflow>
36
64
  For each test:
37
65
  1. **Write** - Create test using the project's framework and patterns
@@ -163,6 +191,34 @@ You are a Test Fixer Agent that debugs failing tests and fixes issues. You work
163
191
  Continue until all tests pass.
164
192
  </workflow>
165
193
 
194
+ <test_tagging>
195
+ When creating or fixing tests, add metadata tags for organization and filtering:
196
+
197
+ **Platform Tags** (indexed, fast filtering):
198
+ - @feature:name - Feature area (e.g., auth, checkout, dashboard)
199
+ - @owner:email - Test owner/maintainer
200
+ - @priority:critical|high|medium|low - Test priority
201
+ - @test_type:smoke|e2e|regression|integration|unit - Test category
202
+ - @ticket:PROJ-123 - Related ticket/issue
203
+ - @slow - Flag for long-running tests
204
+ - @flaky - Flag for known flaky tests
205
+
206
+ **Custom Tags** (flexible metadata):
207
+ - @key:value - Any custom metadata (e.g., @browser:chrome, @viewport:mobile)
208
+
209
+ **Playwright - Use native tag property (preferred):**
210
+ test("User can complete purchase", {
211
+ tag: ['@feature:checkout', '@priority:high', '@test_type:e2e', '@owner:qa@example.com']
212
+ }, async ({ page }) => {
213
+ // test code
214
+ });
215
+
216
+ **WebdriverIO/Other frameworks - Use title tags:**
217
+ it("@feature:checkout @priority:high @test_type:e2e User can complete purchase", async () => {
218
+ // test code
219
+ });
220
+ </test_tagging>
221
+
166
222
  <root_causes>
167
223
  **Selector** - Element changed or locator is fragile \u2192 update selector, add wait, make more specific
168
224
 
@@ -190,6 +246,20 @@ Continue until all tests pass.
190
246
  - Don't skip or remove tests without understanding the failure
191
247
  </fixing_principles>
192
248
 
249
+ <browser_inspection>
250
+ If available in /mcp commands, use Playwright MCP for live debugging when the failure is unclear from all available assets:
251
+ - Test code, error logs, and stack traces
252
+ - Application code and related files in the repo
253
+ - Configuration and test setup files
254
+
255
+ Execute the test flow with MCP to observe actual behavior:
256
+ - Navigate and interact as the test does
257
+ - Verify element states, attributes, and content
258
+ - Check console errors and runtime issues
259
+ - Test selectors and locators against live DOM
260
+ - Inspect page state at each step
261
+ </browser_inspection>
262
+
193
263
  <flakiness>
194
264
  After fixing, verify stability by running 2-3 times. Watch for:
195
265
  - Inconsistent pass/fail results
@@ -760,10 +830,39 @@ Never ask about routing, data scope, UI interactions, empty states, or error han
760
830
  <output_format>
761
831
  **Risk Assessment**: [HIGH/MEDIUM/LOW] - one line justification
762
832
  **User Journeys**: "User can [action] to [achieve goal]"
763
- **Test Cases**: Name, assertions, test data needs
833
+ **Test Cases**: Name, assertions, test data needs, suggested tags
764
834
  **Not Testing**: What you're skipping and why (shows judgment)
765
835
  </output_format>
766
836
 
837
+ <test_tagging>
838
+ Include metadata tags in test plans for organization and filtering:
839
+
840
+ **Platform Tags** (indexed, fast filtering):
841
+ - @feature:name - Feature area (e.g., auth, checkout, dashboard)
842
+ - @owner:email - Test owner/maintainer
843
+ - @priority:critical|high|medium|low - Test priority (align with risk assessment)
844
+ - @test_type:smoke|e2e|regression|integration|unit - Test category
845
+ - @ticket:PROJ-123 - Related ticket/issue
846
+ - @slow - Flag for long-running tests
847
+ - @flaky - Flag for known flaky tests
848
+
849
+ **Custom Tags** (flexible metadata):
850
+ - @key:value - Any custom metadata (e.g., @browser:chrome, @viewport:mobile)
851
+
852
+ Map risk levels to priority tags:
853
+ - HIGH risk \u2192 @priority:critical or @priority:high
854
+ - MEDIUM risk \u2192 @priority:medium
855
+ - LOW risk \u2192 @priority:low
856
+
857
+ **Playwright - Use native tag property (preferred):**
858
+ test("User can complete purchase", {
859
+ tag: ['@feature:checkout', '@priority:high', '@test_type:e2e']
860
+ }, async ({ page }) => { });
861
+
862
+ **WebdriverIO/Other frameworks - Use title tags:**
863
+ it("@feature:checkout @priority:high @test_type:e2e User can complete purchase", async () => { });
864
+ </test_tagging>
865
+
767
866
  <example>
768
867
  **Scenario**: Read-only analytics dashboard
769
868
 
@@ -1155,7 +1254,7 @@ function getToolGroupCounts(tools) {
1155
1254
  );
1156
1255
  return Object.entries(groups).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
1157
1256
  }
1158
- var AVAILABLE_MODELS, DATE_SUFFIX_REGEX, SMALL_COST_MULTIPLIER, MEDIUM_COST_MULTIPLIER, PREMIUM_COST_MULTIPLIER, SMALL_COST_LABEL, MEDIUM_COST_LABEL, PREMIUM_COST_LABEL, MODEL_TIERS, CONTEXT_WINDOWS, util, objectUtil, ZodParsedType, getParsedType, ZodIssueCode, ZodError, errorMap, overrideErrorMap, makeIssue, ParseStatus, INVALID, DIRTY, OK, isAborted, isDirty, isValid, isAsync, errorUtil, ParseInputLazyPath, handleResult, ZodType, cuidRegex, cuid2Regex, ulidRegex, uuidRegex, nanoidRegex, jwtRegex, durationRegex, emailRegex, _emojiRegex, emojiRegex, ipv4Regex, ipv4CidrRegex, ipv6Regex, ipv6CidrRegex, base64Regex, base64urlRegex, dateRegexSource, dateRegex, ZodString, ZodNumber, ZodBigInt, ZodBoolean, ZodDate, ZodSymbol, ZodUndefined, ZodNull, ZodAny, ZodUnknown, ZodNever, ZodVoid, ZodArray, ZodObject, ZodUnion, getDiscriminator, ZodDiscriminatedUnion, ZodIntersection, ZodTuple, ZodRecord, ZodMap, ZodSet, ZodFunction, ZodLazy, ZodLiteral, ZodEnum, ZodNativeEnum, ZodPromise, ZodEffects, ZodOptional, ZodNullable, ZodDefault, ZodCatch, ZodNaN, ZodBranded, ZodPipeline, ZodReadonly, ZodFirstPartyTypeKind, stringType, numberType, booleanType, dateType, unknownType, arrayType, objectType, unionType, discriminatedUnionType, recordType, functionType, lazyType, literalType, enumType, promiseType, coerce, MAX_API_KEY_NAME_LENGTH, apiKeySchema, apiKeyUsageSchema, createApiKeyRequestSchema, apiKeyResponseSchema, apiKeyUsageSummarySchema, genericErrorSchema, validationErrorSchema, feedbackCategorySchema, FEEDBACK_CATEGORIES, createFeedbackSchema, feedbackResponseSchema, listFeedbackQuerySchema, feedbackListResponseSchema, healthMetricSchema, healthMetricDailyItemSchema, healthMetricsWithDailySchema, healthAnalyticsPeriodSchema, healthAnalyticsDailyItemSchema, healthAnalyticsResponseSchema, MAX_TIMEZONE_CHAR_LENGTH, organizationSchema, organizationSettingsSchema, textBlockSchema, toolUseBlockSchema, toolResultBlockSchema, thinkingBlockSchema, imageBlockSchema, contentBlockSchema, sessionSchema, createSessionRequestSchema, updateSessionRequestSchema, messageSchema, createMessageRequestSchema, cliEventSchema, createCLISessionRequestSchema, queryResultSchema, queryTurnSchema, queryContentSchema, queryUsageSchema, querySchema, runStatusSchema, testResultStatusSchema, testOutcomeSchema, attachmentKindSchema, stepCategorySchema, runSummarySchema, ciMetadataSchema, gitMetadataSchema, playwrightConfigSchema, errorInfoSchema, locationSchema, sourceSnippetSchema, runSchema, annotationSchema, testSchema, testResultSchema, baseStepSchema, stepSchema, attachmentSchema, listRunsQuerySchema, listTestsQuerySchema, runsListResponseSchema, runDetailResponseSchema, testsListResponseSchema, testDetailResponseSchema, testHistoryItemSchema, testHistoryResponseSchema, topOffenderSchema, topOffendersResponseSchema, trendPointSchema, trendsResponseSchema, errorCategorySchema, failureClusterSchema, newFailureSchema, runInsightsResponseSchema, FailureCategoryEnum, SelectorTypeEnum, FailureCategoryStatsSchema, FailureCategoriesResponseSchema, FailingSelectorStatsSchema, FailingSelectorsResponseSchema, newFailureItemSchema, newFailuresResponseSchema, flakyTestItemSchema, flakyTestsResponseSchema, slowestTestItemSchema, slowestTestsResponseSchema, runSummaryEmailFailureSchema, runSummaryEmailReportSchema, sendRunReportRequestSchema, metricWithTrendSchema, weekOverWeekMetricsSchema, ciComputeTimeSchema, investigationCandidateSchema, failureCategoryBreakdownSchema, stabilityTrendSchema, folderStabilitySchema, managerReportSchema, managerReportQuerySchema, sendManagerReportRequestSchema, reportAttachmentLinkSchema, developerReportRegressionSchema, developerReportFlakyTestSchema, developerReportSlowTestSchema, developerRunSummaryReportSchema, executiveReportStatusSchema, executiveMetricSchema, executiveKeyMetricsSchema, executiveTrendPointSchema, executiveTrendsSchema, executiveFlakyOffenderSchema, executiveSlowestOffenderSchema, executiveTopOffendersSchema, executiveReportSchema, executiveReportQuerySchema, sendExecutiveReportRequestSchema, fixAssignmentStatusSchema, fixAssignmentSchema, testWithAssignmentSchema, listAssignmentsQuerySchema, listTestsWithAssignmentsQuerySchema, assignmentsListResponseSchema, assigneeInfoSchema, assigneesListResponseSchema, assignTestsRequestSchema, assignTestRequestSchema, reassignTestRequestSchema, completeAssignmentsRequestSchema, completeAssignmentRequestSchema, bulkReassignRequestSchema, assignmentResponseSchema, assignTestsResponseSchema, testsWithAssignmentsResponseSchema, bulkReassignResponseSchema, userAssignmentStatsSchema, SECONDS_PER_MINUTE, SECONDS_PER_HOUR, SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR;
1257
+ var AVAILABLE_MODELS, DATE_SUFFIX_REGEX, SMALL_COST_MULTIPLIER, MEDIUM_COST_MULTIPLIER, PREMIUM_COST_MULTIPLIER, SMALL_COST_LABEL, MEDIUM_COST_LABEL, PREMIUM_COST_LABEL, MODEL_TIERS, CONTEXT_WINDOWS, util, objectUtil, ZodParsedType, getParsedType, ZodIssueCode, ZodError, errorMap, overrideErrorMap, makeIssue, ParseStatus, INVALID, DIRTY, OK, isAborted, isDirty, isValid, isAsync, errorUtil, ParseInputLazyPath, handleResult, ZodType, cuidRegex, cuid2Regex, ulidRegex, uuidRegex, nanoidRegex, jwtRegex, durationRegex, emailRegex, _emojiRegex, emojiRegex, ipv4Regex, ipv4CidrRegex, ipv6Regex, ipv6CidrRegex, base64Regex, base64urlRegex, dateRegexSource, dateRegex, ZodString, ZodNumber, ZodBigInt, ZodBoolean, ZodDate, ZodSymbol, ZodUndefined, ZodNull, ZodAny, ZodUnknown, ZodNever, ZodVoid, ZodArray, ZodObject, ZodUnion, getDiscriminator, ZodDiscriminatedUnion, ZodIntersection, ZodTuple, ZodRecord, ZodMap, ZodSet, ZodFunction, ZodLazy, ZodLiteral, ZodEnum, ZodNativeEnum, ZodPromise, ZodEffects, ZodOptional, ZodNullable, ZodDefault, ZodCatch, ZodNaN, ZodBranded, ZodPipeline, ZodReadonly, ZodFirstPartyTypeKind, stringType, numberType, booleanType, dateType, unknownType, arrayType, objectType, unionType, discriminatedUnionType, recordType, functionType, lazyType, literalType, enumType, promiseType, coerce, MAX_API_KEY_NAME_LENGTH, apiKeySchema, apiKeyUsageSchema, createApiKeyRequestSchema, apiKeyResponseSchema, apiKeyUsageSummarySchema, genericErrorSchema, validationErrorSchema, feedbackCategorySchema, FEEDBACK_CATEGORIES, createFeedbackSchema, feedbackResponseSchema, listFeedbackQuerySchema, feedbackListResponseSchema, healthMetricSchema, healthMetricDailyItemSchema, healthMetricsWithDailySchema, healthAnalyticsPeriodSchema, healthAnalyticsDailyItemSchema, healthAnalyticsResponseSchema, MAX_TIMEZONE_CHAR_LENGTH, organizationSchema, organizationSettingsSchema, textBlockSchema, toolUseBlockSchema, toolResultBlockSchema, thinkingBlockSchema, imageBlockSchema, contentBlockSchema, sessionSchema, createSessionRequestSchema, updateSessionRequestSchema, messageSchema, createMessageRequestSchema, cliEventSchema, createCLISessionRequestSchema, queryResultSchema, queryTurnSchema, queryContentSchema, queryUsageSchema, querySchema, runStatusSchema, testResultStatusSchema, testOutcomeSchema, attachmentKindSchema, stepCategorySchema, runSummarySchema, ciMetadataSchema, gitMetadataSchema, playwrightConfigSchema, errorInfoSchema, locationSchema, sourceSnippetSchema, runSchema, annotationSchema, testSchema, testResultSchema, baseStepSchema, stepSchema, attachmentSchema, listRunsQuerySchema, listTestsQuerySchema, runsListResponseSchema, runDetailResponseSchema, coverageTestItemSchema, secondaryTagMetricSchema, tagCoverageSchema, coverageStatsSchema, coverageAlertSchema, coverageDashboardResponseSchema, coverageDashboardQuerySchema, testsListResponseSchema, testDetailResponseSchema, testHistoryItemSchema, testHistoryResponseSchema, topOffenderSchema, topOffendersResponseSchema, trendPointSchema, trendsResponseSchema, errorCategorySchema, failureClusterSchema, newFailureSchema, runInsightsResponseSchema, FailureCategoryEnum, SelectorTypeEnum, FailureCategoryStatsSchema, FailureCategoriesResponseSchema, FailingSelectorStatsSchema, FailingSelectorsResponseSchema, newFailureItemSchema, newFailuresResponseSchema, flakyTestItemSchema, flakyTestsResponseSchema, slowestTestItemSchema, slowestTestsResponseSchema, runSummaryEmailFailureSchema, runSummaryEmailReportSchema, sendRunReportRequestSchema, metricWithTrendSchema, weekOverWeekMetricsSchema, ciComputeTimeSchema, investigationCandidateSchema, failureCategoryBreakdownSchema, stabilityTrendSchema, folderStabilitySchema, managerReportSchema, managerReportQuerySchema, sendManagerReportRequestSchema, reportAttachmentLinkSchema, developerReportRegressionSchema, developerReportFlakyTestSchema, developerReportSlowTestSchema, developerRunSummaryReportSchema, executiveReportStatusSchema, executiveMetricSchema, executiveKeyMetricsSchema, executiveTrendPointSchema, executiveTrendsSchema, executiveFlakyOffenderSchema, executiveSlowestOffenderSchema, executiveTopOffendersSchema, executiveReportSchema, executiveReportQuerySchema, sendExecutiveReportRequestSchema, fixAssignmentStatusSchema, fixAssignmentSchema, testWithAssignmentSchema, listAssignmentsQuerySchema, listTestsWithAssignmentsQuerySchema, assignmentsListResponseSchema, assigneeInfoSchema, assigneesListResponseSchema, assignTestsRequestSchema, assignTestRequestSchema, reassignTestRequestSchema, completeAssignmentsRequestSchema, completeAssignmentRequestSchema, bulkReassignRequestSchema, assignmentResponseSchema, assignTestsResponseSchema, testsWithAssignmentsResponseSchema, bulkReassignResponseSchema, userAssignmentStatsSchema, testCatalogPrioritySchema, testCatalogTypeSchema, testCatalogAssignmentStatusSchema, testCatalogAssignmentSchema, testCatalogDefinitionSchema, testCatalogListItemSchema, testCatalogHistoryEntrySchema, testCatalogListQuerySchema, testCatalogListResponseSchema, testCatalogDetailResponseSchema, testCatalogFilterOptionsSchema, testCatalogAssigneeInfoSchema, testCatalogAssignRequestSchema, testCatalogUpdateAssignmentRequestSchema, testCatalogAssignmentResponseSchema, testCatalogFolderNodeSchema, testCatalogFolderTreeResponseSchema, testCatalogFolderTestsQuerySchema, SECONDS_PER_MINUTE, SECONDS_PER_HOUR, SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR;
1159
1258
  var init_shared_es = __esm({
1160
1259
  "../shared/dist/shared.es.mjs"() {
1161
1260
  "use strict";
@@ -5424,8 +5523,17 @@ var init_shared_es = __esm({
5424
5523
  status: testResultStatusSchema.optional(),
5425
5524
  isFlaky: coerce.boolean().optional(),
5426
5525
  file: stringType().optional(),
5427
- groupByTestId: coerce.boolean().optional()
5526
+ groupByTestId: coerce.boolean().optional(),
5428
5527
  // When true, paginate by unique testId (grouped tests)
5528
+ // Tag filters
5529
+ owner: stringType().optional(),
5530
+ priority: stringType().optional(),
5531
+ // Will validate against testCatalogPrioritySchema in backend
5532
+ feature: stringType().optional(),
5533
+ testType: stringType().optional(),
5534
+ // Will validate against testCatalogTypeSchema in backend
5535
+ tags: stringType().optional()
5536
+ // Comma-separated custom tags
5429
5537
  });
5430
5538
  runsListResponseSchema = objectType({
5431
5539
  runs: arrayType(runSchema),
@@ -5437,6 +5545,86 @@ var init_shared_es = __esm({
5437
5545
  tests: arrayType(testSchema).optional(),
5438
5546
  testsTotal: numberType().optional()
5439
5547
  });
5548
+ coverageTestItemSchema = objectType({
5549
+ testId: stringType(),
5550
+ title: stringType(),
5551
+ file: stringType(),
5552
+ status: testResultStatusSchema.nullable(),
5553
+ passRate: numberType().nullable(),
5554
+ // 0-100
5555
+ lastRunAt: stringType().nullable()
5556
+ });
5557
+ secondaryTagMetricSchema = objectType({
5558
+ tag: stringType(),
5559
+ category: stringType(),
5560
+ // e.g., "type", "priority"
5561
+ testCount: numberType(),
5562
+ coverage: numberType()
5563
+ // 0-100 percentage
5564
+ });
5565
+ tagCoverageSchema = objectType({
5566
+ tag: stringType(),
5567
+ testCount: numberType(),
5568
+ passedCount: numberType(),
5569
+ failedCount: numberType(),
5570
+ coverage: numberType(),
5571
+ // 0-100 percentage
5572
+ tests: arrayType(coverageTestItemSchema),
5573
+ secondaryTags: arrayType(secondaryTagMetricSchema).optional()
5574
+ // Secondary metrics like type, priority
5575
+ });
5576
+ coverageStatsSchema = objectType({
5577
+ overallHealth: numberType(),
5578
+ // 0-100 percentage
5579
+ totalTests: numberType()
5580
+ });
5581
+ coverageAlertSchema = objectType({
5582
+ tag: stringType(),
5583
+ previousCoverage: numberType(),
5584
+ currentCoverage: numberType(),
5585
+ changePct: numberType()
5586
+ });
5587
+ coverageDashboardResponseSchema = objectType({
5588
+ stats: coverageStatsSchema,
5589
+ tagCoverages: arrayType(tagCoverageSchema),
5590
+ alerts: arrayType(coverageAlertSchema),
5591
+ period: objectType({
5592
+ start: stringType(),
5593
+ end: stringType(),
5594
+ days: numberType()
5595
+ })
5596
+ });
5597
+ coverageDashboardQuerySchema = objectType({
5598
+ // Option 1: Period-based (e.g., '7d', '14d', '30d')
5599
+ period: stringType().regex(/^\d+d$/, "Period must be in format '7d', '14d', '30d', etc.").optional(),
5600
+ // Option 2: Date range (YYYY-MM-DD format or ISO datetime strings)
5601
+ startDate: stringType().regex(
5602
+ /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?)?$/,
5603
+ "startDate must be in format YYYY-MM-DD or ISO datetime"
5604
+ ).optional(),
5605
+ endDate: stringType().regex(
5606
+ /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?)?$/,
5607
+ "endDate must be in format YYYY-MM-DD or ISO datetime"
5608
+ ).optional(),
5609
+ // Optional tag filter to drill down into specific tag
5610
+ tag: stringType().optional()
5611
+ }).refine(
5612
+ (data) => {
5613
+ const hasPeriod = !!data.period;
5614
+ const hasDateRange = !!data.startDate && !!data.endDate;
5615
+ const hasPartialDateRange = !!data.startDate && !data.endDate || !data.startDate && !!data.endDate;
5616
+ if (hasPartialDateRange) {
5617
+ return false;
5618
+ }
5619
+ if (hasPeriod && hasDateRange) {
5620
+ return false;
5621
+ }
5622
+ return true;
5623
+ },
5624
+ {
5625
+ message: "Provide either 'period' OR both 'startDate' and 'endDate', not a mix"
5626
+ }
5627
+ );
5440
5628
  testsListResponseSchema = objectType({
5441
5629
  tests: arrayType(testSchema),
5442
5630
  total: numberType(),
@@ -5958,7 +6146,7 @@ var init_shared_es = __esm({
5958
6146
  assigned: numberType(),
5959
6147
  completed: numberType(),
5960
6148
  failed: numberType(),
5961
- free: numberType()
6149
+ unassigned: numberType()
5962
6150
  })
5963
6151
  });
5964
6152
  assigneeInfoSchema = objectType({
@@ -6030,7 +6218,7 @@ var init_shared_es = __esm({
6030
6218
  assigned: numberType(),
6031
6219
  completed: numberType(),
6032
6220
  failed: numberType(),
6033
- free: numberType()
6221
+ unassigned: numberType()
6034
6222
  })
6035
6223
  });
6036
6224
  bulkReassignResponseSchema = objectType({
@@ -6049,6 +6237,173 @@ var init_shared_es = __esm({
6049
6237
  completed: numberType(),
6050
6238
  failed: numberType()
6051
6239
  });
6240
+ testCatalogPrioritySchema = enumType([
6241
+ "critical",
6242
+ "high",
6243
+ "medium",
6244
+ "low"
6245
+ ]);
6246
+ testCatalogTypeSchema = enumType([
6247
+ "smoke",
6248
+ "e2e",
6249
+ "regression",
6250
+ "integration",
6251
+ "unit"
6252
+ ]);
6253
+ testCatalogAssignmentStatusSchema = enumType([
6254
+ "assigned",
6255
+ "completed",
6256
+ "failed"
6257
+ ]);
6258
+ testCatalogAssignmentSchema = objectType({
6259
+ id: stringType().uuid(),
6260
+ assignedTo: stringType(),
6261
+ assignedAt: stringType(),
6262
+ status: testCatalogAssignmentStatusSchema
6263
+ });
6264
+ testCatalogDefinitionSchema = objectType({
6265
+ id: stringType().uuid(),
6266
+ readableId: stringType(),
6267
+ // TST-123
6268
+ testId: stringType(),
6269
+ // Stable ID from @id: tag or hash
6270
+ file: stringType(),
6271
+ title: stringType(),
6272
+ titlePath: arrayType(stringType()),
6273
+ projectName: stringType().nullable(),
6274
+ // Parsed metadata
6275
+ owner: stringType().nullable(),
6276
+ priority: testCatalogPrioritySchema.nullable(),
6277
+ feature: stringType().nullable(),
6278
+ ticketId: stringType().nullable(),
6279
+ testType: testCatalogTypeSchema.nullable(),
6280
+ isSlow: booleanType(),
6281
+ isFlakyTagged: booleanType(),
6282
+ // Raw data
6283
+ tags: arrayType(stringType()).nullable(),
6284
+ customMetadata: recordType(stringType()).nullable(),
6285
+ // Stats
6286
+ totalRuns: numberType(),
6287
+ passCount: numberType(),
6288
+ failCount: numberType(),
6289
+ flakyCount: numberType(),
6290
+ lastStatus: stringType().nullable(),
6291
+ lastRunAt: stringType().nullable(),
6292
+ lastRunId: stringType().nullable(),
6293
+ avgDurationMs: numberType().nullable(),
6294
+ passRate: numberType().nullable(),
6295
+ // 0-100
6296
+ // Assignment
6297
+ assignment: testCatalogAssignmentSchema.nullable(),
6298
+ // Timestamps
6299
+ createdAt: stringType(),
6300
+ updatedAt: stringType()
6301
+ });
6302
+ testCatalogListItemSchema = objectType({
6303
+ id: stringType().uuid(),
6304
+ readableId: stringType().optional(),
6305
+ testId: stringType(),
6306
+ file: stringType(),
6307
+ title: stringType(),
6308
+ projectName: stringType().nullable(),
6309
+ // Key metadata
6310
+ owner: stringType().nullable(),
6311
+ priority: testCatalogPrioritySchema.nullable(),
6312
+ feature: stringType().nullable(),
6313
+ isSlow: booleanType(),
6314
+ isFlakyTagged: booleanType(),
6315
+ // Stats
6316
+ totalRuns: numberType(),
6317
+ passRate: numberType().nullable(),
6318
+ flakyCount: numberType(),
6319
+ lastStatus: stringType().nullable(),
6320
+ lastRunAt: stringType().nullable(),
6321
+ avgDurationMs: numberType().nullable(),
6322
+ // Assignment
6323
+ assignment: testCatalogAssignmentSchema.nullable()
6324
+ });
6325
+ testCatalogHistoryEntrySchema = objectType({
6326
+ runId: stringType().uuid(),
6327
+ status: stringType(),
6328
+ durationMs: numberType(),
6329
+ date: stringType(),
6330
+ branch: stringType().optional(),
6331
+ errorSummary: stringType().optional()
6332
+ });
6333
+ testCatalogListQuerySchema = objectType({
6334
+ page: coerce.number().int().min(1).default(1),
6335
+ limit: coerce.number().int().min(1).max(100).default(20),
6336
+ status: enumType(["all", "passed", "failed", "flaky", "skipped"]).optional().default("all"),
6337
+ owner: stringType().optional(),
6338
+ assignee: stringType().optional(),
6339
+ feature: stringType().optional(),
6340
+ priority: testCatalogPrioritySchema.optional(),
6341
+ search: stringType().optional(),
6342
+ sortBy: enumType([
6343
+ "lastRunAt",
6344
+ "passRate",
6345
+ "flakyCount",
6346
+ "totalRuns",
6347
+ "title",
6348
+ "avgDurationMs"
6349
+ ]).optional().default("lastRunAt"),
6350
+ sortOrder: enumType(["asc", "desc"]).optional().default("desc")
6351
+ });
6352
+ testCatalogListResponseSchema = objectType({
6353
+ tests: arrayType(testCatalogListItemSchema),
6354
+ total: numberType(),
6355
+ page: numberType(),
6356
+ limit: numberType()
6357
+ });
6358
+ testCatalogDetailResponseSchema = objectType({
6359
+ test: testCatalogListItemSchema,
6360
+ history: arrayType(testCatalogHistoryEntrySchema)
6361
+ });
6362
+ testCatalogFilterOptionsSchema = objectType({
6363
+ owners: arrayType(stringType()),
6364
+ features: arrayType(stringType()),
6365
+ priorities: arrayType(testCatalogPrioritySchema),
6366
+ testTypes: arrayType(testCatalogTypeSchema)
6367
+ });
6368
+ testCatalogAssigneeInfoSchema = objectType({
6369
+ id: stringType(),
6370
+ name: stringType(),
6371
+ email: stringType().optional(),
6372
+ activeTests: numberType()
6373
+ });
6374
+ testCatalogAssignRequestSchema = objectType({
6375
+ assignedTo: stringType(),
6376
+ notes: stringType().optional()
6377
+ });
6378
+ testCatalogUpdateAssignmentRequestSchema = objectType({
6379
+ status: enumType(["completed", "failed"]),
6380
+ notes: stringType().optional()
6381
+ });
6382
+ testCatalogAssignmentResponseSchema = objectType({
6383
+ id: stringType().uuid(),
6384
+ testId: stringType().uuid(),
6385
+ assignedTo: stringType(),
6386
+ status: testCatalogAssignmentStatusSchema
6387
+ });
6388
+ testCatalogFolderNodeSchema = objectType({
6389
+ name: stringType(),
6390
+ path: stringType(),
6391
+ type: enumType(["folder", "file"]),
6392
+ testCount: numberType(),
6393
+ hasChildren: booleanType()
6394
+ });
6395
+ testCatalogFolderTreeResponseSchema = objectType({
6396
+ nodes: arrayType(testCatalogFolderNodeSchema)
6397
+ });
6398
+ testCatalogFolderTestsQuerySchema = objectType({
6399
+ folder: stringType(),
6400
+ status: enumType(["all", "passed", "failed", "flaky", "skipped"]).optional().default("all"),
6401
+ owner: stringType().optional(),
6402
+ assignee: stringType().optional(),
6403
+ feature: stringType().optional(),
6404
+ priority: testCatalogPrioritySchema.optional(),
6405
+ search: stringType().optional()
6406
+ });
6052
6407
  SECONDS_PER_MINUTE = 60;
6053
6408
  SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
6054
6409
  SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
@@ -6333,7 +6688,7 @@ var CLI_VERSION;
6333
6688
  var init_version = __esm({
6334
6689
  "src/version.ts"() {
6335
6690
  "use strict";
6336
- CLI_VERSION = "0.0.43";
6691
+ CLI_VERSION = "0.0.45";
6337
6692
  }
6338
6693
  });
6339
6694
 
@@ -7069,15 +7424,39 @@ var init_api_client = __esm({
7069
7424
  return data;
7070
7425
  }
7071
7426
  /**
7072
- * Assign tests to yourself (or someone else)
7427
+ * Bulk get test details for multiple tests
7428
+ * @param testIds - Array of test IDs
7429
+ * @returns Array of test details (null for not found tests)
7430
+ */
7431
+ async getTestDetailsBulk(testIds) {
7432
+ const url = `${this.apiUrl}/v1/reporting/tests/bulk`;
7433
+ logger.debug(`Bulk fetching ${testIds.length} test details`);
7434
+ const response = await fetch(url, {
7435
+ method: "POST",
7436
+ headers: {
7437
+ "Content-Type": "application/json",
7438
+ Authorization: `Bearer ${this.apiKey}`
7439
+ },
7440
+ body: JSON.stringify({ testIds })
7441
+ });
7442
+ if (!response.ok) {
7443
+ const errorText = await response.text();
7444
+ throw new ApiError(response.status, response.statusText, errorText);
7445
+ }
7446
+ const data = await response.json();
7447
+ logger.debug(`Fetched ${data.tests.length} test details`);
7448
+ return data.tests;
7449
+ }
7450
+ /**
7451
+ * Assign a test to yourself (or someone else)
7073
7452
  * @param params - Assignment parameters
7074
- * @returns Assignment result with assigned tests and conflicts
7453
+ * @returns Assignment result
7075
7454
  */
7076
- async assignTests(params) {
7077
- const url = `${this.apiUrl}/v1/fix-assignments/assign`;
7078
- logger.debug(`Assigning tests`, {
7079
- runId: params.runId,
7080
- count: params.testRunIds.length
7455
+ async assignTest(params) {
7456
+ const url = `${this.apiUrl}/v1/tests-catalog/assign`;
7457
+ logger.debug(`Assigning test`, {
7458
+ testId: params.testId,
7459
+ assignedTo: params.assignedTo
7081
7460
  });
7082
7461
  const response = await fetch(url, {
7083
7462
  method: "POST",
@@ -7086,7 +7465,37 @@ var init_api_client = __esm({
7086
7465
  Authorization: `Bearer ${this.apiKey}`
7087
7466
  },
7088
7467
  body: JSON.stringify({
7089
- testRunIds: params.testRunIds,
7468
+ testId: params.testId,
7469
+ assignedTo: params.assignedTo
7470
+ })
7471
+ });
7472
+ if (!response.ok) {
7473
+ const errorText = await response.text();
7474
+ throw new ApiError(response.status, response.statusText, errorText);
7475
+ }
7476
+ const data = await response.json();
7477
+ logger.debug(`Test assigned: ${params.testId} -> ${params.assignedTo}`);
7478
+ return data;
7479
+ }
7480
+ /**
7481
+ * Bulk assign multiple tests to yourself (or someone else)
7482
+ * @param params - Bulk assignment parameters
7483
+ * @returns Bulk assignment result with successful assignments and conflicts
7484
+ */
7485
+ async assignTestsBulk(params) {
7486
+ const url = `${this.apiUrl}/v1/tests-catalog/assign-bulk`;
7487
+ logger.debug(`Bulk assigning ${params.testIds.length} tests`, {
7488
+ testIds: params.testIds,
7489
+ assignedTo: params.assignedTo
7490
+ });
7491
+ const response = await fetch(url, {
7492
+ method: "POST",
7493
+ headers: {
7494
+ "Content-Type": "application/json",
7495
+ Authorization: `Bearer ${this.apiKey}`
7496
+ },
7497
+ body: JSON.stringify({
7498
+ testIds: params.testIds,
7090
7499
  assignedTo: params.assignedTo
7091
7500
  })
7092
7501
  });
@@ -7096,18 +7505,18 @@ var init_api_client = __esm({
7096
7505
  }
7097
7506
  const data = await response.json();
7098
7507
  logger.debug(
7099
- `Assigned ${data.assigned.length} tests, ${data.conflicts.length} conflicts`
7508
+ `Bulk assigned ${data.successful.length} tests, ${data.conflicts.length} conflicts`
7100
7509
  );
7101
7510
  return data;
7102
7511
  }
7103
7512
  /**
7104
- * Mark assignment as complete
7513
+ * Mark assignment as complete or failed
7105
7514
  * @param params - Completion parameters
7106
7515
  * @returns Success status
7107
7516
  */
7108
7517
  async completeAssignment(params) {
7109
- const url = `${this.apiUrl}/v1/fix-assignments/${params.assignmentId}/status`;
7110
- logger.debug(`Completing assignment`, {
7518
+ const url = `${this.apiUrl}/v1/tests-catalog/${params.assignmentId}/status`;
7519
+ logger.debug(`Updating assignment status`, {
7111
7520
  assignmentId: params.assignmentId,
7112
7521
  status: params.status
7113
7522
  });
@@ -7119,7 +7528,7 @@ var init_api_client = __esm({
7119
7528
  },
7120
7529
  body: JSON.stringify({
7121
7530
  status: params.status,
7122
- fixSessionId: params.fixSessionId
7531
+ notes: params.notes
7123
7532
  })
7124
7533
  });
7125
7534
  if (!response.ok) {
@@ -7127,7 +7536,7 @@ var init_api_client = __esm({
7127
7536
  throw new ApiError(response.status, response.statusText, errorText);
7128
7537
  }
7129
7538
  const data = await response.json();
7130
- logger.debug(`Assignment completed: ${params.assignmentId}`);
7539
+ logger.debug(`Assignment updated: ${params.assignmentId} -> ${params.status}`);
7131
7540
  return data;
7132
7541
  }
7133
7542
  /**
@@ -7136,7 +7545,7 @@ var init_api_client = __esm({
7136
7545
  * @returns Success status
7137
7546
  */
7138
7547
  async releaseAssignment(assignmentId) {
7139
- const url = `${this.apiUrl}/v1/fix-assignments/${assignmentId}`;
7548
+ const url = `${this.apiUrl}/v1/tests-catalog/${assignmentId}`;
7140
7549
  logger.debug(`Releasing assignment: ${assignmentId}`);
7141
7550
  const response = await fetch(url, {
7142
7551
  method: "DELETE",
@@ -7153,12 +7562,12 @@ var init_api_client = __esm({
7153
7562
  return data;
7154
7563
  }
7155
7564
  /**
7156
- * Get my current assignments
7157
- * @returns List of current assignments
7565
+ * Get my current work (active test assignments)
7566
+ * @returns List of current assignments with test info
7158
7567
  */
7159
- async getMyAssignments() {
7160
- const url = `${this.apiUrl}/v1/fix-assignments?status=assigned`;
7161
- logger.debug(`Fetching my assignments`);
7568
+ async getMyWork() {
7569
+ const url = `${this.apiUrl}/v1/tests-catalog/my-work`;
7570
+ logger.debug(`Fetching my work assignments`);
7162
7571
  const response = await fetch(url, {
7163
7572
  method: "GET",
7164
7573
  headers: {
@@ -7170,17 +7579,22 @@ var init_api_client = __esm({
7170
7579
  throw new ApiError(response.status, response.statusText, errorText);
7171
7580
  }
7172
7581
  const data = await response.json();
7173
- logger.debug(`Fetched ${data.assignments.length} assignments`);
7174
- return { assignments: data.assignments };
7582
+ logger.debug(`Fetched ${data.assignments?.length || 0} active assignments`);
7583
+ return data;
7175
7584
  }
7176
7585
  /**
7177
- * Get assignments for a specific run (to show what others are working on)
7586
+ * Get test catalog tests for a run (to show tests and their assignment status)
7178
7587
  * @param runId - The run ID
7179
- * @returns List of assignments for the run
7588
+ * @param query - Optional query parameters for filtering
7589
+ * @returns List of tests with their assignment info
7180
7590
  */
7181
- async getRunAssignments(runId) {
7182
- const url = `${this.apiUrl}/v1/fix-assignments?runId=${runId}&status=assigned`;
7183
- logger.debug(`Fetching assignments for run: ${runId}`);
7591
+ async getRunTestsCatalog(runId, query2) {
7592
+ const urlParams = new URLSearchParams();
7593
+ if (query2?.page) urlParams.set("page", query2.page.toString());
7594
+ if (query2?.limit) urlParams.set("limit", query2.limit.toString());
7595
+ if (query2?.status) urlParams.set("status", query2.status);
7596
+ const url = `${this.apiUrl}/v1/tests-catalog/runs/${runId}?${urlParams.toString()}`;
7597
+ logger.debug(`Fetching tests catalog for run: ${runId}`);
7184
7598
  const response = await fetch(url, {
7185
7599
  method: "GET",
7186
7600
  headers: {
@@ -7192,14 +7606,10 @@ var init_api_client = __esm({
7192
7606
  throw new ApiError(response.status, response.statusText, errorText);
7193
7607
  }
7194
7608
  const data = await response.json();
7195
- logger.debug(`Fetched ${data.assignments.length} assignments for run ${runId}`);
7196
- return {
7197
- assignments: data.assignments.map((a) => ({
7198
- testRunId: a.testRunId,
7199
- assignedTo: a.assignedTo,
7200
- assignedAt: a.assignedAt
7201
- }))
7202
- };
7609
+ logger.debug(
7610
+ `Fetched ${data.tests?.length || 0} tests for run ${runId}`
7611
+ );
7612
+ return data;
7203
7613
  }
7204
7614
  };
7205
7615
  }
@@ -12260,36 +12670,22 @@ var init_TestSelector = __esm({
12260
12670
  run,
12261
12671
  onSelect,
12262
12672
  onCancel,
12263
- assignments = []
12673
+ assignments = /* @__PURE__ */ new Map()
12264
12674
  }) => {
12265
12675
  const [allTests, setAllTests] = useState7([]);
12266
12676
  const [selectedTests, setSelectedTests] = useState7(/* @__PURE__ */ new Set());
12267
12677
  const [cursorIndex, setCursorIndex] = useState7(0);
12268
12678
  const [isLoading, setIsLoading] = useState7(false);
12269
- const [isLoadingAssignments, setIsLoadingAssignments] = useState7(true);
12270
12679
  const [hasMore, setHasMore] = useState7(true);
12271
12680
  const [totalTests, setTotalTests] = useState7(0);
12272
12681
  const [error, setError] = useState7(null);
12273
12682
  const [showAvailableOnly, setShowAvailableOnly] = useState7(true);
12274
12683
  const [groupByFile, setGroupByFile] = useState7(false);
12275
- const assignedTestMap = new Map(assignments.map((a) => [a.testRunId, a]));
12276
- const assignedTestIds = new Set(assignments.map((a) => a.testRunId));
12684
+ const assignedTestMap = assignments;
12685
+ const assignedTestIds = new Set(assignments.keys());
12277
12686
  useEffect7(() => {
12278
12687
  loadMoreTests();
12279
12688
  }, []);
12280
- useEffect7(() => {
12281
- fetchAssignments();
12282
- }, [run.id]);
12283
- const fetchAssignments = async () => {
12284
- setIsLoadingAssignments(true);
12285
- try {
12286
- const result = await apiClient.getRunAssignments(run.id);
12287
- } catch (err) {
12288
- console.error("Failed to load assignments:", err);
12289
- } finally {
12290
- setIsLoadingAssignments(false);
12291
- }
12292
- };
12293
12689
  const loadMoreTests = async () => {
12294
12690
  if (isLoading || !hasMore) {
12295
12691
  return;
@@ -12298,15 +12694,16 @@ var init_TestSelector = __esm({
12298
12694
  setError(null);
12299
12695
  try {
12300
12696
  const page = Math.floor(allTests.length / PAGE_SIZE2) + 1;
12301
- const result = await apiClient.getRunTests(run.id, {
12697
+ const result = await apiClient.getRunTestsCatalog(run.id, {
12302
12698
  page,
12303
12699
  limit: PAGE_SIZE2,
12304
12700
  status: "failed"
12305
12701
  // Only fetch failed tests
12306
12702
  });
12307
- setTotalTests(result.total);
12703
+ setTotalTests(result.total ?? result.tests.length);
12308
12704
  const loadedCount = allTests.length + result.tests.length;
12309
- setHasMore(result.tests.length === PAGE_SIZE2 && loadedCount < result.total);
12705
+ const total = result.total ?? loadedCount;
12706
+ setHasMore(result.tests.length === PAGE_SIZE2 && loadedCount < total);
12310
12707
  setAllTests((prev) => [...prev, ...result.tests]);
12311
12708
  } catch (err) {
12312
12709
  setError(err instanceof Error ? err.message : String(err));
@@ -12377,7 +12774,7 @@ var init_TestSelector = __esm({
12377
12774
  setSelectedTests(newSelected);
12378
12775
  };
12379
12776
  useInput3((input, key) => {
12380
- if (allTests.length === 0 && !isLoading && !isLoadingAssignments) {
12777
+ if (allTests.length === 0 && !isLoading) {
12381
12778
  if (key.escape || input === "q") {
12382
12779
  onCancel();
12383
12780
  }
@@ -12508,7 +12905,7 @@ var init_TestSelector = __esm({
12508
12905
  const displayAssignee = assignee.startsWith("cli:") ? assignee.slice(4) : assignee;
12509
12906
  return /* @__PURE__ */ React21.createElement(Box18, { key: test.id, marginBottom: 0 }, /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : isAssigned ? theme.text.dim : theme.text.primary }, indicator), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, color: isAssigned ? theme.text.dim : isChecked ? "green" : isSelected ? "black" : theme.text.dim }, isAssigned ? "\u{1F504}" : checkbox), /* @__PURE__ */ React21.createElement(Text16, null, " "), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : isAssigned ? theme.text.dim : theme.text.primary }, file, line && /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, ":", line), isAssigned && /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 "), /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, displayAssignee)), /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, " - "), title));
12510
12907
  })
12511
- )), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", marginTop: 1 }, !groupByFile && filteredTests.length > VISIBLE_ITEMS2 && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", adjustedStart + 1, "-", adjustedEnd, " of ", filteredTests.length, " ", showAvailableOnly ? "available" : "", " tests", hasMore && !isLoading && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " (scroll for more)"))), groupByFile && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", fileGroups.length, " file", fileGroups.length !== 1 ? "s" : "", " \u2022 ", filteredTests.length, " total test", filteredTests.length !== 1 ? "s" : "")), /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "n"), " none \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "s"), " next 10 \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "f"), " group files \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "t"), " toggle filter \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Enter"), " fix selected")), selectedTests.size > 0 && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, selectedTests.size, " test", selectedTests.size !== 1 ? "s" : "", " selected")), (isLoading || isLoadingAssignments) && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "cyan" }, isLoading ? "Loading more tests..." : "Loading assignments..."))));
12908
+ )), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", marginTop: 1 }, !groupByFile && filteredTests.length > VISIBLE_ITEMS2 && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", adjustedStart + 1, "-", adjustedEnd, " of ", filteredTests.length, " ", showAvailableOnly ? "available" : "", " tests", hasMore && !isLoading && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " (scroll for more)"))), groupByFile && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", fileGroups.length, " file", fileGroups.length !== 1 ? "s" : "", " \u2022 ", filteredTests.length, " total test", filteredTests.length !== 1 ? "s" : "")), /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "n"), " none \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "s"), " next 10 \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "f"), " group files \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "t"), " toggle filter \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Enter"), " fix selected")), selectedTests.size > 0 && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, selectedTests.size, " test", selectedTests.size !== 1 ? "s" : "", " selected")), isLoading && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "cyan" }, "Loading more tests..."))));
12512
12909
  };
12513
12910
  }
12514
12911
  });
@@ -12534,7 +12931,8 @@ var init_FixFlow = __esm({
12534
12931
  const [step, setStep] = useState8(initialRunId ? "select-run" : "select-run");
12535
12932
  const [selectedRun, setSelectedRun] = useState8(null);
12536
12933
  const [selectedTests, setSelectedTests] = useState8([]);
12537
- const [assignments, setAssignments] = useState8([]);
12934
+ const [assignments, setAssignments] = useState8(/* @__PURE__ */ new Map());
12935
+ const [testCatalogMap, setTestCatalogMap] = useState8(/* @__PURE__ */ new Map());
12538
12936
  const [assignmentIds, setAssignmentIds] = useState8(/* @__PURE__ */ new Map());
12539
12937
  const [loadingProgress, setLoadingProgress] = useState8({ current: 0, total: 0 });
12540
12938
  const [loadError, setLoadError] = useState8(null);
@@ -12558,8 +12956,25 @@ var init_FixFlow = __esm({
12558
12956
  };
12559
12957
  const fetchAssignments = async (runId) => {
12560
12958
  try {
12561
- const result = await apiClient.getRunAssignments(runId);
12562
- setAssignments(result.assignments);
12959
+ const result = await apiClient.getRunTestsCatalog(runId, {
12960
+ status: "failed",
12961
+ limit: 1e3
12962
+ // Get all failed tests for assignment lookup
12963
+ });
12964
+ const assignmentMap = /* @__PURE__ */ new Map();
12965
+ const catalogMap = /* @__PURE__ */ new Map();
12966
+ for (const test of result.tests) {
12967
+ catalogMap.set(test.id, test.testId);
12968
+ if (test.assignment) {
12969
+ assignmentMap.set(test.id, {
12970
+ testId: test.testId,
12971
+ assignedTo: test.assignment.assignedTo,
12972
+ assignedAt: test.assignment.assignedAt
12973
+ });
12974
+ }
12975
+ }
12976
+ setAssignments(assignmentMap);
12977
+ setTestCatalogMap(catalogMap);
12563
12978
  } catch (err) {
12564
12979
  console.error("Failed to load assignments:", err);
12565
12980
  }
@@ -12569,16 +12984,46 @@ var init_FixFlow = __esm({
12569
12984
  setStep("claiming-tests");
12570
12985
  setLoadError(null);
12571
12986
  try {
12572
- if (selectedRun) {
12573
- const claimResult = await apiClient.assignTests({
12574
- runId: selectedRun.id,
12575
- testRunIds: tests.map((t) => t.id)
12576
- });
12577
- if (claimResult.conflicts.length > 0) {
12578
- const conflictList = claimResult.conflicts.slice(0, 5).map((c) => `${c.file}: ${c.title} (claimed by ${c.currentAssignee})`).join("\n\u2022 ");
12579
- const moreCount = claimResult.conflicts.length - 5;
12987
+ const assignmentMap = /* @__PURE__ */ new Map();
12988
+ const testCatalogUuids = [];
12989
+ const testRunToCatalogMap = /* @__PURE__ */ new Map();
12990
+ for (const test of tests) {
12991
+ const testCatalogUuid = testCatalogMap.get(test.id);
12992
+ if (!testCatalogUuid) {
12993
+ throw new Error(`Test catalog entry not found for test: ${test.file}:${test.title}`);
12994
+ }
12995
+ testCatalogUuids.push(testCatalogUuid);
12996
+ testRunToCatalogMap.set(testCatalogUuid, test.id);
12997
+ }
12998
+ const result = await apiClient.assignTestsBulk({
12999
+ testIds: testCatalogUuids
13000
+ });
13001
+ for (const assignment of result.successful) {
13002
+ const testRunId = testRunToCatalogMap.get(assignment.testId);
13003
+ if (testRunId) {
13004
+ assignmentMap.set(testRunId, assignment.id);
13005
+ }
13006
+ }
13007
+ const conflicts = [];
13008
+ for (const conflict of result.conflicts) {
13009
+ const testRunId = testRunToCatalogMap.get(conflict.testId);
13010
+ if (testRunId) {
13011
+ const test = tests.find((t) => t.id === testRunId);
13012
+ if (test) {
13013
+ conflicts.push({
13014
+ test,
13015
+ assignee: conflict.assignedTo
13016
+ });
13017
+ }
13018
+ }
13019
+ }
13020
+ if (conflicts.length > 0) {
13021
+ const successfullyClaimed = tests.filter((test) => assignmentMap.has(test.id));
13022
+ if (successfullyClaimed.length === 0) {
13023
+ const conflictList = conflicts.slice(0, 3).map((c) => `${c.test.file}: ${c.test.title} (claimed by ${c.assignee})`).join("\n\u2022 ");
13024
+ const moreCount = conflicts.length - 3;
12580
13025
  setLoadError(
12581
- `${claimResult.conflicts.length} test${claimResult.conflicts.length > 1 ? "s were" : " was"} already claimed by others:
13026
+ `All selected tests are already claimed by others:
12582
13027
  \u2022 ${conflictList}${moreCount > 0 ? `
12583
13028
  \u2022 ... and ${moreCount} more` : ""}
12584
13029
 
@@ -12587,28 +13032,22 @@ Please select different tests.`
12587
13032
  setStep("error");
12588
13033
  return;
12589
13034
  }
12590
- const assignmentMap = /* @__PURE__ */ new Map();
12591
- claimResult.assigned.forEach((assignment) => {
12592
- assignmentMap.set(assignment.testRunId, assignment.id);
12593
- });
12594
- setAssignmentIds(assignmentMap);
13035
+ setSelectedTests(successfullyClaimed);
13036
+ console.log(`Skipped ${conflicts.length} already claimed test(s), continuing with ${successfullyClaimed.length} test(s)`);
12595
13037
  }
13038
+ setAssignmentIds(assignmentMap);
13039
+ const testsToLoad = conflicts.length > 0 ? tests.filter((test) => assignmentMap.has(test.id)) : tests;
12596
13040
  setStep("loading-details");
12597
- setLoadingProgress({ current: 0, total: tests.length });
12598
- const testDetails = [];
12599
- const batchSize = 5;
12600
- for (let i = 0; i < tests.length; i += batchSize) {
12601
- const batch = tests.slice(i, i + batchSize);
12602
- const batchResults = await Promise.all(
12603
- batch.map((test) => apiClient.getTestDetail(test.id))
12604
- );
12605
- testDetails.push(...batchResults);
12606
- setLoadingProgress({ current: testDetails.length, total: tests.length });
12607
- }
13041
+ setLoadingProgress({ current: 0, total: testsToLoad.length });
13042
+ const testIds = testsToLoad.map((test) => test.id);
13043
+ const testDetailsArray = await apiClient.getTestDetailsBulk(testIds);
13044
+ const testDetails = testDetailsArray.filter(
13045
+ (detail) => detail !== null
13046
+ );
12608
13047
  const testContexts = testDetails.map((test) => ({ test }));
12609
13048
  const prompt = buildFixPrompt(testContexts);
12610
13049
  setStep("fixing");
12611
- onStartFix(prompt, tests);
13050
+ onStartFix(prompt, testsToLoad);
12612
13051
  } catch (err) {
12613
13052
  const errorMessage = err instanceof Error ? err.message : String(err);
12614
13053
  if (errorMessage.includes("network") || errorMessage.includes("fetch")) {
@@ -12636,7 +13075,7 @@ Press ESC to go back and try again.`);
12636
13075
  };
12637
13076
  const handleTestCancel = () => {
12638
13077
  setSelectedRun(null);
12639
- setAssignments([]);
13078
+ setAssignments(/* @__PURE__ */ new Map());
12640
13079
  setStep("select-run");
12641
13080
  };
12642
13081
  const markAssignmentsComplete = async (fixSessionId) => {
@@ -12648,8 +13087,7 @@ Press ESC to go back and try again.`);
12648
13087
  Array.from(assignmentIds.values()).map(
12649
13088
  (assignmentId) => apiClient.completeAssignment({
12650
13089
  assignmentId,
12651
- status: "completed",
12652
- fixSessionId
13090
+ status: "completed"
12653
13091
  })
12654
13092
  )
12655
13093
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supatest/cli",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "description": "Supatest CLI - AI-powered task automation for CI/CD",
5
5
  "type": "module",
6
6
  "bin": {