@aida-dev/metrics 0.3.1 → 0.5.0

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.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,196 @@
1
- import { CommitStream, Commit } from '@aida-dev/core';
1
+ import { Commit, CommitStream } from '@aida-dev/core';
2
2
  import { z } from 'zod';
3
3
 
4
+ declare const Attribution: z.ZodObject<{
5
+ commitsTotal: z.ZodNumber;
6
+ ai: z.ZodNumber;
7
+ human: z.ZodNumber;
8
+ unknown: z.ZodNumber;
9
+ coverage: z.ZodNumber;
10
+ defaultAttribution: z.ZodEnum<["ai", "human", "unknown"]>;
11
+ coverageThreshold: z.ZodNumber;
12
+ belowThreshold: z.ZodBoolean;
13
+ modes: z.ZodObject<{
14
+ none: z.ZodNumber;
15
+ autocomplete: z.ZodNumber;
16
+ assisted: z.ZodNumber;
17
+ agent: z.ZodNumber;
18
+ unknown: z.ZodNumber;
19
+ }, "strip", z.ZodTypeAny, {
20
+ unknown: number;
21
+ none: number;
22
+ autocomplete: number;
23
+ assisted: number;
24
+ agent: number;
25
+ }, {
26
+ unknown: number;
27
+ none: number;
28
+ autocomplete: number;
29
+ assisted: number;
30
+ agent: number;
31
+ }>;
32
+ modeEvidence: z.ZodObject<{
33
+ declared: z.ZodNumber;
34
+ inferred: z.ZodNumber;
35
+ none: z.ZodNumber;
36
+ }, "strip", z.ZodTypeAny, {
37
+ none: number;
38
+ declared: number;
39
+ inferred: number;
40
+ }, {
41
+ none: number;
42
+ declared: number;
43
+ inferred: number;
44
+ }>;
45
+ }, "strip", z.ZodTypeAny, {
46
+ ai: number;
47
+ human: number;
48
+ unknown: number;
49
+ commitsTotal: number;
50
+ coverage: number;
51
+ defaultAttribution: "ai" | "human" | "unknown";
52
+ coverageThreshold: number;
53
+ belowThreshold: boolean;
54
+ modes: {
55
+ unknown: number;
56
+ none: number;
57
+ autocomplete: number;
58
+ assisted: number;
59
+ agent: number;
60
+ };
61
+ modeEvidence: {
62
+ none: number;
63
+ declared: number;
64
+ inferred: number;
65
+ };
66
+ }, {
67
+ ai: number;
68
+ human: number;
69
+ unknown: number;
70
+ commitsTotal: number;
71
+ coverage: number;
72
+ defaultAttribution: "ai" | "human" | "unknown";
73
+ coverageThreshold: number;
74
+ belowThreshold: boolean;
75
+ modes: {
76
+ unknown: number;
77
+ none: number;
78
+ autocomplete: number;
79
+ assisted: number;
80
+ agent: number;
81
+ };
82
+ modeEvidence: {
83
+ none: number;
84
+ declared: number;
85
+ inferred: number;
86
+ };
87
+ }>;
88
+ type Attribution = z.infer<typeof Attribution>;
89
+ declare const AgeStats: z.ZodObject<{
90
+ commits: z.ZodNumber;
91
+ avgAgeDays: z.ZodNumber;
92
+ medianAgeDays: z.ZodNumber;
93
+ }, "strip", z.ZodTypeAny, {
94
+ commits: number;
95
+ avgAgeDays: number;
96
+ medianAgeDays: number;
97
+ }, {
98
+ commits: number;
99
+ avgAgeDays: number;
100
+ medianAgeDays: number;
101
+ }>;
102
+ type AgeStats = z.infer<typeof AgeStats>;
103
+ declare const FileCategory: z.ZodEnum<["source", "tests", "migrations", "config", "docs", "generated"]>;
104
+ type FileCategory = z.infer<typeof FileCategory>;
105
+ declare const CategoryCounts: z.ZodObject<{
106
+ source: z.ZodNumber;
107
+ tests: z.ZodNumber;
108
+ migrations: z.ZodNumber;
109
+ config: z.ZodNumber;
110
+ docs: z.ZodNumber;
111
+ generated: z.ZodNumber;
112
+ }, "strip", z.ZodTypeAny, {
113
+ source: number;
114
+ tests: number;
115
+ migrations: number;
116
+ config: number;
117
+ docs: number;
118
+ generated: number;
119
+ }, {
120
+ source: number;
121
+ tests: number;
122
+ migrations: number;
123
+ config: number;
124
+ docs: number;
125
+ generated: number;
126
+ }>;
127
+ type CategoryCounts = z.infer<typeof CategoryCounts>;
128
+ declare const CohortContext: z.ZodObject<{
129
+ age: z.ZodNullable<z.ZodObject<{
130
+ commits: z.ZodNumber;
131
+ avgAgeDays: z.ZodNumber;
132
+ medianAgeDays: z.ZodNumber;
133
+ }, "strip", z.ZodTypeAny, {
134
+ commits: number;
135
+ avgAgeDays: number;
136
+ medianAgeDays: number;
137
+ }, {
138
+ commits: number;
139
+ avgAgeDays: number;
140
+ medianAgeDays: number;
141
+ }>>;
142
+ taskMix: z.ZodNullable<z.ZodObject<{
143
+ source: z.ZodNumber;
144
+ tests: z.ZodNumber;
145
+ migrations: z.ZodNumber;
146
+ config: z.ZodNumber;
147
+ docs: z.ZodNumber;
148
+ generated: z.ZodNumber;
149
+ }, "strip", z.ZodTypeAny, {
150
+ source: number;
151
+ tests: number;
152
+ migrations: number;
153
+ config: number;
154
+ docs: number;
155
+ generated: number;
156
+ }, {
157
+ source: number;
158
+ tests: number;
159
+ migrations: number;
160
+ config: number;
161
+ docs: number;
162
+ generated: number;
163
+ }>>;
164
+ }, "strip", z.ZodTypeAny, {
165
+ age: {
166
+ commits: number;
167
+ avgAgeDays: number;
168
+ medianAgeDays: number;
169
+ } | null;
170
+ taskMix: {
171
+ source: number;
172
+ tests: number;
173
+ migrations: number;
174
+ config: number;
175
+ docs: number;
176
+ generated: number;
177
+ } | null;
178
+ }, {
179
+ age: {
180
+ commits: number;
181
+ avgAgeDays: number;
182
+ medianAgeDays: number;
183
+ } | null;
184
+ taskMix: {
185
+ source: number;
186
+ tests: number;
187
+ migrations: number;
188
+ config: number;
189
+ docs: number;
190
+ generated: number;
191
+ } | null;
192
+ }>;
193
+ type CohortContext = z.infer<typeof CohortContext>;
4
194
  declare const MergeRatio: z.ZodObject<{
5
195
  aiCommitsTotal: z.ZodNumber;
6
196
  aiCommitsMerged: z.ZodNumber;
@@ -67,27 +257,28 @@ declare const CohortMergeRatio: z.ZodObject<{
67
257
  commitsMerged: z.ZodNumber;
68
258
  mergeRatio: z.ZodNumber;
69
259
  }, "strip", z.ZodTypeAny, {
70
- mergeRatio: number;
71
260
  commitsTotal: number;
261
+ mergeRatio: number;
72
262
  commitsMerged: number;
73
263
  }, {
74
- mergeRatio: number;
75
264
  commitsTotal: number;
265
+ mergeRatio: number;
76
266
  commitsMerged: number;
77
267
  }>;
78
268
  type CohortMergeRatio = z.infer<typeof CohortMergeRatio>;
79
269
  declare const Baseline: z.ZodObject<{
270
+ assumed: z.ZodBoolean;
80
271
  mergeRatio: z.ZodObject<{
81
272
  commitsTotal: z.ZodNumber;
82
273
  commitsMerged: z.ZodNumber;
83
274
  mergeRatio: z.ZodNumber;
84
275
  }, "strip", z.ZodTypeAny, {
85
- mergeRatio: number;
86
276
  commitsTotal: number;
277
+ mergeRatio: number;
87
278
  commitsMerged: number;
88
279
  }, {
89
- mergeRatio: number;
90
280
  commitsTotal: number;
281
+ mergeRatio: number;
91
282
  commitsMerged: number;
92
283
  }>;
93
284
  persistence: z.ZodObject<{
@@ -138,8 +329,8 @@ declare const Baseline: z.ZodObject<{
138
329
  }>;
139
330
  }, "strip", z.ZodTypeAny, {
140
331
  mergeRatio: {
141
- mergeRatio: number;
142
332
  commitsTotal: number;
333
+ mergeRatio: number;
143
334
  commitsMerged: number;
144
335
  };
145
336
  persistence: {
@@ -154,10 +345,11 @@ declare const Baseline: z.ZodObject<{
154
345
  d90_plus: number;
155
346
  };
156
347
  };
348
+ assumed: boolean;
157
349
  }, {
158
350
  mergeRatio: {
159
- mergeRatio: number;
160
351
  commitsTotal: number;
352
+ mergeRatio: number;
161
353
  commitsMerged: number;
162
354
  };
163
355
  persistence: {
@@ -172,6 +364,7 @@ declare const Baseline: z.ZodObject<{
172
364
  d90_plus: number;
173
365
  };
174
366
  };
367
+ assumed: boolean;
175
368
  }>;
176
369
  type Baseline = z.infer<typeof Baseline>;
177
370
  declare const Delta: z.ZodObject<{
@@ -202,6 +395,90 @@ declare const Metrics: z.ZodObject<{
202
395
  }>;
203
396
  repoPath: z.ZodString;
204
397
  defaultBranch: z.ZodString;
398
+ attribution: z.ZodObject<{
399
+ commitsTotal: z.ZodNumber;
400
+ ai: z.ZodNumber;
401
+ human: z.ZodNumber;
402
+ unknown: z.ZodNumber;
403
+ coverage: z.ZodNumber;
404
+ defaultAttribution: z.ZodEnum<["ai", "human", "unknown"]>;
405
+ coverageThreshold: z.ZodNumber;
406
+ belowThreshold: z.ZodBoolean;
407
+ modes: z.ZodObject<{
408
+ none: z.ZodNumber;
409
+ autocomplete: z.ZodNumber;
410
+ assisted: z.ZodNumber;
411
+ agent: z.ZodNumber;
412
+ unknown: z.ZodNumber;
413
+ }, "strip", z.ZodTypeAny, {
414
+ unknown: number;
415
+ none: number;
416
+ autocomplete: number;
417
+ assisted: number;
418
+ agent: number;
419
+ }, {
420
+ unknown: number;
421
+ none: number;
422
+ autocomplete: number;
423
+ assisted: number;
424
+ agent: number;
425
+ }>;
426
+ modeEvidence: z.ZodObject<{
427
+ declared: z.ZodNumber;
428
+ inferred: z.ZodNumber;
429
+ none: z.ZodNumber;
430
+ }, "strip", z.ZodTypeAny, {
431
+ none: number;
432
+ declared: number;
433
+ inferred: number;
434
+ }, {
435
+ none: number;
436
+ declared: number;
437
+ inferred: number;
438
+ }>;
439
+ }, "strip", z.ZodTypeAny, {
440
+ ai: number;
441
+ human: number;
442
+ unknown: number;
443
+ commitsTotal: number;
444
+ coverage: number;
445
+ defaultAttribution: "ai" | "human" | "unknown";
446
+ coverageThreshold: number;
447
+ belowThreshold: boolean;
448
+ modes: {
449
+ unknown: number;
450
+ none: number;
451
+ autocomplete: number;
452
+ assisted: number;
453
+ agent: number;
454
+ };
455
+ modeEvidence: {
456
+ none: number;
457
+ declared: number;
458
+ inferred: number;
459
+ };
460
+ }, {
461
+ ai: number;
462
+ human: number;
463
+ unknown: number;
464
+ commitsTotal: number;
465
+ coverage: number;
466
+ defaultAttribution: "ai" | "human" | "unknown";
467
+ coverageThreshold: number;
468
+ belowThreshold: boolean;
469
+ modes: {
470
+ unknown: number;
471
+ none: number;
472
+ autocomplete: number;
473
+ assisted: number;
474
+ agent: number;
475
+ };
476
+ modeEvidence: {
477
+ none: number;
478
+ declared: number;
479
+ inferred: number;
480
+ };
481
+ }>;
205
482
  mergeRatio: z.ZodObject<{
206
483
  aiCommitsTotal: z.ZodNumber;
207
484
  aiCommitsMerged: z.ZodNumber;
@@ -261,18 +538,213 @@ declare const Metrics: z.ZodObject<{
261
538
  d90_plus: number;
262
539
  };
263
540
  }>;
264
- baseline: z.ZodObject<{
541
+ cohorts: z.ZodObject<{
542
+ ai: z.ZodObject<{
543
+ age: z.ZodNullable<z.ZodObject<{
544
+ commits: z.ZodNumber;
545
+ avgAgeDays: z.ZodNumber;
546
+ medianAgeDays: z.ZodNumber;
547
+ }, "strip", z.ZodTypeAny, {
548
+ commits: number;
549
+ avgAgeDays: number;
550
+ medianAgeDays: number;
551
+ }, {
552
+ commits: number;
553
+ avgAgeDays: number;
554
+ medianAgeDays: number;
555
+ }>>;
556
+ taskMix: z.ZodNullable<z.ZodObject<{
557
+ source: z.ZodNumber;
558
+ tests: z.ZodNumber;
559
+ migrations: z.ZodNumber;
560
+ config: z.ZodNumber;
561
+ docs: z.ZodNumber;
562
+ generated: z.ZodNumber;
563
+ }, "strip", z.ZodTypeAny, {
564
+ source: number;
565
+ tests: number;
566
+ migrations: number;
567
+ config: number;
568
+ docs: number;
569
+ generated: number;
570
+ }, {
571
+ source: number;
572
+ tests: number;
573
+ migrations: number;
574
+ config: number;
575
+ docs: number;
576
+ generated: number;
577
+ }>>;
578
+ }, "strip", z.ZodTypeAny, {
579
+ age: {
580
+ commits: number;
581
+ avgAgeDays: number;
582
+ medianAgeDays: number;
583
+ } | null;
584
+ taskMix: {
585
+ source: number;
586
+ tests: number;
587
+ migrations: number;
588
+ config: number;
589
+ docs: number;
590
+ generated: number;
591
+ } | null;
592
+ }, {
593
+ age: {
594
+ commits: number;
595
+ avgAgeDays: number;
596
+ medianAgeDays: number;
597
+ } | null;
598
+ taskMix: {
599
+ source: number;
600
+ tests: number;
601
+ migrations: number;
602
+ config: number;
603
+ docs: number;
604
+ generated: number;
605
+ } | null;
606
+ }>;
607
+ baseline: z.ZodObject<{
608
+ age: z.ZodNullable<z.ZodObject<{
609
+ commits: z.ZodNumber;
610
+ avgAgeDays: z.ZodNumber;
611
+ medianAgeDays: z.ZodNumber;
612
+ }, "strip", z.ZodTypeAny, {
613
+ commits: number;
614
+ avgAgeDays: number;
615
+ medianAgeDays: number;
616
+ }, {
617
+ commits: number;
618
+ avgAgeDays: number;
619
+ medianAgeDays: number;
620
+ }>>;
621
+ taskMix: z.ZodNullable<z.ZodObject<{
622
+ source: z.ZodNumber;
623
+ tests: z.ZodNumber;
624
+ migrations: z.ZodNumber;
625
+ config: z.ZodNumber;
626
+ docs: z.ZodNumber;
627
+ generated: z.ZodNumber;
628
+ }, "strip", z.ZodTypeAny, {
629
+ source: number;
630
+ tests: number;
631
+ migrations: number;
632
+ config: number;
633
+ docs: number;
634
+ generated: number;
635
+ }, {
636
+ source: number;
637
+ tests: number;
638
+ migrations: number;
639
+ config: number;
640
+ docs: number;
641
+ generated: number;
642
+ }>>;
643
+ }, "strip", z.ZodTypeAny, {
644
+ age: {
645
+ commits: number;
646
+ avgAgeDays: number;
647
+ medianAgeDays: number;
648
+ } | null;
649
+ taskMix: {
650
+ source: number;
651
+ tests: number;
652
+ migrations: number;
653
+ config: number;
654
+ docs: number;
655
+ generated: number;
656
+ } | null;
657
+ }, {
658
+ age: {
659
+ commits: number;
660
+ avgAgeDays: number;
661
+ medianAgeDays: number;
662
+ } | null;
663
+ taskMix: {
664
+ source: number;
665
+ tests: number;
666
+ migrations: number;
667
+ config: number;
668
+ docs: number;
669
+ generated: number;
670
+ } | null;
671
+ }>;
672
+ }, "strip", z.ZodTypeAny, {
673
+ ai: {
674
+ age: {
675
+ commits: number;
676
+ avgAgeDays: number;
677
+ medianAgeDays: number;
678
+ } | null;
679
+ taskMix: {
680
+ source: number;
681
+ tests: number;
682
+ migrations: number;
683
+ config: number;
684
+ docs: number;
685
+ generated: number;
686
+ } | null;
687
+ };
688
+ baseline: {
689
+ age: {
690
+ commits: number;
691
+ avgAgeDays: number;
692
+ medianAgeDays: number;
693
+ } | null;
694
+ taskMix: {
695
+ source: number;
696
+ tests: number;
697
+ migrations: number;
698
+ config: number;
699
+ docs: number;
700
+ generated: number;
701
+ } | null;
702
+ };
703
+ }, {
704
+ ai: {
705
+ age: {
706
+ commits: number;
707
+ avgAgeDays: number;
708
+ medianAgeDays: number;
709
+ } | null;
710
+ taskMix: {
711
+ source: number;
712
+ tests: number;
713
+ migrations: number;
714
+ config: number;
715
+ docs: number;
716
+ generated: number;
717
+ } | null;
718
+ };
719
+ baseline: {
720
+ age: {
721
+ commits: number;
722
+ avgAgeDays: number;
723
+ medianAgeDays: number;
724
+ } | null;
725
+ taskMix: {
726
+ source: number;
727
+ tests: number;
728
+ migrations: number;
729
+ config: number;
730
+ docs: number;
731
+ generated: number;
732
+ } | null;
733
+ };
734
+ }>;
735
+ baseline: z.ZodNullable<z.ZodObject<{
736
+ assumed: z.ZodBoolean;
265
737
  mergeRatio: z.ZodObject<{
266
738
  commitsTotal: z.ZodNumber;
267
739
  commitsMerged: z.ZodNumber;
268
740
  mergeRatio: z.ZodNumber;
269
741
  }, "strip", z.ZodTypeAny, {
270
- mergeRatio: number;
271
742
  commitsTotal: number;
743
+ mergeRatio: number;
272
744
  commitsMerged: number;
273
745
  }, {
274
- mergeRatio: number;
275
746
  commitsTotal: number;
747
+ mergeRatio: number;
276
748
  commitsMerged: number;
277
749
  }>;
278
750
  persistence: z.ZodObject<{
@@ -323,8 +795,8 @@ declare const Metrics: z.ZodObject<{
323
795
  }>;
324
796
  }, "strip", z.ZodTypeAny, {
325
797
  mergeRatio: {
326
- mergeRatio: number;
327
798
  commitsTotal: number;
799
+ mergeRatio: number;
328
800
  commitsMerged: number;
329
801
  };
330
802
  persistence: {
@@ -339,10 +811,11 @@ declare const Metrics: z.ZodObject<{
339
811
  d90_plus: number;
340
812
  };
341
813
  };
814
+ assumed: boolean;
342
815
  }, {
343
816
  mergeRatio: {
344
- mergeRatio: number;
345
817
  commitsTotal: number;
818
+ mergeRatio: number;
346
819
  commitsMerged: number;
347
820
  };
348
821
  persistence: {
@@ -357,8 +830,9 @@ declare const Metrics: z.ZodObject<{
357
830
  d90_plus: number;
358
831
  };
359
832
  };
360
- }>;
361
- delta: z.ZodObject<{
833
+ assumed: boolean;
834
+ }>>;
835
+ delta: z.ZodNullable<z.ZodObject<{
362
836
  mergeRatio: z.ZodNumber;
363
837
  avgPersistenceDays: z.ZodNumber;
364
838
  medianPersistenceDays: z.ZodNumber;
@@ -370,7 +844,7 @@ declare const Metrics: z.ZodObject<{
370
844
  mergeRatio: number;
371
845
  avgPersistenceDays: number;
372
846
  medianPersistenceDays: number;
373
- }>;
847
+ }>>;
374
848
  caveats: z.ZodArray<z.ZodString, "many">;
375
849
  }, "strip", z.ZodTypeAny, {
376
850
  generatedAt: string;
@@ -380,6 +854,28 @@ declare const Metrics: z.ZodObject<{
380
854
  };
381
855
  repoPath: string;
382
856
  defaultBranch: string;
857
+ attribution: {
858
+ ai: number;
859
+ human: number;
860
+ unknown: number;
861
+ commitsTotal: number;
862
+ coverage: number;
863
+ defaultAttribution: "ai" | "human" | "unknown";
864
+ coverageThreshold: number;
865
+ belowThreshold: boolean;
866
+ modes: {
867
+ unknown: number;
868
+ none: number;
869
+ autocomplete: number;
870
+ assisted: number;
871
+ agent: number;
872
+ };
873
+ modeEvidence: {
874
+ none: number;
875
+ declared: number;
876
+ inferred: number;
877
+ };
878
+ };
383
879
  mergeRatio: {
384
880
  aiCommitsTotal: number;
385
881
  aiCommitsMerged: number;
@@ -397,10 +893,42 @@ declare const Metrics: z.ZodObject<{
397
893
  d90_plus: number;
398
894
  };
399
895
  };
896
+ cohorts: {
897
+ ai: {
898
+ age: {
899
+ commits: number;
900
+ avgAgeDays: number;
901
+ medianAgeDays: number;
902
+ } | null;
903
+ taskMix: {
904
+ source: number;
905
+ tests: number;
906
+ migrations: number;
907
+ config: number;
908
+ docs: number;
909
+ generated: number;
910
+ } | null;
911
+ };
912
+ baseline: {
913
+ age: {
914
+ commits: number;
915
+ avgAgeDays: number;
916
+ medianAgeDays: number;
917
+ } | null;
918
+ taskMix: {
919
+ source: number;
920
+ tests: number;
921
+ migrations: number;
922
+ config: number;
923
+ docs: number;
924
+ generated: number;
925
+ } | null;
926
+ };
927
+ };
400
928
  baseline: {
401
929
  mergeRatio: {
402
- mergeRatio: number;
403
930
  commitsTotal: number;
931
+ mergeRatio: number;
404
932
  commitsMerged: number;
405
933
  };
406
934
  persistence: {
@@ -415,12 +943,13 @@ declare const Metrics: z.ZodObject<{
415
943
  d90_plus: number;
416
944
  };
417
945
  };
418
- };
946
+ assumed: boolean;
947
+ } | null;
419
948
  delta: {
420
949
  mergeRatio: number;
421
950
  avgPersistenceDays: number;
422
951
  medianPersistenceDays: number;
423
- };
952
+ } | null;
424
953
  caveats: string[];
425
954
  }, {
426
955
  generatedAt: string;
@@ -430,6 +959,28 @@ declare const Metrics: z.ZodObject<{
430
959
  };
431
960
  repoPath: string;
432
961
  defaultBranch: string;
962
+ attribution: {
963
+ ai: number;
964
+ human: number;
965
+ unknown: number;
966
+ commitsTotal: number;
967
+ coverage: number;
968
+ defaultAttribution: "ai" | "human" | "unknown";
969
+ coverageThreshold: number;
970
+ belowThreshold: boolean;
971
+ modes: {
972
+ unknown: number;
973
+ none: number;
974
+ autocomplete: number;
975
+ assisted: number;
976
+ agent: number;
977
+ };
978
+ modeEvidence: {
979
+ none: number;
980
+ declared: number;
981
+ inferred: number;
982
+ };
983
+ };
433
984
  mergeRatio: {
434
985
  aiCommitsTotal: number;
435
986
  aiCommitsMerged: number;
@@ -447,10 +998,42 @@ declare const Metrics: z.ZodObject<{
447
998
  d90_plus: number;
448
999
  };
449
1000
  };
1001
+ cohorts: {
1002
+ ai: {
1003
+ age: {
1004
+ commits: number;
1005
+ avgAgeDays: number;
1006
+ medianAgeDays: number;
1007
+ } | null;
1008
+ taskMix: {
1009
+ source: number;
1010
+ tests: number;
1011
+ migrations: number;
1012
+ config: number;
1013
+ docs: number;
1014
+ generated: number;
1015
+ } | null;
1016
+ };
1017
+ baseline: {
1018
+ age: {
1019
+ commits: number;
1020
+ avgAgeDays: number;
1021
+ medianAgeDays: number;
1022
+ } | null;
1023
+ taskMix: {
1024
+ source: number;
1025
+ tests: number;
1026
+ migrations: number;
1027
+ config: number;
1028
+ docs: number;
1029
+ generated: number;
1030
+ } | null;
1031
+ };
1032
+ };
450
1033
  baseline: {
451
1034
  mergeRatio: {
452
- mergeRatio: number;
453
1035
  commitsTotal: number;
1036
+ mergeRatio: number;
454
1037
  commitsMerged: number;
455
1038
  };
456
1039
  persistence: {
@@ -465,22 +1048,31 @@ declare const Metrics: z.ZodObject<{
465
1048
  d90_plus: number;
466
1049
  };
467
1050
  };
468
- };
1051
+ assumed: boolean;
1052
+ } | null;
469
1053
  delta: {
470
1054
  mergeRatio: number;
471
1055
  avgPersistenceDays: number;
472
1056
  medianPersistenceDays: number;
473
- };
1057
+ } | null;
474
1058
  caveats: string[];
475
1059
  }>;
