@contractkit/plugin-typescript 0.18.0 → 0.19.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.
@@ -23,30 +23,30 @@
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">57.28% </span>
26
+ <span class="strong">58.17% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>114/199</span>
28
+ <span class='fraction'>185/318</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">46.93% </span>
33
+ <span class="strong">47.22% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>46/98</span>
35
+ <span class='fraction'>68/144</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
- <span class="strong">59.25% </span>
40
+ <span class="strong">63.04% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>16/27</span>
42
+ <span class='fraction'>29/46</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
- <span class="strong">61.27% </span>
47
+ <span class="strong">62.78% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>106/173</span>
49
+ <span class='fraction'>167/266</span>
50
50
  </div>
51
51
 
52
52
 
@@ -589,7 +589,266 @@
589
589
  <a name='L524'></a><a href='#L524'>524</a>
590
590
  <a name='L525'></a><a href='#L525'>525</a>
591
591
  <a name='L526'></a><a href='#L526'>526</a>
592
- <a name='L527'></a><a href='#L527'>527</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
592
+ <a name='L527'></a><a href='#L527'>527</a>
593
+ <a name='L528'></a><a href='#L528'>528</a>
594
+ <a name='L529'></a><a href='#L529'>529</a>
595
+ <a name='L530'></a><a href='#L530'>530</a>
596
+ <a name='L531'></a><a href='#L531'>531</a>
597
+ <a name='L532'></a><a href='#L532'>532</a>
598
+ <a name='L533'></a><a href='#L533'>533</a>
599
+ <a name='L534'></a><a href='#L534'>534</a>
600
+ <a name='L535'></a><a href='#L535'>535</a>
601
+ <a name='L536'></a><a href='#L536'>536</a>
602
+ <a name='L537'></a><a href='#L537'>537</a>
603
+ <a name='L538'></a><a href='#L538'>538</a>
604
+ <a name='L539'></a><a href='#L539'>539</a>
605
+ <a name='L540'></a><a href='#L540'>540</a>
606
+ <a name='L541'></a><a href='#L541'>541</a>
607
+ <a name='L542'></a><a href='#L542'>542</a>
608
+ <a name='L543'></a><a href='#L543'>543</a>
609
+ <a name='L544'></a><a href='#L544'>544</a>
610
+ <a name='L545'></a><a href='#L545'>545</a>
611
+ <a name='L546'></a><a href='#L546'>546</a>
612
+ <a name='L547'></a><a href='#L547'>547</a>
613
+ <a name='L548'></a><a href='#L548'>548</a>
614
+ <a name='L549'></a><a href='#L549'>549</a>
615
+ <a name='L550'></a><a href='#L550'>550</a>
616
+ <a name='L551'></a><a href='#L551'>551</a>
617
+ <a name='L552'></a><a href='#L552'>552</a>
618
+ <a name='L553'></a><a href='#L553'>553</a>
619
+ <a name='L554'></a><a href='#L554'>554</a>
620
+ <a name='L555'></a><a href='#L555'>555</a>
621
+ <a name='L556'></a><a href='#L556'>556</a>
622
+ <a name='L557'></a><a href='#L557'>557</a>
623
+ <a name='L558'></a><a href='#L558'>558</a>
624
+ <a name='L559'></a><a href='#L559'>559</a>
625
+ <a name='L560'></a><a href='#L560'>560</a>
626
+ <a name='L561'></a><a href='#L561'>561</a>
627
+ <a name='L562'></a><a href='#L562'>562</a>
628
+ <a name='L563'></a><a href='#L563'>563</a>
629
+ <a name='L564'></a><a href='#L564'>564</a>
630
+ <a name='L565'></a><a href='#L565'>565</a>
631
+ <a name='L566'></a><a href='#L566'>566</a>
632
+ <a name='L567'></a><a href='#L567'>567</a>
633
+ <a name='L568'></a><a href='#L568'>568</a>
634
+ <a name='L569'></a><a href='#L569'>569</a>
635
+ <a name='L570'></a><a href='#L570'>570</a>
636
+ <a name='L571'></a><a href='#L571'>571</a>
637
+ <a name='L572'></a><a href='#L572'>572</a>
638
+ <a name='L573'></a><a href='#L573'>573</a>
639
+ <a name='L574'></a><a href='#L574'>574</a>
640
+ <a name='L575'></a><a href='#L575'>575</a>
641
+ <a name='L576'></a><a href='#L576'>576</a>
642
+ <a name='L577'></a><a href='#L577'>577</a>
643
+ <a name='L578'></a><a href='#L578'>578</a>
644
+ <a name='L579'></a><a href='#L579'>579</a>
645
+ <a name='L580'></a><a href='#L580'>580</a>
646
+ <a name='L581'></a><a href='#L581'>581</a>
647
+ <a name='L582'></a><a href='#L582'>582</a>
648
+ <a name='L583'></a><a href='#L583'>583</a>
649
+ <a name='L584'></a><a href='#L584'>584</a>
650
+ <a name='L585'></a><a href='#L585'>585</a>
651
+ <a name='L586'></a><a href='#L586'>586</a>
652
+ <a name='L587'></a><a href='#L587'>587</a>
653
+ <a name='L588'></a><a href='#L588'>588</a>
654
+ <a name='L589'></a><a href='#L589'>589</a>
655
+ <a name='L590'></a><a href='#L590'>590</a>
656
+ <a name='L591'></a><a href='#L591'>591</a>
657
+ <a name='L592'></a><a href='#L592'>592</a>
658
+ <a name='L593'></a><a href='#L593'>593</a>
659
+ <a name='L594'></a><a href='#L594'>594</a>
660
+ <a name='L595'></a><a href='#L595'>595</a>
661
+ <a name='L596'></a><a href='#L596'>596</a>
662
+ <a name='L597'></a><a href='#L597'>597</a>
663
+ <a name='L598'></a><a href='#L598'>598</a>
664
+ <a name='L599'></a><a href='#L599'>599</a>
665
+ <a name='L600'></a><a href='#L600'>600</a>
666
+ <a name='L601'></a><a href='#L601'>601</a>
667
+ <a name='L602'></a><a href='#L602'>602</a>
668
+ <a name='L603'></a><a href='#L603'>603</a>
669
+ <a name='L604'></a><a href='#L604'>604</a>
670
+ <a name='L605'></a><a href='#L605'>605</a>
671
+ <a name='L606'></a><a href='#L606'>606</a>
672
+ <a name='L607'></a><a href='#L607'>607</a>
673
+ <a name='L608'></a><a href='#L608'>608</a>
674
+ <a name='L609'></a><a href='#L609'>609</a>
675
+ <a name='L610'></a><a href='#L610'>610</a>
676
+ <a name='L611'></a><a href='#L611'>611</a>
677
+ <a name='L612'></a><a href='#L612'>612</a>
678
+ <a name='L613'></a><a href='#L613'>613</a>
679
+ <a name='L614'></a><a href='#L614'>614</a>
680
+ <a name='L615'></a><a href='#L615'>615</a>
681
+ <a name='L616'></a><a href='#L616'>616</a>
682
+ <a name='L617'></a><a href='#L617'>617</a>
683
+ <a name='L618'></a><a href='#L618'>618</a>
684
+ <a name='L619'></a><a href='#L619'>619</a>
685
+ <a name='L620'></a><a href='#L620'>620</a>
686
+ <a name='L621'></a><a href='#L621'>621</a>
687
+ <a name='L622'></a><a href='#L622'>622</a>
688
+ <a name='L623'></a><a href='#L623'>623</a>
689
+ <a name='L624'></a><a href='#L624'>624</a>
690
+ <a name='L625'></a><a href='#L625'>625</a>
691
+ <a name='L626'></a><a href='#L626'>626</a>
692
+ <a name='L627'></a><a href='#L627'>627</a>
693
+ <a name='L628'></a><a href='#L628'>628</a>
694
+ <a name='L629'></a><a href='#L629'>629</a>
695
+ <a name='L630'></a><a href='#L630'>630</a>
696
+ <a name='L631'></a><a href='#L631'>631</a>
697
+ <a name='L632'></a><a href='#L632'>632</a>
698
+ <a name='L633'></a><a href='#L633'>633</a>
699
+ <a name='L634'></a><a href='#L634'>634</a>
700
+ <a name='L635'></a><a href='#L635'>635</a>
701
+ <a name='L636'></a><a href='#L636'>636</a>
702
+ <a name='L637'></a><a href='#L637'>637</a>
703
+ <a name='L638'></a><a href='#L638'>638</a>
704
+ <a name='L639'></a><a href='#L639'>639</a>
705
+ <a name='L640'></a><a href='#L640'>640</a>
706
+ <a name='L641'></a><a href='#L641'>641</a>
707
+ <a name='L642'></a><a href='#L642'>642</a>
708
+ <a name='L643'></a><a href='#L643'>643</a>
709
+ <a name='L644'></a><a href='#L644'>644</a>
710
+ <a name='L645'></a><a href='#L645'>645</a>
711
+ <a name='L646'></a><a href='#L646'>646</a>
712
+ <a name='L647'></a><a href='#L647'>647</a>
713
+ <a name='L648'></a><a href='#L648'>648</a>
714
+ <a name='L649'></a><a href='#L649'>649</a>
715
+ <a name='L650'></a><a href='#L650'>650</a>
716
+ <a name='L651'></a><a href='#L651'>651</a>
717
+ <a name='L652'></a><a href='#L652'>652</a>
718
+ <a name='L653'></a><a href='#L653'>653</a>
719
+ <a name='L654'></a><a href='#L654'>654</a>
720
+ <a name='L655'></a><a href='#L655'>655</a>
721
+ <a name='L656'></a><a href='#L656'>656</a>
722
+ <a name='L657'></a><a href='#L657'>657</a>
723
+ <a name='L658'></a><a href='#L658'>658</a>
724
+ <a name='L659'></a><a href='#L659'>659</a>
725
+ <a name='L660'></a><a href='#L660'>660</a>
726
+ <a name='L661'></a><a href='#L661'>661</a>
727
+ <a name='L662'></a><a href='#L662'>662</a>
728
+ <a name='L663'></a><a href='#L663'>663</a>
729
+ <a name='L664'></a><a href='#L664'>664</a>
730
+ <a name='L665'></a><a href='#L665'>665</a>
731
+ <a name='L666'></a><a href='#L666'>666</a>
732
+ <a name='L667'></a><a href='#L667'>667</a>
733
+ <a name='L668'></a><a href='#L668'>668</a>
734
+ <a name='L669'></a><a href='#L669'>669</a>
735
+ <a name='L670'></a><a href='#L670'>670</a>
736
+ <a name='L671'></a><a href='#L671'>671</a>
737
+ <a name='L672'></a><a href='#L672'>672</a>
738
+ <a name='L673'></a><a href='#L673'>673</a>
739
+ <a name='L674'></a><a href='#L674'>674</a>
740
+ <a name='L675'></a><a href='#L675'>675</a>
741
+ <a name='L676'></a><a href='#L676'>676</a>
742
+ <a name='L677'></a><a href='#L677'>677</a>
743
+ <a name='L678'></a><a href='#L678'>678</a>
744
+ <a name='L679'></a><a href='#L679'>679</a>
745
+ <a name='L680'></a><a href='#L680'>680</a>
746
+ <a name='L681'></a><a href='#L681'>681</a>
747
+ <a name='L682'></a><a href='#L682'>682</a>
748
+ <a name='L683'></a><a href='#L683'>683</a>
749
+ <a name='L684'></a><a href='#L684'>684</a>
750
+ <a name='L685'></a><a href='#L685'>685</a>
751
+ <a name='L686'></a><a href='#L686'>686</a>
752
+ <a name='L687'></a><a href='#L687'>687</a>
753
+ <a name='L688'></a><a href='#L688'>688</a>
754
+ <a name='L689'></a><a href='#L689'>689</a>
755
+ <a name='L690'></a><a href='#L690'>690</a>
756
+ <a name='L691'></a><a href='#L691'>691</a>
757
+ <a name='L692'></a><a href='#L692'>692</a>
758
+ <a name='L693'></a><a href='#L693'>693</a>
759
+ <a name='L694'></a><a href='#L694'>694</a>
760
+ <a name='L695'></a><a href='#L695'>695</a>
761
+ <a name='L696'></a><a href='#L696'>696</a>
762
+ <a name='L697'></a><a href='#L697'>697</a>
763
+ <a name='L698'></a><a href='#L698'>698</a>
764
+ <a name='L699'></a><a href='#L699'>699</a>
765
+ <a name='L700'></a><a href='#L700'>700</a>
766
+ <a name='L701'></a><a href='#L701'>701</a>
767
+ <a name='L702'></a><a href='#L702'>702</a>
768
+ <a name='L703'></a><a href='#L703'>703</a>
769
+ <a name='L704'></a><a href='#L704'>704</a>
770
+ <a name='L705'></a><a href='#L705'>705</a>
771
+ <a name='L706'></a><a href='#L706'>706</a>
772
+ <a name='L707'></a><a href='#L707'>707</a>
773
+ <a name='L708'></a><a href='#L708'>708</a>
774
+ <a name='L709'></a><a href='#L709'>709</a>
775
+ <a name='L710'></a><a href='#L710'>710</a>
776
+ <a name='L711'></a><a href='#L711'>711</a>
777
+ <a name='L712'></a><a href='#L712'>712</a>
778
+ <a name='L713'></a><a href='#L713'>713</a>
779
+ <a name='L714'></a><a href='#L714'>714</a>
780
+ <a name='L715'></a><a href='#L715'>715</a>
781
+ <a name='L716'></a><a href='#L716'>716</a>
782
+ <a name='L717'></a><a href='#L717'>717</a>
783
+ <a name='L718'></a><a href='#L718'>718</a>
784
+ <a name='L719'></a><a href='#L719'>719</a>
785
+ <a name='L720'></a><a href='#L720'>720</a>
786
+ <a name='L721'></a><a href='#L721'>721</a>
787
+ <a name='L722'></a><a href='#L722'>722</a>
788
+ <a name='L723'></a><a href='#L723'>723</a>
789
+ <a name='L724'></a><a href='#L724'>724</a>
790
+ <a name='L725'></a><a href='#L725'>725</a>
791
+ <a name='L726'></a><a href='#L726'>726</a>
792
+ <a name='L727'></a><a href='#L727'>727</a>
793
+ <a name='L728'></a><a href='#L728'>728</a>
794
+ <a name='L729'></a><a href='#L729'>729</a>
795
+ <a name='L730'></a><a href='#L730'>730</a>
796
+ <a name='L731'></a><a href='#L731'>731</a>
797
+ <a name='L732'></a><a href='#L732'>732</a>
798
+ <a name='L733'></a><a href='#L733'>733</a>
799
+ <a name='L734'></a><a href='#L734'>734</a>
800
+ <a name='L735'></a><a href='#L735'>735</a>
801
+ <a name='L736'></a><a href='#L736'>736</a>
802
+ <a name='L737'></a><a href='#L737'>737</a>
803
+ <a name='L738'></a><a href='#L738'>738</a>
804
+ <a name='L739'></a><a href='#L739'>739</a>
805
+ <a name='L740'></a><a href='#L740'>740</a>
806
+ <a name='L741'></a><a href='#L741'>741</a>
807
+ <a name='L742'></a><a href='#L742'>742</a>
808
+ <a name='L743'></a><a href='#L743'>743</a>
809
+ <a name='L744'></a><a href='#L744'>744</a>
810
+ <a name='L745'></a><a href='#L745'>745</a>
811
+ <a name='L746'></a><a href='#L746'>746</a>
812
+ <a name='L747'></a><a href='#L747'>747</a>
813
+ <a name='L748'></a><a href='#L748'>748</a>
814
+ <a name='L749'></a><a href='#L749'>749</a>
815
+ <a name='L750'></a><a href='#L750'>750</a>
816
+ <a name='L751'></a><a href='#L751'>751</a>
817
+ <a name='L752'></a><a href='#L752'>752</a>
818
+ <a name='L753'></a><a href='#L753'>753</a>
819
+ <a name='L754'></a><a href='#L754'>754</a>
820
+ <a name='L755'></a><a href='#L755'>755</a>
821
+ <a name='L756'></a><a href='#L756'>756</a>
822
+ <a name='L757'></a><a href='#L757'>757</a>
823
+ <a name='L758'></a><a href='#L758'>758</a>
824
+ <a name='L759'></a><a href='#L759'>759</a>
825
+ <a name='L760'></a><a href='#L760'>760</a>
826
+ <a name='L761'></a><a href='#L761'>761</a>
827
+ <a name='L762'></a><a href='#L762'>762</a>
828
+ <a name='L763'></a><a href='#L763'>763</a>
829
+ <a name='L764'></a><a href='#L764'>764</a>
830
+ <a name='L765'></a><a href='#L765'>765</a>
831
+ <a name='L766'></a><a href='#L766'>766</a>
832
+ <a name='L767'></a><a href='#L767'>767</a>
833
+ <a name='L768'></a><a href='#L768'>768</a>
834
+ <a name='L769'></a><a href='#L769'>769</a>
835
+ <a name='L770'></a><a href='#L770'>770</a>
836
+ <a name='L771'></a><a href='#L771'>771</a>
837
+ <a name='L772'></a><a href='#L772'>772</a>
838
+ <a name='L773'></a><a href='#L773'>773</a>
839
+ <a name='L774'></a><a href='#L774'>774</a>
840
+ <a name='L775'></a><a href='#L775'>775</a>
841
+ <a name='L776'></a><a href='#L776'>776</a>
842
+ <a name='L777'></a><a href='#L777'>777</a>
843
+ <a name='L778'></a><a href='#L778'>778</a>
844
+ <a name='L779'></a><a href='#L779'>779</a>
845
+ <a name='L780'></a><a href='#L780'>780</a>
846
+ <a name='L781'></a><a href='#L781'>781</a>
847
+ <a name='L782'></a><a href='#L782'>782</a>
848
+ <a name='L783'></a><a href='#L783'>783</a>
849
+ <a name='L784'></a><a href='#L784'>784</a>
850
+ <a name='L785'></a><a href='#L785'>785</a>
851
+ <a name='L786'></a><a href='#L786'>786</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
593
852
  <span class="cline-any cline-neutral">&nbsp;</span>
