github-issue-tower-defence-management 1.83.0 → 1.84.1

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 (37) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +67 -4
  3. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +19 -2
  4. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  5. package/bin/adapter/entry-points/handlers/consoleListsWriter.js +43 -0
  6. package/bin/adapter/entry-points/handlers/consoleListsWriter.js.map +1 -0
  7. package/bin/adapter/proxy/RateLimitCache.js +27 -2
  8. package/bin/adapter/proxy/RateLimitCache.js.map +1 -1
  9. package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +5 -1
  10. package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
  11. package/bin/domain/usecases/StartPreparationUseCase.js +6 -6
  12. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  13. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +101 -0
  14. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -0
  15. package/package.json +1 -1
  16. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +18 -0
  17. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +167 -0
  18. package/src/adapter/entry-points/handlers/consoleListsWriter.ts +60 -0
  19. package/src/adapter/proxy/RateLimitCache.test.ts +95 -0
  20. package/src/adapter/proxy/RateLimitCache.ts +32 -1
  21. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +43 -0
  22. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +6 -1
  23. package/src/domain/entities/ClaudeTokenUsage.ts +1 -0
  24. package/src/domain/usecases/StartPreparationUseCase.test.ts +343 -0
  25. package/src/domain/usecases/StartPreparationUseCase.ts +6 -6
  26. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +372 -0
  27. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +206 -0
  28. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  29. package/types/adapter/entry-points/handlers/consoleListsWriter.d.ts +13 -0
  30. package/types/adapter/entry-points/handlers/consoleListsWriter.d.ts.map +1 -0
  31. package/types/adapter/proxy/RateLimitCache.d.ts +1 -0
  32. package/types/adapter/proxy/RateLimitCache.d.ts.map +1 -1
  33. package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
  34. package/types/domain/entities/ClaudeTokenUsage.d.ts +1 -0
  35. package/types/domain/entities/ClaudeTokenUsage.d.ts.map +1 -1
  36. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +63 -0
  37. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -0