476
1060
  type Metrics = z.infer<typeof Metrics>;
477
1061
 
478
- declare function calculateMergeRatio(commitStream: CommitStream): MergeRatio;
479
- declare function calculateBaselineMergeRatio(commitStream: CommitStream): CohortMergeRatio;
1062
+ declare function calculateAgeStats(commits: Commit[], now: Date): AgeStats | null;
1063
+ declare function categorizeFile(path: string): FileCategory;
1064
+ declare function calculateCategoryCounts(commits: Commit[]): CategoryCounts | null;
1065
+
1066
+ declare function calculateMergeRatio(commitStream: CommitStream, isTarget?: (commit: Commit) => boolean): MergeRatio;
1067
+ declare function calculateBaselineMergeRatio(commitStream: CommitStream, isTarget?: (commit: Commit) => boolean): CohortMergeRatio;
480
1068
 
481
1069
  declare function calculatePersistence(commitStream: CommitStream, isTarget?: (commit: Commit) => boolean): Persistence;
482
- declare function calculateBaselinePersistence(commitStream: CommitStream): Persistence;
1070
+ declare function calculateBaselinePersistence(commitStream: CommitStream, isTarget?: (commit: Commit) => boolean): Persistence;
483
1071
 
484
- declare function calculateMetrics(commitStream: CommitStream): Metrics;
1072
+ interface MetricsOptions {
1073
+ defaultAttribution?: 'ai' | 'human' | 'unknown';
1074
+ coverageThreshold?: number;
1075
+ }
1076
+ declare function calculateMetrics(commitStream: CommitStream, options?: MetricsOptions): Metrics;
485
1077
 