594
853
  <span class="cline-any cline-neutral">&nbsp;</span>
595
854
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -688,6 +947,35 @@
688
947
  <span class="cline-any cline-neutral">&nbsp;</span>
689
948
  <span class="cline-any cline-neutral">&nbsp;</span>
690
949
  <span class="cline-any cline-neutral">&nbsp;</span>
950
+ <span class="cline-any cline-yes">1x</span>
951
+ <span class="cline-any cline-neutral">&nbsp;</span>
952
+ <span class="cline-any cline-yes">1x</span>
953
+ <span class="cline-any cline-neutral">&nbsp;</span>
954
+ <span class="cline-any cline-neutral">&nbsp;</span>
955
+ <span class="cline-any cline-neutral">&nbsp;</span>
956
+ <span class="cline-any cline-yes">1x</span>
957
+ <span class="cline-any cline-neutral">&nbsp;</span>
958
+ <span class="cline-any cline-neutral">&nbsp;</span>
959
+ <span class="cline-any cline-yes">1x</span>
960
+ <span class="cline-any cline-yes">1x</span>
961
+ <span class="cline-any cline-neutral">&nbsp;</span>
962
+ <span class="cline-any cline-neutral">&nbsp;</span>
963
+ <span class="cline-any cline-neutral">&nbsp;</span>
964
+ <span class="cline-any cline-neutral">&nbsp;</span>
965
+ <span class="cline-any cline-neutral">&nbsp;</span>
966
+ <span class="cline-any cline-neutral">&nbsp;</span>
967
+ <span class="cline-any cline-neutral">&nbsp;</span>
968
+ <span class="cline-any cline-yes">17x</span>
969
+ <span class="cline-any cline-neutral">&nbsp;</span>
970
+ <span class="cline-any cline-neutral">&nbsp;</span>
971
+ <span class="cline-any cline-yes">17x</span>
972
+ <span class="cline-any cline-neutral">&nbsp;</span>
973
+ <span class="cline-any cline-neutral">&nbsp;</span>
974
+ <span class="cline-any cline-neutral">&nbsp;</span>
975
+ <span class="cline-any cline-neutral">&nbsp;</span>
976
+ <span class="cline-any cline-neutral">&nbsp;</span>
977
+ <span class="cline-any cline-neutral">&nbsp;</span>
978
+ <span class="cline-any cline-neutral">&nbsp;</span>
691
979
  <span class="cline-any cline-neutral">&nbsp;</span>
692
980
  <span class="cline-any cline-neutral">&nbsp;</span>
693
981
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -699,13 +987,129 @@
699
987
  <span class="cline-any cline-neutral">&nbsp;</span>
700
988
  <span class="cline-any cline-neutral">&nbsp;</span>
701
989
  <span class="cline-any cline-neutral">&nbsp;</span>
990
+ <span class="cline-any cline-yes">18x</span>
991
+ <span class="cline-any cline-yes">18x</span>
702
992
  <span class="cline-any cline-neutral">&nbsp;</span>
993
+ <span class="cline-any cline-yes">18x</span>
994
+ <span class="cline-any cline-yes">18x</span>
703
995
  <span class="cline-any cline-neutral">&nbsp;</span>
996
+ <span class="cline-any cline-yes">18x</span>
997
+ <span class="cline-any cline-yes">18x</span>
998
+ <span class="cline-any cline-yes">18x</span>
999
+ <span class="cline-any cline-yes">18x</span>
704
1000
  <span class="cline-any cline-neutral">&nbsp;</span>
1001
+ <span class="cline-any cline-yes">18x</span>
705
1002
  <span class="cline-any cline-neutral">&nbsp;</span>
706
1003
  <span class="cline-any cline-neutral">&nbsp;</span>
707
1004
  <span class="cline-any cline-neutral">&nbsp;</span>
708
1005
  <span class="cline-any cline-neutral">&nbsp;</span>
1006
+ <span class="cline-any cline-neutral">&nbsp;</span>
1007
+ <span class="cline-any cline-neutral">&nbsp;</span>
1008
+ <span class="cline-any cline-neutral">&nbsp;</span>
1009
+ <span class="cline-any cline-neutral">&nbsp;</span>
1010
+ <span class="cline-any cline-neutral">&nbsp;</span>
1011
+ <span class="cline-any cline-yes">18x</span>
1012
+ <span class="cline-any cline-neutral">&nbsp;</span>
1013
+ <span class="cline-any cline-yes">18x</span>
1014
+ <span class="cline-any cline-yes">49x</span>
1015
+ <span class="cline-any cline-neutral">&nbsp;</span>
1016
+ <span class="cline-any cline-neutral">&nbsp;</span>
1017
+ <span class="cline-any cline-neutral">&nbsp;</span>
1018
+ <span class="cline-any cline-neutral">&nbsp;</span>
1019
+ <span class="cline-any cline-neutral">&nbsp;</span>
1020
+ <span class="cline-any cline-neutral">&nbsp;</span>
1021
+ <span class="cline-any cline-neutral">&nbsp;</span>
1022
+ <span class="cline-any cline-yes">18x</span>
1023
+ <span class="cline-any cline-yes">18x</span>
1024
+ <span class="cline-any cline-yes">3x</span>
1025
+ <span class="cline-any cline-neutral">&nbsp;</span>
1026
+ <span class="cline-any cline-yes">18x</span>
1027
+ <span class="cline-any cline-neutral">&nbsp;</span>
1028
+ <span class="cline-any cline-neutral">&nbsp;</span>
1029
+ <span class="cline-any cline-neutral">&nbsp;</span>
1030
+ <span class="cline-any cline-neutral">&nbsp;</span>
1031
+ <span class="cline-any cline-yes">3x</span>
1032
+ <span class="cline-any cline-yes">3x</span>
1033
+ <span class="cline-any cline-yes">3x</span>
1034
+ <span class="cline-any cline-yes">3x</span>
1035
+ <span class="cline-any cline-yes">3x</span>
1036
+ <span class="cline-any cline-no">&nbsp;</span>
1037
+ <span class="cline-any cline-neutral">&nbsp;</span>
1038
+ <span class="cline-any cline-neutral">&nbsp;</span>
1039
+ <span class="cline-any cline-yes">3x</span>
1040
+ <span class="cline-any cline-neutral">&nbsp;</span>
1041
+ <span class="cline-any cline-neutral">&nbsp;</span>
1042
+ <span class="cline-any cline-neutral">&nbsp;</span>
1043
+ <span class="cline-any cline-neutral">&nbsp;</span>
1044
+ <span class="cline-any cline-yes">16x</span>
1045
+ <span class="cline-any cline-yes">16x</span>
1046
+ <span class="cline-any cline-yes">16x</span>
1047
+ <span class="cline-any cline-yes">16x</span>
1048
+ <span class="cline-any cline-yes">16x</span>
1049
+ <span class="cline-any cline-yes">16x</span>
1050
+ <span class="cline-any cline-yes">16x</span>
1051
+ <span class="cline-any cline-yes">1x</span>
1052
+ <span class="cline-any cline-neutral">&nbsp;</span>
1053
+ <span class="cline-any cline-yes">16x</span>
1054
+ <span class="cline-any cline-yes">4x</span>
1055
+ <span class="cline-any cline-yes">4x</span>
1056
+ <span class="cline-any cline-no">&nbsp;</span>
1057
+ <span class="cline-any cline-neutral">&nbsp;</span>
1058
+ <span class="cline-any cline-neutral">&nbsp;</span>
1059
+ <span class="cline-any cline-neutral">&nbsp;</span>
1060
+ <span class="cline-any cline-neutral">&nbsp;</span>
1061
+ <span class="cline-any cline-yes">16x</span>
1062
+ <span class="cline-any cline-neutral">&nbsp;</span>
1063
+ <span class="cline-any cline-neutral">&nbsp;</span>
1064
+ <span class="cline-any cline-neutral">&nbsp;</span>
1065
+ <span class="cline-any cline-yes">1x</span>
1066
+ <span class="cline-any cline-yes">1x</span>
1067
+ <span class="cline-any cline-yes">1x</span>
1068
+ <span class="cline-any cline-no">&nbsp;</span>
1069
+ <span class="cline-any cline-no">&nbsp;</span>
1070
+ <span class="cline-any cline-no">&nbsp;</span>
1071
+ <span class="cline-any cline-no">&nbsp;</span>
1072
+ <span class="cline-any cline-neutral">&nbsp;</span>
1073
+ <span class="cline-any cline-yes">1x</span>
1074
+ <span class="cline-any cline-neutral">&nbsp;</span>
1075
+ <span class="cline-any cline-neutral">&nbsp;</span>
1076
+ <span class="cline-any cline-neutral">&nbsp;</span>
1077
+ <span class="cline-any cline-neutral">&nbsp;</span>
1078
+ <span class="cline-any cline-yes">19x</span>
1079
+ <span class="cline-any cline-yes">19x</span>
1080
+ <span class="cline-any cline-yes">2x</span>
1081
+ <span class="cline-any cline-yes">2x</span>
1082
+ <span class="cline-any cline-yes">2x</span>
1083
+ <span class="cline-any cline-no">&nbsp;</span>
1084
+ <span class="cline-any cline-no">&nbsp;</span>
1085
+ <span class="cline-any cline-neutral">&nbsp;</span>
1086
+ <span class="cline-any cline-yes">2x</span>
1087
+ <span class="cline-any cline-no">&nbsp;</span>
1088
+ <span class="cline-any cline-no">&nbsp;</span>
1089
+ <span class="cline-any cline-neutral">&nbsp;</span>
1090
+ <span class="cline-any cline-neutral">&nbsp;</span>
1091
+ <span class="cline-any cline-yes">19x</span>
1092
+ <span class="cline-any cline-neutral">&nbsp;</span>
1093
+ <span class="cline-any cline-neutral">&nbsp;</span>
1094
+ <span class="cline-any cline-neutral">&nbsp;</span>
1095
+ <span class="cline-any cline-neutral">&nbsp;</span>
1096
+ <span class="cline-any cline-yes">38x</span>
1097
+ <span class="cline-any cline-yes">38x</span>
1098
+ <span class="cline-any cline-no">&nbsp;</span>
1099
+ <span class="cline-any cline-neutral">&nbsp;</span>
1100
+ <span class="cline-any cline-yes">38x</span>
1101
+ <span class="cline-any cline-neutral">&nbsp;</span>
1102
+ <span class="cline-any cline-neutral">&nbsp;</span>
1103
+ <span class="cline-any cline-neutral">&nbsp;</span>
1104
+ <span class="cline-any cline-neutral">&nbsp;</span>
1105
+ <span class="cline-any cline-neutral">&nbsp;</span>
1106
+ <span class="cline-any cline-neutral">&nbsp;</span>
1107
+ <span class="cline-any cline-neutral">&nbsp;</span>
1108
+ <span class="cline-any cline-neutral">&nbsp;</span>
1109
+ <span class="cline-any cline-neutral">&nbsp;</span>
1110
+ <span class="cline-any cline-neutral">&nbsp;</span>
1111
+ <span class="cline-any cline-yes">14x</span>
1112
+ <span class="cline-any cline-yes">14x</span>
709
1113
  <span class="cline-any cline-yes">14x</span>