@@ -1270,6 +1270,7 @@ describe('StartPreparationUseCase', () => {
1270
1270
  sevenDayUtilization: 0,
1271
1271
  blocked: false,
1272
1272
  rejected: false,
1273
+ fiveHourRejected: false,
1273
1274
  blockedUntilEpoch: 0,
1274
1275
  modelWeeklyLimits: {},
1275
1276
  },
@@ -1280,6 +1281,7 @@ describe('StartPreparationUseCase', () => {
1280
1281
  sevenDayUtilization: 0,
1281
1282
  blocked: false,
1282
1283
  rejected: false,
1284
+ fiveHourRejected: false,
1283
1285
  blockedUntilEpoch: 0,
1284
1286
  modelWeeklyLimits: {},
1285
1287
  },
@@ -1339,6 +1341,7 @@ describe('StartPreparationUseCase', () => {
1339
1341
  sevenDayUtilization: 0,
1340
1342
  blocked: false,
1341
1343
  rejected: false,
1344
+ fiveHourRejected: false,
1342
1345
  blockedUntilEpoch: 0,
1343
1346
  modelWeeklyLimits: {},
1344
1347
  },
@@ -1349,6 +1352,7 @@ describe('StartPreparationUseCase', () => {
1349
1352
  sevenDayUtilization: 0,
1350
1353
  blocked: false,
1351
1354
  rejected: false,
1355
+ fiveHourRejected: false,
1352
1356
  blockedUntilEpoch: 0,
1353
1357
  modelWeeklyLimits: {},
1354
1358
  },
@@ -2377,6 +2381,7 @@ describe('StartPreparationUseCase', () => {
2377
2381
  sevenDayUtilization: 0,
2378
2382
  blocked: false,
2379
2383
  rejected: false,
2384
+ fiveHourRejected: false,
2380
2385
  blockedUntilEpoch: 0,
2381
2386
  modelWeeklyLimits: {},
2382
2387
  },
@@ -2456,6 +2461,7 @@ describe('StartPreparationUseCase', () => {
2456
2461
  sevenDayUtilization: 0,
2457
2462
  blocked: false,
2458
2463
  rejected: false,
2464
+ fiveHourRejected: false,
2459
2465
  blockedUntilEpoch: 0,
2460
2466
  modelWeeklyLimits: {},
2461
2467
  },
@@ -2466,6 +2472,7 @@ describe('StartPreparationUseCase', () => {
2466
2472
  sevenDayUtilization: 0,
2467
2473
  blocked: false,
2468
2474
  rejected: false,
2475
+ fiveHourRejected: false,
2469
2476
  blockedUntilEpoch: 0,
2470
2477
  modelWeeklyLimits: {},
2471
2478
  },
@@ -2578,6 +2585,7 @@ describe('StartPreparationUseCase', () => {
2578
2585
  sevenDayUtilization: 0.1,
2579
2586
  blocked: false,
2580
2587
  rejected: false,
2588
+ fiveHourRejected: false,
2581
2589
  blockedUntilEpoch: 0,
2582
2590
  modelWeeklyLimits: {
2583
2591
  seven_day_opus: {
@@ -2593,6 +2601,7 @@ describe('StartPreparationUseCase', () => {
2593
2601
  sevenDayUtilization: 0.1,
2594
2602
  blocked: false,
2595
2603
  rejected: false,
2604
+ fiveHourRejected: false,
2596
2605
  blockedUntilEpoch: 0,
2597
2606
  modelWeeklyLimits: {
2598
2607
  seven_day_opus: {
@@ -2655,6 +2664,7 @@ describe('StartPreparationUseCase', () => {
2655
2664
  sevenDayUtilization: 0.1,
2656
2665
  blocked: false,
2657
2666
  rejected: false,
2667
+ fiveHourRejected: false,
2658
2668
  blockedUntilEpoch: 0,
2659
2669
  modelWeeklyLimits: {
2660
2670
  seven_day_opus: {
@@ -2670,6 +2680,7 @@ describe('StartPreparationUseCase', () => {
2670
2680
  sevenDayUtilization: 0.1,
2671
2681
  blocked: false,
2672
2682
  rejected: false,
2683
+ fiveHourRejected: false,
2673
2684
  blockedUntilEpoch: 0,
2674
2685
  modelWeeklyLimits: {
2675
2686
  seven_day_opus: {
@@ -2731,6 +2742,7 @@ describe('StartPreparationUseCase', () => {
2731
2742
  sevenDayUtilization: 0.1,
2732
2743
  blocked: false,
2733
2744
  rejected: false,
2745
+ fiveHourRejected: false,
2734
2746
  blockedUntilEpoch: 0,
2735
2747
  modelWeeklyLimits: {
2736
2748
  seven_day_opus: {
@@ -2746,6 +2758,7 @@ describe('StartPreparationUseCase', () => {
2746
2758
  sevenDayUtilization: 0.1,
2747
2759
  blocked: false,
2748
2760
  rejected: false,
2761
+ fiveHourRejected: false,
2749
2762
  blockedUntilEpoch: 0,
2750
2763
  modelWeeklyLimits: {
2751
2764
  seven_day_opus: {
@@ -2804,6 +2817,7 @@ describe('StartPreparationUseCase', () => {
2804
2817
  sevenDayUtilization: 0.7,
2805
2818
  blocked: false,
2806
2819
  rejected: false,
2820
+ fiveHourRejected: false,
2807
2821
  blockedUntilEpoch: 0,
2808
2822
  modelWeeklyLimits: {
2809
2823
  seven_day_opus: {
@@ -2819,6 +2833,7 @@ describe('StartPreparationUseCase', () => {
2819
2833
  sevenDayUtilization: 0.2,
2820
2834
  blocked: false,
2821
2835
  rejected: false,
2836
+ fiveHourRejected: false,
2822
2837
  blockedUntilEpoch: 0,
2823
2838
  modelWeeklyLimits: {
2824
2839
  seven_day_opus: {
@@ -2890,6 +2905,7 @@ describe('StartPreparationUseCase', () => {
2890
2905
  sevenDayUtilization: 0.7,
2891
2906
  blocked: false,
2892
2907
  rejected: false,
2908
+ fiveHourRejected: false,
2893
2909
  blockedUntilEpoch: 0,
2894
2910
  modelWeeklyLimits: {
2895
2911
  seven_day_opus: {
@@ -2905,6 +2921,7 @@ describe('StartPreparationUseCase', () => {
2905
2921
  sevenDayUtilization: 0.2,
2906
2922
  blocked: false,
2907
2923
  rejected: false,
2924
+ fiveHourRejected: false,
2908
2925
  blockedUntilEpoch: 0,
2909
2926
  modelWeeklyLimits: {
2910
2927
  seven_day_opus: {
@@ -2965,6 +2982,7 @@ describe('StartPreparationUseCase', () => {
2965
2982
  sevenDayUtilization: 0,
2966
2983
  blocked: true,
2967
2984
  rejected: false,
2985
+ fiveHourRejected: false,
2968
2986
  blockedUntilEpoch: 0,
2969
2987
  modelWeeklyLimits: {},
2970
2988
  },
@@ -2975,6 +2993,7 @@ describe('StartPreparationUseCase', () => {
2975
2993
  sevenDayUtilization: 0,
2976
2994
  blocked: false,
2977
2995
  rejected: false,
2996
+ fiveHourRejected: false,
2978
2997
  blockedUntilEpoch: 0,
2979
2998
  modelWeeklyLimits: {},
2980
2999
  },
@@ -3031,6 +3050,7 @@ describe('StartPreparationUseCase', () => {
3031
3050
  sevenDayUtilization: 0,
3032
3051
  blocked: false,
3033
3052
  rejected: false,
3053
+ fiveHourRejected: false,
3034
3054
  blockedUntilEpoch: nowEpochSeconds + 90,
3035
3055
  modelWeeklyLimits: {},
3036
3056
  },
@@ -3041,6 +3061,7 @@ describe('StartPreparationUseCase', () => {
3041
3061
  sevenDayUtilization: 0,
3042
3062
  blocked: false,
3043
3063
  rejected: false,
3064
+ fiveHourRejected: false,
3044
3065
  blockedUntilEpoch: 0,
3045
3066
  modelWeeklyLimits: {},
3046
3067
  },
@@ -3096,6 +3117,7 @@ describe('StartPreparationUseCase', () => {
3096
3117
  sevenDayUtilization: 0,
3097
3118
  blocked: true,
3098
3119
  rejected: false,
3120
+ fiveHourRejected: false,
3099
3121
  blockedUntilEpoch: 0,
3100
3122
  modelWeeklyLimits: {},
3101
3123
  },
@@ -3106,6 +3128,7 @@ describe('StartPreparationUseCase', () => {
3106
3128
  sevenDayUtilization: 0,
3107
3129
  blocked: true,
3108
3130
  rejected: false,
3131
+ fiveHourRejected: false,
3109
3132
  blockedUntilEpoch: 0,
3110
3133
  modelWeeklyLimits: {},
3111
3134
  },
@@ -3167,6 +3190,7 @@ describe('StartPreparationUseCase', () => {
3167
3190
  sevenDayUtilization: 0,
3168
3191
  blocked: false,
3169
3192
  rejected: false,
3193
+ fiveHourRejected: false,
3170
3194
  blockedUntilEpoch: 0,
3171
3195
  modelWeeklyLimits: {},
3172
3196
  },
@@ -3177,6 +3201,7 @@ describe('StartPreparationUseCase', () => {
3177
3201
  sevenDayUtilization: 0,
3178
3202
  blocked: false,
3179
3203
  rejected: false,
3204
+ fiveHourRejected: false,
3180
3205
  blockedUntilEpoch: 0,
3181
3206
  modelWeeklyLimits: {},
3182
3207
  },
@@ -3257,6 +3282,7 @@ describe('StartPreparationUseCase', () => {
3257
3282
  sevenDayUtilization: 0.5,
3258
3283
  blocked: false,
3259
3284
  rejected: false,
3285
+ fiveHourRejected: false,
3260
3286
  blockedUntilEpoch: 0,
3261
3287
  modelWeeklyLimits: {
3262
3288
  seven_day_opus: {
@@ -3272,6 +3298,7 @@ describe('StartPreparationUseCase', () => {
3272
3298
  sevenDayUtilization: 0.1,
3273
3299
  blocked: false,
3274
3300
  rejected: false,
3301
+ fiveHourRejected: false,
3275
3302
  blockedUntilEpoch: 0,
3276
3303
  modelWeeklyLimits: {
3277
3304
  seven_day_opus: {
@@ -3287,6 +3314,7 @@ describe('StartPreparationUseCase', () => {
3287
3314
  sevenDayUtilization: 0.7,
3288
3315
  blocked: false,
3289
3316
  rejected: false,
3317
+ fiveHourRejected: false,
3290
3318
  blockedUntilEpoch: 0,
3291
3319
  modelWeeklyLimits: {
3292
3320
  seven_day_opus: {
@@ -3356,6 +3384,7 @@ describe('StartPreparationUseCase', () => {
3356
3384
  sevenDayUtilization: 0.1,
3357
3385
  blocked: false,
3358
3386
  rejected: false,
3387
+ fiveHourRejected: false,
3359
3388
  blockedUntilEpoch: 0,
3360
3389
  modelWeeklyLimits: {
3361
3390
  seven_day_opus: {
@@ -3371,6 +3400,7 @@ describe('StartPreparationUseCase', () => {
3371
3400
  sevenDayUtilization: 0.1,
3372
3401
  blocked: false,
3373
3402
  rejected: false,
3403
+ fiveHourRejected: false,
3374
3404
  blockedUntilEpoch: 0,
3375
3405
  modelWeeklyLimits: {
3376
3406
  seven_day_opus: {
@@ -3436,6 +3466,7 @@ describe('StartPreparationUseCase', () => {
3436
3466
  sevenDayUtilization: 0.9,
3437
3467
  blocked: false,
3438
3468
  rejected: false,
3469
+ fiveHourRejected: false,
3439
3470
  blockedUntilEpoch: 0,
3440
3471
  modelWeeklyLimits: {},
3441
3472
  },
@@ -3491,6 +3522,7 @@ describe('StartPreparationUseCase', () => {
3491
3522
  sevenDayUtilization: 0,
3492
3523
  blocked: false,
3493
3524
  rejected: true,
3525
+ fiveHourRejected: true,
3494
3526
  blockedUntilEpoch: 0,
3495
3527
  modelWeeklyLimits: {},
3496
3528
  },
@@ -3501,6 +3533,7 @@ describe('StartPreparationUseCase', () => {
3501
3533
  sevenDayUtilization: 0,
3502
3534
  blocked: false,
3503
3535
  rejected: false,
3536
+ fiveHourRejected: false,
3504
3537
  blockedUntilEpoch: 0,
3505
3538
  modelWeeklyLimits: {},
3506
3539
  },
@@ -3556,6 +3589,7 @@ describe('StartPreparationUseCase', () => {
3556
3589
  sevenDayUtilization: 0,
3557
3590
  blocked: false,
3558
3591
  rejected: false,
3592
+ fiveHourRejected: false,
3559
3593
  blockedUntilEpoch: 0,
3560
3594
  modelWeeklyLimits: {},
3561
3595
  },
@@ -3566,6 +3600,7 @@ describe('StartPreparationUseCase', () => {
3566
3600
  sevenDayUtilization: 0,
3567
3601
  blocked: false,
3568
3602
  rejected: false,
3603
+ fiveHourRejected: false,
3569
3604
  blockedUntilEpoch: 0,
3570
3605
  modelWeeklyLimits: {},
3571
3606
  },
@@ -3621,6 +3656,7 @@ describe('StartPreparationUseCase', () => {
3621
3656
  sevenDayUtilization: 0,
3622
3657
  blocked: false,
3623
3658
  rejected: true,
3659
+ fiveHourRejected: true,
3624
3660
  blockedUntilEpoch: 0,
3625
3661
  modelWeeklyLimits: {},
3626
3662
  },
@@ -3631,6 +3667,7 @@ describe('StartPreparationUseCase', () => {
3631
3667
  sevenDayUtilization: 0,
3632
3668
  blocked: false,
3633
3669
  rejected: false,
3670
+ fiveHourRejected: false,
3634
3671
  blockedUntilEpoch: 0,
3635
3672
  modelWeeklyLimits: {},
3636
3673
  },
@@ -3686,6 +3723,7 @@ describe('StartPreparationUseCase', () => {
3686
3723
  sevenDayUtilization: 0,
3687
3724
  blocked: false,
3688
3725
  rejected: true,
3726
+ fiveHourRejected: true,
3689
3727
  blockedUntilEpoch: 0,
3690
3728
  modelWeeklyLimits: {},
3691
3729
  },
@@ -3696,6 +3734,7 @@ describe('StartPreparationUseCase', () => {
3696
3734
  sevenDayUtilization: 0,
3697
3735
  blocked: false,
3698
3736
  rejected: true,
3737
+ fiveHourRejected: true,
3699
3738
  blockedUntilEpoch: 0,
3700
3739
  modelWeeklyLimits: {},
3701
3740
  },
@@ -3803,6 +3842,7 @@ describe('StartPreparationUseCase', () => {
3803
3842
  sevenDayUtilization: 0,
3804
3843
  blocked: false,
3805
3844
  rejected: false,
3845
+ fiveHourRejected: false,
3806
3846
  blockedUntilEpoch: 0,
3807
3847
  modelWeeklyLimits: {
3808
3848
  seven_day_sonnet: { rejected: false, resetsAt: pastReset },
@@ -3815,6 +3855,7 @@ describe('StartPreparationUseCase', () => {
3815
3855
  sevenDayUtilization: 0,
3816
3856
  blocked: false,
3817
3857
  rejected: false,
3858
+ fiveHourRejected: false,
3818
3859
  blockedUntilEpoch: 0,
3819
3860
  modelWeeklyLimits: {},
3820
3861
  },
@@ -3871,6 +3912,7 @@ describe('StartPreparationUseCase', () => {
3871
3912
  sevenDayUtilization: 0,
3872
3913
  blocked: false,
3873
3914
  rejected: false,
3915
+ fiveHourRejected: false,
3874
3916
  blockedUntilEpoch: 0,
3875
3917
  modelWeeklyLimits: {
3876
3918
  seven_day_sonnet: { rejected: true, resetsAt: futureReset },
@@ -3883,6 +3925,7 @@ describe('StartPreparationUseCase', () => {
3883
3925
  sevenDayUtilization: 0,
3884
3926
  blocked: false,
3885
3927
  rejected: false,
3928
+ fiveHourRejected: false,
3886
3929
  blockedUntilEpoch: 0,
3887
3930
  modelWeeklyLimits: {},
3888
3931
  },
@@ -3939,6 +3982,7 @@ describe('StartPreparationUseCase', () => {
3939
3982
  sevenDayUtilization: 0,
3940
3983
  blocked: false,
3941
3984
  rejected: false,
3985
+ fiveHourRejected: false,
3942
3986
  blockedUntilEpoch: 0,
3943
3987
  modelWeeklyLimits: {
3944
3988
  seven_day: { rejected: true, resetsAt: futureReset },
@@ -3951,6 +3995,7 @@ describe('StartPreparationUseCase', () => {
3951
3995
  sevenDayUtilization: 0,
3952
3996
  blocked: false,
3953
3997
  rejected: false,
3998
+ fiveHourRejected: false,
3954
3999
  blockedUntilEpoch: 0,
3955
4000
  modelWeeklyLimits: {},
3956
4001
  },
@@ -4007,6 +4052,7 @@ describe('StartPreparationUseCase', () => {
4007
4052
  sevenDayUtilization: 0.1,
4008
4053
  blocked: false,
4009
4054
  rejected: false,
4055
+ fiveHourRejected: false,
4010
4056
  blockedUntilEpoch: 0,
4011
4057
  modelWeeklyLimits: {
4012
4058
  seven_day_opus: {
@@ -4022,6 +4068,7 @@ describe('StartPreparationUseCase', () => {
4022
4068
  sevenDayUtilization: 0.1,
4023
4069
  blocked: false,
4024
4070
  rejected: false,
4071
+ fiveHourRejected: false,
4025
4072
  blockedUntilEpoch: 0,
4026
4073
  modelWeeklyLimits: {
4027
4074
  seven_day_opus: {
@@ -4083,6 +4130,7 @@ describe('StartPreparationUseCase', () => {
4083
4130
  sevenDayUtilization: 0.1,
4084
4131
  blocked: true,
4085
4132
  rejected: false,
4133
+ fiveHourRejected: false,
4086
4134
  blockedUntilEpoch: 0,
4087
4135
  modelWeeklyLimits: {
4088
4136
  seven_day_opus: {
@@ -4098,6 +4146,7 @@ describe('StartPreparationUseCase', () => {
4098
4146
  sevenDayUtilization: 0.1,
4099
4147
  blocked: false,
4100
4148
  rejected: false,
4149
+ fiveHourRejected: false,
4101
4150
  blockedUntilEpoch: 0,
4102
4151
  modelWeeklyLimits: {
4103
4152
  seven_day_opus: {
@@ -4169,6 +4218,7 @@ describe('StartPreparationUseCase', () => {
4169
4218
  sevenDayUtilization: 0.1,
4170
4219
  blocked: false,
4171
4220
  rejected: false,
4221
+ fiveHourRejected: false,
4172
4222
  blockedUntilEpoch: 0,
4173
4223
  modelWeeklyLimits: {},
4174
4224
  },
@@ -4179,6 +4229,7 @@ describe('StartPreparationUseCase', () => {
4179
4229
  sevenDayUtilization: 0.1,
4180
4230
  blocked: false,
4181
4231
  rejected: false,
4232
+ fiveHourRejected: false,
4182
4233
  blockedUntilEpoch: 0,
4183
4234
  modelWeeklyLimits: {
4184
4235
  seven_day_opus: {
@@ -4258,6 +4309,7 @@ describe('StartPreparationUseCase', () => {
4258
4309
  sevenDayUtilization: 0.1,
4259
4310
  blocked: false,
4260
4311
  rejected: false,
4312
+ fiveHourRejected: false,
4261
4313
  blockedUntilEpoch: 0,
4262
4314
  modelWeeklyLimits: {
4263
4315
  seven_day: {
@@ -4273,6 +4325,7 @@ describe('StartPreparationUseCase', () => {
4273
4325
  sevenDayUtilization: 0.1,
4274
4326
  blocked: false,
4275
4327
  rejected: false,
4328
+ fiveHourRejected: false,
4276
4329
  blockedUntilEpoch: 0,
4277
4330
  modelWeeklyLimits: {
4278
4331
  seven_day: {
@@ -4288,6 +4341,7 @@ describe('StartPreparationUseCase', () => {
4288
4341
  sevenDayUtilization: 0.1,
4289
4342
  blocked: false,
4290
4343
  rejected: false,
4344
+ fiveHourRejected: false,
4291
4345
  blockedUntilEpoch: 0,
4292
4346
  modelWeeklyLimits: {
4293
4347
  seven_day: {
@@ -4353,6 +4407,7 @@ describe('StartPreparationUseCase', () => {
4353
4407
  sevenDayUtilization: 0.1,
4354
4408
  blocked: false,
4355
4409
  rejected: false,
4410
+ fiveHourRejected: false,
4356
4411
  blockedUntilEpoch: 0,
4357
4412
  modelWeeklyLimits: {
4358
4413
  seven_day: { rejected: false, resetsAt: sharedResetsAt },
@@ -4365,6 +4420,7 @@ describe('StartPreparationUseCase', () => {
4365
4420
  sevenDayUtilization: 0.1,
4366
4421
  blocked: false,
4367
4422
  rejected: false,
4423
+ fiveHourRejected: false,
4368
4424
  blockedUntilEpoch: 0,
4369
4425
  modelWeeklyLimits: {
4370
4426
  seven_day: { rejected: false, resetsAt: sharedResetsAt },
@@ -4424,6 +4480,7 @@ describe('StartPreparationUseCase', () => {
4424
4480
  sevenDayUtilization: 0.1,
4425
4481
  blocked: false,
4426
4482
  rejected: false,
4483
+ fiveHourRejected: false,
4427
4484
  blockedUntilEpoch: 0,
4428
4485
  modelWeeklyLimits: {
4429
4486
  seven_day_opus: { rejected: false, resetsAt: sharedResetsAt },
@@ -4436,6 +4493,7 @@ describe('StartPreparationUseCase', () => {
4436
4493
  sevenDayUtilization: 0.1,
4437
4494
  blocked: false,
4438
4495
  rejected: false,
4496
+ fiveHourRejected: false,
4439
4497
  blockedUntilEpoch: 0,
4440
4498
  modelWeeklyLimits: {
4441
4499
  seven_day_opus: { rejected: false, resetsAt: sharedResetsAt },
@@ -4630,6 +4688,7 @@ describe('StartPreparationUseCase', () => {
4630
4688
  sevenDayUtilization: 0,
4631
4689
  blocked: false,
4632
4690
  rejected: false,
4691
+ fiveHourRejected: false,
4633
4692
  blockedUntilEpoch: 0,
4634
4693
  modelWeeklyLimits: {
4635
4694
  seven_day_sonnet: { rejected: true, resetsAt: futureReset },
@@ -4696,6 +4755,7 @@ describe('StartPreparationUseCase', () => {
4696
4755
  sevenDayUtilization: 0,
4697
4756
  blocked: false,
4698
4757
  rejected: false,
4758
+ fiveHourRejected: false,
4699
4759
  blockedUntilEpoch: 0,
4700
4760
  modelWeeklyLimits: {
4701
4761
  seven_day_sonnet: { rejected: true, resetsAt: futureReset },
@@ -4750,6 +4810,7 @@ describe('StartPreparationUseCase', () => {
4750
4810
  sevenDayUtilization: 0,
4751
4811
  blocked: false,
4752
4812
  rejected: false,
4813
+ fiveHourRejected: false,
4753
4814
  blockedUntilEpoch: 0,
4754
4815
  modelWeeklyLimits: {
4755
4816
  seven_day_sonnet: { rejected: true, resetsAt: futureReset },
@@ -4810,6 +4871,7 @@ describe('StartPreparationUseCase', () => {
4810
4871
  sevenDayUtilization: 0,
4811
4872
  blocked: false,
4812
4873
  rejected: false,
4874
+ fiveHourRejected: false,
4813
4875
  blockedUntilEpoch: 0,
4814
4876
  modelWeeklyLimits: {},
4815
4877
  },
@@ -4862,6 +4924,7 @@ describe('StartPreparationUseCase', () => {
4862
4924
  sevenDayUtilization: 0,
4863
4925
  blocked: false,
4864
4926
  rejected: false,
4927
+ fiveHourRejected: false,
4865
4928
  blockedUntilEpoch: 0,
4866
4929
  modelWeeklyLimits: {
4867
4930
  seven_day_sonnet: { rejected: true, resetsAt: futureReset },
@@ -4916,6 +4979,7 @@ describe('StartPreparationUseCase', () => {
4916
4979
  sevenDayUtilization: 0,
4917
4980
  blocked: false,
4918
4981
  rejected: false,
4982
+ fiveHourRejected: false,
4919
4983
  blockedUntilEpoch: 0,
4920
4984
  modelWeeklyLimits: {},
4921
4985
  },
@@ -4986,6 +5050,7 @@ describe('StartPreparationUseCase', () => {
4986
5050
  sevenDayUtilization: 0,
4987
5051
  blocked: false,
4988
5052
  rejected: false,
5053
+ fiveHourRejected: false,
4989
5054
  blockedUntilEpoch: 0,
4990
5055
  modelWeeklyLimits: {
4991
5056
  seven_day_sonnet: { rejected: true, resetsAt: futureReset },
@@ -4998,6 +5063,7 @@ describe('StartPreparationUseCase', () => {
4998
5063
  sevenDayUtilization: 0,
4999
5064
  blocked: false,
5000
5065
  rejected: false,
5066
+ fiveHourRejected: false,
5001
5067
  blockedUntilEpoch: 0,
5002
5068
  modelWeeklyLimits: {},
5003
5069
  },
@@ -5059,6 +5125,7 @@ describe('StartPreparationUseCase', () => {
5059
5125
  sevenDayUtilization: 0,
5060
5126
  blocked: false,
5061
5127
  rejected: false,
5128
+ fiveHourRejected: false,
5062
5129
  blockedUntilEpoch: 0,
5063
5130
  modelWeeklyLimits: {
5064
5131
  seven_day_sonnet: { rejected: true, resetsAt: futureReset },
@@ -5119,6 +5186,7 @@ describe('StartPreparationUseCase', () => {
5119
5186
  sevenDayUtilization: 0,
5120
5187
  blocked: false,
5121
5188
  rejected: false,
5189
+ fiveHourRejected: false,
5122
5190
  blockedUntilEpoch: 0,
5123
5191
  modelWeeklyLimits: {
5124
5192
  seven_day: { rejected: true, resetsAt: futureReset },
@@ -5177,6 +5245,7 @@ describe('StartPreparationUseCase', () => {
5177
5245
  sevenDayUtilization: 0,
5178
5246
  blocked: false,
5179
5247
  rejected: false,
5248
+ fiveHourRejected: false,
5180
5249
  blockedUntilEpoch: 0,
5181
5250
  modelWeeklyLimits: {
5182
5251
  seven_day_sonnet: { rejected: true, resetsAt: futureReset },
@@ -5210,6 +5279,186 @@ describe('StartPreparationUseCase', () => {
5210
5279
  },
5211
5280
  });
5212
5281
  });
5282
+
5283
+ it('includes a token whose unified representative-claim status is rejected (seven_day_sonnet) while its Opus weekly window is allowed and routes it to Opus', async () => {
5284
+ const awaitingIssue = createMockIssue({
5285
+ url: 'url1',
5286
+ title: 'Issue 1',
5287
+ labels: ['category:impl'],
5288
+ status: 'Awaiting Workspace',
5289
+ number: 1,
5290
+ itemId: 'item-1',
5291
+ });
5292
+ mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
5293
+ mockIssueRepository.getStoryObjectMap.mockResolvedValue(
5294
+ createMockStoryObjectMap([awaitingIssue]),
5295
+ );
5296
+ mockLocalCommandRunner.runCommand.mockResolvedValue({
5297
+ stdout: '',
5298
+ stderr: '',
5299
+ exitCode: 0,
5300
+ });
5301
+ mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
5302
+ {
5303
+ name: 'token-sonnet-rejected-unified',
5304
+ token: 'token-sonnet-rejected-unified',
5305
+ fiveHourUtilization: 0.53,
5306
+ sevenDayUtilization: 0.88,
5307
+ blocked: false,
5308
+ rejected: true,
5309
+ fiveHourRejected: false,
5310
+ blockedUntilEpoch: 0,
5311
+ modelWeeklyLimits: {
5312
+ seven_day_sonnet: { rejected: true, resetsAt: futureReset },
5313
+ seven_day_opus: { rejected: false, resetsAt: futureReset },
5314
+ },
5315
+ },
5316
+ ]);
5317
+
5318
+ await useCase.run({
5319
+ projectUrl: 'https://github.com/user/repo',
5320
+ defaultAgentName: 'agent1',
5321
+ defaultLlmModelName: 'claude-sonnet-4-6',
5322
+ fallbackLlmModelName: 'claude-opus-4-8',
5323
+ defaultLlmAgentName: null,
5324
+ configFilePath: '/path/to/config.yml',
5325
+ maximumPreparingIssuesCount: null,
5326
+ utilizationPercentageThreshold: 90,
5327
+ allowedIssueAuthors: null,
5328
+ codexHomeCandidates: null,
5329
+ allowIssueCacheMinutes: 0,
5330
+ labelsAsLlmAgentName: null,
5331
+ });
5332
+
5333
+ expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
5334
+ expect(
5335
+ mockLocalCommandRunner.runCommand.mock.calls[0][2]?.env
5336
+ ?.CLAUDE_CODE_OAUTH_TOKEN,
5337
+ ).toBe('token-sonnet-rejected-unified');
5338
+ expect(mockLocalCommandRunner.runCommand.mock.calls[0][1][2]).toBe(
5339
+ 'claude-opus-4-8',
5340
+ );
5341
+ });
5342
+
5343
+ it('excludes a token whose 5-hour window is rejected even when its Opus weekly window is allowed', async () => {
5344
+ const awaitingIssue = createMockIssue({
5345
+ url: 'url1',
5346
+ title: 'Issue 1',
5347
+ labels: ['category:impl'],
5348
+ status: 'Awaiting Workspace',
5349
+ number: 1,
5350
+ itemId: 'item-1',
5351
+ });
5352
+ mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
5353
+ mockIssueRepository.getStoryObjectMap.mockResolvedValue(
5354
+ createMockStoryObjectMap([awaitingIssue]),
5355
+ );
5356
+ mockLocalCommandRunner.runCommand.mockResolvedValue({
5357
+ stdout: '',
5358
+ stderr: '',
5359
+ exitCode: 0,
5360
+ });
5361
+ mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
5362
+ {
5363
+ name: 'token-5h-rejected',
5364
+ token: 'token-5h-rejected',
5365
+ fiveHourUtilization: 0.1,
5366
+ sevenDayUtilization: 0,
5367
+ blocked: false,
5368
+ rejected: true,
5369
+ fiveHourRejected: true,
5370
+ blockedUntilEpoch: 0,
5371
+ modelWeeklyLimits: {
5372
+ seven_day_opus: { rejected: false, resetsAt: futureReset },
5373
+ },
5374
+ },
5375
+ ]);
5376
+ const consoleWarnSpy = jest
5377
+ .spyOn(console, 'warn')
5378
+ .mockImplementation(() => {});
5379
+
5380
+ await useCase.run({
5381
+ projectUrl: 'https://github.com/user/repo',
5382
+ defaultAgentName: 'agent1',
5383
+ defaultLlmModelName: 'claude-sonnet-4-6',
5384
+ fallbackLlmModelName: 'claude-opus-4-8',
5385
+ defaultLlmAgentName: null,
5386
+ configFilePath: '/path/to/config.yml',
5387
+ maximumPreparingIssuesCount: null,
5388
+ utilizationPercentageThreshold: 90,
5389
+ allowedIssueAuthors: null,
5390
+ codexHomeCandidates: null,
5391
+ allowIssueCacheMinutes: 0,
5392
+ labelsAsLlmAgentName: null,
5393
+ });
5394
+
5395
+ expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
5396
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
5397
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
5398
+ expect.stringContaining('Skipping starting preparation'),
5399
+ );
5400
+ consoleWarnSpy.mockRestore();
5401
+ });
5402
+
5403
+ it('excludes a token whose unified status is rejected and whose seven_day_sonnet and seven_day_opus weekly windows are both rejected', async () => {
5404
+ const awaitingIssue = createMockIssue({
5405
+ url: 'url1',
5406
+ title: 'Issue 1',
5407
+ labels: ['category:impl'],
5408
+ status: 'Awaiting Workspace',
5409
+ number: 1,
5410
+ itemId: 'item-1',
5411
+ });
5412
+ mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
5413
+ mockIssueRepository.getStoryObjectMap.mockResolvedValue(
5414
+ createMockStoryObjectMap([awaitingIssue]),
5415
+ );
5416
+ mockLocalCommandRunner.runCommand.mockResolvedValue({
5417
+ stdout: '',
5418
+ stderr: '',
5419
+ exitCode: 0,
5420
+ });
5421
+ mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
5422
+ {
5423
+ name: 'token-both-rejected-unified',
5424
+ token: 'token-both-rejected-unified',
5425
+ fiveHourUtilization: 0.1,
5426
+ sevenDayUtilization: 0,
5427
+ blocked: false,
5428
+ rejected: true,
5429
+ fiveHourRejected: false,
5430
+ blockedUntilEpoch: 0,
5431
+ modelWeeklyLimits: {
5432
+ seven_day_sonnet: { rejected: true, resetsAt: futureReset },
5433
+ seven_day_opus: { rejected: true, resetsAt: futureReset },
5434
+ },
5435
+ },
5436
+ ]);
5437
+ const consoleWarnSpy = jest
5438
+ .spyOn(console, 'warn')
5439
+ .mockImplementation(() => {});
5440
+
5441
+ await useCase.run({
5442
+ projectUrl: 'https://github.com/user/repo',
5443
+ defaultAgentName: 'agent1',
5444
+ defaultLlmModelName: 'claude-sonnet-4-6',
5445
+ fallbackLlmModelName: 'claude-opus-4-8',
5446
+ defaultLlmAgentName: null,
5447
+ configFilePath: '/path/to/config.yml',
5448
+ maximumPreparingIssuesCount: null,
5449
+ utilizationPercentageThreshold: 90,
5450
+ allowedIssueAuthors: null,
5451
+ codexHomeCandidates: null,
5452
+ allowIssueCacheMinutes: 0,
5453
+ labelsAsLlmAgentName: null,
5454
+ });
5455
+
5456
+ expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
5457
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
5458
+ expect.stringContaining('Skipping starting preparation'),
5459
+ );
5460
+ consoleWarnSpy.mockRestore();
5461
+ });
5213
5462
  });
5214
5463
 
5215
5464
  describe('per-token in-flight global concurrency enforcement', () => {
@@ -5241,6 +5490,7 @@ describe('StartPreparationUseCase', () => {
5241
5490
  sevenDayUtilization: 0.9,
5242
5491
  blocked: false,
5243
5492
  rejected: false,
5493
+ fiveHourRejected: false,
5244
5494
  blockedUntilEpoch: 0,
5245
5495
  modelWeeklyLimits: {},
5246
5496
  },
@@ -5295,6 +5545,7 @@ describe('StartPreparationUseCase', () => {
5295
5545
  sevenDayUtilization: 0.9,
5296
5546
  blocked: false,
5297
5547
  rejected: false,
5548
+ fiveHourRejected: false,
5298
5549
  blockedUntilEpoch: 0,
5299
5550
  modelWeeklyLimits: {},
5300
5551
  },
@@ -5349,6 +5600,7 @@ describe('StartPreparationUseCase', () => {
5349
5600
  sevenDayUtilization: 0.9,
5350
5601
  blocked: false,
5351
5602
  rejected: false,
5603
+ fiveHourRejected: false,
5352
5604
  blockedUntilEpoch: 0,
5353
5605
  modelWeeklyLimits: {},
5354
5606
  },
@@ -5359,6 +5611,7 @@ describe('StartPreparationUseCase', () => {
5359
5611
  sevenDayUtilization: 0.1,
5360
5612
  blocked: false,
5361
5613
  rejected: false,
5614
+ fiveHourRejected: false,
5362
5615
  blockedUntilEpoch: 0,
5363
5616
  modelWeeklyLimits: {},
5364
5617
  },
@@ -5448,6 +5701,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5448
5701
  sevenDayUtilization: 0.8,
5449
5702
  blocked: false,
5450
5703
  rejected: false,
5704
+ fiveHourRejected: false,
5451
5705
  blockedUntilEpoch: 0,
5452
5706
  modelWeeklyLimits: {
5453
5707
  seven_day: {
@@ -5463,6 +5717,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5463
5717
  sevenDayUtilization: 0.1,
5464
5718
  blocked: false,
5465
5719
  rejected: false,
5720
+ fiveHourRejected: false,
5466
5721
  blockedUntilEpoch: 0,
5467
5722
  modelWeeklyLimits: {
5468
5723
  seven_day: {
@@ -5478,6 +5733,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5478
5733
  sevenDayUtilization: 0,
5479
5734
  blocked: true,
5480
5735
  rejected: false,
5736
+ fiveHourRejected: false,
5481
5737
  blockedUntilEpoch: 0,
5482
5738
  modelWeeklyLimits: {},
5483
5739
  },
@@ -5500,6 +5756,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5500
5756
  sevenDayUtilization: 0,
5501
5757
  blocked: false,
5502
5758
  rejected: false,
5759
+ fiveHourRejected: false,
5503
5760
  blockedUntilEpoch: 0,
5504
5761
  modelWeeklyLimits: {},
5505
5762
  },
@@ -5520,6 +5777,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5520
5777
  sevenDayUtilization: 0,
5521
5778
  blocked: false,
5522
5779
  rejected: false,
5780
+ fiveHourRejected: false,
5523
5781
  blockedUntilEpoch: 0,
5524
5782
  modelWeeklyLimits: {},
5525
5783
  },
@@ -5541,6 +5799,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5541
5799
  sevenDayUtilization: 0,
5542
5800
  blocked: false,
5543
5801
  rejected: false,
5802
+ fiveHourRejected: false,
5544
5803
  blockedUntilEpoch: 0,
5545
5804
  modelWeeklyLimits: {},
5546
5805
  },
@@ -5564,6 +5823,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5564
5823
  sevenDayUtilization: 0,
5565
5824
  blocked: false,
5566
5825
  rejected: false,
5826
+ fiveHourRejected: false,
5567
5827
  blockedUntilEpoch: nowEpochSeconds + 90,
5568
5828
  modelWeeklyLimits: {},
5569
5829
  },
@@ -5574,6 +5834,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5574
5834
  sevenDayUtilization: 0,
5575
5835
  blocked: false,
5576
5836
  rejected: false,
5837
+ fiveHourRejected: false,
5577
5838
  blockedUntilEpoch: 0,
5578
5839
  modelWeeklyLimits: {},
5579
5840
  },
@@ -5599,6 +5860,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5599
5860
  sevenDayUtilization: 0,
5600
5861
  blocked: false,
5601
5862
  rejected: false,
5863
+ fiveHourRejected: false,
5602
5864
  blockedUntilEpoch: nowEpochSeconds - 1,
5603
5865
  modelWeeklyLimits: {},
5604
5866
  },
@@ -5611,6 +5873,87 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
5611
5873
  expect(result[0].cooldownExcluded).toBe(false);
5612
5874
  expect(result[0].thresholdExcluded).toBe(false);
5613
5875
  });
5876
+
5877
+ it('selects a token whose unified status is rejected but whose requested model weekly window is allowed', () => {
5878
+ const nowEpochSeconds = Math.floor(Date.now() / 1000);
5879
+ const tokenUsages: ClaudeTokenUsage[] = [
5880
+ {
5881
+ name: 'sonnet-rejected-opus-ok',
5882
+ token: 'sk-ant-sonnet-rejected',
5883
+ fiveHourUtilization: 0.53,
5884
+ sevenDayUtilization: 0.88,
5885
+ blocked: false,
5886
+ rejected: true,
5887
+ fiveHourRejected: false,
5888
+ blockedUntilEpoch: 0,
5889
+ modelWeeklyLimits: {
5890
+ seven_day_sonnet: {
5891
+ rejected: true,
5892
+ resetsAt: nowEpochSeconds + 20 * 3600,
5893
+ },
5894
+ seven_day_opus: {
5895
+ rejected: false,
5896
+ resetsAt: nowEpochSeconds + 20 * 3600,
5897
+ },
5898
+ },
5899
+ },
5900
+ ];
5901
+
5902
+ const result = useCase.buildRotationOrder(
5903
+ tokenUsages,
5904
+ 90,
5905
+ 'claude-opus-4-8',
5906
+ );
5907
+
5908
+ expect(result).toHaveLength(1);
5909
+ expect(result[0].name).toBe('sonnet-rejected-opus-ok');
5910
+ expect(result[0].rejected).toBe(false);
5911
+ expect(result[0].thresholdExcluded).toBe(false);
5912
+ expect(result[0].cooldownExcluded).toBe(false);
5913
+ });
5914
+
5915
+ it('excludes a token whose 5-hour window is rejected and marks it rejected in the rotation entry', () => {
5916
+ const nowEpochSeconds = Math.floor(Date.now() / 1000);
5917
+ const tokenUsages: ClaudeTokenUsage[] = [
5918
+ {
5919
+ name: 'five-hour-rejected',
5920
+ token: 'sk-ant-5h-rejected',
5921
+ fiveHourUtilization: 0.1,
5922
+ sevenDayUtilization: 0,
5923
+ blocked: false,
5924
+ rejected: true,
5925
+ fiveHourRejected: true,
5926
+ blockedUntilEpoch: 0,
5927
+ modelWeeklyLimits: {
5928
+ seven_day_opus: {
5929
+ rejected: false,
5930
+ resetsAt: nowEpochSeconds + 20 * 3600,
5931
+ },
5932
+ },
5933
+ },
5934
+ {
5935
+ name: 'available',
5936
+ token: 'sk-ant-available',
5937
+ fiveHourUtilization: 0.2,
5938
+ sevenDayUtilization: 0,
5939
+ blocked: false,
5940
+ rejected: false,
5941
+ fiveHourRejected: false,
5942
+ blockedUntilEpoch: 0,
5943
+ modelWeeklyLimits: {},
5944
+ },
5945
+ ];
5946
+
5947
+ const result = useCase.buildRotationOrder(tokenUsages, 90, null);
5948
+
5949
+ expect(result[0].name).toBe('available');
5950
+ const excluded = result.find(
5951
+ (entry) => entry.name === 'five-hour-rejected',
5952
+ );
5953
+ expect(excluded?.rejected).toBe(true);
5954
+ expect(excluded?.thresholdExcluded).toBe(false);
5955
+ expect(excluded?.cooldownExcluded).toBe(false);
5956
+ });
5614
5957
  });
5615
5958
 
5616
5959
  describe('StartPreparationUseCase.getTokenConcurrentLimit', () => {