486
- export { Baseline, CohortMergeRatio, Delta, MergeRatio, Metrics, Persistence, calculateBaselineMergeRatio, calculateBaselinePersistence, calculateMergeRatio, calculateMetrics, calculatePersistence };
1078
+ export { AgeStats, Attribution, Baseline, CategoryCounts, CohortContext, CohortMergeRatio, Delta, FileCategory, MergeRatio, Metrics, type MetricsOptions, Persistence, calculateAgeStats, calculateBaselineMergeRatio, calculateBaselinePersistence, calculateCategoryCounts, calculateMergeRatio, calculateMetrics, calculatePersistence, categorizeFile };
package/dist/index.js CHANGED
@@ -1,6 +1,63 @@
1
1
  // src/index.ts
2
2
  import { formatISODate } from "@aida-dev/core";
3
3
 
4
+ // src/cohort.ts
5
+ import { daysBetween } from "@aida-dev/core";
6
+ function calculateAgeStats(commits, now) {
7
+ if (commits.length === 0) {
8
+ return null;
9
+ }
10
+ const ages = commits.map((commit) => daysBetween(new Date(commit.authorDate), now)).sort((a, b) => a - b);
11
+ const avg = ages.reduce((sum, days) => sum + days, 0) / ages.length;
12
+ const median = ages.length % 2 === 0 ? (ages[ages.length / 2 - 1] + ages[ages.length / 2]) / 2 : ages[Math.floor(ages.length / 2)];
13
+ return {
14
+ commits: commits.length,
15
+ avgAgeDays: Math.round(avg * 100) / 100,
16
+ medianAgeDays: Math.round(median * 100) / 100
17
+ };
18
+ }
19
+ function categorizeFile(path) {
20
+ const lower = path.toLowerCase();
21
+ const base = lower.split("/").pop() ?? lower;
22
+ if (/^(pnpm-lock\.yaml|package-lock\.json|yarn\.lock|bun\.lockb?|composer\.lock|cargo\.lock|gemfile\.lock|poetry\.lock|changelog(\.[a-z]+)?)$/.test(
23
+ base
24
+ ) || /\.(snap|min\.js|min\.css)$/.test(base) || /(^|\/)(dist|build|out|coverage|\.next|node_modules)\//.test(lower)) {
25
+ return "generated";
26
+ }
27
+ if (/(^|\/)(__tests__|__mocks__|tests?|spec|e2e|cypress)\//.test(lower) || /\.(test|spec)\.[a-z]+$/.test(base)) {
28
+ return "tests";
29
+ }
30
+ if (/(^|\/)migrations?\//.test(lower)) {
31
+ return "migrations";
32
+ }
33
+ if (/\.(md|mdx|rst|adoc|txt)$/.test(base) || /(^|\/)docs?\//.test(lower)) {
34
+ return "docs";
35
+ }
36
+ if (base.startsWith(".") || /\.(json|ya?ml|toml|ini|cfg|conf|env|properties)$/.test(base) || /\.config\.[a-z]+$/.test(base) || /(^|\/)(\.github|\.circleci|\.vscode|config)\//.test(lower) || /^(dockerfile|makefile|tsconfig.*|eslint.*|prettier.*)/.test(base)) {
37
+ return "config";
38
+ }
39
+ return "source";
40
+ }
41
+ function calculateCategoryCounts(commits) {
42
+ if (commits.length === 0) {
43
+ return null;
44
+ }
45
+ const counts = {
46
+ source: 0,
47
+ tests: 0,
48
+ migrations: 0,
49
+ config: 0,
50
+ docs: 0,
51
+ generated: 0
52
+ };
53
+ for (const commit of commits) {
54
+ for (const file of commit.stats.files) {
55
+ counts[categorizeFile(file.path)]++;
56
+ }
57
+ }
58
+ return counts;
59
+ }
60
+
4
61
  // src/merge-ratio.ts
5
62
  function computeMergeRatio(commits) {
6
63
  const merged = commits.filter((commit) => commit.inDefaultBranchAncestry);
@@ -10,8 +67,8 @@ function computeMergeRatio(commits) {
10
67
  mergeRatio: commits.length > 0 ? merged.length / commits.length : 0
11
68
  };
12
69
  }