710
1114
  <span class="cline-any cline-yes">14x</span>
711
1115
  <span class="cline-any cline-yes">14x</span>
@@ -716,12 +1120,8 @@
716
1120
  <span class="cline-any cline-neutral">&nbsp;</span>
717
1121
  <span class="cline-any cline-neutral">&nbsp;</span>
718
1122
  <span class="cline-any cline-yes">14x</span>
719
- <span class="cline-any cline-neutral">&nbsp;</span>
720
1123
  <span class="cline-any cline-yes">14x</span>
721
- <span class="cline-any cline-yes">3x</span>
722
- <span class="cline-any cline-neutral">&nbsp;</span>
723
- <span class="cline-any cline-neutral">&nbsp;</span>
724
- <span class="cline-any cline-neutral">&nbsp;</span>
1124
+ <span class="cline-any cline-yes">14x</span>
725
1125
  <span class="cline-any cline-yes">3x</span>
726
1126
  <span class="cline-any cline-yes">3x</span>
727
1127
  <span class="cline-any cline-yes">3x</span>
@@ -729,26 +1129,45 @@
729
1129
  <span class="cline-any cline-yes">3x</span>
730
1130
  <span class="cline-any cline-yes">3x</span>
731
1131
  <span class="cline-any cline-yes">3x</span>
732
- <span class="cline-any cline-no">&nbsp;</span>
733
1132
  <span class="cline-any cline-neutral">&nbsp;</span>
1133
+ <span class="cline-any cline-neutral">&nbsp;</span>
1134
+ <span class="cline-any cline-neutral">&nbsp;</span>
1135
+ <span class="cline-any cline-neutral">&nbsp;</span>
1136
+ <span class="cline-any cline-neutral">&nbsp;</span>
1137
+ <span class="cline-any cline-yes">14x</span>
1138
+ <span class="cline-any cline-yes">3x</span>
1139
+ <span class="cline-any cline-yes">3x</span>
734
1140
  <span class="cline-any cline-yes">3x</span>
735
- <span class="cline-any cline-no">&nbsp;</span>
1141
+ <span class="cline-any cline-neutral">&nbsp;</span>
1142
+ <span class="cline-any cline-neutral">&nbsp;</span>
1143
+ <span class="cline-any cline-neutral">&nbsp;</span>
1144
+ <span class="cline-any cline-neutral">&nbsp;</span>
736
1145
  <span class="cline-any cline-neutral">&nbsp;</span>
737
1146
  <span class="cline-any cline-neutral">&nbsp;</span>
738
1147
  <span class="cline-any cline-neutral">&nbsp;</span>
739
1148
  <span class="cline-any cline-neutral">&nbsp;</span>
740
1149
  <span class="cline-any cline-neutral">&nbsp;</span>
741
1150
  <span class="cline-any cline-yes">3x</span>
1151
+ <span class="cline-any cline-neutral">&nbsp;</span>
1152
+ <span class="cline-any cline-neutral">&nbsp;</span>
1153
+ <span class="cline-any cline-neutral">&nbsp;</span>
742
1154
  <span class="cline-any cline-yes">3x</span>
1155
+ <span class="cline-any cline-neutral">&nbsp;</span>
1156
+ <span class="cline-any cline-neutral">&nbsp;</span>
1157
+ <span class="cline-any cline-neutral">&nbsp;</span>
1158
+ <span class="cline-any cline-neutral">&nbsp;</span>
1159
+ <span class="cline-any cline-neutral">&nbsp;</span>
743
1160
  <span class="cline-any cline-yes">3x</span>
744
1161
  <span class="cline-any cline-yes">3x</span>
745
1162
  <span class="cline-any cline-neutral">&nbsp;</span>
746
1163
  <span class="cline-any cline-neutral">&nbsp;</span>
747
1164
  <span class="cline-any cline-neutral">&nbsp;</span>
748
1165
  <span class="cline-any cline-neutral">&nbsp;</span>
1166
+ <span class="cline-any cline-neutral">&nbsp;</span>
749
1167
  <span class="cline-any cline-yes">14x</span>
750
1168
  <span class="cline-any cline-yes">13x</span>
751
1169
  <span class="cline-any cline-yes">13x</span>
1170
+ <span class="cline-any cline-yes">13x</span>
752
1171
  <span class="cline-any cline-neutral">&nbsp;</span>
753
1172
  <span class="cline-any cline-neutral">&nbsp;</span>
754
1173
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -756,6 +1175,14 @@
756
1175
  <span class="cline-any cline-neutral">&nbsp;</span>
757
1176
  <span class="cline-any cline-neutral">&nbsp;</span>
758
1177
  <span class="cline-any cline-neutral">&nbsp;</span>
1178
+ <span class="cline-any cline-neutral">&nbsp;</span>
1179
+ <span class="cline-any cline-neutral">&nbsp;</span>
1180
+ <span class="cline-any cline-neutral">&nbsp;</span>
1181
+ <span class="cline-any cline-neutral">&nbsp;</span>
1182
+ <span class="cline-any cline-neutral">&nbsp;</span>
1183
+ <span class="cline-any cline-yes">13x</span>
1184
+ <span class="cline-any cline-neutral">&nbsp;</span>
1185
+ <span class="cline-any cline-neutral">&nbsp;</span>
759
1186
  <span class="cline-any cline-yes">13x</span>
760
1187
  <span class="cline-any cline-neutral">&nbsp;</span>
761
1188
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -768,6 +1195,20 @@
768
1195
  <span class="cline-any cline-neutral">&nbsp;</span>
769
1196
  <span class="cline-any cline-neutral">&nbsp;</span>
770
1197
  <span class="cline-any cline-neutral">&nbsp;</span>
1198
+ <span class="cline-any cline-neutral">&nbsp;</span>
1199
+ <span class="cline-any cline-neutral">&nbsp;</span>
1200
+ <span class="cline-any cline-neutral">&nbsp;</span>
1201
+ <span class="cline-any cline-neutral">&nbsp;</span>
1202
+ <span class="cline-any cline-neutral">&nbsp;</span>
1203
+ <span class="cline-any cline-neutral">&nbsp;</span>
1204
+ <span class="cline-any cline-neutral">&nbsp;</span>
1205
+ <span class="cline-any cline-neutral">&nbsp;</span>
1206
+ <span class="cline-any cline-neutral">&nbsp;</span>
1207
+ <span class="cline-any cline-neutral">&nbsp;</span>
1208
+ <span class="cline-any cline-neutral">&nbsp;</span>
1209
+ <span class="cline-any cline-neutral">&nbsp;</span>
1210
+ <span class="cline-any cline-neutral">&nbsp;</span>
1211
+ <span class="cline-any cline-neutral">&nbsp;</span>
771
1212
  <span class="cline-any cline-yes">4x</span>
772
1213
  <span class="cline-any cline-yes">4x</span>
773
1214
  <span class="cline-any cline-yes">4x</span>
@@ -775,9 +1216,11 @@
775
1216
  <span class="cline-any cline-neutral">&nbsp;</span>
776
1217
  <span class="cline-any cline-neutral">&nbsp;</span>
777
1218
  <span class="cline-any cline-yes">4x</span>
1219
+ <span class="cline-any cline-yes">4x</span>
778
1220
  <span class="cline-any cline-neutral">&nbsp;</span>
779
1221
  <span class="cline-any cline-yes">4x</span>
780
1222
  <span class="cline-any cline-yes">4x</span>
1223
+ <span class="cline-any cline-yes">4x</span>
781
1224
  <span class="cline-any cline-yes">5x</span>
782
1225
  <span class="cline-any cline-yes">4x</span>
783
1226
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -787,9 +1230,9 @@
787
1230
  <span class="cline-any cline-neutral">&nbsp;</span>
788
1231
  <span class="cline-any cline-neutral">&nbsp;</span>
789
1232
  <span class="cline-any cline-yes">4x</span>
1233
+ <span class="cline-any cline-yes">4x</span>
790
1234
  <span class="cline-any cline-no">&nbsp;</span>
791
1235
  <span class="cline-any cline-no">&nbsp;</span>
792
- <span class="cline-any cline-neutral">&nbsp;</span>
793
1236
  <span class="cline-any cline-no">&nbsp;</span>
794
1237
  <span class="cline-any cline-no">&nbsp;</span>
795
1238
  <span class="cline-any cline-no">&nbsp;</span>
@@ -799,13 +1242,30 @@
799
1242
  <span class="cline-any cline-no">&nbsp;</span>
800
1243
  <span class="cline-any cline-no">&nbsp;</span>
801
1244
  <span class="cline-any cline-no">&nbsp;</span>
1245
+ <span class="cline-any cline-neutral">&nbsp;</span>
1246
+ <span class="cline-any cline-neutral">&nbsp;</span>
1247
+ <span class="cline-any cline-neutral">&nbsp;</span>
1248
+ <span class="cline-any cline-neutral">&nbsp;</span>
1249
+ <span class="cline-any cline-neutral">&nbsp;</span>
1250
+ <span class="cline-any cline-yes">4x</span>
1251
+ <span class="cline-any cline-no">&nbsp;</span>
802
1252
  <span class="cline-any cline-no">&nbsp;</span>
803
1253
  <span class="cline-any cline-no">&nbsp;</span>
804
1254
  <span class="cline-any cline-neutral">&nbsp;</span>
805
1255
  <span class="cline-any cline-neutral">&nbsp;</span>
806
1256
  <span class="cline-any cline-neutral">&nbsp;</span>
1257
+ <span class="cline-any cline-neutral">&nbsp;</span>
1258
+ <span class="cline-any cline-neutral">&nbsp;</span>
1259
+ <span class="cline-any cline-neutral">&nbsp;</span>
1260
+ <span class="cline-any cline-neutral">&nbsp;</span>
1261
+ <span class="cline-any cline-neutral">&nbsp;</span>
1262
+ <span class="cline-any cline-neutral">&nbsp;</span>
1263
+ <span class="cline-any cline-neutral">&nbsp;</span>
807
1264
  <span class="cline-any cline-no">&nbsp;</span>
808
1265
  <span class="cline-any cline-neutral">&nbsp;</span>
1266
+ <span class="cline-any cline-neutral">&nbsp;</span>
1267
+ <span class="cline-any cline-neutral">&nbsp;</span>
1268
+ <span class="cline-any cline-neutral">&nbsp;</span>
809
1269
  <span class="cline-any cline-no">&nbsp;</span>
810
1270
  <span class="cline-any cline-no">&nbsp;</span>
811
1271
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -834,9 +1294,6 @@
834
1294
  <span class="cline-any cline-neutral">&nbsp;</span>
835
1295
  <span class="cline-any cline-neutral">&nbsp;</span>
836
1296
  <span class="cline-any cline-neutral">&nbsp;</span>
837
- <span class="cline-any cline-neutral">&nbsp;</span>
838
- <span class="cline-any cline-neutral">&nbsp;</span>
839
- <span class="cline-any cline-neutral">&nbsp;</span>
840
1297
  <span class="cline-any cline-yes">4x</span>
841
1298
  <span class="cline-any cline-yes">4x</span>
842
1299
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -844,7 +1301,6 @@
844
1301
  <span class="cline-any cline-yes">4x</span>
845
1302
  <span class="cline-any cline-yes">5x</span>
846
1303
  <span class="cline-any cline-yes">5x</span>
847
- <span class="cline-any cline-neutral">&nbsp;</span>
848
1304
  <span class="cline-any cline-yes">5x</span>
849
1305
  <span class="cline-any cline-yes">5x</span>
850
1306
  <span class="cline-any cline-yes">2x</span>
@@ -861,9 +1317,33 @@
861
1317
  <span class="cline-any cline-neutral">&nbsp;</span>
862
1318
  <span class="cline-any cline-neutral">&nbsp;</span>
863
1319
  <span class="cline-any cline-yes">4x</span>
1320
+ <span class="cline-any cline-yes">3x</span>
864
1321
  <span class="cline-any cline-yes">2x</span>
865
1322
  <span class="cline-any cline-yes">2x</span>
866
1323
  <span class="cline-any cline-yes">2x</span>
1324
+ <span class="cline-any cline-yes">2x</span>
1325
+ <span class="cline-any cline-neutral">&nbsp;</span>
1326
+ <span class="cline-any cline-neutral">&nbsp;</span>
1327
+ <span class="cline-any cline-neutral">&nbsp;</span>
1328
+ <span class="cline-any cline-neutral">&nbsp;</span>
1329
+ <span class="cline-any cline-neutral">&nbsp;</span>
1330
+ <span class="cline-any cline-neutral">&nbsp;</span>
1331
+ <span class="cline-any cline-neutral">&nbsp;</span>
1332
+ <span class="cline-any cline-neutral">&nbsp;</span>
1333
+ <span class="cline-any cline-neutral">&nbsp;</span>
1334
+ <span class="cline-any cline-neutral">&nbsp;</span>
1335
+ <span class="cline-any cline-neutral">&nbsp;</span>
1336
+ <span class="cline-any cline-neutral">&nbsp;</span>
1337
+ <span class="cline-any cline-yes">2x</span>
1338
+ <span class="cline-any cline-neutral">&nbsp;</span>
1339
+ <span class="cline-any cline-neutral">&nbsp;</span>
1340
+ <span class="cline-any cline-yes">2x</span>
1341
+ <span class="cline-any cline-neutral">&nbsp;</span>
1342
+ <span class="cline-any cline-neutral">&nbsp;</span>
1343
+ <span class="cline-any cline-neutral">&nbsp;</span>
1344
+ <span class="cline-any cline-neutral">&nbsp;</span>
1345
+ <span class="cline-any cline-neutral">&nbsp;</span>
1346
+ <span class="cline-any cline-neutral">&nbsp;</span>
867
1347
  <span class="cline-any cline-neutral">&nbsp;</span>
868
1348
  <span class="cline-any cline-neutral">&nbsp;</span>
869
1349
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -881,6 +1361,7 @@
881
1361
  <span class="cline-any cline-yes">1x</span>
882
1362
  <span class="cline-any cline-yes">1x</span>
883
1363
  <span class="cline-any cline-yes">1x</span>
1364
+ <span class="cline-any cline-yes">1x</span>
884
1365
  <span class="cline-any cline-neutral">&nbsp;</span>
885
1366
  <span class="cline-any cline-neutral">&nbsp;</span>
886
1367
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -892,13 +1373,33 @@
892
1373
  <span class="cline-any cline-neutral">&nbsp;</span>
893
1374
  <span class="cline-any cline-neutral">&nbsp;</span>
894
1375
  <span class="cline-any cline-neutral">&nbsp;</span>
