@aida-dev/metrics 0.4.0 → 0.6.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 +572 -4
- package/dist/index.js +213 -59
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Commit, CommitStream } from '@aida-dev/core';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
declare const Attribution: z.ZodObject<{
|
|
@@ -10,6 +10,38 @@ declare const Attribution: z.ZodObject<{
|
|
|
10
10
|
defaultAttribution: z.ZodEnum<["ai", "human", "unknown"]>;
|
|
11
11
|
coverageThreshold: z.ZodNumber;
|
|
12
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
|
+
}>;
|
|
13
45
|
}, "strip", z.ZodTypeAny, {
|
|
14
46
|
ai: number;
|
|
15
47
|
human: number;
|
|
@@ -19,6 +51,18 @@ declare const Attribution: z.ZodObject<{
|
|
|
19
51
|
defaultAttribution: "ai" | "human" | "unknown";
|
|
20
52
|
coverageThreshold: number;
|
|
21
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
|
+
};
|
|
22
66
|
}, {
|
|
23
67
|
ai: number;
|
|
24
68
|
human: number;
|
|
@@ -28,8 +72,125 @@ declare const Attribution: z.ZodObject<{
|
|
|
28
72
|
defaultAttribution: "ai" | "human" | "unknown";
|
|
29
73
|
coverageThreshold: number;
|
|
30
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
|
+
};
|
|
31
87
|
}>;
|
|
32
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>;
|
|
33
194
|
declare const MergeRatio: z.ZodObject<{
|
|
34
195
|
aiCommitsTotal: z.ZodNumber;
|
|
35
196
|
aiCommitsMerged: z.ZodNumber;
|
|
@@ -46,6 +207,9 @@ declare const MergeRatio: z.ZodObject<{
|
|
|
46
207
|
type MergeRatio = z.infer<typeof MergeRatio>;
|
|
47
208
|
declare const Persistence: z.ZodObject<{
|
|
48
209
|
commitsConsidered: z.ZodNumber;
|
|
210
|
+
filesConsidered: z.ZodNumber;
|
|
211
|
+
filesExcluded: z.ZodNumber;
|
|
212
|
+
censored: z.ZodNumber;
|
|
49
213
|
avgDays: z.ZodNumber;
|
|
50
214
|
medianDays: z.ZodNumber;
|
|
51
215
|
buckets: z.ZodObject<{
|
|
@@ -69,6 +233,9 @@ declare const Persistence: z.ZodObject<{
|
|
|
69
233
|
}>;
|
|
70
234
|
}, "strip", z.ZodTypeAny, {
|
|
71
235
|
commitsConsidered: number;
|
|
236
|
+
filesConsidered: number;
|
|
237
|
+
filesExcluded: number;
|
|
238
|
+
censored: number;
|
|
72
239
|
avgDays: number;
|
|
73
240
|
medianDays: number;
|
|
74
241
|
buckets: {
|
|
@@ -80,6 +247,9 @@ declare const Persistence: z.ZodObject<{
|
|
|
80
247
|
};
|
|
81
248
|
}, {
|
|
82
249
|
commitsConsidered: number;
|
|
250
|
+
filesConsidered: number;
|
|
251
|
+
filesExcluded: number;
|
|
252
|
+
censored: number;
|
|
83
253
|
avgDays: number;
|
|
84
254
|
medianDays: number;
|
|
85
255
|
buckets: {
|
|
@@ -122,6 +292,9 @@ declare const Baseline: z.ZodObject<{
|
|
|
122
292
|
}>;
|
|
123
293
|
persistence: z.ZodObject<{
|
|
124
294
|
commitsConsidered: z.ZodNumber;
|
|
295
|
+
filesConsidered: z.ZodNumber;
|
|
296
|
+
filesExcluded: z.ZodNumber;
|
|
297
|
+
censored: z.ZodNumber;
|
|
125
298
|
avgDays: z.ZodNumber;
|
|
126
299
|
medianDays: z.ZodNumber;
|
|
127
300
|
buckets: z.ZodObject<{
|
|
@@ -145,6 +318,9 @@ declare const Baseline: z.ZodObject<{
|
|
|
145
318
|
}>;
|
|
146
319
|
}, "strip", z.ZodTypeAny, {
|
|
147
320
|
commitsConsidered: number;
|
|
321
|
+
filesConsidered: number;
|
|
322
|
+
filesExcluded: number;
|
|
323
|
+
censored: number;
|
|
148
324
|
avgDays: number;
|
|
149
325
|
medianDays: number;
|
|
150
326
|
buckets: {
|
|
@@ -156,6 +332,9 @@ declare const Baseline: z.ZodObject<{
|
|
|
156
332
|
};
|
|
157
333
|
}, {
|
|
158
334
|
commitsConsidered: number;
|
|
335
|
+
filesConsidered: number;
|
|
336
|
+
filesExcluded: number;
|
|
337
|
+
censored: number;
|
|
159
338
|
avgDays: number;
|
|
160
339
|
medianDays: number;
|
|
161
340
|
buckets: {
|
|
@@ -174,6 +353,9 @@ declare const Baseline: z.ZodObject<{
|
|
|
174
353
|
};
|
|
175
354
|
persistence: {
|
|
176
355
|
commitsConsidered: number;
|
|
356
|
+
filesConsidered: number;
|
|
357
|
+
filesExcluded: number;
|
|
358
|
+
censored: number;
|
|
177
359
|
avgDays: number;
|
|
178
360
|
medianDays: number;
|
|
179
361
|
buckets: {
|
|
@@ -193,6 +375,9 @@ declare const Baseline: z.ZodObject<{
|
|
|
193
375
|
};
|
|
194
376
|
persistence: {
|
|
195
377
|
commitsConsidered: number;
|
|
378
|
+
filesConsidered: number;
|
|
379
|
+
filesExcluded: number;
|
|
380
|
+
censored: number;
|
|
196
381
|
avgDays: number;
|
|
197
382
|
medianDays: number;
|
|
198
383
|
buckets: {
|
|
@@ -243,6 +428,38 @@ declare const Metrics: z.ZodObject<{
|
|
|
243
428
|
defaultAttribution: z.ZodEnum<["ai", "human", "unknown"]>;
|
|
244
429
|
coverageThreshold: z.ZodNumber;
|
|
245
430
|
belowThreshold: z.ZodBoolean;
|
|
431
|
+
modes: z.ZodObject<{
|
|
432
|
+
none: z.ZodNumber;
|
|
433
|
+
autocomplete: z.ZodNumber;
|
|
434
|
+
assisted: z.ZodNumber;
|
|
435
|
+
agent: z.ZodNumber;
|
|
436
|
+
unknown: z.ZodNumber;
|
|
437
|
+
}, "strip", z.ZodTypeAny, {
|
|
438
|
+
unknown: number;
|
|
439
|
+
none: number;
|
|
440
|
+
autocomplete: number;
|
|
441
|
+
assisted: number;
|
|
442
|
+
agent: number;
|
|
443
|
+
}, {
|
|
444
|
+
unknown: number;
|
|
445
|
+
none: number;
|
|
446
|
+
autocomplete: number;
|
|
447
|
+
assisted: number;
|
|
448
|
+
agent: number;
|
|
449
|
+
}>;
|
|
450
|
+
modeEvidence: z.ZodObject<{
|
|
451
|
+
declared: z.ZodNumber;
|
|
452
|
+
inferred: z.ZodNumber;
|
|
453
|
+
none: z.ZodNumber;
|
|
454
|
+
}, "strip", z.ZodTypeAny, {
|
|
455
|
+
none: number;
|
|
456
|
+
declared: number;
|
|
457
|
+
inferred: number;
|
|
458
|
+
}, {
|
|
459
|
+
none: number;
|
|
460
|
+
declared: number;
|
|
461
|
+
inferred: number;
|
|
462
|
+
}>;
|
|
246
463
|
}, "strip", z.ZodTypeAny, {
|
|
247
464
|
ai: number;
|
|
248
465
|
human: number;
|
|
@@ -252,6 +469,18 @@ declare const Metrics: z.ZodObject<{
|
|
|
252
469
|
defaultAttribution: "ai" | "human" | "unknown";
|
|
253
470
|
coverageThreshold: number;
|
|
254
471
|
belowThreshold: boolean;
|
|
472
|
+
modes: {
|
|
473
|
+
unknown: number;
|
|
474
|
+
none: number;
|
|
475
|
+
autocomplete: number;
|
|
476
|
+
assisted: number;
|
|
477
|
+
agent: number;
|
|
478
|
+
};
|
|
479
|
+
modeEvidence: {
|
|
480
|
+
none: number;
|
|
481
|
+
declared: number;
|
|
482
|
+
inferred: number;
|
|
483
|
+
};
|
|
255
484
|
}, {
|
|
256
485
|
ai: number;
|
|
257
486
|
human: number;
|
|
@@ -261,6 +490,18 @@ declare const Metrics: z.ZodObject<{
|
|
|
261
490
|
defaultAttribution: "ai" | "human" | "unknown";
|
|
262
491
|
coverageThreshold: number;
|
|
263
492
|
belowThreshold: boolean;
|
|
493
|
+
modes: {
|
|
494
|
+
unknown: number;
|
|
495
|
+
none: number;
|
|
496
|
+
autocomplete: number;
|
|
497
|
+
assisted: number;
|
|
498
|
+
agent: number;
|
|
499
|
+
};
|
|
500
|
+
modeEvidence: {
|
|
501
|
+
none: number;
|
|
502
|
+
declared: number;
|
|
503
|
+
inferred: number;
|
|
504
|
+
};
|
|
264
505
|
}>;
|
|
265
506
|
mergeRatio: z.ZodObject<{
|
|
266
507
|
aiCommitsTotal: z.ZodNumber;
|
|
@@ -277,6 +518,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
277
518
|
}>;
|
|
278
519
|
persistence: z.ZodObject<{
|
|
279
520
|
commitsConsidered: z.ZodNumber;
|
|
521
|
+
filesConsidered: z.ZodNumber;
|
|
522
|
+
filesExcluded: z.ZodNumber;
|
|
523
|
+
censored: z.ZodNumber;
|
|
280
524
|
avgDays: z.ZodNumber;
|
|
281
525
|
medianDays: z.ZodNumber;
|
|
282
526
|
buckets: z.ZodObject<{
|
|
@@ -300,6 +544,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
300
544
|
}>;
|
|
301
545
|
}, "strip", z.ZodTypeAny, {
|
|
302
546
|
commitsConsidered: number;
|
|
547
|
+
filesConsidered: number;
|
|
548
|
+
filesExcluded: number;
|
|
549
|
+
censored: number;
|
|
303
550
|
avgDays: number;
|
|
304
551
|
medianDays: number;
|
|
305
552
|
buckets: {
|
|
@@ -311,6 +558,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
311
558
|
};
|
|
312
559
|
}, {
|
|
313
560
|
commitsConsidered: number;
|
|
561
|
+
filesConsidered: number;
|
|
562
|
+
filesExcluded: number;
|
|
563
|
+
censored: number;
|
|
314
564
|
avgDays: number;
|
|
315
565
|
medianDays: number;
|
|
316
566
|
buckets: {
|
|
@@ -321,6 +571,200 @@ declare const Metrics: z.ZodObject<{
|
|
|
321
571
|
d90_plus: number;
|
|
322
572
|
};
|
|
323
573
|
}>;
|
|
574
|
+
cohorts: z.ZodObject<{
|
|
575
|
+
ai: z.ZodObject<{
|
|
576
|
+
age: z.ZodNullable<z.ZodObject<{
|
|
577
|
+
commits: z.ZodNumber;
|
|
578
|
+
avgAgeDays: z.ZodNumber;
|
|
579
|
+
medianAgeDays: z.ZodNumber;
|
|
580
|
+
}, "strip", z.ZodTypeAny, {
|
|
581
|
+
commits: number;
|
|
582
|
+
avgAgeDays: number;
|
|
583
|
+
medianAgeDays: number;
|
|
584
|
+
}, {
|
|
585
|
+
commits: number;
|
|
586
|
+
avgAgeDays: number;
|
|
587
|
+
medianAgeDays: number;
|
|
588
|
+
}>>;
|
|
589
|
+
taskMix: z.ZodNullable<z.ZodObject<{
|
|
590
|
+
source: z.ZodNumber;
|
|
591
|
+
tests: z.ZodNumber;
|
|
592
|
+
migrations: z.ZodNumber;
|
|
593
|
+
config: z.ZodNumber;
|
|
594
|
+
docs: z.ZodNumber;
|
|
595
|
+
generated: z.ZodNumber;
|
|
596
|
+
}, "strip", z.ZodTypeAny, {
|
|
597
|
+
source: number;
|
|
598
|
+
tests: number;
|
|
599
|
+
migrations: number;
|
|
600
|
+
config: number;
|
|
601
|
+
docs: number;
|
|
602
|
+
generated: number;
|
|
603
|
+
}, {
|
|
604
|
+
source: number;
|
|
605
|
+
tests: number;
|
|
606
|
+
migrations: number;
|
|
607
|
+
config: number;
|
|
608
|
+
docs: number;
|
|
609
|
+
generated: number;
|
|
610
|
+
}>>;
|
|
611
|
+
}, "strip", z.ZodTypeAny, {
|
|
612
|
+
age: {
|
|
613
|
+
commits: number;
|
|
614
|
+
avgAgeDays: number;
|
|
615
|
+
medianAgeDays: number;
|
|
616
|
+
} | null;
|
|
617
|
+
taskMix: {
|
|
618
|
+
source: number;
|
|
619
|
+
tests: number;
|
|
620
|
+
migrations: number;
|
|
621
|
+
config: number;
|
|
622
|
+
docs: number;
|
|
623
|
+
generated: number;
|
|
624
|
+
} | null;
|
|
625
|
+
}, {
|
|
626
|
+
age: {
|
|
627
|
+
commits: number;
|
|
628
|
+
avgAgeDays: number;
|
|
629
|
+
medianAgeDays: number;
|
|
630
|
+
} | null;
|
|
631
|
+
taskMix: {
|
|
632
|
+
source: number;
|
|
633
|
+
tests: number;
|
|
634
|
+
migrations: number;
|
|
635
|
+
config: number;
|
|
636
|
+
docs: number;
|
|
637
|
+
generated: number;
|
|
638
|
+
} | null;
|
|
639
|
+
}>;
|
|
640
|
+
baseline: z.ZodObject<{
|
|
641
|
+
age: z.ZodNullable<z.ZodObject<{
|
|
642
|
+
commits: z.ZodNumber;
|
|
643
|
+
avgAgeDays: z.ZodNumber;
|
|
644
|
+
medianAgeDays: z.ZodNumber;
|
|
645
|
+
}, "strip", z.ZodTypeAny, {
|
|
646
|
+
commits: number;
|
|
647
|
+
avgAgeDays: number;
|
|
648
|
+
medianAgeDays: number;
|
|
649
|
+
}, {
|
|
650
|
+
commits: number;
|
|
651
|
+
avgAgeDays: number;
|
|
652
|
+
medianAgeDays: number;
|
|
653
|
+
}>>;
|
|
654
|
+
taskMix: z.ZodNullable<z.ZodObject<{
|
|
655
|
+
source: z.ZodNumber;
|
|
656
|
+
tests: z.ZodNumber;
|
|
657
|
+
migrations: z.ZodNumber;
|
|
658
|
+
config: z.ZodNumber;
|
|
659
|
+
docs: z.ZodNumber;
|
|
660
|
+
generated: z.ZodNumber;
|
|
661
|
+
}, "strip", z.ZodTypeAny, {
|
|
662
|
+
source: number;
|
|
663
|
+
tests: number;
|
|
664
|
+
migrations: number;
|
|
665
|
+
config: number;
|
|
666
|
+
docs: number;
|
|
667
|
+
generated: number;
|
|
668
|
+
}, {
|
|
669
|
+
source: number;
|
|
670
|
+
tests: number;
|
|
671
|
+
migrations: number;
|
|
672
|
+
config: number;
|
|
673
|
+
docs: number;
|
|
674
|
+
generated: number;
|
|
675
|
+
}>>;
|
|
676
|
+
}, "strip", z.ZodTypeAny, {
|
|
677
|
+
age: {
|
|
678
|
+
commits: number;
|
|
679
|
+
avgAgeDays: number;
|
|
680
|
+
medianAgeDays: number;
|
|
681
|
+
} | null;
|
|
682
|
+
taskMix: {
|
|
683
|
+
source: number;
|
|
684
|
+
tests: number;
|
|
685
|
+
migrations: number;
|
|
686
|
+
config: number;
|
|
687
|
+
docs: number;
|
|
688
|
+
generated: number;
|
|
689
|
+
} | null;
|
|
690
|
+
}, {
|
|
691
|
+
age: {
|
|
692
|
+
commits: number;
|
|
693
|
+
avgAgeDays: number;
|
|
694
|
+
medianAgeDays: number;
|
|
695
|
+
} | null;
|
|
696
|
+
taskMix: {
|
|
697
|
+
source: number;
|
|
698
|
+
tests: number;
|
|
699
|
+
migrations: number;
|
|
700
|
+
config: number;
|
|
701
|
+
docs: number;
|
|
702
|
+
generated: number;
|
|
703
|
+
} | null;
|
|
704
|
+
}>;
|
|
705
|
+
}, "strip", z.ZodTypeAny, {
|
|
706
|
+
ai: {
|
|
707
|
+
age: {
|
|
708
|
+
commits: number;
|
|
709
|
+
avgAgeDays: number;
|
|
710
|
+
medianAgeDays: number;
|
|
711
|
+
} | null;
|
|
712
|
+
taskMix: {
|
|
713
|
+
source: number;
|
|
714
|
+
tests: number;
|
|
715
|
+
migrations: number;
|
|
716
|
+
config: number;
|
|
717
|
+
docs: number;
|
|
718
|
+
generated: number;
|
|
719
|
+
} | null;
|
|
720
|
+
};
|
|
721
|
+
baseline: {
|
|
722
|
+
age: {
|
|
723
|
+
commits: number;
|
|
724
|
+
avgAgeDays: number;
|
|
725
|
+
medianAgeDays: number;
|
|
726
|
+
} | null;
|
|
727
|
+
taskMix: {
|
|
728
|
+
source: number;
|
|
729
|
+
tests: number;
|
|
730
|
+
migrations: number;
|
|
731
|
+
config: number;
|
|
732
|
+
docs: number;
|
|
733
|
+
generated: number;
|
|
734
|
+
} | null;
|
|
735
|
+
};
|
|
736
|
+
}, {
|
|
737
|
+
ai: {
|
|
738
|
+
age: {
|
|
739
|
+
commits: number;
|
|
740
|
+
avgAgeDays: number;
|
|
741
|
+
medianAgeDays: number;
|
|
742
|
+
} | null;
|
|
743
|
+
taskMix: {
|
|
744
|
+
source: number;
|
|
745
|
+
tests: number;
|
|
746
|
+
migrations: number;
|
|
747
|
+
config: number;
|
|
748
|
+
docs: number;
|
|
749
|
+
generated: number;
|
|
750
|
+
} | null;
|
|
751
|
+
};
|
|
752
|
+
baseline: {
|
|
753
|
+
age: {
|
|
754
|
+
commits: number;
|
|
755
|
+
avgAgeDays: number;
|
|
756
|
+
medianAgeDays: number;
|
|
757
|
+
} | null;
|
|
758
|
+
taskMix: {
|
|
759
|
+
source: number;
|
|
760
|
+
tests: number;
|
|
761
|
+
migrations: number;
|
|
762
|
+
config: number;
|
|
763
|
+
docs: number;
|
|
764
|
+
generated: number;
|
|
765
|
+
} | null;
|
|
766
|
+
};
|
|
767
|
+
}>;
|
|
324
768
|
baseline: z.ZodNullable<z.ZodObject<{
|
|
325
769
|
assumed: z.ZodBoolean;
|
|
326
770
|
mergeRatio: z.ZodObject<{
|
|
@@ -338,6 +782,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
338
782
|
}>;
|
|
339
783
|
persistence: z.ZodObject<{
|
|
340
784
|
commitsConsidered: z.ZodNumber;
|
|
785
|
+
filesConsidered: z.ZodNumber;
|
|
786
|
+
filesExcluded: z.ZodNumber;
|
|
787
|
+
censored: z.ZodNumber;
|
|
341
788
|
avgDays: z.ZodNumber;
|
|
342
789
|
medianDays: z.ZodNumber;
|
|
343
790
|
buckets: z.ZodObject<{
|
|
@@ -361,6 +808,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
361
808
|
}>;
|
|
362
809
|
}, "strip", z.ZodTypeAny, {
|
|
363
810
|
commitsConsidered: number;
|
|
811
|
+
filesConsidered: number;
|
|
812
|
+
filesExcluded: number;
|
|
813
|
+
censored: number;
|
|
364
814
|
avgDays: number;
|
|
365
815
|
medianDays: number;
|
|
366
816
|
buckets: {
|
|
@@ -372,6 +822,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
372
822
|
};
|
|
373
823
|
}, {
|
|
374
824
|
commitsConsidered: number;
|
|
825
|
+
filesConsidered: number;
|
|
826
|
+
filesExcluded: number;
|
|
827
|
+
censored: number;
|
|
375
828
|
avgDays: number;
|
|
376
829
|
medianDays: number;
|
|
377
830
|
buckets: {
|
|
@@ -390,6 +843,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
390
843
|
};
|
|
391
844
|
persistence: {
|
|
392
845
|
commitsConsidered: number;
|
|
846
|
+
filesConsidered: number;
|
|
847
|
+
filesExcluded: number;
|
|
848
|
+
censored: number;
|
|
393
849
|
avgDays: number;
|
|
394
850
|
medianDays: number;
|
|
395
851
|
buckets: {
|
|
@@ -409,6 +865,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
409
865
|
};
|
|
410
866
|
persistence: {
|
|
411
867
|
commitsConsidered: number;
|
|
868
|
+
filesConsidered: number;
|
|
869
|
+
filesExcluded: number;
|
|
870
|
+
censored: number;
|
|
412
871
|
avgDays: number;
|
|
413
872
|
medianDays: number;
|
|
414
873
|
buckets: {
|
|
@@ -452,6 +911,18 @@ declare const Metrics: z.ZodObject<{
|
|
|
452
911
|
defaultAttribution: "ai" | "human" | "unknown";
|
|
453
912
|
coverageThreshold: number;
|
|
454
913
|
belowThreshold: boolean;
|
|
914
|
+
modes: {
|
|
915
|
+
unknown: number;
|
|
916
|
+
none: number;
|
|
917
|
+
autocomplete: number;
|
|
918
|
+
assisted: number;
|
|
919
|
+
agent: number;
|
|
920
|
+
};
|
|
921
|
+
modeEvidence: {
|
|
922
|
+
none: number;
|
|
923
|
+
declared: number;
|
|
924
|
+
inferred: number;
|
|
925
|
+
};
|
|
455
926
|
};
|
|
456
927
|
mergeRatio: {
|
|
457
928
|
aiCommitsTotal: number;
|
|
@@ -460,6 +931,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
460
931
|
};
|
|
461
932
|
persistence: {
|
|
462
933
|
commitsConsidered: number;
|
|
934
|
+
filesConsidered: number;
|
|
935
|
+
filesExcluded: number;
|
|
936
|
+
censored: number;
|
|
463
937
|
avgDays: number;
|
|
464
938
|
medianDays: number;
|
|
465
939
|
buckets: {
|
|
@@ -470,6 +944,38 @@ declare const Metrics: z.ZodObject<{
|
|
|
470
944
|
d90_plus: number;
|
|
471
945
|
};
|
|
472
946
|
};
|
|
947
|
+
cohorts: {
|
|
948
|
+
ai: {
|
|
949
|
+
age: {
|
|
950
|
+
commits: number;
|
|
951
|
+
avgAgeDays: number;
|
|
952
|
+
medianAgeDays: number;
|
|
953
|
+
} | null;
|
|
954
|
+
taskMix: {
|
|
955
|
+
source: number;
|
|
956
|
+
tests: number;
|
|
957
|
+
migrations: number;
|
|
958
|
+
config: number;
|
|
959
|
+
docs: number;
|
|
960
|
+
generated: number;
|
|
961
|
+
} | null;
|
|
962
|
+
};
|
|
963
|
+
baseline: {
|
|
964
|
+
age: {
|
|
965
|
+
commits: number;
|
|
966
|
+
avgAgeDays: number;
|
|
967
|
+
medianAgeDays: number;
|
|
968
|
+
} | null;
|
|
969
|
+
taskMix: {
|
|
970
|
+
source: number;
|
|
971
|
+
tests: number;
|
|
972
|
+
migrations: number;
|
|
973
|
+
config: number;
|
|
974
|
+
docs: number;
|
|
975
|
+
generated: number;
|
|
976
|
+
} | null;
|
|
977
|
+
};
|
|
978
|
+
};
|
|
473
979
|
baseline: {
|
|
474
980
|
mergeRatio: {
|
|
475
981
|
commitsTotal: number;
|
|
@@ -478,6 +984,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
478
984
|
};
|
|
479
985
|
persistence: {
|
|
480
986
|
commitsConsidered: number;
|
|
987
|
+
filesConsidered: number;
|
|
988
|
+
filesExcluded: number;
|
|
989
|
+
censored: number;
|
|
481
990
|
avgDays: number;
|
|
482
991
|
medianDays: number;
|
|
483
992
|
buckets: {
|
|
@@ -513,6 +1022,18 @@ declare const Metrics: z.ZodObject<{
|
|
|
513
1022
|
defaultAttribution: "ai" | "human" | "unknown";
|
|
514
1023
|
coverageThreshold: number;
|
|
515
1024
|
belowThreshold: boolean;
|
|
1025
|
+
modes: {
|
|
1026
|
+
unknown: number;
|
|
1027
|
+
none: number;
|
|
1028
|
+
autocomplete: number;
|
|
1029
|
+
assisted: number;
|
|
1030
|
+
agent: number;
|
|
1031
|
+
};
|
|
1032
|
+
modeEvidence: {
|
|
1033
|
+
none: number;
|
|
1034
|
+
declared: number;
|
|
1035
|
+
inferred: number;
|
|
1036
|
+
};
|
|
516
1037
|
};
|
|
517
1038
|
mergeRatio: {
|
|
518
1039
|
aiCommitsTotal: number;
|
|
@@ -521,6 +1042,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
521
1042
|
};
|
|
522
1043
|
persistence: {
|
|
523
1044
|
commitsConsidered: number;
|
|
1045
|
+
filesConsidered: number;
|
|
1046
|
+
filesExcluded: number;
|
|
1047
|
+
censored: number;
|
|
524
1048
|
avgDays: number;
|
|
525
1049
|
medianDays: number;
|
|
526
1050
|
buckets: {
|
|
@@ -531,6 +1055,38 @@ declare const Metrics: z.ZodObject<{
|
|
|
531
1055
|
d90_plus: number;
|
|
532
1056
|
};
|
|
533
1057
|
};
|
|
1058
|
+
cohorts: {
|
|
1059
|
+
ai: {
|
|
1060
|
+
age: {
|
|
1061
|
+
commits: number;
|
|
1062
|
+
avgAgeDays: number;
|
|
1063
|
+
medianAgeDays: number;
|
|
1064
|
+
} | null;
|
|
1065
|
+
taskMix: {
|
|
1066
|
+
source: number;
|
|
1067
|
+
tests: number;
|
|
1068
|
+
migrations: number;
|
|
1069
|
+
config: number;
|
|
1070
|
+
docs: number;
|
|
1071
|
+
generated: number;
|
|
1072
|
+
} | null;
|
|
1073
|
+
};
|
|
1074
|
+
baseline: {
|
|
1075
|
+
age: {
|
|
1076
|
+
commits: number;
|
|
1077
|
+
avgAgeDays: number;
|
|
1078
|
+
medianAgeDays: number;
|
|
1079
|
+
} | null;
|
|
1080
|
+
taskMix: {
|
|
1081
|
+
source: number;
|
|
1082
|
+
tests: number;
|
|
1083
|
+
migrations: number;
|
|
1084
|
+
config: number;
|
|
1085
|
+
docs: number;
|
|
1086
|
+
generated: number;
|
|
1087
|
+
} | null;
|
|
1088
|
+
};
|
|
1089
|
+
};
|
|
534
1090
|
baseline: {
|
|
535
1091
|
mergeRatio: {
|
|
536
1092
|
commitsTotal: number;
|
|
@@ -539,6 +1095,9 @@ declare const Metrics: z.ZodObject<{
|
|
|
539
1095
|
};
|
|
540
1096
|
persistence: {
|
|
541
1097
|
commitsConsidered: number;
|
|
1098
|
+
filesConsidered: number;
|
|
1099
|
+
filesExcluded: number;
|
|
1100
|
+
censored: number;
|
|
542
1101
|
avgDays: number;
|
|
543
1102
|
medianDays: number;
|
|
544
1103
|
buckets: {
|
|
@@ -560,11 +1119,20 @@ declare const Metrics: z.ZodObject<{
|
|
|
560
1119
|
}>;
|
|
561
1120
|
type Metrics = z.infer<typeof Metrics>;
|
|
562
1121
|
|
|
1122
|
+
declare function calculateAgeStats(commits: Commit[], now: Date): AgeStats | null;
|
|
1123
|
+
declare function categorizeFile(path: string): FileCategory;
|
|
1124
|
+
declare function calculateCategoryCounts(commits: Commit[]): CategoryCounts | null;
|
|
1125
|
+
|
|
563
1126
|
declare function calculateMergeRatio(commitStream: CommitStream, isTarget?: (commit: Commit) => boolean): MergeRatio;
|
|
564
1127
|
declare function calculateBaselineMergeRatio(commitStream: CommitStream, isTarget?: (commit: Commit) => boolean): CohortMergeRatio;
|
|
565
1128
|
|
|
566
|
-
declare
|
|
567
|
-
|
|
1129
|
+
declare const DEFAULT_PERSISTENCE_EXCLUDED_CATEGORIES: FileCategory[];
|
|
1130
|
+
interface PersistenceOptions {
|
|
1131
|
+
excludeCategories?: FileCategory[];
|
|
1132
|
+
observationEnd?: Date;
|
|
1133
|
+
}
|
|
1134
|
+
declare function calculatePersistence(commitStream: CommitStream, isTarget?: (commit: Commit) => boolean, options?: PersistenceOptions): Persistence;
|
|
1135
|
+
declare function calculateBaselinePersistence(commitStream: CommitStream, isTarget?: (commit: Commit) => boolean, options?: PersistenceOptions): Persistence;
|
|
568
1136
|
|
|
569
1137
|
interface MetricsOptions {
|
|
570
1138
|
defaultAttribution?: 'ai' | 'human' | 'unknown';
|
|
@@ -572,4 +1140,4 @@ interface MetricsOptions {
|
|
|
572
1140
|
}
|
|
573
1141
|
declare function calculateMetrics(commitStream: CommitStream, options?: MetricsOptions): Metrics;
|
|
574
1142
|
|
|
575
|
-
export { Attribution, Baseline, CohortMergeRatio, Delta, MergeRatio, Metrics, type MetricsOptions, Persistence, calculateBaselineMergeRatio, calculateBaselinePersistence, calculateMergeRatio, calculateMetrics, calculatePersistence };
|
|
1143
|
+
export { AgeStats, Attribution, Baseline, CategoryCounts, CohortContext, CohortMergeRatio, DEFAULT_PERSISTENCE_EXCLUDED_CATEGORIES, Delta, FileCategory, MergeRatio, Metrics, type MetricsOptions, Persistence, type PersistenceOptions, 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);
|
|
@@ -25,78 +82,96 @@ function calculateBaselineMergeRatio(commitStream, isTarget = (commit) => commit
|
|
|
25
82
|
}
|
|
26
83
|
|
|
27
84
|
// src/persistence.ts
|
|
28
|
-
import { daysBetween } from "@aida-dev/core";
|
|
29
|
-
|
|
85
|
+
import { daysBetween as daysBetween2 } from "@aida-dev/core";
|
|
86
|
+
var DEFAULT_PERSISTENCE_EXCLUDED_CATEGORIES = [
|
|
87
|
+
"migrations",
|
|
88
|
+
"generated"
|
|
89
|
+
];
|
|
90
|
+
function calculatePersistence(commitStream, isTarget = (commit) => commit.tags.attribution === "ai", options = {}) {
|
|
91
|
+
const {
|
|
92
|
+
excludeCategories = DEFAULT_PERSISTENCE_EXCLUDED_CATEGORIES,
|
|
93
|
+
observationEnd = new Date(commitStream.generatedAt)
|
|
94
|
+
} = options;
|
|
95
|
+
const excluded = new Set(excludeCategories);
|
|
30
96
|
const targetCommits = commitStream.commits.filter(isTarget);
|
|
97
|
+
const empty = {
|
|
98
|
+
commitsConsidered: targetCommits.length,
|
|
99
|
+
filesConsidered: 0,
|
|
100
|
+
filesExcluded: 0,
|
|
101
|
+
censored: 0,
|
|
102
|
+
avgDays: 0,
|
|
103
|
+
medianDays: 0,
|
|
104
|
+
buckets: { d0_1: 0, d2_7: 0, d8_30: 0, d31_90: 0, d90_plus: 0 }
|
|
105
|
+
};
|
|
31
106
|
if (targetCommits.length === 0) {
|
|
32
|
-
return {
|
|
33
|
-
commitsConsidered: 0,
|
|
34
|
-
avgDays: 0,
|
|
35
|
-
medianDays: 0,
|
|
36
|
-
buckets: {
|
|
37
|
-
d0_1: 0,
|
|
38
|
-
d2_7: 0,
|
|
39
|
-
d8_30: 0,
|
|
40
|
-
d31_90: 0,
|
|
41
|
-
d90_plus: 0
|
|
42
|
-
}
|
|
43
|
-
};
|
|
107
|
+
return { ...empty, commitsConsidered: 0 };
|
|
44
108
|
}
|
|
45
|
-
const fileLifecycles = /* @__PURE__ */ new Map();
|
|
46
109
|
const sortedCommits = [...commitStream.commits].sort(
|
|
47
110
|
(a, b) => new Date(a.authorDate).getTime() - new Date(b.authorDate).getTime()
|
|
48
111
|
);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
60
|
-
}
|
|
112
|
+
const lifecycles = /* @__PURE__ */ new Map();
|
|
113
|
+
let filesExcluded = 0;
|
|
114
|
+
sortedCommits.forEach((commit, index) => {
|
|
115
|
+
if (!isTarget(commit)) return;
|
|
116
|
+
for (const file of commit.stats.files) {
|
|
117
|
+
if (lifecycles.has(file.path)) continue;
|
|
118
|
+
if (excluded.has(categorizeFile(file.path))) {
|
|
119
|
+
filesExcluded++;
|
|
120
|
+
lifecycles.set(file.path, { firstTargetIndex: -1, firstTargetDate: /* @__PURE__ */ new Date(0), eventDate: null });
|
|
121
|
+
continue;
|
|
61
122
|
}
|
|
123
|
+
lifecycles.set(file.path, {
|
|
124
|
+
firstTargetIndex: index,
|
|
125
|
+
firstTargetDate: new Date(commit.authorDate),
|
|
126
|
+
eventDate: null
|
|
127
|
+
});
|
|
62
128
|
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const commitDate = new Date(commit.authorDate);
|
|
129
|
+
});
|
|
130
|
+
sortedCommits.forEach((commit, index) => {
|
|
66
131
|
for (const file of commit.stats.files) {
|
|
67
|
-
const lifecycle =
|
|
68
|
-
if (lifecycle)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
132
|
+
const lifecycle = lifecycles.get(file.path);
|
|
133
|
+
if (!lifecycle || lifecycle.firstTargetIndex < 0) continue;
|
|
134
|
+
if (index <= lifecycle.firstTargetIndex) continue;
|
|
135
|
+
if (lifecycle.eventDate === null) {
|
|
136
|
+
lifecycle.eventDate = new Date(commit.authorDate);
|
|
72
137
|
}
|
|
73
138
|
}
|
|
139
|
+
});
|
|
140
|
+
const survivalDays = [];
|
|
141
|
+
let censored = 0;
|
|
142
|
+
for (const lifecycle of lifecycles.values()) {
|
|
143
|
+
if (lifecycle.firstTargetIndex < 0) continue;
|
|
144
|
+
if (lifecycle.eventDate) {
|
|
145
|
+
survivalDays.push(daysBetween2(lifecycle.firstTargetDate, lifecycle.eventDate));
|
|
146
|
+
} else {
|
|
147
|
+
censored++;
|
|
148
|
+
survivalDays.push(Math.max(0, daysBetween2(lifecycle.firstTargetDate, observationEnd)));
|
|
149
|
+
}
|
|
74
150
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
lifecycle.persistenceDays = daysBetween(lifecycle.firstTargetCommitDate, lifecycle.lastSeenDate);
|
|
78
|
-
persistenceDays.push(lifecycle.persistenceDays);
|
|
151
|
+
if (survivalDays.length === 0) {
|
|
152
|
+
return { ...empty, filesExcluded };
|
|
79
153
|
}
|
|
80
|
-
const avgDays =
|
|
81
|
-
const sortedDays = [...
|
|
82
|
-
const medianDays = sortedDays.length
|
|
83
|
-
const buckets = {
|
|
84
|
-
d0_1: persistenceDays.filter((days) => days <= 1).length,
|
|
85
|
-
d2_7: persistenceDays.filter((days) => days >= 2 && days <= 7).length,
|
|
86
|
-
d8_30: persistenceDays.filter((days) => days >= 8 && days <= 30).length,
|
|
87
|
-
d31_90: persistenceDays.filter((days) => days >= 31 && days <= 90).length,
|
|
88
|
-
d90_plus: persistenceDays.filter((days) => days > 90).length
|
|
89
|
-
};
|
|
154
|
+
const avgDays = survivalDays.reduce((sum, days) => sum + days, 0) / survivalDays.length;
|
|
155
|
+
const sortedDays = [...survivalDays].sort((a, b) => a - b);
|
|
156
|
+
const medianDays = sortedDays.length % 2 === 0 ? (sortedDays[sortedDays.length / 2 - 1] + sortedDays[sortedDays.length / 2]) / 2 : sortedDays[Math.floor(sortedDays.length / 2)];
|
|
90
157
|
return {
|
|
91
158
|
commitsConsidered: targetCommits.length,
|
|
159
|
+
filesConsidered: survivalDays.length,
|
|
160
|
+
filesExcluded,
|
|
161
|
+
censored,
|
|
92
162
|
avgDays: Math.round(avgDays * 100) / 100,
|
|
93
|
-
// Round to 2 decimal places
|
|
94
163
|
medianDays: Math.round(medianDays * 100) / 100,
|
|
95
|
-
buckets
|
|
164
|
+
buckets: {
|
|
165
|
+
d0_1: survivalDays.filter((days) => days <= 1).length,
|
|
166
|
+
d2_7: survivalDays.filter((days) => days >= 2 && days <= 7).length,
|
|
167
|
+
d8_30: survivalDays.filter((days) => days >= 8 && days <= 30).length,
|
|
168
|
+
d31_90: survivalDays.filter((days) => days >= 31 && days <= 90).length,
|
|
169
|
+
d90_plus: survivalDays.filter((days) => days > 90).length
|
|
170
|
+
}
|
|
96
171
|
};
|
|
97
172
|
}
|
|
98
|
-
function calculateBaselinePersistence(commitStream, isTarget = (commit) => commit.tags.attribution === "human") {
|
|
99
|
-
return calculatePersistence(commitStream, isTarget);
|
|
173
|
+
function calculateBaselinePersistence(commitStream, isTarget = (commit) => commit.tags.attribution === "human", options = {}) {
|
|
174
|
+
return calculatePersistence(commitStream, isTarget, options);
|
|
100
175
|
}
|
|
101
176
|
|
|
102
177
|
// src/schema/metrics.ts
|
|
@@ -111,7 +186,20 @@ var Attribution = z.object({
|
|
|
111
186
|
defaultAttribution: z.enum(["ai", "human", "unknown"]),
|
|
112
187
|
// prior applied to unknown commits
|
|
113
188
|
coverageThreshold: z.number().min(0).max(1),
|
|
114
|
-
belowThreshold: z.boolean()
|
|
189
|
+
belowThreshold: z.boolean(),
|
|
190
|
+
// Autonomy axis (#25): commit counts per mode and per mode-evidence level
|
|
191
|
+
modes: z.object({
|
|
192
|
+
none: z.number().int().nonnegative(),
|
|
193
|
+
autocomplete: z.number().int().nonnegative(),
|
|
194
|
+
assisted: z.number().int().nonnegative(),
|
|
195
|
+
agent: z.number().int().nonnegative(),
|
|
196
|
+
unknown: z.number().int().nonnegative()
|
|
197
|
+
}),
|
|
198
|
+
modeEvidence: z.object({
|
|
199
|
+
declared: z.number().int().nonnegative(),
|
|
200
|
+
inferred: z.number().int().nonnegative(),
|
|
201
|
+
none: z.number().int().nonnegative()
|
|
202
|
+
})
|
|
115
203
|
});
|
|
116
204
|
var MergeRatio = z.object({
|
|
117
205
|
aiCommitsTotal: z.number().int().nonnegative(),
|
|
@@ -120,6 +208,10 @@ var MergeRatio = z.object({
|
|
|
120
208
|
});
|
|
121
209
|
var Persistence = z.object({
|
|
122
210
|
commitsConsidered: z.number().int().nonnegative(),
|
|
211
|
+
filesConsidered: z.number().int().nonnegative(),
|
|
212
|
+
filesExcluded: z.number().int().nonnegative(),
|
|
213
|
+
censored: z.number().int().nonnegative(),
|
|
214
|
+
// files that survived the whole window
|
|
123
215
|
avgDays: z.number().nonnegative(),
|
|
124
216
|
medianDays: z.number().nonnegative(),
|
|
125
217
|
buckets: z.object({
|
|
@@ -130,6 +222,31 @@ var Persistence = z.object({
|
|
|
130
222
|
d90_plus: z.number().int().nonnegative()
|
|
131
223
|
})
|
|
132
224
|
});
|
|
225
|
+
var AgeStats = z.object({
|
|
226
|
+
commits: z.number().int().nonnegative(),
|
|
227
|
+
avgAgeDays: z.number().nonnegative(),
|
|
228
|
+
medianAgeDays: z.number().nonnegative()
|
|
229
|
+
});
|
|
230
|
+
var FileCategory = z.enum([
|
|
231
|
+
"source",
|
|
232
|
+
"tests",
|
|
233
|
+
"migrations",
|
|
234
|
+
"config",
|
|
235
|
+
"docs",
|
|
236
|
+
"generated"
|
|
237
|
+
]);
|
|
238
|
+
var CategoryCounts = z.object({
|
|
239
|
+
source: z.number().int().nonnegative(),
|
|
240
|
+
tests: z.number().int().nonnegative(),
|
|
241
|
+
migrations: z.number().int().nonnegative(),
|
|
242
|
+
config: z.number().int().nonnegative(),
|
|
243
|
+
docs: z.number().int().nonnegative(),
|
|
244
|
+
generated: z.number().int().nonnegative()
|
|
245
|
+
});
|
|
246
|
+
var CohortContext = z.object({
|
|
247
|
+
age: AgeStats.nullable(),
|
|
248
|
+
taskMix: CategoryCounts.nullable()
|
|
249
|
+
});
|
|
133
250
|
var CohortMergeRatio = z.object({
|
|
134
251
|
commitsTotal: z.number().int().nonnegative(),
|
|
135
252
|
commitsMerged: z.number().int().nonnegative(),
|
|
@@ -158,6 +275,12 @@ var Metrics = z.object({
|
|
|
158
275
|
attribution: Attribution,
|
|
159
276
|
mergeRatio: MergeRatio,
|
|
160
277
|
persistence: Persistence,
|
|
278
|
+
// Fairness context (#29, #36): age and task mix per cohort, so consumers
|
|
279
|
+
// can judge whether the AI-vs-baseline comparison is apples-to-apples.
|
|
280
|
+
cohorts: z.object({
|
|
281
|
+
ai: CohortContext,
|
|
282
|
+
baseline: CohortContext
|
|
283
|
+
}),
|
|
161
284
|
// Null when no commit is attributed 'human' and no defaultAttribution prior
|
|
162
285
|
// assigns the unknowns: AIDA does not invent a comparison cohort.
|
|
163
286
|
baseline: Baseline.nullable(),
|
|
@@ -173,8 +296,12 @@ function round(value, decimals) {
|
|
|
173
296
|
function calculateMetrics(commitStream, options = {}) {
|
|
174
297
|
const { defaultAttribution = "unknown", coverageThreshold = 0.7 } = options;
|
|
175
298
|
const counts = { ai: 0, human: 0, unknown: 0 };
|
|
299
|
+
const modes = { none: 0, autocomplete: 0, assisted: 0, agent: 0, unknown: 0 };
|
|
300
|
+
const modeEvidence = { declared: 0, inferred: 0, none: 0 };
|
|
176
301
|
for (const commit of commitStream.commits) {
|
|
177
302
|
counts[commit.tags.attribution]++;
|
|
303
|
+
modes[commit.tags.mode]++;
|
|
304
|
+
modeEvidence[commit.tags.modeEvidence]++;
|
|
178
305
|
}
|
|
179
306
|
const total = commitStream.commits.length;
|
|
180
307
|
const coverage = total > 0 ? (counts.ai + counts.human) / total : 0;
|
|
@@ -186,13 +313,29 @@ function calculateMetrics(commitStream, options = {}) {
|
|
|
186
313
|
coverage: round(coverage, 4),
|
|
187
314
|
defaultAttribution,
|
|
188
315
|
coverageThreshold,
|
|
189
|
-
belowThreshold: coverage < coverageThreshold
|
|
316
|
+
belowThreshold: coverage < coverageThreshold,
|
|
317
|
+
modes,
|
|
318
|
+
modeEvidence
|
|
190
319
|
};
|
|
191
|
-
const
|
|
192
|
-
const
|
|
320
|
+
const isExcluded = (commit) => commit.tags.sources.includes("manifest:excluded");
|
|
321
|
+
const isAI = (commit) => commit.tags.attribution === "ai" || defaultAttribution === "ai" && commit.tags.attribution === "unknown" && !isExcluded(commit);
|
|
322
|
+
const isBaseline = (commit) => commit.tags.attribution === "human" || defaultAttribution === "human" && commit.tags.attribution === "unknown" && !isExcluded(commit);
|
|
193
323
|
const mergeRatio = calculateMergeRatio(commitStream, isAI);
|
|
194
324
|
const persistence = calculatePersistence(commitStream, isAI);
|
|
195
|
-
const
|
|
325
|
+
const now = /* @__PURE__ */ new Date();
|
|
326
|
+
const aiCommits = commitStream.commits.filter(isAI);
|
|
327
|
+
const baselineCommits = commitStream.commits.filter(isBaseline);
|
|
328
|
+
const cohorts = {
|
|
329
|
+
ai: {
|
|
330
|
+
age: calculateAgeStats(aiCommits, now),
|
|
331
|
+
taskMix: calculateCategoryCounts(aiCommits)
|
|
332
|
+
},
|
|
333
|
+
baseline: {
|
|
334
|
+
age: calculateAgeStats(baselineCommits, now),
|
|
335
|
+
taskMix: calculateCategoryCounts(baselineCommits)
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
const baselineSize = baselineCommits.length;
|
|
196
339
|
const baselineAssumed = defaultAttribution === "human" && counts.unknown > 0;
|
|
197
340
|
const baseline = baselineSize > 0 ? {
|
|
198
341
|
assumed: baselineAssumed,
|
|
@@ -210,6 +353,8 @@ function calculateMetrics(commitStream, options = {}) {
|
|
|
210
353
|
const caveats = [
|
|
211
354
|
`Attribution coverage is ${(coverage * 100).toFixed(1)}%: metrics only describe commits whose provenance is known.`,
|
|
212
355
|
"Persistence is file-level, not line-level.",
|
|
356
|
+
"Persistence is survival: days until the first subsequent modification. Files never modified again are censored at collection time. Migrations and generated files (convention-driven lifecycles) are excluded.",
|
|
357
|
+
"Persistence comparisons are only meaningful between cohorts of similar age and task mix \u2014 check the cohorts section before reading the delta.",
|
|
213
358
|
"Merge ratio: commits from all branches checked against default branch ancestry. Squash merges may undercount unmerged commits.",
|
|
214
359
|
"Time-windowed collection (--since) also windows the ancestry check: commits merged into the default branch before the window may appear unmerged.",
|
|
215
360
|
"AI tagging uses heuristic patterns; false positives/negatives possible."
|
|
@@ -235,23 +380,32 @@ function calculateMetrics(commitStream, options = {}) {
|
|
|
235
380
|
attribution,
|
|
236
381
|
mergeRatio,
|
|
237
382
|
persistence,
|
|
383
|
+
cohorts,
|
|
238
384
|
baseline,
|
|
239
385
|
delta,
|
|
240
386
|
caveats
|
|
241
387
|
};
|
|
242
388
|
}
|
|
243
389
|
export {
|
|
390
|
+
AgeStats,
|
|
244
391
|
Attribution,
|
|
245
392
|
Baseline,
|
|
393
|
+
CategoryCounts,
|
|
394
|
+
CohortContext,
|
|
246
395
|
CohortMergeRatio,
|
|
396
|
+
DEFAULT_PERSISTENCE_EXCLUDED_CATEGORIES,
|
|
247
397
|
Delta,
|
|
398
|
+
FileCategory,
|
|
248
399
|
MergeRatio,
|
|
249
400
|
Metrics,
|
|
250
401
|
Persistence,
|
|
402
|
+
calculateAgeStats,
|
|
251
403
|
calculateBaselineMergeRatio,
|
|
252
404
|
calculateBaselinePersistence,
|
|
405
|
+
calculateCategoryCounts,
|
|
253
406
|
calculateMergeRatio,
|
|
254
407
|
calculateMetrics,
|
|
255
|
-
calculatePersistence
|
|
408
|
+
calculatePersistence,
|
|
409
|
+
categorizeFile
|
|
256
410
|
};
|
|
257
411
|
//# 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 { Commit, CommitStream, formatISODate } from '@aida-dev/core';\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 './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 for (const commit of commitStream.commits) {\n counts[commit.tags.attribution]++;\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 };\n\n // Cohort membership: unknown commits join a cohort only via the prior.\n const isAI = (commit: Commit) =>\n commit.tags.attribution === 'ai' ||\n (defaultAttribution === 'ai' && commit.tags.attribution === 'unknown');\n const isBaseline = (commit: Commit) =>\n commit.tags.attribution === 'human' ||\n (defaultAttribution === 'human' && commit.tags.attribution === 'unknown');\n\n const mergeRatio = calculateMergeRatio(commitStream, isAI);\n const persistence = calculatePersistence(commitStream, isAI);\n\n // No baseline cohort → no baseline, no delta. AIDA does not invent a\n // comparison out of unattributed commits.\n const baselineSize = commitStream.commits.filter(isBaseline).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 '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 baseline,\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(\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});\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 // 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 // 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 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 is survival: days until the first subsequent modification. Files never modified again are censored at collection time. Migrations and generated files (convention-driven lifecycles) are excluded.',\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 { categorizeFile } from './cohort.js';\nimport { FileCategory, Persistence } from './schema/metrics.js';\n\n// Categories whose lifecycle is governed by convention, not code quality:\n// migrations are append-only (never modified once applied), generated files\n// (lockfiles, changelogs) churn on every release regardless of author.\n// Either way their survival carries no signal, so they are excluded from\n// persistence by default. They still appear in the task-mix table.\nexport const DEFAULT_PERSISTENCE_EXCLUDED_CATEGORIES: FileCategory[] = [\n 'migrations',\n 'generated',\n];\n\nexport interface PersistenceOptions {\n excludeCategories?: FileCategory[];\n // End of the observation window, used to measure censored files (never\n // modified again). Defaults to the stream's collection time.\n observationEnd?: Date;\n}\n\ninterface FileLifecycle {\n firstTargetIndex: number;\n firstTargetDate: Date;\n // Set when a later commit modifies or deletes the file: survival ends\n eventDate: Date | null;\n}\n\n// Persistence = survival: days from the first target-cohort touch of a file\n// until the FIRST subsequent modification (or deletion) by any commit.\n// Files never touched again are censored at the observation end — they\n// survived the whole window, which is the best possible outcome, not zero.\nexport function calculatePersistence(\n commitStream: CommitStream,\n isTarget: (commit: Commit) => boolean = (commit) => commit.tags.attribution === 'ai',\n options: PersistenceOptions = {}\n): Persistence {\n const {\n excludeCategories = DEFAULT_PERSISTENCE_EXCLUDED_CATEGORIES,\n observationEnd = new Date(commitStream.generatedAt),\n } = options;\n const excluded = new Set<FileCategory>(excludeCategories);\n\n const targetCommits = commitStream.commits.filter(isTarget);\n\n const empty: Persistence = {\n commitsConsidered: targetCommits.length,\n filesConsidered: 0,\n filesExcluded: 0,\n censored: 0,\n avgDays: 0,\n medianDays: 0,\n buckets: { d0_1: 0, d2_7: 0, d8_30: 0, d31_90: 0, d90_plus: 0 },\n };\n\n if (targetCommits.length === 0) {\n return { ...empty, commitsConsidered: 0 };\n }\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 // First target-cohort touch per file\n const lifecycles = new Map<string, FileLifecycle>();\n let filesExcluded = 0;\n sortedCommits.forEach((commit, index) => {\n if (!isTarget(commit)) return;\n for (const file of commit.stats.files) {\n if (lifecycles.has(file.path)) continue;\n if (excluded.has(categorizeFile(file.path))) {\n filesExcluded++;\n lifecycles.set(file.path, { firstTargetIndex: -1, firstTargetDate: new Date(0), eventDate: null });\n continue;\n }\n lifecycles.set(file.path, {\n firstTargetIndex: index,\n firstTargetDate: new Date(commit.authorDate),\n eventDate: null,\n });\n }\n });\n\n // First subsequent modification (or deletion) ends the survival clock\n sortedCommits.forEach((commit, index) => {\n for (const file of commit.stats.files) {\n const lifecycle = lifecycles.get(file.path);\n if (!lifecycle || lifecycle.firstTargetIndex < 0) continue; // excluded category\n if (index <= lifecycle.firstTargetIndex) continue; // not later than first touch\n if (lifecycle.eventDate === null) {\n lifecycle.eventDate = new Date(commit.authorDate);\n }\n }\n });\n\n const survivalDays: number[] = [];\n let censored = 0;\n for (const lifecycle of lifecycles.values()) {\n if (lifecycle.firstTargetIndex < 0) continue; // excluded category\n if (lifecycle.eventDate) {\n survivalDays.push(daysBetween(lifecycle.firstTargetDate, lifecycle.eventDate));\n } else {\n // Never modified again: survived to the end of the observation window\n censored++;\n survivalDays.push(Math.max(0, daysBetween(lifecycle.firstTargetDate, observationEnd)));\n }\n }\n\n if (survivalDays.length === 0) {\n return { ...empty, filesExcluded };\n }\n\n const avgDays = survivalDays.reduce((sum, days) => sum + days, 0) / survivalDays.length;\n const sortedDays = [...survivalDays].sort((a, b) => a - b);\n const medianDays =\n sortedDays.length % 2 === 0\n ? (sortedDays[sortedDays.length / 2 - 1] + sortedDays[sortedDays.length / 2]) / 2\n : sortedDays[Math.floor(sortedDays.length / 2)];\n\n return {\n commitsConsidered: targetCommits.length,\n filesConsidered: survivalDays.length,\n filesExcluded,\n censored,\n avgDays: Math.round(avgDays * 100) / 100,\n medianDays: Math.round(medianDays * 100) / 100,\n buckets: {\n d0_1: survivalDays.filter((days) => days <= 1).length,\n d2_7: survivalDays.filter((days) => days >= 2 && days <= 7).length,\n d8_30: survivalDays.filter((days) => days >= 8 && days <= 30).length,\n d31_90: survivalDays.filter((days) => days >= 31 && days <= 90).length,\n d90_plus: survivalDays.filter((days) => days > 90).length,\n },\n };\n}\n\nexport function calculateBaselinePersistence(\n commitStream: CommitStream,\n isTarget: (commit: Commit) => boolean = (commit) => commit.tags.attribution === 'human',\n options: PersistenceOptions = {}\n): Persistence {\n return calculatePersistence(commitStream, isTarget, options);\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\n// Persistence = survival: days from first target-cohort touch of a file to\n// the first subsequent modification. Files never modified again are censored\n// at the observation end (they survived the window — the best outcome).\n// Migrations and generated files are excluded by default: their lifecycle is\n// convention-driven and carries no quality signal.\nexport const Persistence = z.object({\n commitsConsidered: z.number().int().nonnegative(),\n filesConsidered: z.number().int().nonnegative(),\n filesExcluded: z.number().int().nonnegative(),\n censored: z.number().int().nonnegative(), // files that survived the whole window\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":"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","names":["daysBetween","daysBetween"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aida-dev/metrics",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.
|
|
14
|
+
"@aida-dev/core": "0.10.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"tsup": "^8.0.0",
|