13
- function calculateMergeRatio(commitStream) {
14
- const aiCommits = commitStream.commits.filter((commit) => commit.tags.ai);
70
+ function calculateMergeRatio(commitStream, isTarget = (commit) => commit.tags.attribution === "ai") {
71
+ const aiCommits = commitStream.commits.filter(isTarget);
15
72
  const result = computeMergeRatio(aiCommits);
16
73
  return {
17
74
  aiCommitsTotal: result.commitsTotal,
@@ -19,14 +76,14 @@ function calculateMergeRatio(commitStream) {
19
76
  mergeRatio: result.mergeRatio
20
77
  };
21
78
  }
22
- function calculateBaselineMergeRatio(commitStream) {
23
- const nonAICommits = commitStream.commits.filter((commit) => !commit.tags.ai);
24
- return computeMergeRatio(nonAICommits);
79
+ function calculateBaselineMergeRatio(commitStream, isTarget = (commit) => commit.tags.attribution === "human") {
80
+ const baselineCommits = commitStream.commits.filter(isTarget);
81
+ return computeMergeRatio(baselineCommits);
25
82
  }
26
83
 
27
84
  // src/persistence.ts
28
- import { daysBetween } from "@aida-dev/core";
29
- function calculatePersistence(commitStream, isTarget = (commit) => commit.tags.ai) {
85
+ import { daysBetween as daysBetween2 } from "@aida-dev/core";
86
+ function calculatePersistence(commitStream, isTarget = (commit) => commit.tags.attribution === "ai") {
30
87
  const targetCommits = commitStream.commits.filter(isTarget);
31
88
  if (targetCommits.length === 0) {
32
89
  return {
@@ -74,7 +131,7 @@ function calculatePersistence(commitStream, isTarget = (commit) => commit.tags.a
74
131
  }
75
132
  const persistenceDays = [];
76
133
  for (const lifecycle of fileLifecycles.values()) {
77
- lifecycle.persistenceDays = daysBetween(lifecycle.firstTargetCommitDate, lifecycle.lastSeenDate);
134
+ lifecycle.persistenceDays = daysBetween2(lifecycle.firstTargetCommitDate, lifecycle.lastSeenDate);
78
135
  persistenceDays.push(lifecycle.persistenceDays);
79
136
  }
80
137
  const avgDays = persistenceDays.length > 0 ? persistenceDays.reduce((sum, days) => sum + days, 0) / persistenceDays.length : 0;
@@ -95,12 +152,37 @@ function calculatePersistence(commitStream, isTarget = (commit) => commit.tags.a
95
152
  buckets
96
153
  };
97
154
  }
98
- function calculateBaselinePersistence(commitStream) {
99
- return calculatePersistence(commitStream, (commit) => !commit.tags.ai);
155
+ function calculateBaselinePersistence(commitStream, isTarget = (commit) => commit.tags.attribution === "human") {
156
+ return calculatePersistence(commitStream, isTarget);
100
157
  }
101
158
 
102
159
  // src/schema/metrics.ts
103
160
  import { z } from "zod";
161
+ var Attribution = z.object({
162
+ commitsTotal: z.number().int().nonnegative(),
163
+ ai: z.number().int().nonnegative(),
164
+ human: z.number().int().nonnegative(),
165
+ unknown: z.number().int().nonnegative(),
166
+ coverage: z.number().min(0).max(1),
167
+ // (ai + human) / total
168
+ defaultAttribution: z.enum(["ai", "human", "unknown"]),
169
+ // prior applied to unknown commits
170
+ coverageThreshold: z.number().min(0).max(1),
171
+ belowThreshold: z.boolean(),
172
+ // Autonomy axis (#25): commit counts per mode and per mode-evidence level
173
+ modes: z.object({
174
+ none: z.number().int().nonnegative(),
175
+ autocomplete: z.number().int().nonnegative(),
176
+ assisted: z.number().int().nonnegative(),
177
+ agent: z.number().int().nonnegative(),
178
+ unknown: z.number().int().nonnegative()
179
+ }),
180
+ modeEvidence: z.object({
181
+ declared: z.number().int().nonnegative(),
182
+ inferred: z.number().int().nonnegative(),
183
+ none: z.number().int().nonnegative()
184
+ })
185
+ });
104
186
  var MergeRatio = z.object({
105
187
  aiCommitsTotal: z.number().int().nonnegative(),
106
188
  aiCommitsMerged: z.number().int().nonnegative(),
@@ -118,12 +200,40 @@ var Persistence = z.object({
118
200
  d90_plus: z.number().int().nonnegative()
119
201
  })
120
202
  });
203
+ var AgeStats = z.object({
204
+ commits: z.number().int().nonnegative(),
205
+ avgAgeDays: z.number().nonnegative(),
206
+ medianAgeDays: z.number().nonnegative()
207
+ });
208
+ var FileCategory = z.enum([
209
+ "source",
210
+ "tests",
211
+ "migrations",
212
+ "config",
213
+ "docs",
214
+ "generated"
215
+ ]);
216
+ var CategoryCounts = z.object({
217
+ source: z.number().int().nonnegative(),
218
+ tests: z.number().int().nonnegative(),
219
+ migrations: z.number().int().nonnegative(),
220
+ config: z.number().int().nonnegative(),
221
+ docs: z.number().int().nonnegative(),
222
+ generated: z.number().int().nonnegative()
223
+ });
224
+ var CohortContext = z.object({
225
+ age: AgeStats.nullable(),
226
+ taskMix: CategoryCounts.nullable()
227
+ });
121
228
  var CohortMergeRatio = z.object({
122
229
  commitsTotal: z.number().int().nonnegative(),
123
230
  commitsMerged: z.number().int().nonnegative(),
124
231
  mergeRatio: z.number().min(0).max(1)
125
232
  });
126
233
  var Baseline = z.object({
234
+ // True when the cohort includes 'unknown' commits via defaultAttribution:
235
+ // the baseline is an assumption, not observed attribution.
236
+ assumed: z.boolean(),
127
237
  mergeRatio: CohortMergeRatio,
128
238
  persistence: Persistence
129
239
  });
@@ -140,10 +250,19 @@ var Metrics = z.object({
140
250
  }),
141
251
  repoPath: z.string(),
142
252
  defaultBranch: z.string(),
253
+ attribution: Attribution,
143
254
  mergeRatio: MergeRatio,
144
255
  persistence: Persistence,
145
- baseline: Baseline,
146
- delta: Delta,
256
+ // Fairness context (#29, #36): age and task mix per cohort, so consumers
257
+ // can judge whether the AI-vs-baseline comparison is apples-to-apples.
258
+ cohorts: z.object({
259
+ ai: CohortContext,
260
+ baseline: CohortContext
261
+ }),
262
+ // Null when no commit is attributed 'human' and no defaultAttribution prior
263
+ // assigns the unknowns: AIDA does not invent a comparison cohort.
264
+ baseline: Baseline.nullable(),
265
+ delta: Delta.nullable(),
147
266
  caveats: z.array(z.string())
148
267
  });
149
268
 
@@ -152,22 +271,81 @@ function round(value, decimals) {
152
271
  const factor = 10 ** decimals;
153
272
  return Math.round(value * factor) / factor;
154
273
  }
155
- function calculateMetrics(commitStream) {
156
- const mergeRatio = calculateMergeRatio(commitStream);
157
- const persistence = calculatePersistence(commitStream);
158
- const baselineMergeRatio = calculateBaselineMergeRatio(commitStream);
159
- const baselinePersistence = calculateBaselinePersistence(commitStream);
160
- const delta = {
161
- mergeRatio: round(mergeRatio.mergeRatio - baselineMergeRatio.mergeRatio, 4),
162
- avgPersistenceDays: round(persistence.avgDays - baselinePersistence.avgDays, 2),
163
- medianPersistenceDays: round(persistence.medianDays - baselinePersistence.medianDays, 2)
274
+ function calculateMetrics(commitStream, options = {}) {
275
+ const { defaultAttribution = "unknown", coverageThreshold = 0.7 } = options;
276
+ const counts = { ai: 0, human: 0, unknown: 0 };
277
+ const modes = { none: 0, autocomplete: 0, assisted: 0, agent: 0, unknown: 0 };
278
+ const modeEvidence = { declared: 0, inferred: 0, none: 0 };
279
+ for (const commit of commitStream.commits) {
280
+ counts[commit.tags.attribution]++;
281
+ modes[commit.tags.mode]++;
282
+ modeEvidence[commit.tags.modeEvidence]++;
283
+ }
284
+ const total = commitStream.commits.length;
285
+ const coverage = total > 0 ? (counts.ai + counts.human) / total : 0;
286
+ const attribution = {
287
+ commitsTotal: total,
288
+ ai: counts.ai,
289
+ human: counts.human,
290
+ unknown: counts.unknown,
291
+ coverage: round(coverage, 4),
292
+ defaultAttribution,
293
+ coverageThreshold,
294
+ belowThreshold: coverage < coverageThreshold,
295
+ modes,
296
+ modeEvidence
297
+ };
298
+ const isExcluded = (commit) => commit.tags.sources.includes("manifest:excluded");
299
+ const isAI = (commit) => commit.tags.attribution === "ai" || defaultAttribution === "ai" && commit.tags.attribution === "unknown" && !isExcluded(commit);
300
+ const isBaseline = (commit) => commit.tags.attribution === "human" || defaultAttribution === "human" && commit.tags.attribution === "unknown" && !isExcluded(commit);
301
+ const mergeRatio = calculateMergeRatio(commitStream, isAI);
302
+ const persistence = calculatePersistence(commitStream, isAI);
303
+ const now = /* @__PURE__ */ new Date();
304
+ const aiCommits = commitStream.commits.filter(isAI);
305
+ const baselineCommits = commitStream.commits.filter(isBaseline);
306
+ const cohorts = {
307
+ ai: {
308
+ age: calculateAgeStats(aiCommits, now),
309
+ taskMix: calculateCategoryCounts(aiCommits)
310
+ },
311
+ baseline: {
312
+ age: calculateAgeStats(baselineCommits, now),
313
+ taskMix: calculateCategoryCounts(baselineCommits)
314
+ }
164
315
  };
316
+ const baselineSize = baselineCommits.length;
317
+ const baselineAssumed = defaultAttribution === "human" && counts.unknown > 0;
318
+ const baseline = baselineSize > 0 ? {
319
+ assumed: baselineAssumed,
320
+ mergeRatio: calculateBaselineMergeRatio(commitStream, isBaseline),
321
+ persistence: calculateBaselinePersistence(commitStream, isBaseline)
322
+ } : null;
323
+ const delta = baseline ? {
324
+ mergeRatio: round(mergeRatio.mergeRatio - baseline.mergeRatio.mergeRatio, 4),
325
+ avgPersistenceDays: round(persistence.avgDays - baseline.persistence.avgDays, 2),
326
+ medianPersistenceDays: round(
327
+ persistence.medianDays - baseline.persistence.medianDays,
328
+ 2
329
+ )
330
+ } : null;
165
331
  const caveats = [
332
+ `Attribution coverage is ${(coverage * 100).toFixed(1)}%: metrics only describe commits whose provenance is known.`,
166
333
  "Persistence is file-level, not line-level.",
334
+ "Persistence comparisons are only meaningful between cohorts of similar age and task mix \u2014 check the cohorts section before reading the delta.",
167
335
  "Merge ratio: commits from all branches checked against default branch ancestry. Squash merges may undercount unmerged commits.",
168
- "AI tagging uses heuristic patterns; false positives/negatives possible.",
169
- "Baseline covers all non-AI-tagged commits; undetected AI usage may leak into the baseline."
336
+ "Time-windowed collection (--since) also windows the ancestry check: commits merged into the default branch before the window may appear unmerged.",
337
+ "AI tagging uses heuristic patterns; false positives/negatives possible."
170
338
  ];
339
+ if (baseline?.assumed) {
340
+ caveats.push(
341
+ `Baseline includes ${counts.unknown} unattributed commit(s) assumed human via defaultAttribution \u2014 undetected AI usage may leak into it.`
342
+ );
343
+ }
344
+ if (!baseline) {
345
+ caveats.push(
346
+ 'No baseline: no commits are attributed as human. Set defaultAttribution to "human" in .aida.json if unattributed commits in this repo are human-authored.'
347
+ );
348
+ }
171
349
  return {
172
350
  generatedAt: formatISODate(/* @__PURE__ */ new Date()),
173
351
  window: {
@@ -176,27 +354,34 @@ function calculateMetrics(commitStream) {
176
354
  },
177
355
  repoPath: commitStream.repoPath,
178
356
  defaultBranch: commitStream.defaultBranch,
357
+ attribution,
179
358
  mergeRatio,
180
359
  persistence,
181
- baseline: {
182
- mergeRatio: baselineMergeRatio,
183
- persistence: baselinePersistence
184
- },
360
+ cohorts,
361
+ baseline,
185
362
  delta,
186
363
  caveats
187
364
  };
188
365
  }
189
366
  export {
367
+ AgeStats,
368
+ Attribution,
190
369
  Baseline,
370
+ CategoryCounts,
371
+ CohortContext,
191
372
  CohortMergeRatio,
192
373
  Delta,
374
+ FileCategory,
193
375
  MergeRatio,
194
376
  Metrics,
195
377
  Persistence,
378
+ calculateAgeStats,
196
379
  calculateBaselineMergeRatio,
197
380
  calculateBaselinePersistence,
381
+ calculateCategoryCounts,
198
382
  calculateMergeRatio,
199
383
  calculateMetrics,
200
- calculatePersistence
384
+ calculatePersistence,
385
+ categorizeFile
201
386
  };
202
387
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/merge-ratio.ts","../src/persistence.ts","../src/schema/metrics.ts"],"sourcesContent":["import { CommitStream, formatISODate } from '@aida-dev/core';\nimport { calculateBaselineMergeRatio, calculateMergeRatio } from './merge-ratio.js';\nimport { calculateBaselinePersistence, calculatePersistence } from './persistence.js';\nimport { Metrics } from './schema/metrics.js';\n\nexport * from './schema/metrics.js';\nexport * from './merge-ratio.js';\nexport * from './persistence.js';\n\nfunction round(value: number, decimals: number): number {\n const factor = 10 ** decimals;\n return Math.round(value * factor) / factor;\n}\n\nexport function calculateMetrics(commitStream: CommitStream): Metrics {\n const mergeRatio = calculateMergeRatio(commitStream);\n const persistence = calculatePersistence(commitStream);\n const baselineMergeRatio = calculateBaselineMergeRatio(commitStream);\n const baselinePersistence = calculateBaselinePersistence(commitStream);\n\n const delta = {\n mergeRatio: round(mergeRatio.mergeRatio - baselineMergeRatio.mergeRatio, 4),\n avgPersistenceDays: round(persistence.avgDays - baselinePersistence.avgDays, 2),\n medianPersistenceDays: round(persistence.medianDays - baselinePersistence.medianDays, 2),\n };\n\n const caveats = [\n 'Persistence is file-level, not line-level.',\n 'Merge ratio: commits from all branches checked against default branch ancestry. Squash merges may undercount unmerged commits.',\n 'AI tagging uses heuristic patterns; false positives/negatives possible.',\n 'Baseline covers all non-AI-tagged commits; undetected AI usage may leak into the baseline.',\n ];\n\n return {\n generatedAt: formatISODate(new Date()),\n window: {\n since: commitStream.since,\n until: commitStream.until,\n },\n repoPath: commitStream.repoPath,\n defaultBranch: commitStream.defaultBranch,\n mergeRatio,\n persistence,\n baseline: {\n mergeRatio: baselineMergeRatio,\n persistence: baselinePersistence,\n },\n delta,\n caveats,\n };\n}\n","import { Commit, CommitStream } from '@aida-dev/core';\nimport { CohortMergeRatio, MergeRatio } from './schema/metrics.js';\n\nfunction computeMergeRatio(commits: Commit[]): CohortMergeRatio {\n const merged = commits.filter((commit) => commit.inDefaultBranchAncestry);\n\n return {\n commitsTotal: commits.length,\n commitsMerged: merged.length,\n mergeRatio: commits.length > 0 ? merged.length / commits.length : 0,\n };\n}\n\nexport function calculateMergeRatio(commitStream: CommitStream): MergeRatio {\n const aiCommits = commitStream.commits.filter((commit) => commit.tags.ai);\n const result = computeMergeRatio(aiCommits);\n\n return {\n aiCommitsTotal: result.commitsTotal,\n aiCommitsMerged: result.commitsMerged,\n mergeRatio: result.mergeRatio,\n };\n}\n\nexport function calculateBaselineMergeRatio(commitStream: CommitStream): CohortMergeRatio {\n const nonAICommits = commitStream.commits.filter((commit) => !commit.tags.ai);\n return computeMergeRatio(nonAICommits);\n}\n","import { Commit, CommitStream, daysBetween } from '@aida-dev/core';\nimport { Persistence } from './schema/metrics.js';\n\ninterface FileLifecycle {\n path: string;\n firstTargetCommitDate: Date;\n lastSeenDate: Date;\n persistenceDays: number;\n}\n\nexport function calculatePersistence(\n commitStream: CommitStream,\n isTarget: (commit: Commit) => boolean = (commit) => commit.tags.ai\n): Persistence {\n const targetCommits = commitStream.commits.filter(isTarget);\n\n if (targetCommits.length === 0) {\n return {\n commitsConsidered: 0,\n avgDays: 0,\n medianDays: 0,\n buckets: {\n d0_1: 0,\n d2_7: 0,\n d8_30: 0,\n d31_90: 0,\n d90_plus: 0,\n },\n };\n }\n\n // Group files by path and track their lifecycle\n const fileLifecycles = new Map<string, FileLifecycle>();\n\n // Sort commits by date (oldest first)\n const sortedCommits = [...commitStream.commits].sort(\n (a, b) => new Date(a.authorDate).getTime() - new Date(b.authorDate).getTime()\n );\n\n // Track first target commit per file\n for (const commit of sortedCommits) {\n if (isTarget(commit)) {\n const commitDate = new Date(commit.authorDate);\n\n for (const file of commit.stats.files) {\n if (!fileLifecycles.has(file.path)) {\n fileLifecycles.set(file.path, {\n path: file.path,\n firstTargetCommitDate: commitDate,\n lastSeenDate: commitDate,\n persistenceDays: 0,\n });\n }\n }\n }\n }\n\n // Track last seen date for each file\n for (const commit of sortedCommits) {\n const commitDate = new Date(commit.authorDate);\n\n for (const file of commit.stats.files) {\n const lifecycle = fileLifecycles.get(file.path);\n if (lifecycle) {\n // Update last seen date if file is not deleted\n if (file.status !== 'deleted') {\n lifecycle.lastSeenDate = commitDate;\n }\n }\n }\n }\n\n // Calculate persistence days for each file\n const persistenceDays: number[] = [];\n\n for (const lifecycle of fileLifecycles.values()) {\n lifecycle.persistenceDays = daysBetween(lifecycle.firstTargetCommitDate, lifecycle.lastSeenDate);\n persistenceDays.push(lifecycle.persistenceDays);\n }\n\n // Calculate statistics\n const avgDays =\n persistenceDays.length > 0\n ? persistenceDays.reduce((sum, days) => sum + days, 0) / persistenceDays.length\n : 0;\n\n const sortedDays = [...persistenceDays].sort((a, b) => a - b);\n const medianDays =\n sortedDays.length > 0\n ? sortedDays.length % 2 === 0\n ? (sortedDays[sortedDays.length / 2 - 1] + sortedDays[sortedDays.length / 2]) / 2\n : sortedDays[Math.floor(sortedDays.length / 2)]\n : 0;\n\n // Calculate buckets\n const buckets = {\n d0_1: persistenceDays.filter((days) => days <= 1).length,\n d2_7: persistenceDays.filter((days) => days >= 2 && days <= 7).length,\n d8_30: persistenceDays.filter((days) => days >= 8 && days <= 30).length,\n d31_90: persistenceDays.filter((days) => days >= 31 && days <= 90).length,\n d90_plus: persistenceDays.filter((days) => days > 90).length,\n };\n\n return {\n commitsConsidered: targetCommits.length,\n avgDays: Math.round(avgDays * 100) / 100, // Round to 2 decimal places\n medianDays: Math.round(medianDays * 100) / 100,\n buckets,\n };\n}\n\nexport function calculateBaselinePersistence(commitStream: CommitStream): Persistence {\n return calculatePersistence(commitStream, (commit) => !commit.tags.ai);\n}\n","import { z } from 'zod';\n\nexport const MergeRatio = z.object({\n aiCommitsTotal: z.number().int().nonnegative(),\n aiCommitsMerged: z.number().int().nonnegative(),\n mergeRatio: z.number().min(0).max(1),\n});\n\nexport const Persistence = z.object({\n commitsConsidered: z.number().int().nonnegative(),\n avgDays: z.number().nonnegative(),\n medianDays: z.number().nonnegative(),\n buckets: z.object({\n d0_1: z.number().int().nonnegative(),\n d2_7: z.number().int().nonnegative(),\n d8_30: z.number().int().nonnegative(),\n d31_90: z.number().int().nonnegative(),\n d90_plus: z.number().int().nonnegative(),\n }),\n});\n\nexport const CohortMergeRatio = z.object({\n commitsTotal: z.number().int().nonnegative(),\n commitsMerged: z.number().int().nonnegative(),\n mergeRatio: z.number().min(0).max(1),\n});\n\nexport const Baseline = z.object({\n mergeRatio: CohortMergeRatio,\n persistence: Persistence,\n});\n\nexport const Delta = z.object({\n mergeRatio: z.number(),\n avgPersistenceDays: z.number(),\n medianPersistenceDays: z.number(),\n});\n\nexport const Metrics = z.object({\n generatedAt: z.string().datetime(),\n window: z.object({\n since: z.string().optional(),\n until: z.string().optional(),\n }),\n repoPath: z.string(),\n defaultBranch: z.string(),\n mergeRatio: MergeRatio,\n persistence: Persistence,\n baseline: Baseline,\n delta: Delta,\n caveats: z.array(z.string()),\n});\n\nexport type MergeRatio = z.infer<typeof MergeRatio>;\nexport type Persistence = z.infer<typeof Persistence>;\nexport type CohortMergeRatio = z.infer<typeof CohortMergeRatio>;\nexport type Baseline = z.infer<typeof Baseline>;\nexport type Delta = z.infer<typeof Delta>;\nexport type Metrics = z.infer<typeof Metrics>;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/cohort.ts","../src/merge-ratio.ts","../src/persistence.ts","../src/schema/metrics.ts"],"sourcesContent":["import { Commit, CommitStream, formatISODate } from '@aida-dev/core';\nimport { calculateAgeStats, calculateCategoryCounts } from './cohort.js';\nimport { calculateBaselineMergeRatio, calculateMergeRatio } from './merge-ratio.js';\nimport { calculateBaselinePersistence, calculatePersistence } from './persistence.js';\nimport { Attribution, Metrics } from './schema/metrics.js';\n\nexport * from './schema/metrics.js';\nexport * from './cohort.js';\nexport * from './merge-ratio.js';\nexport * from './persistence.js';\n\nexport interface MetricsOptions {\n // Prior applied to 'unknown' commits: which cohort (if any) they join.\n defaultAttribution?: 'ai' | 'human' | 'unknown';\n coverageThreshold?: number;\n}\n\nfunction round(value: number, decimals: number): number {\n const factor = 10 ** decimals;\n return Math.round(value * factor) / factor;\n}\n\nexport function calculateMetrics(\n commitStream: CommitStream,\n options: MetricsOptions = {}\n): Metrics {\n const { defaultAttribution = 'unknown', coverageThreshold = 0.7 } = options;\n\n const counts = { ai: 0, human: 0, unknown: 0 };\n const modes = { none: 0, autocomplete: 0, assisted: 0, agent: 0, unknown: 0 };\n const modeEvidence = { declared: 0, inferred: 0, none: 0 };\n for (const commit of commitStream.commits) {\n counts[commit.tags.attribution]++;\n modes[commit.tags.mode]++;\n modeEvidence[commit.tags.modeEvidence]++;\n }\n const total = commitStream.commits.length;\n const coverage = total > 0 ? (counts.ai + counts.human) / total : 0;\n\n const attribution: Attribution = {\n commitsTotal: total,\n ai: counts.ai,\n human: counts.human,\n unknown: counts.unknown,\n coverage: round(coverage, 4),\n defaultAttribution,\n coverageThreshold,\n belowThreshold: coverage < coverageThreshold,\n modes,\n modeEvidence,\n };\n\n // Cohort membership: unknown commits join a cohort only via the prior.\n // Manifest-excluded commits (release bots, merges) never do — they were\n // excluded precisely to stay out of both cohorts.\n const isExcluded = (commit: Commit) => commit.tags.sources.includes('manifest:excluded');\n const isAI = (commit: Commit) =>\n commit.tags.attribution === 'ai' ||\n (defaultAttribution === 'ai' && commit.tags.attribution === 'unknown' && !isExcluded(commit));\n const isBaseline = (commit: Commit) =>\n commit.tags.attribution === 'human' ||\n (defaultAttribution === 'human' &&\n commit.tags.attribution === 'unknown' &&\n !isExcluded(commit));\n\n const mergeRatio = calculateMergeRatio(commitStream, isAI);\n const persistence = calculatePersistence(commitStream, isAI);\n\n // Fairness context (#29, #36): cohort age and task mix\n const now = new Date();\n const aiCommits = commitStream.commits.filter(isAI);\n const baselineCommits = commitStream.commits.filter(isBaseline);\n const cohorts = {\n ai: {\n age: calculateAgeStats(aiCommits, now),\n taskMix: calculateCategoryCounts(aiCommits),\n },\n baseline: {\n age: calculateAgeStats(baselineCommits, now),\n taskMix: calculateCategoryCounts(baselineCommits),\n },\n };\n\n // No baseline cohort → no baseline, no delta. AIDA does not invent a\n // comparison out of unattributed commits.\n const baselineSize = baselineCommits.length;\n const baselineAssumed = defaultAttribution === 'human' && counts.unknown > 0;\n\n const baseline =\n baselineSize > 0\n ? {\n assumed: baselineAssumed,\n mergeRatio: calculateBaselineMergeRatio(commitStream, isBaseline),\n persistence: calculateBaselinePersistence(commitStream, isBaseline),\n }\n : null;\n\n const delta = baseline\n ? {\n mergeRatio: round(mergeRatio.mergeRatio - baseline.mergeRatio.mergeRatio, 4),\n avgPersistenceDays: round(persistence.avgDays - baseline.persistence.avgDays, 2),\n medianPersistenceDays: round(\n persistence.medianDays - baseline.persistence.medianDays,\n 2\n ),\n }\n : null;\n\n const caveats = [\n `Attribution coverage is ${(coverage * 100).toFixed(1)}%: metrics only describe commits whose provenance is known.`,\n 'Persistence is file-level, not line-level.',\n 'Persistence comparisons are only meaningful between cohorts of similar age and task mix — check the cohorts section before reading the delta.',\n 'Merge ratio: commits from all branches checked against default branch ancestry. Squash merges may undercount unmerged commits.',\n 'Time-windowed collection (--since) also windows the ancestry check: commits merged into the default branch before the window may appear unmerged.',\n 'AI tagging uses heuristic patterns; false positives/negatives possible.',\n ];\n if (baseline?.assumed) {\n caveats.push(\n `Baseline includes ${counts.unknown} unattributed commit(s) assumed human via defaultAttribution — undetected AI usage may leak into it.`\n );\n }\n if (!baseline) {\n caveats.push(\n 'No baseline: no commits are attributed as human. Set defaultAttribution to \"human\" in .aida.json if unattributed commits in this repo are human-authored.'\n );\n }\n\n return {\n generatedAt: formatISODate(new Date()),\n window: {\n since: commitStream.since,\n until: commitStream.until,\n },\n repoPath: commitStream.repoPath,\n defaultBranch: commitStream.defaultBranch,\n attribution,\n mergeRatio,\n persistence,\n cohorts,\n baseline,\n delta,\n caveats,\n };\n}\n","import { Commit, daysBetween } from '@aida-dev/core';\nimport { AgeStats, CategoryCounts, FileCategory } from './schema/metrics.js';\n\n// Cohort age (#29): persistence comparisons between cohorts of different\n// ages are misleading — old commits have had more time to accumulate\n// survival. Reporting each cohort's age lets consumers judge fairness.\nexport function calculateAgeStats(commits: Commit[], now: Date): AgeStats | null {\n if (commits.length === 0) {\n return null;\n }\n\n const ages = commits\n .map((commit) => daysBetween(new Date(commit.authorDate), now))\n .sort((a, b) => a - b);\n\n const avg = ages.reduce((sum, days) => sum + days, 0) / ages.length;\n const median =\n ages.length % 2 === 0\n ? (ages[ages.length / 2 - 1] + ages[ages.length / 2]) / 2\n : ages[Math.floor(ages.length / 2)];\n\n return {\n commits: commits.length,\n avgAgeDays: Math.round(avg * 100) / 100,\n medianAgeDays: Math.round(median * 100) / 100,\n };\n}\n\n// File categorization (#36, step 1): AI is often pointed at boilerplate,\n// tests, and migrations, which survive longer because nobody touches them.\n// Reporting each cohort's category mix shows when an AI-vs-baseline\n// comparison is apples-to-oranges. Order matters: first match wins.\nexport function categorizeFile(path: string): FileCategory {\n const lower = path.toLowerCase();\n const base = lower.split('/').pop() ?? lower;\n\n // Generated artifacts (lockfiles, changelogs, snapshots, build output)\n if (\n /^(pnpm-lock\\.yaml|package-lock\\.json|yarn\\.lock|bun\\.lockb?|composer\\.lock|cargo\\.lock|gemfile\\.lock|poetry\\.lock|changelog(\\.[a-z]+)?)$/.test(\n base\n ) ||\n /\\.(snap|min\\.js|min\\.css)$/.test(base) ||\n /(^|\\/)(dist|build|out|coverage|\\.next|node_modules)\\//.test(lower)\n ) {\n return 'generated';\n }\n\n if (\n /(^|\\/)(__tests__|__mocks__|tests?|spec|e2e|cypress)\\//.test(lower) ||\n /\\.(test|spec)\\.[a-z]+$/.test(base)\n ) {\n return 'tests';\n }\n\n if (/(^|\\/)migrations?\\//.test(lower)) {\n return 'migrations';\n }\n\n if (/\\.(md|mdx|rst|adoc|txt)$/.test(base) || /(^|\\/)docs?\\//.test(lower)) {\n return 'docs';\n }\n\n if (\n base.startsWith('.') ||\n /\\.(json|ya?ml|toml|ini|cfg|conf|env|properties)$/.test(base) ||\n /\\.config\\.[a-z]+$/.test(base) ||\n /(^|\\/)(\\.github|\\.circleci|\\.vscode|config)\\//.test(lower) ||\n /^(dockerfile|makefile|tsconfig.*|eslint.*|prettier.*)/.test(base)\n ) {\n return 'config';\n }\n\n return 'source';\n}\n\nexport function calculateCategoryCounts(commits: Commit[]): CategoryCounts | null {\n if (commits.length === 0) {\n return null;\n }\n\n const counts: CategoryCounts = {\n source: 0,\n tests: 0,\n migrations: 0,\n config: 0,\n docs: 0,\n generated: 0,\n };\n\n for (const commit of commits) {\n for (const file of commit.stats.files) {\n counts[categorizeFile(file.path)]++;\n }\n }\n\n return counts;\n}\n","import { Commit, CommitStream } from '@aida-dev/core';\nimport { CohortMergeRatio, MergeRatio } from './schema/metrics.js';\n\nfunction computeMergeRatio(commits: Commit[]): CohortMergeRatio {\n const merged = commits.filter((commit) => commit.inDefaultBranchAncestry);\n\n return {\n commitsTotal: commits.length,\n commitsMerged: merged.length,\n mergeRatio: commits.length > 0 ? merged.length / commits.length : 0,\n };\n}\n\nexport function calculateMergeRatio(\n commitStream: CommitStream,\n isTarget: (commit: Commit) => boolean = (commit) => commit.tags.attribution === 'ai'\n): MergeRatio {\n const aiCommits = commitStream.commits.filter(isTarget);\n const result = computeMergeRatio(aiCommits);\n\n return {\n aiCommitsTotal: result.commitsTotal,\n aiCommitsMerged: result.commitsMerged,\n mergeRatio: result.mergeRatio,\n };\n}\n\nexport function calculateBaselineMergeRatio(\n commitStream: CommitStream,\n isTarget: (commit: Commit) => boolean = (commit) => commit.tags.attribution === 'human'\n): CohortMergeRatio {\n const baselineCommits = commitStream.commits.filter(isTarget);\n return computeMergeRatio(baselineCommits);\n}\n","import { Commit, CommitStream, daysBetween } from '@aida-dev/core';\nimport { Persistence } from './schema/metrics.js';\n\ninterface FileLifecycle {\n path: string;\n firstTargetCommitDate: Date;\n lastSeenDate: Date;\n persistenceDays: number;\n}\n\nexport function calculatePersistence(\n commitStream: CommitStream,\n isTarget: (commit: Commit) => boolean = (commit) => commit.tags.attribution === 'ai'\n): Persistence {\n const targetCommits = commitStream.commits.filter(isTarget);\n\n if (targetCommits.length === 0) {\n return {\n commitsConsidered: 0,\n avgDays: 0,\n medianDays: 0,\n buckets: {\n d0_1: 0,\n d2_7: 0,\n d8_30: 0,\n d31_90: 0,\n d90_plus: 0,\n },\n };\n }\n\n // Group files by path and track their lifecycle\n const fileLifecycles = new Map<string, FileLifecycle>();\n\n // Sort commits by date (oldest first)\n const sortedCommits = [...commitStream.commits].sort(\n (a, b) => new Date(a.authorDate).getTime() - new Date(b.authorDate).getTime()\n );\n\n // Track first target commit per file\n for (const commit of sortedCommits) {\n if (isTarget(commit)) {\n const commitDate = new Date(commit.authorDate);\n\n for (const file of commit.stats.files) {\n if (!fileLifecycles.has(file.path)) {\n fileLifecycles.set(file.path, {\n path: file.path,\n firstTargetCommitDate: commitDate,\n lastSeenDate: commitDate,\n persistenceDays: 0,\n });\n }\n }\n }\n }\n\n // Track last seen date for each file\n for (const commit of sortedCommits) {\n const commitDate = new Date(commit.authorDate);\n\n for (const file of commit.stats.files) {\n const lifecycle = fileLifecycles.get(file.path);\n if (lifecycle) {\n // Update last seen date if file is not deleted\n if (file.status !== 'deleted') {\n lifecycle.lastSeenDate = commitDate;\n }\n }\n }\n }\n\n // Calculate persistence days for each file\n const persistenceDays: number[] = [];\n\n for (const lifecycle of fileLifecycles.values()) {\n lifecycle.persistenceDays = daysBetween(lifecycle.firstTargetCommitDate, lifecycle.lastSeenDate);\n persistenceDays.push(lifecycle.persistenceDays);\n }\n\n // Calculate statistics\n const avgDays =\n persistenceDays.length > 0\n ? persistenceDays.reduce((sum, days) => sum + days, 0) / persistenceDays.length\n : 0;\n\n const sortedDays = [...persistenceDays].sort((a, b) => a - b);\n const medianDays =\n sortedDays.length > 0\n ? sortedDays.length % 2 === 0\n ? (sortedDays[sortedDays.length / 2 - 1] + sortedDays[sortedDays.length / 2]) / 2\n : sortedDays[Math.floor(sortedDays.length / 2)]\n : 0;\n\n // Calculate buckets\n const buckets = {\n d0_1: persistenceDays.filter((days) => days <= 1).length,\n d2_7: persistenceDays.filter((days) => days >= 2 && days <= 7).length,\n d8_30: persistenceDays.filter((days) => days >= 8 && days <= 30).length,\n d31_90: persistenceDays.filter((days) => days >= 31 && days <= 90).length,\n d90_plus: persistenceDays.filter((days) => days > 90).length,\n };\n\n return {\n commitsConsidered: targetCommits.length,\n avgDays: Math.round(avgDays * 100) / 100, // Round to 2 decimal places\n medianDays: Math.round(medianDays * 100) / 100,\n buckets,\n };\n}\n\nexport function calculateBaselinePersistence(\n commitStream: CommitStream,\n isTarget: (commit: Commit) => boolean = (commit) => commit.tags.attribution === 'human'\n): Persistence {\n return calculatePersistence(commitStream, isTarget);\n}\n","import { z } from 'zod';\n\n// Attribution coverage (#34): the headline metric. Every other number in this\n// file is only as trustworthy as this block says it is.\nexport const Attribution = z.object({\n commitsTotal: z.number().int().nonnegative(),\n ai: z.number().int().nonnegative(),\n human: z.number().int().nonnegative(),\n unknown: z.number().int().nonnegative(),\n coverage: z.number().min(0).max(1), // (ai + human) / total\n defaultAttribution: z.enum(['ai', 'human', 'unknown']), // prior applied to unknown commits\n coverageThreshold: z.number().min(0).max(1),\n belowThreshold: z.boolean(),\n // Autonomy axis (#25): commit counts per mode and per mode-evidence level\n modes: z.object({\n none: z.number().int().nonnegative(),\n autocomplete: z.number().int().nonnegative(),\n assisted: z.number().int().nonnegative(),\n agent: z.number().int().nonnegative(),\n unknown: z.number().int().nonnegative(),\n }),\n modeEvidence: z.object({\n declared: z.number().int().nonnegative(),\n inferred: z.number().int().nonnegative(),\n none: z.number().int().nonnegative(),\n }),\n});\n\nexport const MergeRatio = z.object({\n aiCommitsTotal: z.number().int().nonnegative(),\n aiCommitsMerged: z.number().int().nonnegative(),\n mergeRatio: z.number().min(0).max(1),\n});\n\nexport const Persistence = z.object({\n commitsConsidered: z.number().int().nonnegative(),\n avgDays: z.number().nonnegative(),\n medianDays: z.number().nonnegative(),\n buckets: z.object({\n d0_1: z.number().int().nonnegative(),\n d2_7: z.number().int().nonnegative(),\n d8_30: z.number().int().nonnegative(),\n d31_90: z.number().int().nonnegative(),\n d90_plus: z.number().int().nonnegative(),\n }),\n});\n\n// Cohort age (#29): context for judging whether a persistence comparison\n// between cohorts is fair — older cohorts accumulate survival by default.\nexport const AgeStats = z.object({\n commits: z.number().int().nonnegative(),\n avgAgeDays: z.number().nonnegative(),\n medianAgeDays: z.number().nonnegative(),\n});\n\n// Task mix (#36): what kind of files each cohort touched. A persistence\n// comparison is only meaningful when the mixes are similar.\nexport const FileCategory = z.enum([\n 'source',\n 'tests',\n 'migrations',\n 'config',\n 'docs',\n 'generated',\n]);\n\nexport const CategoryCounts = z.object({\n source: z.number().int().nonnegative(),\n tests: z.number().int().nonnegative(),\n migrations: z.number().int().nonnegative(),\n config: z.number().int().nonnegative(),\n docs: z.number().int().nonnegative(),\n generated: z.number().int().nonnegative(),\n});\n\nexport const CohortContext = z.object({\n age: AgeStats.nullable(),\n taskMix: CategoryCounts.nullable(),\n});\n\nexport const CohortMergeRatio = z.object({\n commitsTotal: z.number().int().nonnegative(),\n commitsMerged: z.number().int().nonnegative(),\n mergeRatio: z.number().min(0).max(1),\n});\n\nexport const Baseline = z.object({\n // True when the cohort includes 'unknown' commits via defaultAttribution:\n // the baseline is an assumption, not observed attribution.\n assumed: z.boolean(),\n mergeRatio: CohortMergeRatio,\n persistence: Persistence,\n});\n\nexport const Delta = z.object({\n mergeRatio: z.number(),\n avgPersistenceDays: z.number(),\n medianPersistenceDays: z.number(),\n});\n\nexport const Metrics = z.object({\n generatedAt: z.string().datetime(),\n window: z.object({\n since: z.string().optional(),\n until: z.string().optional(),\n }),\n repoPath: z.string(),\n defaultBranch: z.string(),\n attribution: Attribution,\n mergeRatio: MergeRatio,\n persistence: Persistence,\n // Fairness context (#29, #36): age and task mix per cohort, so consumers\n // can judge whether the AI-vs-baseline comparison is apples-to-apples.\n cohorts: z.object({\n ai: CohortContext,\n baseline: CohortContext,\n }),\n // Null when no commit is attributed 'human' and no defaultAttribution prior\n // assigns the unknowns: AIDA does not invent a comparison cohort.\n baseline: Baseline.nullable(),\n delta: Delta.nullable(),\n caveats: z.array(z.string()),\n});\n\nexport type Attribution = z.infer<typeof Attribution>;\nexport type AgeStats = z.infer<typeof AgeStats>;\nexport type FileCategory = z.infer<typeof FileCategory>;\nexport type CategoryCounts = z.infer<typeof CategoryCounts>;\nexport type CohortContext = z.infer<typeof CohortContext>;\nexport type MergeRatio = z.infer<typeof MergeRatio>;\nexport type Persistence = z.infer<typeof Persistence>;\nexport type CohortMergeRatio = z.infer<typeof CohortMergeRatio>;\nexport type Baseline = z.infer<typeof Baseline>;\nexport type Delta = z.infer<typeof Delta>;\nexport type Metrics = z.infer<typeof Metrics>;\n"],"mappings":";AAAA,SAA+B,qBAAqB;;;ACApD,SAAiB,mBAAmB;AAM7B,SAAS,kBAAkB,SAAmB,KAA4B;AAC/E,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,QACV,IAAI,CAAC,WAAW,YAAY,IAAI,KAAK,OAAO,UAAU,GAAG,GAAG,CAAC,EAC7D,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAEvB,QAAM,MAAM,KAAK,OAAO,CAAC,KAAK,SAAS,MAAM,MAAM,CAAC,IAAI,KAAK;AAC7D,QAAM,SACJ,KAAK,SAAS,MAAM,KACf,KAAK,KAAK,SAAS,IAAI,CAAC,IAAI,KAAK,KAAK,SAAS,CAAC,KAAK,IACtD,KAAK,KAAK,MAAM,KAAK,SAAS,CAAC,CAAC;AAEtC,SAAO;AAAA,IACL,SAAS,QAAQ;AAAA,IACjB,YAAY,KAAK,MAAM,MAAM,GAAG,IAAI;AAAA,IACpC,eAAe,KAAK,MAAM,SAAS,GAAG,IAAI;AAAA,EAC5C;AACF;AAMO,SAAS,eAAe,MAA4B;AACzD,QAAM,QAAQ,KAAK,YAAY;AAC/B,QAAM,OAAO,MAAM,MAAM,GAAG,EAAE,IAAI,KAAK;AAGvC,MACE,2IAA2I;AAAA,IACzI;AAAA,EACF,KACA,6BAA6B,KAAK,IAAI,KACtC,wDAAwD,KAAK,KAAK,GAClE;AACA,WAAO;AAAA,EACT;AAEA,MACE,wDAAwD,KAAK,KAAK,KAClE,yBAAyB,KAAK,IAAI,GAClC;AACA,WAAO;AAAA,EACT;AAEA,MAAI,sBAAsB,KAAK,KAAK,GAAG;AACrC,WAAO;AAAA,EACT;AAEA,MAAI,2BAA2B,KAAK,IAAI,KAAK,gBAAgB,KAAK,KAAK,GAAG;AACxE,WAAO;AAAA,EACT;AAEA,MACE,KAAK,WAAW,GAAG,KACnB,mDAAmD,KAAK,IAAI,KAC5D,oBAAoB,KAAK,IAAI,KAC7B,gDAAgD,KAAK,KAAK,KAC1D,wDAAwD,KAAK,IAAI,GACjE;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,wBAAwB,SAA0C;AAChF,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,SAAyB;AAAA,IAC7B,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,WAAW;AAAA,EACb;AAEA,aAAW,UAAU,SAAS;AAC5B,eAAW,QAAQ,OAAO,MAAM,OAAO;AACrC,aAAO,eAAe,KAAK,IAAI,CAAC;AAAA,IAClC;AAAA,EACF;AAEA,SAAO;AACT;;;AC7FA,SAAS,kBAAkB,SAAqC;AAC9D,QAAM,SAAS,QAAQ,OAAO,CAAC,WAAW,OAAO,uBAAuB;AAExE,SAAO;AAAA,IACL,cAAc,QAAQ;AAAA,IACtB,eAAe,OAAO;AAAA,IACtB,YAAY,QAAQ,SAAS,IAAI,OAAO,SAAS,QAAQ,SAAS;AAAA,EACpE;AACF;AAEO,SAAS,oBACd,cACA,WAAwC,CAAC,WAAW,OAAO,KAAK,gBAAgB,MACpE;AACZ,QAAM,YAAY,aAAa,QAAQ,OAAO,QAAQ;AACtD,QAAM,SAAS,kBAAkB,SAAS;AAE1C,SAAO;AAAA,IACL,gBAAgB,OAAO;AAAA,IACvB,iBAAiB,OAAO;AAAA,IACxB,YAAY,OAAO;AAAA,EACrB;AACF;AAEO,SAAS,4BACd,cACA,WAAwC,CAAC,WAAW,OAAO,KAAK,gBAAgB,SAC9D;AAClB,QAAM,kBAAkB,aAAa,QAAQ,OAAO,QAAQ;AAC5D,SAAO,kBAAkB,eAAe;AAC1C;;;ACjCA,SAA+B,eAAAA,oBAAmB;AAU3C,SAAS,qBACd,cACA,WAAwC,CAAC,WAAW,OAAO,KAAK,gBAAgB,MACnE;AACb,QAAM,gBAAgB,aAAa,QAAQ,OAAO,QAAQ;AAE1D,MAAI,cAAc,WAAW,GAAG;AAC9B,WAAO;AAAA,MACL,mBAAmB;AAAA,MACnB,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,SAAS;AAAA,QACP,MAAM;AAAA,QACN,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAGA,QAAM,iBAAiB,oBAAI,IAA2B;AAGtD,QAAM,gBAAgB,CAAC,GAAG,aAAa,OAAO,EAAE;AAAA,IAC9C,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ;AAAA,EAC9E;AAGA,aAAW,UAAU,eAAe;AAClC,QAAI,SAAS,MAAM,GAAG;AACpB,YAAM,aAAa,IAAI,KAAK,OAAO,UAAU;AAE7C,iBAAW,QAAQ,OAAO,MAAM,OAAO;AACrC,YAAI,CAAC,eAAe,IAAI,KAAK,IAAI,GAAG;AAClC,yBAAe,IAAI,KAAK,MAAM;AAAA,YAC5B,MAAM,KAAK;AAAA,YACX,uBAAuB;AAAA,YACvB,cAAc;AAAA,YACd,iBAAiB;AAAA,UACnB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,aAAW,UAAU,eAAe;AAClC,UAAM,aAAa,IAAI,KAAK,OAAO,UAAU;AAE7C,eAAW,QAAQ,OAAO,MAAM,OAAO;AACrC,YAAM,YAAY,eAAe,IAAI,KAAK,IAAI;AAC9C,UAAI,WAAW;AAEb,YAAI,KAAK,WAAW,WAAW;AAC7B,oBAAU,eAAe;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,kBAA4B,CAAC;AAEnC,aAAW,aAAa,eAAe,OAAO,GAAG;AAC/C,cAAU,kBAAkBA,aAAY,UAAU,uBAAuB,UAAU,YAAY;AAC/F,oBAAgB,KAAK,UAAU,eAAe;AAAA,EAChD;AAGA,QAAM,UACJ,gBAAgB,SAAS,IACrB,gBAAgB,OAAO,CAAC,KAAK,SAAS,MAAM,MAAM,CAAC,IAAI,gBAAgB,SACvE;AAEN,QAAM,aAAa,CAAC,GAAG,eAAe,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC5D,QAAM,aACJ,WAAW,SAAS,IAChB,WAAW,SAAS,MAAM,KACvB,WAAW,WAAW,SAAS,IAAI,CAAC,IAAI,WAAW,WAAW,SAAS,CAAC,KAAK,IAC9E,WAAW,KAAK,MAAM,WAAW,SAAS,CAAC,CAAC,IAC9C;AAGN,QAAM,UAAU;AAAA,IACd,MAAM,gBAAgB,OAAO,CAAC,SAAS,QAAQ,CAAC,EAAE;AAAA,IAClD,MAAM,gBAAgB,OAAO,CAAC,SAAS,QAAQ,KAAK,QAAQ,CAAC,EAAE;AAAA,IAC/D,OAAO,gBAAgB,OAAO,CAAC,SAAS,QAAQ,KAAK,QAAQ,EAAE,EAAE;AAAA,IACjE,QAAQ,gBAAgB,OAAO,CAAC,SAAS,QAAQ,MAAM,QAAQ,EAAE,EAAE;AAAA,IACnE,UAAU,gBAAgB,OAAO,CAAC,SAAS,OAAO,EAAE,EAAE;AAAA,EACxD;AAEA,SAAO;AAAA,IACL,mBAAmB,cAAc;AAAA,IACjC,SAAS,KAAK,MAAM,UAAU,GAAG,IAAI;AAAA;AAAA,IACrC,YAAY,KAAK,MAAM,aAAa,GAAG,IAAI;AAAA,IAC3C;AAAA,EACF;AACF;AAEO,SAAS,6BACd,cACA,WAAwC,CAAC,WAAW,OAAO,KAAK,gBAAgB,SACnE;AACb,SAAO,qBAAqB,cAAc,QAAQ;AACpD;;;ACpHA,SAAS,SAAS;AAIX,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACpC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA;AAAA,EACjC,oBAAoB,EAAE,KAAK,CAAC,MAAM,SAAS,SAAS,CAAC;AAAA;AAAA,EACrD,mBAAmB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,EAC1C,gBAAgB,EAAE,QAAQ;AAAA;AAAA,EAE1B,OAAO,EAAE,OAAO;AAAA,IACd,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACnC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IAC3C,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACpC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACxC,CAAC;AAAA,EACD,cAAc,EAAE,OAAO;AAAA,IACrB,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACvC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACvC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACrC,CAAC;AACH,CAAC;AAEM,IAAM,aAAa,EAAE,OAAO;AAAA,EACjC,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC7C,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC9C,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AACrC,CAAC;AAEM,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAChD,SAAS,EAAE,OAAO,EAAE,YAAY;AAAA,EAChC,YAAY,EAAE,OAAO,EAAE,YAAY;AAAA,EACnC,SAAS,EAAE,OAAO;AAAA,IAChB,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACnC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACpC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACrC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACzC,CAAC;AACH,CAAC;AAIM,IAAM,WAAW,EAAE,OAAO;AAAA,EAC/B,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACtC,YAAY,EAAE,OAAO,EAAE,YAAY;AAAA,EACnC,eAAe,EAAE,OAAO,EAAE,YAAY;AACxC,CAAC;AAIM,IAAM,eAAe,EAAE,KAAK;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACpC,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACzC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACrC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACnC,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAC1C,CAAC;AAEM,IAAM,gBAAgB,EAAE,OAAO;AAAA,EACpC,KAAK,SAAS,SAAS;AAAA,EACvB,SAAS,eAAe,SAAS;AACnC,CAAC;AAEM,IAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC5C,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AACrC,CAAC;AAEM,IAAM,WAAW,EAAE,OAAO;AAAA;AAAA;AAAA,EAG/B,SAAS,EAAE,QAAQ;AAAA,EACnB,YAAY;AAAA,EACZ,aAAa;AACf,CAAC;AAEM,IAAM,QAAQ,EAAE,OAAO;AAAA,EAC5B,YAAY,EAAE,OAAO;AAAA,EACrB,oBAAoB,EAAE,OAAO;AAAA,EAC7B,uBAAuB,EAAE,OAAO;AAClC,CAAC;AAEM,IAAM,UAAU,EAAE,OAAO;AAAA,EAC9B,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,QAAQ,EAAE,OAAO;AAAA,IACf,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,IAC3B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,CAAC;AAAA,EACD,UAAU,EAAE,OAAO;AAAA,EACnB,eAAe,EAAE,OAAO;AAAA,EACxB,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA;AAAA;AAAA,EAGb,SAAS,EAAE,OAAO;AAAA,IAChB,IAAI;AAAA,IACJ,UAAU;AAAA,EACZ,CAAC;AAAA;AAAA;AAAA,EAGD,UAAU,SAAS,SAAS;AAAA,EAC5B,OAAO,MAAM,SAAS;AAAA,EACtB,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC;AAC7B,CAAC;;;AJzGD,SAAS,MAAM,OAAe,UAA0B;AACtD,QAAM,SAAS,MAAM;AACrB,SAAO,KAAK,MAAM,QAAQ,MAAM,IAAI;AACtC;AAEO,SAAS,iBACd,cACA,UAA0B,CAAC,GAClB;AACT,QAAM,EAAE,qBAAqB,WAAW,oBAAoB,IAAI,IAAI;AAEpE,QAAM,SAAS,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,EAAE;AAC7C,QAAM,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,EAAE;AAC5E,QAAM,eAAe,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,EAAE;AACzD,aAAW,UAAU,aAAa,SAAS;AACzC,WAAO,OAAO,KAAK,WAAW;AAC9B,UAAM,OAAO,KAAK,IAAI;AACtB,iBAAa,OAAO,KAAK,YAAY;AAAA,EACvC;AACA,QAAM,QAAQ,aAAa,QAAQ;AACnC,QAAM,WAAW,QAAQ,KAAK,OAAO,KAAK,OAAO,SAAS,QAAQ;AAElE,QAAM,cAA2B;AAAA,IAC/B,cAAc;AAAA,IACd,IAAI,OAAO;AAAA,IACX,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,IAChB,UAAU,MAAM,UAAU,CAAC;AAAA,IAC3B;AAAA,IACA;AAAA,IACA,gBAAgB,WAAW;AAAA,IAC3B;AAAA,IACA;AAAA,EACF;AAKA,QAAM,aAAa,CAAC,WAAmB,OAAO,KAAK,QAAQ,SAAS,mBAAmB;AACvF,QAAM,OAAO,CAAC,WACZ,OAAO,KAAK,gBAAgB,QAC3B,uBAAuB,QAAQ,OAAO,KAAK,gBAAgB,aAAa,CAAC,WAAW,MAAM;AAC7F,QAAM,aAAa,CAAC,WAClB,OAAO,KAAK,gBAAgB,WAC3B,uBAAuB,WACtB,OAAO,KAAK,gBAAgB,aAC5B,CAAC,WAAW,MAAM;AAEtB,QAAM,aAAa,oBAAoB,cAAc,IAAI;AACzD,QAAM,cAAc,qBAAqB,cAAc,IAAI;AAG3D,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,YAAY,aAAa,QAAQ,OAAO,IAAI;AAClD,QAAM,kBAAkB,aAAa,QAAQ,OAAO,UAAU;AAC9D,QAAM,UAAU;AAAA,IACd,IAAI;AAAA,MACF,KAAK,kBAAkB,WAAW,GAAG;AAAA,MACrC,SAAS,wBAAwB,SAAS;AAAA,IAC5C;AAAA,IACA,UAAU;AAAA,MACR,KAAK,kBAAkB,iBAAiB,GAAG;AAAA,MAC3C,SAAS,wBAAwB,eAAe;AAAA,IAClD;AAAA,EACF;AAIA,QAAM,eAAe,gBAAgB;AACrC,QAAM,kBAAkB,uBAAuB,WAAW,OAAO,UAAU;AAE3E,QAAM,WACJ,eAAe,IACX;AAAA,IACE,SAAS;AAAA,IACT,YAAY,4BAA4B,cAAc,UAAU;AAAA,IAChE,aAAa,6BAA6B,cAAc,UAAU;AAAA,EACpE,IACA;AAEN,QAAM,QAAQ,WACV;AAAA,IACE,YAAY,MAAM,WAAW,aAAa,SAAS,WAAW,YAAY,CAAC;AAAA,IAC3E,oBAAoB,MAAM,YAAY,UAAU,SAAS,YAAY,SAAS,CAAC;AAAA,IAC/E,uBAAuB;AAAA,MACrB,YAAY,aAAa,SAAS,YAAY;AAAA,MAC9C;AAAA,IACF;AAAA,EACF,IACA;AAEJ,QAAM,UAAU;AAAA,IACd,4BAA4B,WAAW,KAAK,QAAQ,CAAC,CAAC;AAAA,IACtD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,UAAU,SAAS;AACrB,YAAQ;AAAA,MACN,qBAAqB,OAAO,OAAO;AAAA,IACrC;AAAA,EACF;AACA,MAAI,CAAC,UAAU;AACb,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,aAAa,cAAc,oBAAI,KAAK,CAAC;AAAA,IACrC,QAAQ;AAAA,MACN,OAAO,aAAa;AAAA,MACpB,OAAO,aAAa;AAAA,IACtB;AAAA,IACA,UAAU,aAAa;AAAA,IACvB,eAAe,aAAa;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["daysBetween"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aida-dev/metrics",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Metrics engine for AIDA (AI Development Accounting) - Merge ratio, persistence, and AI impact analysis",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "dependencies": {
13
13
  "zod": "^3.22.4",
14
- "@aida-dev/core": "0.8.0"
14
+ "@aida-dev/core": "0.10.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "tsup": "^8.0.0",