1376
+ <span class="cline-any cline-yes">1x</span>
1377
+ <span class="cline-any cline-neutral">&nbsp;</span>
1378
+ <span class="cline-any cline-neutral">&nbsp;</span>
1379
+ <span class="cline-any cline-yes">1x</span>
1380
+ <span class="cline-any cline-neutral">&nbsp;</span>
1381
+ <span class="cline-any cline-neutral">&nbsp;</span>
1382
+ <span class="cline-any cline-neutral">&nbsp;</span>
1383
+ <span class="cline-any cline-neutral">&nbsp;</span>
1384
+ <span class="cline-any cline-neutral">&nbsp;</span>
1385
+ <span class="cline-any cline-neutral">&nbsp;</span>
1386
+ <span class="cline-any cline-neutral">&nbsp;</span>
1387
+ <span class="cline-any cline-neutral">&nbsp;</span>
1388
+ <span class="cline-any cline-neutral">&nbsp;</span>
895
1389
  <span class="cline-any cline-neutral">&nbsp;</span>
896
1390
  <span class="cline-any cline-neutral">&nbsp;</span>
897
1391
  <span class="cline-any cline-neutral">&nbsp;</span>
898
1392
  <span class="cline-any cline-neutral">&nbsp;</span>
899
- <span class="cline-any cline-yes">4x</span>
900
1393
  <span class="cline-any cline-neutral">&nbsp;</span>
901
1394
  <span class="cline-any cline-neutral">&nbsp;</span>
1395
+ <span class="cline-any cline-neutral">&nbsp;</span>
1396
+ <span class="cline-any cline-neutral">&nbsp;</span>
1397
+ <span class="cline-any cline-neutral">&nbsp;</span>
1398
+ <span class="cline-any cline-neutral">&nbsp;</span>
1399
+ <span class="cline-any cline-neutral">&nbsp;</span>
1400
+ <span class="cline-any cline-neutral">&nbsp;</span>
1401
+ <span class="cline-any cline-yes">4x</span>
1402
+ <span class="cline-any cline-neutral">&nbsp;</span>
902
1403
  <span class="cline-any cline-yes">4x</span>
903
1404
  <span class="cline-any cline-yes">4x</span>
904
1405
  <span class="cline-any cline-yes">4x</span>
@@ -960,12 +1461,6 @@
960
1461
  <span class="cline-any cline-neutral">&nbsp;</span>
961
1462
  <span class="cline-any cline-neutral">&nbsp;</span>
962
1463
  <span class="cline-any cline-neutral">&nbsp;</span>
963
- <span class="cline-any cline-neutral">&nbsp;</span>
964
- <span class="cline-any cline-neutral">&nbsp;</span>
965
- <span class="cline-any cline-neutral">&nbsp;</span>
966
- <span class="cline-any cline-neutral">&nbsp;</span>
967
- <span class="cline-any cline-neutral">&nbsp;</span>
968
- <span class="cline-any cline-neutral">&nbsp;</span>
969
1464
  <span class="cline-any cline-yes">4x</span>
970
1465
  <span class="cline-any cline-yes">4x</span>
971
1466
  <span class="cline-any cline-yes">4x</span>
@@ -973,8 +1468,6 @@
973
1468
  <span class="cline-any cline-yes">4x</span>
974
1469
  <span class="cline-any cline-yes">4x</span>
975
1470
  <span class="cline-any cline-yes">4x</span>
976
- <span class="cline-any cline-neutral">&nbsp;</span>
977
- <span class="cline-any cline-yes">4x</span>
978
1471
  <span class="cline-any cline-yes">3x</span>
979
1472
  <span class="cline-any cline-yes">3x</span>
980
1473
  <span class="cline-any cline-yes">3x</span>
@@ -995,12 +1488,16 @@
995
1488
  <span class="cline-any cline-neutral">&nbsp;</span>
996
1489
  <span class="cline-any cline-neutral">&nbsp;</span>
997
1490
  <span class="cline-any cline-neutral">&nbsp;</span>
1491
+ <span class="cline-any cline-neutral">&nbsp;</span>
1492
+ <span class="cline-any cline-neutral">&nbsp;</span>
1493
+ <span class="cline-any cline-neutral">&nbsp;</span>
1494
+ <span class="cline-any cline-no">&nbsp;</span>
1495
+ <span class="cline-any cline-no">&nbsp;</span>
998
1496
  <span class="cline-any cline-no">&nbsp;</span>
999
1497
  <span class="cline-any cline-no">&nbsp;</span>
1000
1498
  <span class="cline-any cline-no">&nbsp;</span>
1001
1499
  <span class="cline-any cline-no">&nbsp;</span>
1002
1500
  <span class="cline-any cline-no">&nbsp;</span>
1003
- <span class="cline-any cline-neutral">&nbsp;</span>
1004
1501
  <span class="cline-any cline-neutral">&nbsp;</span>
1005
1502
  <span class="cline-any cline-no">&nbsp;</span>
1006
1503
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1016,10 +1513,19 @@
1016
1513
  <span class="cline-any cline-neutral">&nbsp;</span>
1017
1514
  <span class="cline-any cline-no">&nbsp;</span>
1018
1515
  <span class="cline-any cline-no">&nbsp;</span>
1516
+ <span class="cline-any cline-no">&nbsp;</span>
1517
+ <span class="cline-any cline-no">&nbsp;</span>
1518
+ <span class="cline-any cline-neutral">&nbsp;</span>
1519
+ <span class="cline-any cline-neutral">&nbsp;</span>
1520
+ <span class="cline-any cline-neutral">&nbsp;</span>
1521
+ <span class="cline-any cline-neutral">&nbsp;</span>
1522
+ <span class="cline-any cline-neutral">&nbsp;</span>
1019
1523
  <span class="cline-any cline-neutral">&nbsp;</span>
1020
1524
  <span class="cline-any cline-neutral">&nbsp;</span>
1021
1525
  <span class="cline-any cline-neutral">&nbsp;</span>
1022
1526
  <span class="cline-any cline-neutral">&nbsp;</span>
1527
+ <span class="cline-any cline-no">&nbsp;</span>
1528
+ <span class="cline-any cline-neutral">&nbsp;</span>
1023
1529
  <span class="cline-any cline-neutral">&nbsp;</span>
1024
1530
  <span class="cline-any cline-no">&nbsp;</span>
1025
1531
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -1033,12 +1539,19 @@
1033
1539
  <span class="cline-any cline-neutral">&nbsp;</span>
1034
1540
  <span class="cline-any cline-neutral">&nbsp;</span>
1035
1541
  <span class="cline-any cline-neutral">&nbsp;</span>
1542
+ <span class="cline-any cline-neutral">&nbsp;</span>
1543
+ <span class="cline-any cline-neutral">&nbsp;</span>
1544
+ <span class="cline-any cline-neutral">&nbsp;</span>
1545
+ <span class="cline-any cline-neutral">&nbsp;</span>
1546
+ <span class="cline-any cline-neutral">&nbsp;</span>
1547
+ <span class="cline-any cline-neutral">&nbsp;</span>
1548
+ <span class="cline-any cline-no">&nbsp;</span>
1549
+ <span class="cline-any cline-no">&nbsp;</span>
1036
1550
  <span class="cline-any cline-no">&nbsp;</span>
1037
1551
  <span class="cline-any cline-no">&nbsp;</span>
1038
1552
  <span class="cline-any cline-no">&nbsp;</span>
1039
1553
  <span class="cline-any cline-no">&nbsp;</span>
1040
1554
  <span class="cline-any cline-no">&nbsp;</span>
1041
- <span class="cline-any cline-neutral">&nbsp;</span>
1042
1555
  <span class="cline-any cline-neutral">&nbsp;</span>
1043
1556
  <span class="cline-any cline-no">&nbsp;</span>
1044
1557
  <span class="cline-any cline-no">&nbsp;</span>
@@ -1054,71 +1567,94 @@
1054
1567
  <span class="cline-any cline-neutral">&nbsp;</span>
1055
1568
  <span class="cline-any cline-no">&nbsp;</span>
1056
1569
  <span class="cline-any cline-no">&nbsp;</span>
1570
+ <span class="cline-any cline-no">&nbsp;</span>
1571
+ <span class="cline-any cline-no">&nbsp;</span>
1057
1572
  <span class="cline-any cline-neutral">&nbsp;</span>
1058
1573
  <span class="cline-any cline-neutral">&nbsp;</span>
1059
1574
  <span class="cline-any cline-neutral">&nbsp;</span>
1060
1575
  <span class="cline-any cline-neutral">&nbsp;</span>
1061
1576
  <span class="cline-any cline-neutral">&nbsp;</span>
1062
- <span class="cline-any cline-no">&nbsp;</span>
1063
1577
  <span class="cline-any cline-neutral">&nbsp;</span>
1064
1578
  <span class="cline-any cline-neutral">&nbsp;</span>
1065
1579
  <span class="cline-any cline-neutral">&nbsp;</span>
1066
1580
  <span class="cline-any cline-neutral">&nbsp;</span>
1581
+ <span class="cline-any cline-no">&nbsp;</span>
1067
1582
  <span class="cline-any cline-neutral">&nbsp;</span>
1068
- <span class="cline-any cline-yes">1x</span>
1069
1583
  <span class="cline-any cline-neutral">&nbsp;</span>
1584
+ <span class="cline-any cline-no">&nbsp;</span>
1070
1585
  <span class="cline-any cline-neutral">&nbsp;</span>
1071
1586
  <span class="cline-any cline-neutral">&nbsp;</span>
1072
- <span class="cline-any cline-yes">1x</span>
1073
1587
  <span class="cline-any cline-neutral">&nbsp;</span>
1074
- <span class="cline-any cline-yes">1x</span>
1075
- <span class="cline-any cline-yes">1x</span>
1076
1588
  <span class="cline-any cline-neutral">&nbsp;</span>
1077
- <span class="cline-any cline-yes">1x</span>
1078
- <span class="cline-any cline-no">&nbsp;</span>
1079
1589
  <span class="cline-any cline-neutral">&nbsp;</span>
1080
- <span class="cline-any cline-yes">1x</span>
1081
- <span class="cline-any cline-no">&nbsp;</span>
1082
1590
  <span class="cline-any cline-neutral">&nbsp;</span>
1083
- <span class="cline-any cline-yes">1x</span>
1084
- <span class="cline-any cline-no">&nbsp;</span>
1085
1591
  <span class="cline-any cline-neutral">&nbsp;</span>
1086
1592
  <span class="cline-any cline-neutral">&nbsp;</span>
1087
1593
  <span class="cline-any cline-neutral">&nbsp;</span>
1088
1594
  <span class="cline-any cline-neutral">&nbsp;</span>
1089
1595
  <span class="cline-any cline-neutral">&nbsp;</span>
1090
1596
  <span class="cline-any cline-neutral">&nbsp;</span>
1597
+ <span class="cline-any cline-yes">18x</span>
1598
+ <span class="cline-any cline-no">&nbsp;</span>
1599
+ <span class="cline-any cline-no">&nbsp;</span>
1091
1600
  <span class="cline-any cline-neutral">&nbsp;</span>
1601
+ <span class="cline-any cline-no">&nbsp;</span>
1092
1602
  <span class="cline-any cline-neutral">&nbsp;</span>
1093
1603
  <span class="cline-any cline-neutral">&nbsp;</span>
1094
1604
  <span class="cline-any cline-neutral">&nbsp;</span>
1095
1605
  <span class="cline-any cline-neutral">&nbsp;</span>
1606
+ <span class="cline-any cline-yes">18x</span>
1607
+ <span class="cline-any cline-no">&nbsp;</span>
1608
+ <span class="cline-any cline-no">&nbsp;</span>
1609
+ <span class="cline-any cline-no">&nbsp;</span>
1610
+ <span class="cline-any cline-no">&nbsp;</span>
1611
+ <span class="cline-any cline-no">&nbsp;</span>
1096
1612
  <span class="cline-any cline-neutral">&nbsp;</span>
1097
1613
  <span class="cline-any cline-neutral">&nbsp;</span>
1098
1614
  <span class="cline-any cline-neutral">&nbsp;</span>
1099
- <span class="cline-any cline-yes">17x</span>
1615
+ <span class="cline-any cline-no">&nbsp;</span>
1616
+ <span class="cline-any cline-no">&nbsp;</span>
1617
+ <span class="cline-any cline-no">&nbsp;</span>
1618
+ <span class="cline-any cline-no">&nbsp;</span>
1619
+ <span class="cline-any cline-no">&nbsp;</span>
1620
+ <span class="cline-any cline-no">&nbsp;</span>
1621
+ <span class="cline-any cline-no">&nbsp;</span>
1100
1622
  <span class="cline-any cline-neutral">&nbsp;</span>
1623
+ <span class="cline-any cline-no">&nbsp;</span>
1101
1624
  <span class="cline-any cline-neutral">&nbsp;</span>
1102
1625
  <span class="cline-any cline-neutral">&nbsp;</span>
1103
- <span class="cline-any cline-yes">17x</span>
1104
- <span class="cline-any cline-yes">13x</span>
1626
+ <span class="cline-any cline-no">&nbsp;</span>
1627
+ <span class="cline-any cline-neutral">&nbsp;</span>
1105
1628
  <span class="cline-any cline-neutral">&nbsp;</span>
1106
- <span class="cline-any cline-yes">17x</span>
1107
- <span class="cline-any cline-yes">4x</span>
1108
1629
  <span class="cline-any cline-neutral">&nbsp;</span>
1109
- <span class="cline-any cline-yes">17x</span>
1110
- <span class="cline-any cline-no">&nbsp;</span>
1111
1630
  <span class="cline-any cline-neutral">&nbsp;</span>
1112
- <span class="cline-any cline-yes">17x</span>
1113
- <span class="cline-any cline-no">&nbsp;</span>
1114
1631
  <span class="cline-any cline-neutral">&nbsp;</span>
1115
1632
  <span class="cline-any cline-neutral">&nbsp;</span>
1116
1633
  <span class="cline-any cline-neutral">&nbsp;</span>
1634
+ <span class="cline-any cline-yes">18x</span>
1117
1635
  <span class="cline-any cline-neutral">&nbsp;</span>
1118
1636
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { resolve, join, relative, dirname, basename } from 'node:path';
1637
+ import { existsSync, readFileSync, rmSync, readdirSync, rmdirSync } from 'node:fs';
1119
1638
  import { generateContract } from './codegen-contract.js';
1120
1639
  import { generateOp } from './codegen-operation.js';
1121
- import type { ContractKitPlugin } from '@contractkit/core';
1640
+ import type {
1641
+ ContractKitPlugin,
1642
+ PluginContext,
1643
+ ContractRootNode,
1644
+ OpRootNode,
1645
+ ModelNode,
1646
+ IncrementalManifest,
1647
+ IncrementalUnit,
1648
+ IncrementalOutputFile,
1649
+ } from '@contractkit/core';
1650
+ import {
1651
+ runIncrementalCodegen,
1652
+ parseIncrementalManifest,
1653
+ emptyIncrementalManifest,
1654
+ hashFingerprint,
1655
+ collectTransitiveModelRefs,
1656
+ collectTypeRefs,
1657
+ } from '@contractkit/core';
1122
1658
  import {
1123
1659
  generateSdk,
1124
1660
  generateSdkOptions,
@@ -1150,149 +1686,313 @@ import {
1150
1686
  export interface ServerConfig {
1151
1687
  /** Directory (relative to rootDir) where server files are written. Default: rootDir. */
1152
1688
  baseDir?: string;
1153
- /**
1154
- * When true, `output.types` emits Zod schema files (via `generateContract`).
1155
- * When false/omitted, `output.types` emits plain TypeScript interfaces.
1156
- */
1689
+ /** When true, `output.types` emits Zod schema files (via `generateContract`). When false/omitted, emits plain TypeScript. */
1157
1690
  zod?: boolean;
1158
1691
  output?: {
1159
- /** Path template for Koa router files. Supports {filename}, {dir}, {area}. Default: `{filename}.router.ts`. */
1692
+ /** Path template for Koa router files. Supports {filename}, {dir}, {area}. */
1160
1693
  routes?: string;
1161
- /**
1162
- * Path template for type/schema files. Supports {filename}, {dir}, {area}.
1163
- * Generates Zod schemas when `zod: true`, otherwise plain TypeScript interfaces.
1164
- */
1694
+ /** Path template for type/schema files. Supports {filename}, {dir}, {area}. */
1165
1695
  types?: string;
1166
1696
  };
1167
- /** Import path template for service implementations. Supports {module}. */
1697
+ /** Import path template for service implementations. */
1168
1698
  servicePathTemplate?: string;
1169
- /**
1170
- * Whether to emit handlers for operations marked `internal`. Defaults to `true` —
1171
- * the server still needs routes for internal endpoints. Set to `false` to omit them.
1172
- */
1699
+ /** Whether to emit handlers for `internal` operations. Default true. */
1173
1700
  includeInternal?: boolean;
1174
1701
  }
1175
1702
  &nbsp;
1176
1703
  export interface SdkConfig {
1177
- /** Directory (relative to rootDir) where SDK files are written. Default: rootDir. */
1178
1704
  baseDir?: string;
1179
- /** Name used for the aggregator SDK class (e.g. "homegrown" → `HomegrownSdk`). */
1180
1705
  name?: string;
1181
- /**
1182
- * When true, `output.types` emits Zod schema files (via `generateContract`).
1183
- * When false/omitted, `output.types` emits plain TypeScript interfaces.
1184
- */
1185
1706
  zod?: boolean;
1186
1707
  output?: {
1187
- /** Path template for the SDK aggregator file. Supports {name}. Default: `sdk.ts`. */
1188
1708
  sdk?: string;
1189
- /** Path template for SDK type files. Supports {filename}, {dir}, {area}, {subarea}. */
1190
1709
  types?: string;
1191
- /** Path template for client class files. Supports {filename}, {dir}, {area}, {subarea}. */
1192
1710
  clients?: string;
1193
1711
  };
1194
- /**
1195
- * Whether to emit SDK methods for operations marked `internal`. Defaults to `false` —
1196
- * internal ops are omitted from the SDK so consumers don't pick them up. Set to `true`
1197
- * for an internal-use SDK that should expose them.
1198
- */
1199
1712
  includeInternal?: boolean;
1200
1713
  }
1201
1714
  &nbsp;
1202
1715
  export interface ZodConfig {
1203
- /** Directory (relative to rootDir) where Zod schema files are written. Default: rootDir. */
1204
1716
  baseDir?: string;
1205
- /** Output path template. Supports {filename}, {dir}. Default: `{filename}.schema.ts` alongside source. */
1206
1717
  output?: string;
1207
1718
  }
1208
1719
  &nbsp;
1209
1720
  export interface TypesConfig {
1210
- /** Directory (relative to rootDir) where plain TypeScript type files are written. Default: rootDir. */
1211
1721
  baseDir?: string;
1212
- /** Output path template. Supports {filename}, {dir}. Default: `{filename}.types.ts` alongside source. */
1213
1722
  output?: string;
1214
1723
  }
1215
1724
  &nbsp;
1216
1725
  export interface TypescriptPluginConfig {
1217
- /** Generate Koa router files from `operation` declarations. */
1218
1726
  server?: ServerConfig;
1219
- /** Generate TypeScript SDK client files from `operation` declarations. */
1220
1727
  sdk?: SdkConfig;
1221
- /** Generate Zod schema files from `contract` declarations. */
1222
1728
  zod?: ZodConfig;
1223
- /** Generate plain TypeScript interface/type files from `contract` declarations (no Zod runtime). */
1224
1729
  types?: TypesConfig;
1225
1730
  }
1226
1731
  &nbsp;
1227
- // ─── Server generation ─────────────────────────────────────────────────────
1732
+ // ─── Caching constants ─────────────────────────────────────────────────────
1733
+ &nbsp;
1734
+ /** Bumped when the codegen output shape changes in a way that should bust every per-file fingerprint. */
1735
+ export const TYPESCRIPT_CODEGEN_VERSION = '1';
1736
+ &nbsp;
1737
+ const MANIFEST_FILENAME = '.contractkit-typescript-manifest.json';
1738
+ &nbsp;
1739
+ // ─── Plugin entry points ──────────────────────────────────────────────────
1740
+ &nbsp;
1741
+ const plugin: ContractKitPlugin = {
1742
+ name: 'typescript',
1743
+ async generateTargets(inputs, ctx) {
1744
+ const config = ctx.options as TypescriptPluginConfig;
1745
+ await runTypescriptCodegen(inputs, ctx, config, ctx.rootDir);
1746
+ },
1747
+ };
1748
+ &nbsp;
1749
+ export default plugin;
1750
+ &nbsp;
1751
+ /** Build a `@contractkit/plugin-typescript` instance with explicit configuration, for programmatic use. */
1752
+ export function createTypescriptPlugin(config: TypescriptPluginConfig, rootDir: string): ContractKitPlugin {
1753
+ return {
1754
+ name: 'typescript',
1755
+ async generateTargets(inputs, ctx) {
1756
+ await runTypescriptCodegen(inputs, ctx, config, rootDir);
1757
+ },
1758
+ };
1759
+ }
1760
+ &nbsp;
1761
+ /**
1762
+ * Shared orchestration. Each sub-generator (server / sdk / zod / types) contributes a
1763
+ * set of cacheable units (per-file fingerprints) plus a set of always-regenerated global
1764
+ * files (aggregators, barrels, sdk-options). Units share a single manifest so the cache
1765
+ * survives cross-cutting reads — the manifest lives at `&lt;rootDir&gt;/.contractkit-typescript-manifest.json`.
1766
+ *
1767
+ * Honors `ctx.cacheEnabled` — `--force` bypasses the manifest entirely.
1768
+ */
1769
+ async function runTypescriptCodegen(
1770
+ inputs: Parameters&lt;NonNullable&lt;ContractKitPlugin['generateTargets']&gt;&gt;[0],
1771
+ ctx: PluginContext,
1772
+ config: TypescriptPluginConfig,
1773
+ rootDir: string,
1774
+ ): Promise&lt;void&gt; {
1775
+ const manifestPath = resolve(rootDir, MANIFEST_FILENAME);
1776
+ const prevManifest: IncrementalManifest = ctx.cacheEnabled ? readManifest(<span class="branch-1 cbranch-no" title="branch not covered" >manifestPath) : emptyIncrementalManifest(TYPESCRIPT_CODEGEN_VERSION);</span>
1777
+ &nbsp;
1778
+ const units: IncrementalUnit[] = [];
1779
+ const globalFiles: IncrementalOutputFile[] = [];
1780
+ &nbsp;
1781
+ if (config.server) collectServerOutput(config.server, rootDir, inputs, units);
1782
+ if (config.sdk) collectSdkOutput(config.sdk, rootDir, inputs, units, globalFiles);
1783
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (config.zod) <span class="cstat-no" title="statement not covered" >collectZodOutput(config.zod, rootDir, inputs, units);</span>
1784
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (config.types) <span class="cstat-no" title="statement not covered" >collectTypesOutput(config.types, rootDir, inputs, units);</span>
1785
+ &nbsp;
1786
+ const result = runIncrementalCodegen({
1787
+ codegenVersion: TYPESCRIPT_CODEGEN_VERSION,
1788
+ manifestFilename: manifestPath,
1789
+ prevManifest,
1790
+ globalFiles,
1791
+ units,
1792
+ // Paths are absolute, so existsSync works directly.
1793
+ fileExists: existsSync,
1794
+ });
1795
+ &nbsp;
1796
+ deleteStalePaths(result.deletedPaths);
1797
+ &nbsp;
1798
+ for (const { relativePath, content } of result.filesToWrite) {
1799
+ ctx.emitFile(relativePath, content);
1800
+ }
1801
+ }
1228
1802
  &nbsp;
1229
- function runServerGeneration(
1803
+ // ─── Cross-file dependency analysis ────────────────────────────────────────
1804
+ &nbsp;
1805
+ /** Build a quick lookup from model name → its definition. */
1806
+ function buildModelMap(contractRoots: readonly ContractRootNode[]): Map&lt;string, ModelNode&gt; {
1807
+ const map = new Map&lt;string, ModelNode&gt;();
1808
+ for (const root of contractRoots) {
1809
+ for (const model of root.models) map.set(model.name, model);
1810
+ }
1811
+ return map;
1812
+ }
1813
+ &nbsp;
1814
+ /** Collect every model referenced by this contract root (own models' fields + bases). Used to slice cross-file fingerprint inputs to just what this file actually depends on. */
1815
+ function collectContractRootRefs(root: ContractRootNode, modelMap: Map&lt;string, ModelNode&gt;): Set&lt;string&gt; {
1816
+ const seeds: Parameters&lt;typeof collectTypeRefs&gt;[0][] = [];
1817
+ for (const m of root.models) {
1818
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (m.type) <span class="cstat-no" title="statement not covered" >seeds.push(m.type);</span>
1819
+ for (const f of m.fields) seeds.push(f.type);
1820
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (m.bases) {
1821
+ <span class="cstat-no" title="statement not covered" > for (const b of m.bases) <span class="cstat-no" title="statement not covered" >seeds.push({ kind: 'ref', name: b } as Parameters&lt;typeof collectTypeRefs&gt;[0]);</span></span>
1822
+ }
1823
+ }
1824
+ return collectTransitiveModelRefs(seeds, modelMap);
1825
+ }
1826
+ &nbsp;
1827
+ /** Collect every model referenced by an op root's routes/operations (transitive). */
1828
+ function collectOpRootRefs(root: OpRootNode, modelMap: Map&lt;string, ModelNode&gt;): Set&lt;string&gt; {
1829
+ const seeds: Parameters&lt;typeof collectTypeRefs&gt;[0][] = [];
1830
+ for (const route of root.routes) {
1831
+ if (route.params) seeds.push(...paramSourceTypes(route.params));
1832
+ for (const op of route.operations) {
1833
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (op.query) <span class="cstat-no" title="statement not covered" >seeds.push(...paramSourceTypes(op.query));</span>
1834
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (op.headers) <span class="cstat-no" title="statement not covered" >seeds.push(...paramSourceTypes(op.headers));</span>
1835
+ if (op.request) {
1836
+ for (const body of op.request.bodies) seeds.push(body.bodyType);
1837
+ }
1838
+ for (const resp of op.responses) {
1839
+ if (resp.bodyType) seeds.push(resp.bodyType);
1840
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (resp.headers) {
1841
+ <span class="cstat-no" title="statement not covered" > for (const h of resp.headers) <span class="cstat-no" title="statement not covered" >seeds.push(h.type);</span></span>
1842
+ }
1843
+ }
1844
+ }
1845
+ }
1846
+ return collectTransitiveModelRefs(seeds, modelMap);
1847
+ }
1848
+ &nbsp;
1849
+ function paramSourceTypes(src: NonNullable&lt;OpRootNode['routes'][number]['params']&gt;): Parameters&lt;typeof collectTypeRefs&gt;[0][] {
1850
+ const out: Parameters&lt;typeof collectTypeRefs&gt;[0][] = [];
1851
+ if (src.kind === 'params') {
1852
+ for (const n of src.nodes) out.push(n.type);
1853
+ } else <span class="cstat-no" title="statement not covered" ><span class="missing-if-branch" title="else path not taken" >E</span>if (src.kind === 'ref') {</span>
1854
+ <span class="cstat-no" title="statement not covered" > out.push({ kind: 'ref', name: src.name } as Parameters&lt;typeof collectTypeRefs&gt;[0]);</span>
1855
+ } else <span class="cstat-no" title="statement not covered" >if (src.kind === 'type') {</span>
1856
+ <span class="cstat-no" title="statement not covered" > out.push(src.node);</span>
1857
+ }
1858
+ return out;
1859
+ }
1860
+ &nbsp;
1861
+ /** Build a sorted, JSON-stable record of (modelName -&gt; outPath) for refs this unit depends on. */
1862
+ function sliceOutPathMap(refs: Set&lt;string&gt;, modelOutPaths: Map&lt;string, string&gt;, modelsWithInput: Set&lt;string&gt;, modelsWithOutput: Set&lt;string&gt;): Record&lt;string, string&gt; {
1863
+ const slice: Record&lt;string, string&gt; = {};
1864
+ for (const ref of [...refs].sort()) {
1865
+ const p = modelOutPaths.get(ref);
1866
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (p) <span class="cstat-no" title="statement not covered" >slice[ref] = p;</span>
1867
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (modelsWithInput.has(ref)) {
1868
+ const ip = <span class="cstat-no" title="statement not covered" >modelOutPaths.get(`${ref}Input`);</span>
1869
+ <span class="cstat-no" title="statement not covered" > if (ip) <span class="cstat-no" title="statement not covered" >slice[`${ref}Input`] = ip;</span></span>
1870
+ }
1871
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (modelsWithOutput.has(ref)) {
1872
+ const op = <span class="cstat-no" title="statement not covered" >modelOutPaths.get(`${ref}Output`);</span>
1873
+ <span class="cstat-no" title="statement not covered" > if (op) <span class="cstat-no" title="statement not covered" >slice[`${ref}Output`] = op;</span></span>
1874
+ }
1875
+ }
1876
+ return slice;
1877
+ }
1878
+ &nbsp;
1879
+ /** Slice modelsWithInput/Output to only the names relevant to this unit. */
1880
+ function sliceModelSet(refs: Set&lt;string&gt;, ownNames: Set&lt;string&gt;, set: Set&lt;string&gt;): string[] {
1881
+ const result: string[] = [];
1882
+ for (const name of set) {
1883
+ <span class="cstat-no" title="statement not covered" > if (refs.has(name) || ownNames.has(name)) <span class="cstat-no" title="statement not covered" >result.push(name);</span></span>
1884
+ }
1885
+ return result.sort();
1886
+ }
1887
+ &nbsp;
1888
+ // ─── Server sub-generator ──────────────────────────────────────────────────
1889
+ &nbsp;
1890
+ function collectServerOutput(
1230
1891
  config: ServerConfig,
1231
1892
  rootDir: string,
1232
1893
  inputs: Parameters&lt;NonNullable&lt;ContractKitPlugin['generateTargets']&gt;&gt;[0],
1233
- emitFile: (outPath: string, content: string) =&gt; void,
1894
+ units: IncrementalUnit[],
1234
1895
  ): void {
1235
1896
  const serverBase = resolve(rootDir, config.baseDir ?? '.');
1236
1897
  const modelsWithInput = inputs.modelsWithInput as Set&lt;string&gt;;
1237
1898
  const modelsWithOutput = inputs.modelsWithOutput as Set&lt;string&gt;;
1899
+ const modelMap = buildModelMap(inputs.contractRoots);
1238
1900
  const allFiles = [...inputs.contractRoots.map(r =&gt; r.file), ...inputs.opRoots.map(r =&gt; r.file)];
1239
1901
  const commonRoot = commonDir(allFiles, rootDir);
1902
+ const subConfigKey = stableSubConfig(config);
1240
1903
  &nbsp;
1241
- // ── Types / Zod output ──
1242
- // When output.types is configured we generate type files ourselves and build a
1243
- // local modelOutPaths map so the router generator can resolve import paths.
1244
- let serverModelOutPaths = new Map&lt;string, string&gt;();
1245
- &nbsp;
1904
+ // Pre-pass: register all model outPath. Cross-file refs need to resolve correctly,
1905
+ // which means we need the COMPLETE map (not a slice) even though each unit's fingerprint
1906
+ // only includes its own slice.
1907
+ const serverModelOutPaths = new Map&lt;string, string&gt;();
1908
+ const typeEntries: { ast: ContractRootNode; typeOutPath: string }[] = [];
1246
1909
  if (config.output?.types) {
1247
- serverModelOutPaths = new Map();
1248
- &nbsp;
1249
- // Pass 1: register all model → outPath entries before generating content,
1250
- // so cross-file type refs resolve correctly.
1251
- const typeEntries: { ast: (typeof inputs.contractRoots)[number]; typeOutPath: string }[] = [];
1252
1910
  for (const ast of inputs.contractRoots) {
1253
1911
  const typeOutPath = computeContractOutPath(ast.file, serverBase, config.output.types, '.ts', commonRoot, ast.meta);
1254
1912
  typeEntries.push({ ast, typeOutPath });
1255
1913
  for (const model of ast.models) {
1256
1914
  serverModelOutPaths.set(model.name, typeOutPath);
1257
- <span class="missing-if-branch" title="if path not taken" >I</span>if (modelsWithInput.has(model.name)) {
1258
- <span class="cstat-no" title="statement not covered" > serverModelOutPaths.set(`${model.name}Input`, typeOutPath);</span>
1259
- }
1260
- <span class="missing-if-branch" title="if path not taken" >I</span>if (modelsWithOutput.has(model.name)) {
1261
- <span class="cstat-no" title="statement not covered" > serverModelOutPaths.set(`${model.name}Output`, typeOutPath);</span>
1262
- }
1915
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (modelsWithInput.has(model.name)) <span class="cstat-no" title="statement not covered" >serverModelOutPaths.set(`${model.name}Input`, typeOutPath);</span>
1916
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (modelsWithOutput.has(model.name)) <span class="cstat-no" title="statement not covered" >serverModelOutPaths.set(`${model.name}Output`, typeOutPath);</span>
1263
1917
  }
1264
1918
  }
1919
+ }
1265
1920
  &nbsp;
1266
- // Pass 2: emit type files.
1267
- for (const { ast, typeOutPath } of typeEntries) {
1268
- const ctx = { modelOutPaths: serverModelOutPaths, currentOutPath: typeOutPath, modelsWithInput, modelsWithOutput };
1269
- const content = config.zod ? generateContract(ast, ctx) : generatePlainTypes(ast, ctx);
1270
- emitFile(typeOutPath, content);
1271
- }
1921
+ // ── Per-contract-root types unit ──
1922
+ for (const { ast, typeOutPath } of typeEntries) {
1923
+ const refs = collectContractRootRefs(ast, modelMap);
1924
+ const ownNames = new Set(ast.models.map(m =&gt; m.name));
1925
+ const fingerprint = hashFingerprint({
1926
+ kind: 'server-types',
1927
+ v: TYPESCRIPT_CODEGEN_VERSION,
1928
+ outPath: typeOutPath,
1929
+ root: ast,
1930
+ outPathSlice: sliceOutPathMap(refs, serverModelOutPaths, modelsWithInput, modelsWithOutput),
1931
+ modelsWithInput: sliceModelSet(refs, ownNames, modelsWithInput),
1932
+ modelsWithOutput: sliceModelSet(refs, ownNames, modelsWithOutput),
1933
+ sub: subConfigKey,
1934
+ });
1935
+ units.push({
1936
+ key: `server-types::${typeOutPath}`,
1937
+ fingerprint,
1938
+ render: () =&gt; {
1939
+ const renderCtx = {
1940
+ modelOutPaths: serverModelOutPaths,
1941
+ currentOutPath: typeOutPath,
1942
+ modelsWithInput,
1943
+ modelsWithOutput,
1944
+ };
1945
+ const content = config.zod ? generateContract(ast, renderCtx) : generatePlainTypes(ast, renderCtx);
1946
+ return [{ relativePath: typeOutPath, content }];
1947
+ },
1948
+ });
1272
1949
  }
1273
1950
  &nbsp;
1274
- // ── Routes output ──
1951
+ // ── Per-op-root router unit ──
1275
1952
  for (const ast of inputs.opRoots) {
1276
1953
  const outPath = computeOpOutPath(ast.file, serverBase, config.output?.routes, '.router.ts', commonRoot, ast.meta);
1277
- const content = generateOp(ast, {
1278
- servicePathTemplate: config.servicePathTemplate,
1954
+ const refs = collectOpRootRefs(ast, modelMap);
1955
+ const fingerprint = hashFingerprint({
1956
+ kind: 'server-router',
1957
+ v: TYPESCRIPT_CODEGEN_VERSION,
1279
1958
  outPath,
1280
- modelOutPaths: serverModelOutPaths,
1281
- modelsWithInput,
1282
- modelsWithOutput,
1283
- includeInternal: config.includeInternal,
1959
+ root: ast,
1960
+ // The router imports types from each contract root's type file; the slice covers exactly that.
1961
+ outPathSlice: sliceOutPathMap(refs, serverModelOutPaths, modelsWithInput, modelsWithOutput),
1962
+ modelsWithInput: sliceModelSet(refs, new Set(), modelsWithInput),
1963
+ modelsWithOutput: sliceModelSet(refs, new Set(), modelsWithOutput),
1964
+ servicePathTemplate: config.servicePathTemplate ?? null,
1965
+ includeInternal: config.includeInternal ?? true,
1966
+ sub: subConfigKey,
1967
+ });
1968
+ units.push({
1969
+ key: `server-router::${outPath}`,
1970
+ fingerprint,
1971
+ render: () =&gt; [
1972
+ {
1973
+ relativePath: outPath,
1974
+ content: generateOp(ast, {
1975
+ servicePathTemplate: config.servicePathTemplate,
1976
+ outPath,
1977
+ modelOutPaths: serverModelOutPaths,
1978
+ modelsWithInput,
1979
+ modelsWithOutput,
1980
+ includeInternal: config.includeInternal,
1981
+ }),
1982
+ },
1983
+ ],
1284
1984
  });
1285
- emitFile(outPath, content);
1286
1985
  }
1287
1986
  }
1288
1987
  &nbsp;
1289
- // ─── SDK generation ────────────────────────────────────────────────────────
1988
+ // ─── SDK sub-generator ─────────────────────────────────────────────────────
1290
1989
  &nbsp;
1291
- function runSdkGeneration(
1990
+ function collectSdkOutput(
1292
1991
  config: SdkConfig,
1293
1992
  rootDir: string,
1294
1993
  inputs: Parameters&lt;NonNullable&lt;ContractKitPlugin['generateTargets']&gt;&gt;[0],
1295
- emitFile: (outPath: string, content: string) =&gt; void,
1994
+ units: IncrementalUnit[],
1995
+ globalFiles: IncrementalOutputFile[],
1296
1996
  ): void {
1297
1997
  const sdkBase = config.baseDir<span class="branch-0 cbranch-no" title="branch not covered" > ? resolve(rootDir, config.baseDir) : r</span>ootDir;
1298
1998
  const sdkName = config.name;
@@ -1301,22 +2001,22 @@ function runSdkGeneration(
1301
2001
  ? join(sdkBase, TEMPLATE_VAR_RE.test(<span class="branch-0 cbranch-no" title="branch not covered" >sdkOutput) ? resolveTemplate(sdkOutput, { name: sdkName ?? 'sdk' }) : <span class="branch-1 cbranch-no" title="branch not covered" >s</span>dkOutput)</span>
1302
2002
  : join(sdkBase, 'sdk.ts');
1303
2003
  const sdkOptionsPath = join(dirname(sdkEntryPath), 'sdk-options.ts');
2004
+ const subConfigKey = stableSubConfig(config);
1304
2005
  &nbsp;
1305
2006
  const modelsWithInput = inputs.modelsWithInput as Set&lt;string&gt;;
1306
2007
  const modelsWithOutput = inputs.modelsWithOutput as Set&lt;string&gt;;
2008
+ const modelMap = buildModelMap(inputs.contractRoots);
1307
2009
  const allFiles = [...inputs.contractRoots.map(<span class="fstat-no" title="function not covered" >r =&gt; <span class="cstat-no" title="statement not covered" >r</span>.file)</span>, ...inputs.opRoots.map(r =&gt; r.file)];
1308
2010
  const ckCommonRoot = commonDir(allFiles, rootDir);
1309
2011
  &nbsp;
1310
- let sdkModelOutPaths = new Map&lt;string, string&gt;();
2012
+ const sdkModelOutPaths = new Map&lt;string, string&gt;();
1311
2013
  const sdkTypePaths: string[] = [];
1312
2014
  const sdkClientInfos: { outPath: string; className: string; propertyName: string }[] = [];
1313
2015
  &nbsp;
1314
- // ── SDK types ──
2016
+ // ── Pre-pass: SDK type files ──
2017
+ const sdkContractEntries: { ast: ContractRootNode; typeOutPath: string }[] = [];
1315
2018
  <span class="missing-if-branch" title="if path not taken" >I</span>if (config.output?.types) {
1316
- <span class="cstat-no" title="statement not covered" > sdkModelOutPaths = new Map&lt;string, string&gt;();</span>
1317
2019
  const <span class="cstat-no" title="statement not covered" >publicTypes = computePubliclyReachableTypes(inputs.opRoots, inputs.contractRoots, modelsWithInput, modelsWithOutput);</span>
1318
- &nbsp;
1319
- const sdkContractEntries: { ast: (typeof inputs.contractRoots)[number]; typeOutPath: string }[] = <span class="cstat-no" title="statement not covered" >[];</span>
1320
2020
  <span class="cstat-no" title="statement not covered" > for (const ast of inputs.contractRoots) {</span>
1321
2021
  const <span class="cstat-no" title="statement not covered" >typeOutPath = computeSdkTypeOutPath(ast.file, sdkBase, config.output.types, ckCommonRoot, ast.meta);</span>
1322
2022
  <span class="cstat-no" title="statement not covered" > if (!typeOutPath) <span class="cstat-no" title="statement not covered" >continue;</span></span>
@@ -1329,48 +2029,63 @@ function runSdkGeneration(
1329
2029
  <span class="cstat-no" title="statement not covered" > if (modelsWithOutput.has(model.name)) <span class="cstat-no" title="statement not covered" >sdkModelOutPaths.set(`${model.name}Output`, typeOutPath);</span></span>
1330
2030
  }
1331
2031
  }
2032
+ }
1332
2033
  &nbsp;
1333
- <span class="cstat-no" title="statement not covered" > for (const { ast, typeOutPath } of sdkContractEntries) {</span>
1334
- let content: string;
1335
- <span class="cstat-no" title="statement not covered" > if (config.zod) {</span>
1336
- <span class="cstat-no" title="statement not covered" > content = generateContract(ast, {</span>
1337
- modelOutPaths: sdkModelOutPaths,
1338
- currentOutPath: typeOutPath,
1339
- modelsWithInput,
1340
- modelsWithOutput,
1341
- });
1342
- } else {
1343
- let <span class="cstat-no" title="statement not covered" >rel = relative(dirname(typeOutPath), sdkOptionsPath).replace(/\.ts$/, '.js');</span>
1344
- <span class="cstat-no" title="statement not covered" > if (!rel.startsWith('.')) <span class="cstat-no" title="statement not covered" >rel = './' + rel;</span></span>
1345
- <span class="cstat-no" title="statement not covered" > content = generatePlainTypes(ast, {</span>
1346
- modelOutPaths: sdkModelOutPaths,
1347
- currentOutPath: typeOutPath,
1348
- modelsWithInput,
1349
- modelsWithOutput,
1350
- jsonValueImportPath: rel,
1351
- });
1352
- }
1353
- <span class="cstat-no" title="statement not covered" > emitFile(typeOutPath, content);</span>
1354
- }
2034
+ // ── SDK type units ──
2035
+ for (const { ast, typeOutPath } of sdkContractEntries) {
2036
+ const refs = <span class="cstat-no" title="statement not covered" >collectContractRootRefs(ast, modelMap);</span>
2037
+ const ownNames = <span class="cstat-no" title="statement not covered" >new Set(ast.models.map(<span class="fstat-no" title="function not covered" >m =&gt; <span class="cstat-no" title="statement not covered" >m</span>.name)</span>);</span>
2038
+ const <span class="cstat-no" title="statement not covered" >fingerprint = hashFingerprint({</span>
2039
+ kind: 'sdk-types',
2040
+ v: TYPESCRIPT_CODEGEN_VERSION,
2041
+ outPath: typeOutPath,
2042
+ root: ast,
2043
+ outPathSlice: sliceOutPathMap(refs, sdkModelOutPaths, modelsWithInput, modelsWithOutput),
2044
+ modelsWithInput: sliceModelSet(refs, ownNames, modelsWithInput),
2045
+ modelsWithOutput: sliceModelSet(refs, ownNames, modelsWithOutput),
2046
+ sdkOptionsPath,
2047
+ sub: subConfigKey,
2048
+ });
2049
+ <span class="cstat-no" title="statement not covered" > units.push({</span>
2050
+ key: `sdk-types::${typeOutPath}`,
2051
+ fingerprint,
2052
+ render: <span class="fstat-no" title="function not covered" >() =&gt; {</span>
2053
+ let content: string;
2054
+ <span class="cstat-no" title="statement not covered" > if (config.zod) {</span>
2055
+ <span class="cstat-no" title="statement not covered" > content = generateContract(ast, {</span>
2056
+ modelOutPaths: sdkModelOutPaths,
2057
+ currentOutPath: typeOutPath,
2058
+ modelsWithInput,
2059
+ modelsWithOutput,
2060
+ });
2061
+ } else {
2062
+ let <span class="cstat-no" title="statement not covered" >rel = relative(dirname(typeOutPath), sdkOptionsPath).replace(/\.ts$/, '.js');</span>
2063
+ <span class="cstat-no" title="statement not covered" > if (!rel.startsWith('.')) <span class="cstat-no" title="statement not covered" >rel = './' + rel;</span></span>
2064
+ <span class="cstat-no" title="statement not covered" > content = generatePlainTypes(ast, {</span>
2065
+ modelOutPaths: sdkModelOutPaths,
2066
+ currentOutPath: typeOutPath,
2067
+ modelsWithInput,
2068
+ modelsWithOutput,
2069
+ jsonValueImportPath: rel,
2070
+ });
2071
+ }
2072
+ <span class="cstat-no" title="statement not covered" > return [{ relativePath: typeOutPath, content }];</span>
2073
+ },
2074
+ });
1355
2075
  }
1356
2076
  &nbsp;
1357
- // ── SDK clients ──
1358
- // Group opRoots by (area, subarea):
1359
- // - area + subarea → leaf client emitted as &lt;Area&gt;&lt;Subarea&gt;Client in its own file
1360
- // - area only → no standalone file; methods inlined into &lt;Area&gt;Client in sdk.ts
1361
- // - neither → flat top-level client (legacy behavior)
2077
+ // ── Bucket op roots by area/subarea ──
1362
2078
  interface AreaBucket {
1363
- leaves: { ast: typeof inputs.opRoots[number]; outPath: string; subarea: string }[];
1364
- inlineRoots: typeof inputs.opRoots[number][];
2079
+ leaves: { ast: OpRootNode; outPath: string; subarea: string }[];
2080
+ inlineRoots: OpRootNode[];
1365
2081
  }
1366
2082
  const areaBuckets = new Map&lt;string, AreaBucket&gt;();
1367
- const topLevelEntries: { ast: typeof inputs.opRoots[number]; outPath: string }[] = [];
2083
+ const topLevelEntries: { ast: OpRootNode; outPath: string }[] = [];
1368
2084
  &nbsp;
1369
2085
  <span class="missing-if-branch" title="else path not taken" >E</span>if (config.output?.clients) {
1370
2086
  for (const ast of inputs.opRoots) {
1371
2087
  const sdkOutPath = computeSdkOutPath(ast.file, sdkBase, config.output.clients, ckCommonRoot, ast.meta);
1372
2088
  <span class="missing-if-branch" title="if path not taken" >I</span>if (!sdkOutPath || !hasPublicOperations(ast, config.includeInternal)) <span class="cstat-no" title="statement not covered" >continue;</span>
1373
- &nbsp;
1374
2089
  const { area, subarea } = getAreaSubarea(ast);
1375
2090
  if (area &amp;&amp; subarea) {
1376
2091
  const bucket = areaBuckets.get(area) ?? { leaves: [], inlineRoots: [] };
@@ -1385,46 +2100,91 @@ function runSdkGeneration(
1385
2100
  }
1386
2101
  }
1387
2102
  &nbsp;
1388
- // Emit per-file clients for leaves (subarea) and top-level (no area). Area-only files are inlined later.
1389
- for (const { ast, outPath, subarea } of [...areaBuckets.entries()].flatMap(([area, b]) =&gt; b.leaves.map(l =&gt; ({ ...l, area })))) {
1390
- const className = deriveSubareaClientClassName((ast.meta?.area as string) ?? <span class="branch-1 cbranch-no" title="branch not covered" >'', s</span>ubarea);
1391
- sdkClientInfos.push({ outPath, className, propertyName: deriveSubareaPropertyName(subarea) });
1392
- emitFile(
1393
- outPath,
1394
- generateSdk(ast, {
1395
- typeImportPathTemplate: undefined,
1396
- outPath,
1397
- modelOutPaths: sdkModelOutPaths,
2103
+ // ── Per-leaf-client (area+subarea) units ──
2104
+ for (const [area, bucket] of areaBuckets.entries()) {
2105
+ for (const leaf of bucket.leaves) {
2106
+ const className = deriveSubareaClientClassName(area, leaf.subarea);
2107
+ sdkClientInfos.push({ outPath: leaf.outPath, className, propertyName: deriveSubareaPropertyName(leaf.subarea) });
2108
+ const refs = collectOpRootRefs(leaf.ast, modelMap);
2109
+ const fingerprint = hashFingerprint({
2110
+ kind: 'sdk-leaf-client',
2111
+ v: TYPESCRIPT_CODEGEN_VERSION,
2112
+ outPath: leaf.outPath,
2113
+ root: leaf.ast,
2114
+ outPathSlice: sliceOutPathMap(refs, sdkModelOutPaths, modelsWithInput, modelsWithOutput),
2115
+ modelsWithInput: sliceModelSet(refs, new Set(), modelsWithInput),
2116
+ modelsWithOutput: sliceModelSet(refs, new Set(), modelsWithOutput),
1398
2117
  sdkOptionsPath,
1399
- modelsWithInput,
1400
- modelsWithOutput,
1401
- includeInternal: config.includeInternal,
1402
- clientClassName: className,
1403
- }),
1404
- );
2118
+ className,
2119
+ includeInternal: config.includeInternal ?? false,
2120
+ sub: subConfigKey,
2121
+ });
2122
+ units.push({
2123
+ key: `sdk-leaf-client::${leaf.outPath}`,
2124
+ fingerprint,
2125
+ render: () =&gt; [
2126
+ {
2127
+ relativePath: leaf.outPath,
2128
+ content: generateSdk(leaf.ast, {
2129
+ typeImportPathTemplate: undefined,
2130
+ outPath: leaf.outPath,
2131
+ modelOutPaths: sdkModelOutPaths,
2132
+ sdkOptionsPath,
2133
+ modelsWithInput,
2134
+ modelsWithOutput,
2135
+ includeInternal: config.includeInternal,
2136
+ clientClassName: className,
2137
+ }),
2138
+ },
2139
+ ],
2140
+ });
2141
+ }
1405
2142
  }
2143
+ &nbsp;
2144
+ // ── Top-level (no area) client units ──
1406
2145
  for (const { ast, outPath } of topLevelEntries) {
1407
2146
  const className = deriveClientClassName(ast.file);
1408
2147
  sdkClientInfos.push({ outPath, className, propertyName: deriveClientPropertyName(ast.file) });
1409
- emitFile(
2148
+ const refs = collectOpRootRefs(ast, modelMap);
2149
+ const fingerprint = hashFingerprint({
2150
+ kind: 'sdk-top-client',
2151
+ v: TYPESCRIPT_CODEGEN_VERSION,
1410
2152
  outPath,
1411
- generateSdk(ast, {
1412
- typeImportPathTemplate: undefined,
1413
- outPath,
1414
- modelOutPaths: sdkModelOutPaths,
1415
- sdkOptionsPath,
1416
- modelsWithInput,
1417
- modelsWithOutput,
1418
- includeInternal: config.includeInternal,
1419
- }),
1420
- );
2153
+ root: ast,
2154
+ outPathSlice: sliceOutPathMap(refs, sdkModelOutPaths, modelsWithInput, modelsWithOutput),
2155
+ modelsWithInput: sliceModelSet(refs, new Set(), modelsWithInput),
2156
+ modelsWithOutput: sliceModelSet(refs, new Set(), modelsWithOutput),
2157
+ sdkOptionsPath,
2158
+ includeInternal: config.includeInternal ?? false,
2159
+ sub: subConfigKey,
2160
+ });
2161
+ units.push({
2162
+ key: `sdk-top-client::${outPath}`,
2163
+ fingerprint,
2164
+ render: () =&gt; [
2165
+ {
2166
+ relativePath: outPath,
2167
+ content: generateSdk(ast, {
2168
+ typeImportPathTemplate: undefined,
2169
+ outPath,
2170
+ modelOutPaths: sdkModelOutPaths,
2171
+ sdkOptionsPath,
2172
+ modelsWithInput,
2173
+ modelsWithOutput,
2174
+ includeInternal: config.includeInternal,
2175
+ }),
2176
+ },
2177
+ ],
2178
+ });
1421
2179
  }
1422
2180
  }
1423
2181
  &nbsp;
1424
- // ── sdk-options.ts ──
1425
- emitFile(sdkOptionsPath, generateSdkOptions());
2182
+ // ── Global files: sdk-options, aggregator, barrels, root index ──
2183
+ // The aggregator inlines area-only op roots, so its content depends on each inline root's
2184
+ // full AST. Caching it gains little — the codegen is fast and any inline-root change rebuilds
2185
+ // the file anyway. Same for barrels (one line per imported file). Always regenerate.
2186
+ globalFiles.push({ relativePath: sdkOptionsPath, content: generateSdkOptions() });
1426
2187
  &nbsp;
1427
- // ── sdk.ts aggregator ──
1428
2188
  const hasAnything = sdkClientInfos.length &gt; 0 || areaBuckets.size &gt; 0;
1429
2189
  <span class="missing-if-branch" title="else path not taken" >E</span>if (hasAnything) {
1430
2190
  const sdkEntryDir = dirname(sdkEntryPath);
@@ -1480,26 +2240,18 @@ function runSdkGeneration(
1480
2240
  })),
1481
2241
  }));
1482
2242
  &nbsp;
1483
- emitFile(
1484
- sdkEntryPath,
1485
- generateSdkAggregator({
1486
- topLevelClients,
1487
- areas,
1488
- sdkOptionsImportPath,
1489
- sdkClassName,
1490
- }),
1491
- );
2243
+ globalFiles.push({
2244
+ relativePath: sdkEntryPath,
2245
+ content: generateSdkAggregator({ topLevelClients, areas, sdkOptionsImportPath, sdkClassName }),
2246
+ });
1492
2247
  }
1493
2248
  &nbsp;
1494
- // ── Barrel files ──
1495
2249
  const sdkSrcDir = dirname(sdkEntryPath);
1496
2250
  const sdkTypeBarrels = generateBarrelFiles(sdkTypePaths);
1497
- for (const barrel of sdkTypeBarrels) <span class="cstat-no" title="statement not covered" >emitFile(barrel.outPath, barrel.content);</span>
2251
+ for (const barrel of sdkTypeBarrels) <span class="cstat-no" title="statement not covered" >globalFiles.push({ relativePath: barrel.outPath, content: barrel.content });</span>
1498
2252
  &nbsp;
1499
2253
  const rootExports: string[] = [`export * from './${basename(sdkOptionsPath).replace(/\.ts$/, '.js')}';`];
1500
- <span class="missing-if-branch" title="else path not taken" >E</span>if (hasAnything) {
1501
- rootExports.push(`export * from './${basename(sdkEntryPath).replace(/\.ts$/, '.js')}';`);
1502
- }
2254
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (hasAnything) rootExports.push(`export * from './${basename(sdkEntryPath).replace(/\.ts$/, '.js')}';`);
1503
2255
  for (const c of sdkClientInfos) {
1504
2256
  let rel = relative(sdkSrcDir, c.outPath).replace(/\.ts$/, '.js');
1505
2257
  <span class="missing-if-branch" title="else path not taken" >E</span>if (!rel.startsWith('.')) rel = './' + rel;
@@ -1510,26 +2262,30 @@ function runSdkGeneration(
1510
2262
  <span class="cstat-no" title="statement not covered" > if (!rel.startsWith('.')) <span class="cstat-no" title="statement not covered" >rel = './' + rel;</span></span>
1511
2263
  <span class="cstat-no" title="statement not covered" > rootExports.push(`export * from '${rel}';`);</span>
1512
2264
  }
1513
- emitFile(join(sdkSrcDir, 'index.ts'), `// Auto-generated barrel file\n${rootExports.sort().join('\n')}\n`);
2265
+ globalFiles.push({
2266
+ relativePath: join(sdkSrcDir, 'index.ts'),
2267
+ content: `// Auto-generated barrel file\n${rootExports.sort().join('\n')}\n`,
2268
+ });
1514
2269
  }
1515
2270
  &nbsp;
1516
- // ─── Zod generation ────────────────────────────────────────────────────────
2271
+ // ─── Zod sub-generator ─────────────────────────────────────────────────────
1517
2272
  &nbsp;
1518
- function <span class="fstat-no" title="function not covered" >runZodGeneration(</span>
2273
+ function <span class="fstat-no" title="function not covered" >collectZodOutput(</span>
1519
2274
  config: ZodConfig,
1520
2275
  rootDir: string,
1521
2276
  inputs: Parameters&lt;NonNullable&lt;ContractKitPlugin['generateTargets']&gt;&gt;[0],
1522
- emitFile: (outPath: string, content: string) =&gt; void,
2277
+ units: IncrementalUnit[],
1523
2278
  ): void {
1524
2279
  const <span class="cstat-no" title="statement not covered" >zodBase = resolve(rootDir, config.baseDir ?? '.');</span>
1525
2280
  const allFiles = <span class="cstat-no" title="statement not covered" >[...inputs.contractRoots.map(<span class="fstat-no" title="function not covered" >r =&gt; <span class="cstat-no" title="statement not covered" >r</span>.file)</span>, ...inputs.opRoots.map(<span class="fstat-no" title="function not covered" >r =&gt; <span class="cstat-no" title="statement not covered" >r</span>.file)</span>];</span>
1526
2281
  const <span class="cstat-no" title="statement not covered" >commonRoot = commonDir(allFiles, rootDir);</span>
1527
2282
  const modelsWithInput = <span class="cstat-no" title="statement not covered" >inputs.modelsWithInput </span>as Set&lt;string&gt;;
1528
2283
  const modelsWithOutput = <span class="cstat-no" title="statement not covered" >inputs.modelsWithOutput </span>as Set&lt;string&gt;;
2284
+ const modelMap = <span class="cstat-no" title="statement not covered" >buildModelMap(inputs.contractRoots);</span>
2285
+ const subConfigKey = <span class="cstat-no" title="statement not covered" >stableSubConfig(config);</span>
1529
2286
  &nbsp;
1530
- // Pre-pass: register all model → outPath before generating, so cross-file imports resolve.
1531
2287
  const modelOutPaths = <span class="cstat-no" title="statement not covered" >new Map&lt;string, string&gt;();</span>
1532
- const entries: { ast: (typeof inputs.contractRoots)[number]; outPath: string }[] = <span class="cstat-no" title="statement not covered" >[];</span>
2288
+ const entries: { ast: ContractRootNode; outPath: string }[] = <span class="cstat-no" title="statement not covered" >[];</span>
1533
2289
  <span class="cstat-no" title="statement not covered" > for (const ast of inputs.contractRoots) {</span>
1534
2290
  const <span class="cstat-no" title="statement not covered" >outPath = computeContractOutPath(ast.file, zodBase, config.output, '.schema.ts', commonRoot, ast.meta);</span>
1535
2291
  <span class="cstat-no" title="statement not covered" > entries.push({ ast, outPath });</span>
@@ -1541,33 +2297,49 @@ function <span class="fstat-no" title="function not covered" >runZodGeneration(<
1541
2297
  }
1542
2298
  &nbsp;
1543
2299
  <span class="cstat-no" title="statement not covered" > for (const { ast, outPath } of entries) {</span>
1544
- const <span class="cstat-no" title="statement not covered" >content = generateContract(ast, {</span>
1545
- modelOutPaths,
1546
- currentOutPath: outPath,
1547
- modelsWithInput,
1548
- modelsWithOutput,
2300
+ const refs = <span class="cstat-no" title="statement not covered" >collectContractRootRefs(ast, modelMap);</span>
2301
+ const ownNames = <span class="cstat-no" title="statement not covered" >new Set(ast.models.map(<span class="fstat-no" title="function not covered" >m =&gt; <span class="cstat-no" title="statement not covered" >m</span>.name)</span>);</span>
2302
+ const <span class="cstat-no" title="statement not covered" >fingerprint = hashFingerprint({</span>
2303
+ kind: 'zod',
2304
+ v: TYPESCRIPT_CODEGEN_VERSION,
2305
+ outPath,
2306
+ root: ast,
2307
+ outPathSlice: sliceOutPathMap(refs, modelOutPaths, modelsWithInput, modelsWithOutput),
2308
+ modelsWithInput: sliceModelSet(refs, ownNames, modelsWithInput),
2309
+ modelsWithOutput: sliceModelSet(refs, ownNames, modelsWithOutput),
2310
+ sub: subConfigKey,
2311
+ });
2312
+ <span class="cstat-no" title="statement not covered" > units.push({</span>
2313
+ key: `zod::${outPath}`,
2314
+ fingerprint,
2315
+ render: <span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >[</span></span>
2316
+ {
2317
+ relativePath: outPath,
2318
+ content: generateContract(ast, { modelOutPaths, currentOutPath: outPath, modelsWithInput, modelsWithOutput }),
2319
+ },
2320
+ ],
1549
2321
  });
1550
- <span class="cstat-no" title="statement not covered" > emitFile(outPath, content);</span>
1551
2322
  }
1552
2323
  }
1553
2324
  &nbsp;
1554
- // ─── Types generation ──────────────────────────────────────────────────────
2325
+ // ─── Plain types sub-generator ─────────────────────────────────────────────
1555
2326
  &nbsp;
1556
- function <span class="fstat-no" title="function not covered" >runTypesGeneration(</span>
2327
+ function <span class="fstat-no" title="function not covered" >collectTypesOutput(</span>
1557
2328
  config: TypesConfig,
1558
2329
  rootDir: string,
1559
2330
  inputs: Parameters&lt;NonNullable&lt;ContractKitPlugin['generateTargets']&gt;&gt;[0],
1560
- emitFile: (outPath: string, content: string) =&gt; void,
2331
+ units: IncrementalUnit[],
1561
2332
  ): void {
1562
2333
  const <span class="cstat-no" title="statement not covered" >typesBase = resolve(rootDir, config.baseDir ?? '.');</span>
1563
2334
  const allFiles = <span class="cstat-no" title="statement not covered" >[...inputs.contractRoots.map(<span class="fstat-no" title="function not covered" >r =&gt; <span class="cstat-no" title="statement not covered" >r</span>.file)</span>, ...inputs.opRoots.map(<span class="fstat-no" title="function not covered" >r =&gt; <span class="cstat-no" title="statement not covered" >r</span>.file)</span>];</span>
1564
2335
  const <span class="cstat-no" title="statement not covered" >commonRoot = commonDir(allFiles, rootDir);</span>
1565
2336
  const modelsWithInput = <span class="cstat-no" title="statement not covered" >inputs.modelsWithInput </span>as Set&lt;string&gt;;
1566
2337
  const modelsWithOutput = <span class="cstat-no" title="statement not covered" >inputs.modelsWithOutput </span>as Set&lt;string&gt;;
2338
+ const modelMap = <span class="cstat-no" title="statement not covered" >buildModelMap(inputs.contractRoots);</span>
2339
+ const subConfigKey = <span class="cstat-no" title="statement not covered" >stableSubConfig(config);</span>
1567
2340
  &nbsp;
1568
- // Pre-pass: register all model → outPath before generating.
1569
2341
  const modelOutPaths = <span class="cstat-no" title="statement not covered" >new Map&lt;string, string&gt;();</span>
1570
- const entries: { ast: (typeof inputs.contractRoots)[number]; outPath: string }[] = <span class="cstat-no" title="statement not covered" >[];</span>
2342
+ const entries: { ast: ContractRootNode; outPath: string }[] = <span class="cstat-no" title="statement not covered" >[];</span>
1571
2343
  <span class="cstat-no" title="statement not covered" > for (const ast of inputs.contractRoots) {</span>
1572
2344
  const <span class="cstat-no" title="statement not covered" >outPath = computeContractOutPath(ast.file, typesBase, config.output, '.types.ts', commonRoot, ast.meta);</span>
1573
2345
  <span class="cstat-no" title="statement not covered" > entries.push({ ast, outPath });</span>
@@ -1579,67 +2351,72 @@ function <span class="fstat-no" title="function not covered" >runTypesGeneration
1579
2351
  }
1580
2352
  &nbsp;
1581
2353
  <span class="cstat-no" title="statement not covered" > for (const { ast, outPath } of entries) {</span>
1582
- const <span class="cstat-no" title="statement not covered" >content = generatePlainTypes(ast, {</span>
1583
- modelOutPaths,
1584
- currentOutPath: outPath,
1585
- modelsWithInput,
1586
- modelsWithOutput,
2354
+ const refs = <span class="cstat-no" title="statement not covered" >collectContractRootRefs(ast, modelMap);</span>
2355
+ const ownNames = <span class="cstat-no" title="statement not covered" >new Set(ast.models.map(<span class="fstat-no" title="function not covered" >m =&gt; <span class="cstat-no" title="statement not covered" >m</span>.name)</span>);</span>
2356
+ const <span class="cstat-no" title="statement not covered" >fingerprint = hashFingerprint({</span>
2357
+ kind: 'plain-types',
2358
+ v: TYPESCRIPT_CODEGEN_VERSION,
2359
+ outPath,
2360
+ root: ast,
2361
+ outPathSlice: sliceOutPathMap(refs, modelOutPaths, modelsWithInput, modelsWithOutput),
2362
+ modelsWithInput: sliceModelSet(refs, ownNames, modelsWithInput),
2363
+ modelsWithOutput: sliceModelSet(refs, ownNames, modelsWithOutput),
2364
+ sub: subConfigKey,
2365
+ });
2366
+ <span class="cstat-no" title="statement not covered" > units.push({</span>
2367
+ key: `plain-types::${outPath}`,
2368
+ fingerprint,
2369
+ render: <span class="fstat-no" title="function not covered" >() =&gt; <span class="cstat-no" title="statement not covered" >[</span></span>
2370
+ {
2371
+ relativePath: outPath,
2372
+ content: generatePlainTypes(ast, { modelOutPaths, currentOutPath: outPath, modelsWithInput, modelsWithOutput }),
2373
+ },
2374
+ ],
1587
2375
  });
1588
- <span class="cstat-no" title="statement not covered" > emitFile(outPath, content);</span>
1589
2376
  }
1590
2377
  }
1591
2378
  &nbsp;
1592
- // ─── Combined plugin ────────────────────────────────────────────────────────
2379
+ // ─── Manifest IO + cleanup ─────────────────────────────────────────────────
1593
2380
  &nbsp;
1594
- const plugin: ContractKitPlugin = {
1595
- name: 'typescript',
1596
- cacheKey: 'typescript',
1597
- async generateTargets(inputs, ctx) {
1598
- const config = ctx.options as TypescriptPluginConfig;
2381
+ function readManifest(manifestPath: string): IncrementalManifest {
2382
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (!existsSync(manifestPath)) return emptyIncrementalManifest(TYPESCRIPT_CODEGEN_VERSION);
2383
+ <span class="cstat-no" title="statement not covered" > try {</span>
2384
+ <span class="cstat-no" title="statement not covered" > return parseIncrementalManifest(readFileSync(manifestPath, 'utf-8'));</span>
2385
+ } catch {
2386
+ <span class="cstat-no" title="statement not covered" > return emptyIncrementalManifest(TYPESCRIPT_CODEGEN_VERSION);</span>
2387
+ }
2388
+ }
1599
2389
  &nbsp;
1600
- <span class="missing-if-branch" title="else path not taken" >E</span>if (config.server) {
1601
- runServerGeneration(config.server, ctx.rootDir, inputs, ctx.emitFile.bind(ctx));
2390
+ function deleteStalePaths(absPaths: string[]): void {
2391
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (absPaths.length === 0) return;
2392
+ const removedDirs = <span class="cstat-no" title="statement not covered" >new Set&lt;string&gt;();</span>
2393
+ <span class="cstat-no" title="statement not covered" > for (const abs of absPaths) {</span>
2394
+ <span class="cstat-no" title="statement not covered" > if (existsSync(abs)) {</span>
2395
+ <span class="cstat-no" title="statement not covered" > rmSync(abs, { force: true });</span>
2396
+ <span class="cstat-no" title="statement not covered" > removedDirs.add(dirname(abs));</span>
1602
2397
  }
1603
- <span class="missing-if-branch" title="if path not taken" >I</span>if (config.sdk) {
1604
- <span class="cstat-no" title="statement not covered" > runSdkGeneration(config.sdk, ctx.rootDir, inputs, ctx.emitFile.bind(ctx));</span>
1605
- }
1606
- <span class="missing-if-branch" title="if path not taken" >I</span>if (config.zod) {
1607
- <span class="cstat-no" title="statement not covered" > runZodGeneration(config.zod, ctx.rootDir, inputs, ctx.emitFile.bind(ctx));</span>
1608
- }
1609
- <span class="missing-if-branch" title="if path not taken" >I</span>if (config.types) {
1610
- <span class="cstat-no" title="statement not covered" > runTypesGeneration(config.types, ctx.rootDir, inputs, ctx.emitFile.bind(ctx));</span>
2398
+ }
2399
+ // Walk up affected dirs and remove if empty. Bounded — stops at filesystem root or first non-empty dir.
2400
+ <span class="cstat-no" title="statement not covered" > for (const dir of removedDirs) {</span>
2401
+ let current = <span class="cstat-no" title="statement not covered" >dir;</span>
2402
+ <span class="cstat-no" title="statement not covered" > while (current.length &gt; 1) {</span>
2403
+ <span class="cstat-no" title="statement not covered" > try {</span>
2404
+ <span class="cstat-no" title="statement not covered" > if (readdirSync(current).length === 0) {</span>
2405
+ <span class="cstat-no" title="statement not covered" > rmdirSync(current);</span>
2406
+ <span class="cstat-no" title="statement not covered" > current = dirname(current);</span>
2407
+ } else {
2408
+ <span class="cstat-no" title="statement not covered" > break;</span>
2409
+ }
2410
+ } catch {
2411
+ <span class="cstat-no" title="statement not covered" > break;</span>
2412
+ }
1611
2413
  }
1612
- },
1613
- };
1614
- &nbsp;
1615
- export default plugin;
1616
- &nbsp;
1617
- // ─── Factory: for programmatic use with explicit config ────────────────────
2414
+ }
2415
+ }
1618
2416
  &nbsp;
1619
- /**
1620
- * Build a `@contractkit/plugin-typescript` instance with explicit configuration, for
1621
- * programmatic use (tests, custom build scripts). Prefer the default export when loading
1622
- * via `contractkit.config.json`.
1623
- */
1624
- export function createTypescriptPlugin(config: TypescriptPluginConfig, rootDir: string): ContractKitPlugin {
1625
- return {
1626
- name: 'typescript',
1627
- cacheKey: `typescript:${JSON.stringify(config)}`,
1628
- async generateTargets(inputs, ctx) {
1629
- if (config.server) {
1630
- runServerGeneration(config.server, rootDir, inputs, ctx.emitFile.bind(ctx));
1631
- }
1632
- if (config.sdk) {
1633
- runSdkGeneration(config.sdk, rootDir, inputs, ctx.emitFile.bind(ctx));
1634
- }
1635
- <span class="missing-if-branch" title="if path not taken" >I</span>if (config.zod) {
1636
- <span class="cstat-no" title="statement not covered" > runZodGeneration(config.zod, rootDir, inputs, ctx.emitFile.bind(ctx));</span>
1637
- }
1638
- <span class="missing-if-branch" title="if path not taken" >I</span>if (config.types) {
1639
- <span class="cstat-no" title="statement not covered" > runTypesGeneration(config.types, rootDir, inputs, ctx.emitFile.bind(ctx));</span>
1640
- }
1641
- },
1642
- };
2417
+ /** Stringify a sub-config so it can participate in fingerprints. JSON.stringify gives stable output for typical config shapes. */
2418
+ function stableSubConfig(config: unknown): string {
2419
+ return JSON.stringify(config ?? <span class="branch-1 cbranch-no" title="branch not covered" >null);</span>
1643
2420
  }
1644
2421
  &nbsp;</pre></td></tr></table></pre>
1645
2422
 
@@ -1648,7 +2425,7 @@ export function createTypescriptPlugin(config: TypescriptPluginConfig, rootDir:
1648
2425
  <div class='footer quiet pad2 space-top1 center small'>
1649
2426
  Code coverage generated by
1650
2427
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1651
- at 2026-05-06T16:22:17.029Z
2428
+ at 2026-05-07T11:16:43.674Z
1652
2429
  </div>
1653
2430
  <script src="../prettify.js"></script>
1654
2431
  <script>