50c 3.9.5 → 3.9.7
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.
Potentially problematic release.
This version of 50c might be problematic. Click here for more details.
- package/README.md +175 -201
- package/bin/50c.js +2079 -2278
- package/lib/pre-publish.js +812 -1361
- package/lib/subagent.js +7 -13
- package/lib/team.js +691 -714
- package/lib/tools-registry.js +184 -226
- package/package.json +39 -39
- package/lib/backdoor-checker.js +0 -732
- package/lib/ip-utils.js +0 -47
package/lib/pre-publish.js
CHANGED
|
@@ -1,1361 +1,812 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 50c Pre-Publish Verification Tool
|
|
3
|
-
*
|
|
4
|
-
* Thorough checklist before publishing to:
|
|
5
|
-
* - npm (packages)
|
|
6
|
-
* - GitHub (releases)
|
|
7
|
-
* - arXiv (papers)
|
|
8
|
-
* - Medical journals
|
|
9
|
-
* - Scientific publications
|
|
10
|
-
*
|
|
11
|
-
* "Ask once, verify everything"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
{ id: '
|
|
45
|
-
{ id: '
|
|
46
|
-
{ id: '
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
{ id: '
|
|
50
|
-
{ id: '
|
|
51
|
-
{ id: '
|
|
52
|
-
{ id: '
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{ id: '
|
|
62
|
-
{ id: '
|
|
63
|
-
{ id: '
|
|
64
|
-
{ id: '
|
|
65
|
-
{ id: '
|
|
66
|
-
{ id: '
|
|
67
|
-
{ id: '
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{ id: '
|
|
77
|
-
{ id: '
|
|
78
|
-
{ id: '
|
|
79
|
-
{ id: '
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{ id: '
|
|
84
|
-
{ id: '
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
{ id: '
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
{ id: '
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{ id: '
|
|
98
|
-
{ id: '
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{ id: '
|
|
102
|
-
{ id: '
|
|
103
|
-
{ id: '
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{ id: '
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
{ id: '
|
|
117
|
-
{ id: '
|
|
118
|
-
{ id: '
|
|
119
|
-
{ id: '
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
{ id: '
|
|
123
|
-
{ id: '
|
|
124
|
-
{ id: '
|
|
125
|
-
{ id: '
|
|
126
|
-
{ id: '
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{ id: '
|
|
130
|
-
{ id: '
|
|
131
|
-
{ id: '
|
|
132
|
-
{ id: '
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
{ id: '
|
|
146
|
-
{ id: '
|
|
147
|
-
{ id: '
|
|
148
|
-
{ id: '
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
{ id: '
|
|
152
|
-
{ id: '
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
{ id: '
|
|
162
|
-
{ id: '
|
|
163
|
-
{ id: '
|
|
164
|
-
{ id: '
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
{ id: '
|
|
168
|
-
{ id: '
|
|
169
|
-
{ id: '
|
|
170
|
-
{ id: '
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{ id: '
|
|
179
|
-
{ id: '
|
|
180
|
-
{ id: '
|
|
181
|
-
{ id: '
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
{ id: '
|
|
190
|
-
{ id: '
|
|
191
|
-
{ id: '
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
{ id: '
|
|
195
|
-
{ id: '
|
|
196
|
-
{ id: '
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
{ id: '
|
|
200
|
-
{ id: '
|
|
201
|
-
{ id: '
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
} catch (e) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
},
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
async pkg_repo(ctx) {
|
|
815
|
-
const repo = ctx.packageJson?.repository?.url || ctx.packageJson?.repository;
|
|
816
|
-
if (!repo) return { pass: null, msg: 'No repository URL (optional)' };
|
|
817
|
-
|
|
818
|
-
let checkUrl = String(repo).replace(/^git\+/, '').replace(/\.git$/, '');
|
|
819
|
-
if (checkUrl.startsWith('git://')) {
|
|
820
|
-
checkUrl = checkUrl.replace('git://', 'https://');
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
try {
|
|
824
|
-
const res = await fetch(checkUrl, { method: 'HEAD' });
|
|
825
|
-
return res.ok
|
|
826
|
-
? { pass: true, msg: `${checkUrl} accessible` }
|
|
827
|
-
: { pass: false, msg: `${checkUrl} returned ${res.status}` };
|
|
828
|
-
} catch (e) {
|
|
829
|
-
return { pass: false, msg: `${checkUrl} unreachable` };
|
|
830
|
-
}
|
|
831
|
-
},
|
|
832
|
-
|
|
833
|
-
async syntax_valid(ctx) {
|
|
834
|
-
const { execSync } = require('child_process');
|
|
835
|
-
const jsFiles = ctx.files.filter(f => f.endsWith('.js') || f.endsWith('.mjs'));
|
|
836
|
-
const errors = [];
|
|
837
|
-
|
|
838
|
-
for (const file of jsFiles.slice(0, 20)) {
|
|
839
|
-
try {
|
|
840
|
-
execSync(`node -c "${file}"`, { stdio: 'pipe', cwd: ctx.cwd });
|
|
841
|
-
} catch (e) {
|
|
842
|
-
errors.push(file);
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
return errors.length === 0
|
|
847
|
-
? { pass: true, msg: `${jsFiles.length} JS files valid` }
|
|
848
|
-
: { pass: false, msg: `Syntax errors in: ${errors.join(', ')}` };
|
|
849
|
-
},
|
|
850
|
-
|
|
851
|
-
/**
|
|
852
|
-
* FIXED: Cross-platform — uses Node.js fs instead of Windows-only findstr
|
|
853
|
-
*/
|
|
854
|
-
async no_hardcoded_secrets(ctx) {
|
|
855
|
-
const fs = require('fs');
|
|
856
|
-
const path = require('path');
|
|
857
|
-
|
|
858
|
-
const patterns = [
|
|
859
|
-
/api[_-]?key\s*[=:]\s*["'][a-zA-Z0-9]{20,}/i,
|
|
860
|
-
/password\s*[=:]\s*["'][^"']{8,}/i,
|
|
861
|
-
/secret\s*[=:]\s*["'][a-zA-Z0-9]{20,}/i,
|
|
862
|
-
/\btoken\s*[=:]\s*["'][a-zA-Z0-9]{20,}/i,
|
|
863
|
-
/AWS[_-]?ACCESS[_-]?KEY/,
|
|
864
|
-
/PRIVATE[_-]?KEY/,
|
|
865
|
-
/-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY/,
|
|
866
|
-
/ghp_[a-zA-Z0-9]{36}/, // GitHub personal access token
|
|
867
|
-
/sk-[a-zA-Z0-9]{20,}/, // OpenAI/Stripe secret key
|
|
868
|
-
];
|
|
869
|
-
|
|
870
|
-
const findings = [];
|
|
871
|
-
const sourceFiles = ctx.files.filter(f => /\.(js|ts|mjs|cjs|json)$/.test(f));
|
|
872
|
-
|
|
873
|
-
for (const file of sourceFiles.slice(0, 50)) {
|
|
874
|
-
const basename = path.basename(file);
|
|
875
|
-
if (basename === 'package-lock.json' || basename === 'pre-publish.js') continue;
|
|
876
|
-
|
|
877
|
-
try {
|
|
878
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
879
|
-
const lines = content.split('\n');
|
|
880
|
-
|
|
881
|
-
for (let i = 0; i < lines.length; i++) {
|
|
882
|
-
const line = lines[i];
|
|
883
|
-
if (line.trim().startsWith('//') || line.trim().startsWith('*')) continue;
|
|
884
|
-
|
|
885
|
-
for (const pattern of patterns) {
|
|
886
|
-
if (pattern.test(line)) {
|
|
887
|
-
// Don't flag environment variable reads or pattern definitions
|
|
888
|
-
if (/process\.env|regex|pattern|example|test|mock/i.test(line)) continue;
|
|
889
|
-
findings.push(`${basename}:${i + 1}`);
|
|
890
|
-
break;
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
} catch (e) {}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
return findings.length === 0
|
|
898
|
-
? { pass: true, msg: 'No hardcoded secrets detected' }
|
|
899
|
-
: { pass: false, msg: `Potential secrets: ${findings.slice(0, 5).join(', ')}` };
|
|
900
|
-
},
|
|
901
|
-
|
|
902
|
-
async readme_links_live(ctx) {
|
|
903
|
-
const fs = require('fs');
|
|
904
|
-
const readmePath = ctx.files.find(f => f.toLowerCase().endsWith('readme.md'));
|
|
905
|
-
if (!readmePath) return { pass: false, msg: 'No README.md' };
|
|
906
|
-
|
|
907
|
-
const content = fs.readFileSync(readmePath, 'utf8');
|
|
908
|
-
const urlPattern = /https?:\/\/[^\s\)\"\']+/g;
|
|
909
|
-
const urls = [...new Set(content.match(urlPattern) || [])];
|
|
910
|
-
|
|
911
|
-
const deadLinks = [];
|
|
912
|
-
for (const url of urls.slice(0, 10)) {
|
|
913
|
-
try {
|
|
914
|
-
const res = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) });
|
|
915
|
-
if (!res.ok) deadLinks.push(url);
|
|
916
|
-
} catch (e) {
|
|
917
|
-
deadLinks.push(url);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
return deadLinks.length === 0
|
|
922
|
-
? { pass: true, msg: `${urls.length} links verified` }
|
|
923
|
-
: { pass: false, msg: `Dead links: ${deadLinks.join(', ')}` };
|
|
924
|
-
},
|
|
925
|
-
|
|
926
|
-
async no_localhost(ctx) {
|
|
927
|
-
const fs = require('fs');
|
|
928
|
-
const path = require('path');
|
|
929
|
-
const findings = [];
|
|
930
|
-
|
|
931
|
-
for (const file of ctx.files.filter(f => /\.(js|ts|json)$/.test(f)).slice(0, 30)) {
|
|
932
|
-
if (path.basename(file) === 'pre-publish.js') continue;
|
|
933
|
-
if (path.basename(file) === 'backdoor-checker.js') continue;
|
|
934
|
-
|
|
935
|
-
try {
|
|
936
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
937
|
-
if (/localhost|127\.0\.0\.1|0\.0\.0\.0/.test(content)) {
|
|
938
|
-
const lines = content.split('\n');
|
|
939
|
-
for (let i = 0; i < lines.length; i++) {
|
|
940
|
-
const line = lines[i];
|
|
941
|
-
if (/localhost|127\.0\.0\.1/.test(line) && !line.trim().startsWith('//') && !line.includes('||') && !line.includes('regex') && !line.includes('pattern')) {
|
|
942
|
-
findings.push(`${path.basename(file)}:${i+1}`);
|
|
943
|
-
break;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
} catch (e) {}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
return findings.length === 0
|
|
951
|
-
? { pass: true, msg: 'No localhost references' }
|
|
952
|
-
: { pass: false, msg: `localhost found: ${findings.slice(0,3).join(', ')}` };
|
|
953
|
-
},
|
|
954
|
-
|
|
955
|
-
async size_reasonable(ctx) {
|
|
956
|
-
const { execSync } = require('child_process');
|
|
957
|
-
try {
|
|
958
|
-
const result = execSync('npm pack --dry-run 2>&1', { cwd: ctx.cwd, encoding: 'utf8' });
|
|
959
|
-
const sizeMatch = result.match(/total files.*?(\d+(?:\.\d+)?)\s*(kB|MB|B)/i);
|
|
960
|
-
if (sizeMatch) {
|
|
961
|
-
let sizeKB = parseFloat(sizeMatch[1]);
|
|
962
|
-
if (sizeMatch[2].toLowerCase() === 'mb') sizeKB *= 1024;
|
|
963
|
-
if (sizeMatch[2].toLowerCase() === 'b') sizeKB /= 1024;
|
|
964
|
-
|
|
965
|
-
return sizeKB < 10240
|
|
966
|
-
? { pass: true, msg: `Package size: ${sizeKB.toFixed(1)} KB` }
|
|
967
|
-
: { pass: false, msg: `Package too large: ${sizeKB.toFixed(1)} KB (limit 10MB)` };
|
|
968
|
-
}
|
|
969
|
-
} catch (e) {}
|
|
970
|
-
return { pass: null, msg: 'Could not determine package size' };
|
|
971
|
-
},
|
|
972
|
-
|
|
973
|
-
// ====== ACADEMIC/AI CHECKS (unchanged) ======
|
|
974
|
-
|
|
975
|
-
async latex_compiles(ctx) {
|
|
976
|
-
const { execSync } = require('child_process');
|
|
977
|
-
const texFile = ctx.files.find(f => f.endsWith('.tex') && !f.includes('preamble'));
|
|
978
|
-
if (!texFile) return { pass: false, msg: 'No .tex file found' };
|
|
979
|
-
|
|
980
|
-
try {
|
|
981
|
-
execSync(`pdflatex -interaction=nonstopmode "${texFile}"`, { cwd: ctx.cwd, stdio: 'pipe' });
|
|
982
|
-
return { pass: true, msg: 'LaTeX compiles' };
|
|
983
|
-
} catch (e) {
|
|
984
|
-
return { pass: false, msg: 'LaTeX compilation failed' };
|
|
985
|
-
}
|
|
986
|
-
},
|
|
987
|
-
|
|
988
|
-
async no_placeholder(ctx) {
|
|
989
|
-
const fs = require('fs');
|
|
990
|
-
const findings = [];
|
|
991
|
-
const patterns = /\b(TODO|FIXME|XXX|TBD|PLACEHOLDER)\b/gi;
|
|
992
|
-
|
|
993
|
-
for (const file of ctx.files.slice(0, 50)) {
|
|
994
|
-
try {
|
|
995
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
996
|
-
const matches = content.match(patterns);
|
|
997
|
-
if (matches) {
|
|
998
|
-
findings.push(`${file}: ${matches.length} placeholders`);
|
|
999
|
-
}
|
|
1000
|
-
} catch (e) {}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
return findings.length === 0
|
|
1004
|
-
? { pass: true, msg: 'No placeholders found' }
|
|
1005
|
-
: { pass: false, msg: findings.slice(0, 3).join('; ') };
|
|
1006
|
-
},
|
|
1007
|
-
|
|
1008
|
-
async math_verified_bcalc(ctx) {
|
|
1009
|
-
const fs = require('fs');
|
|
1010
|
-
const texFiles = ctx.files.filter(f => f.endsWith('.tex'));
|
|
1011
|
-
if (texFiles.length === 0) return { pass: null, msg: 'No .tex files' };
|
|
1012
|
-
|
|
1013
|
-
const content = fs.readFileSync(texFiles[0], 'utf8');
|
|
1014
|
-
const equations = content.match(/\$\$[^$]+\$\$/g) || [];
|
|
1015
|
-
const claims = content.match(/\\begin\{theorem\}[\s\S]*?\\end\{theorem\}/g) || [];
|
|
1016
|
-
|
|
1017
|
-
if (equations.length === 0 && claims.length === 0) {
|
|
1018
|
-
return { pass: null, msg: 'No equations/theorems found to verify' };
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
const sample = claims[0] || equations[0];
|
|
1022
|
-
const cleanSample = sample.replace(/\\[a-z]+\{[^}]*\}/g, '').substring(0, 500);
|
|
1023
|
-
|
|
1024
|
-
try {
|
|
1025
|
-
const result = await call50cTool('bcalc', { expression: cleanSample, mode: 'verify' }, 30000);
|
|
1026
|
-
|
|
1027
|
-
if (result.error) return { pass: null, msg: `bCalc unavailable: ${result.error}` };
|
|
1028
|
-
|
|
1029
|
-
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1030
|
-
const hasIssues = /error|invalid|incorrect|false/i.test(resultStr);
|
|
1031
|
-
|
|
1032
|
-
return hasIssues
|
|
1033
|
-
? { pass: false, msg: `bCalc found issues: ${resultStr.substring(0, 100)}` }
|
|
1034
|
-
: { pass: true, msg: 'bCalc verified mathematical expressions' };
|
|
1035
|
-
} catch (e) {
|
|
1036
|
-
return { pass: null, msg: `bCalc check failed: ${e.message}` };
|
|
1037
|
-
}
|
|
1038
|
-
},
|
|
1039
|
-
|
|
1040
|
-
async proofs_verified_genius(ctx) {
|
|
1041
|
-
const fs = require('fs');
|
|
1042
|
-
const texFiles = ctx.files.filter(f => f.endsWith('.tex'));
|
|
1043
|
-
if (texFiles.length === 0) return { pass: null, msg: 'No .tex files' };
|
|
1044
|
-
|
|
1045
|
-
const content = fs.readFileSync(texFiles[0], 'utf8');
|
|
1046
|
-
const proofs = content.match(/\\begin\{proof\}[\s\S]*?\\end\{proof\}/g) || [];
|
|
1047
|
-
if (proofs.length === 0) return { pass: null, msg: 'No formal proofs found' };
|
|
1048
|
-
|
|
1049
|
-
const abstractMatch = content.match(/\\begin\{abstract\}([\s\S]*?)\\end\{abstract\}/);
|
|
1050
|
-
const abstract = abstractMatch ? abstractMatch[1] : '';
|
|
1051
|
-
const firstProof = proofs[0].substring(0, 1000);
|
|
1052
|
-
|
|
1053
|
-
try {
|
|
1054
|
-
const result = await call50cTool('genius_plus', {
|
|
1055
|
-
problem: `Verify this mathematical proof for logical gaps, unstated assumptions, or errors:\n\nContext: ${abstract.substring(0, 300)}\n\nProof:\n${firstProof}`
|
|
1056
|
-
}, 90000);
|
|
1057
|
-
|
|
1058
|
-
if (result.error) return { pass: null, msg: `genius+ unavailable: ${result.error}` };
|
|
1059
|
-
|
|
1060
|
-
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1061
|
-
const redFlags = /gap|flaw|error|incorrect|missing|invalid|contradiction/i.test(resultStr);
|
|
1062
|
-
|
|
1063
|
-
return redFlags
|
|
1064
|
-
? { pass: false, msg: `genius+ found issues: ${resultStr.substring(0, 150)}...` }
|
|
1065
|
-
: { pass: true, msg: 'genius+ verified proof structure' };
|
|
1066
|
-
} catch (e) {
|
|
1067
|
-
return { pass: null, msg: `genius+ check failed: ${e.message}` };
|
|
1068
|
-
}
|
|
1069
|
-
},
|
|
1070
|
-
|
|
1071
|
-
async claims_validated_search(ctx) {
|
|
1072
|
-
const fs = require('fs');
|
|
1073
|
-
const texFiles = ctx.files.filter(f => f.endsWith('.tex'));
|
|
1074
|
-
if (texFiles.length === 0) return { pass: null, msg: 'No .tex files' };
|
|
1075
|
-
|
|
1076
|
-
const content = fs.readFileSync(texFiles[0], 'utf8');
|
|
1077
|
-
const titleMatch = content.match(/\\title\{([^}]+)\}/);
|
|
1078
|
-
const title = titleMatch ? titleMatch[1] : '';
|
|
1079
|
-
|
|
1080
|
-
const noveltyPatterns = /first|novel|new|unprecedented|breakthrough|improve|better than/gi;
|
|
1081
|
-
const hasNoveltyClaims = noveltyPatterns.test(content);
|
|
1082
|
-
|
|
1083
|
-
if (!hasNoveltyClaims || !title) return { pass: null, msg: 'No novelty claims to validate' };
|
|
1084
|
-
|
|
1085
|
-
try {
|
|
1086
|
-
const result = await call50cTool('web_search', {
|
|
1087
|
-
query: title.replace(/[\\{}]/g, ' ').trim(),
|
|
1088
|
-
max_results: 5
|
|
1089
|
-
}, 30000);
|
|
1090
|
-
|
|
1091
|
-
if (result.error) return { pass: null, msg: `web_search unavailable: ${result.error}` };
|
|
1092
|
-
|
|
1093
|
-
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1094
|
-
const duplicateSignals = /already|published|existing|prior art|known result/i.test(resultStr);
|
|
1095
|
-
|
|
1096
|
-
return duplicateSignals
|
|
1097
|
-
? { pass: false, msg: `Potential prior work found - verify novelty: ${resultStr.substring(0, 100)}` }
|
|
1098
|
-
: { pass: true, msg: 'No obvious conflicting prior work found' };
|
|
1099
|
-
} catch (e) {
|
|
1100
|
-
return { pass: null, msg: `Search check failed: ${e.message}` };
|
|
1101
|
-
}
|
|
1102
|
-
},
|
|
1103
|
-
|
|
1104
|
-
async methodology_hints(ctx) {
|
|
1105
|
-
const fs = require('fs');
|
|
1106
|
-
const files = ctx.files.filter(f => /\.(tex|md|txt)$/.test(f));
|
|
1107
|
-
if (files.length === 0) return { pass: null, msg: 'No text files' };
|
|
1108
|
-
|
|
1109
|
-
const content = fs.readFileSync(files[0], 'utf8').substring(0, 2000);
|
|
1110
|
-
const methodsMatch = content.match(/method|experiment|procedure|approach/i);
|
|
1111
|
-
if (!methodsMatch) return { pass: null, msg: 'No methods section detected' };
|
|
1112
|
-
|
|
1113
|
-
try {
|
|
1114
|
-
const result = await call50cTool('hints_plus', {
|
|
1115
|
-
query: `Evaluate scientific methodology for weaknesses: ${content.substring(0, 1000)}`
|
|
1116
|
-
}, 30000);
|
|
1117
|
-
|
|
1118
|
-
if (result.error) return { pass: null, msg: `hints+ unavailable: ${result.error}` };
|
|
1119
|
-
|
|
1120
|
-
return {
|
|
1121
|
-
pass: null,
|
|
1122
|
-
msg: `hints+: ${typeof result === 'string' ? result.substring(0, 200) : JSON.stringify(result).substring(0, 200)}`
|
|
1123
|
-
};
|
|
1124
|
-
} catch (e) {
|
|
1125
|
-
return { pass: null, msg: `hints+ check failed: ${e.message}` };
|
|
1126
|
-
}
|
|
1127
|
-
},
|
|
1128
|
-
};
|
|
1129
|
-
|
|
1130
|
-
/**
|
|
1131
|
-
* Main verification function
|
|
1132
|
-
*/
|
|
1133
|
-
async function verify(profile, options = {}) {
|
|
1134
|
-
const config = PROFILES[profile];
|
|
1135
|
-
if (!config) {
|
|
1136
|
-
return { error: `Unknown profile: ${profile}. Available: ${Object.keys(PROFILES).join(', ')}` };
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
const fs = require('fs');
|
|
1140
|
-
const path = require('path');
|
|
1141
|
-
const cwd = options.cwd || process.cwd();
|
|
1142
|
-
|
|
1143
|
-
// Build context
|
|
1144
|
-
const ctx = {
|
|
1145
|
-
cwd,
|
|
1146
|
-
profile,
|
|
1147
|
-
files: [],
|
|
1148
|
-
packageJson: null,
|
|
1149
|
-
allowlist: loadAllowlist(cwd),
|
|
1150
|
-
};
|
|
1151
|
-
|
|
1152
|
-
// Gather files
|
|
1153
|
-
try {
|
|
1154
|
-
const walk = (dir, depth = 0) => {
|
|
1155
|
-
if (depth > 3) return;
|
|
1156
|
-
const items = fs.readdirSync(dir);
|
|
1157
|
-
for (const item of items) {
|
|
1158
|
-
if (item.startsWith('.') || item === 'node_modules') continue;
|
|
1159
|
-
const full = path.join(dir, item);
|
|
1160
|
-
const stat = fs.statSync(full);
|
|
1161
|
-
if (stat.isDirectory()) {
|
|
1162
|
-
walk(full, depth + 1);
|
|
1163
|
-
} else {
|
|
1164
|
-
ctx.files.push(full);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
};
|
|
1168
|
-
walk(cwd);
|
|
1169
|
-
} catch (e) {}
|
|
1170
|
-
|
|
1171
|
-
// Load package.json if exists
|
|
1172
|
-
try {
|
|
1173
|
-
const pkgPath = path.join(cwd, 'package.json');
|
|
1174
|
-
if (fs.existsSync(pkgPath)) {
|
|
1175
|
-
ctx.packageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
1176
|
-
}
|
|
1177
|
-
} catch (e) {}
|
|
1178
|
-
|
|
1179
|
-
// Run checks
|
|
1180
|
-
const results = {
|
|
1181
|
-
profile: config.name,
|
|
1182
|
-
timestamp: new Date().toISOString(),
|
|
1183
|
-
summary: { pass: 0, fail: 0, skip: 0, manual: 0, critical: 0, high: 0, medium: 0 },
|
|
1184
|
-
checks: [],
|
|
1185
|
-
ready: false,
|
|
1186
|
-
blocked: false,
|
|
1187
|
-
};
|
|
1188
|
-
|
|
1189
|
-
for (const check of config.checks) {
|
|
1190
|
-
const result = { ...check, status: null, msg: '', severity: CHECK_SEVERITY[check.id] || null };
|
|
1191
|
-
|
|
1192
|
-
if (check.empirical && EMPIRICAL_CHECKS[check.id]) {
|
|
1193
|
-
try {
|
|
1194
|
-
const r = await EMPIRICAL_CHECKS[check.id](ctx);
|
|
1195
|
-
result.status = r.pass ? 'PASS' : (r.pass === false ? 'FAIL' : 'SKIP');
|
|
1196
|
-
result.msg = r.msg;
|
|
1197
|
-
} catch (e) {
|
|
1198
|
-
result.status = 'SKIP';
|
|
1199
|
-
result.msg = `Error: ${e.message}`;
|
|
1200
|
-
}
|
|
1201
|
-
} else if (check.empirical) {
|
|
1202
|
-
result.status = 'SKIP';
|
|
1203
|
-
result.msg = 'Check not implemented';
|
|
1204
|
-
} else {
|
|
1205
|
-
result.status = 'MANUAL';
|
|
1206
|
-
result.msg = 'Requires manual review';
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
results.checks.push(result);
|
|
1210
|
-
|
|
1211
|
-
if (result.status === 'PASS') results.summary.pass++;
|
|
1212
|
-
else if (result.status === 'FAIL') {
|
|
1213
|
-
results.summary.fail++;
|
|
1214
|
-
// Track severity of failures
|
|
1215
|
-
if (result.severity === 'CRITICAL') results.summary.critical++;
|
|
1216
|
-
else if (result.severity === 'HIGH') results.summary.high++;
|
|
1217
|
-
else if (result.severity === 'MEDIUM') results.summary.medium++;
|
|
1218
|
-
}
|
|
1219
|
-
else if (result.status === 'SKIP') results.summary.skip++;
|
|
1220
|
-
else results.summary.manual++;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
// Severity-weighted scoring
|
|
1224
|
-
const totalCheckable = results.summary.pass + results.summary.fail + results.summary.manual;
|
|
1225
|
-
const penaltyScore = (results.summary.critical * SEVERITY_WEIGHTS.CRITICAL) +
|
|
1226
|
-
(results.summary.high * SEVERITY_WEIGHTS.HIGH) +
|
|
1227
|
-
(results.summary.medium * SEVERITY_WEIGHTS.MEDIUM);
|
|
1228
|
-
|
|
1229
|
-
results.blocked = results.summary.critical > 0;
|
|
1230
|
-
results.ready = results.summary.fail === 0;
|
|
1231
|
-
results.score = Math.max(0, Math.round((results.summary.pass / Math.max(totalCheckable, 1)) * 100));
|
|
1232
|
-
results.severityScore = penaltyScore;
|
|
1233
|
-
|
|
1234
|
-
// Verdict
|
|
1235
|
-
if (results.blocked) {
|
|
1236
|
-
results.verdict = 'BLOCKED — CRITICAL supply chain failure. DO NOT PUBLISH.';
|
|
1237
|
-
} else if (results.summary.high > 0) {
|
|
1238
|
-
results.verdict = 'NOT READY — HIGH severity issues must be resolved.';
|
|
1239
|
-
} else if (results.summary.fail > 0) {
|
|
1240
|
-
results.verdict = 'NOT READY — Fix failing checks before publishing.';
|
|
1241
|
-
} else {
|
|
1242
|
-
results.verdict = 'READY TO PUBLISH';
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
return results;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
/**
|
|
1249
|
-
* Generate verification receipt as Markdown
|
|
1250
|
-
*/
|
|
1251
|
-
function generateReceipt(results, options = {}) {
|
|
1252
|
-
const lines = [
|
|
1253
|
-
'# 50c Pre-Publish Verification Receipt',
|
|
1254
|
-
'',
|
|
1255
|
-
`**Generated:** ${results.timestamp}`,
|
|
1256
|
-
`**Profile:** ${results.profile}`,
|
|
1257
|
-
`**Directory:** ${options.cwd || 'N/A'}`,
|
|
1258
|
-
'',
|
|
1259
|
-
];
|
|
1260
|
-
|
|
1261
|
-
// Verdict banner
|
|
1262
|
-
if (results.blocked) {
|
|
1263
|
-
lines.push('## BLOCKED — CRITICAL SUPPLY CHAIN FAILURE');
|
|
1264
|
-
lines.push('');
|
|
1265
|
-
lines.push('> This package has CRITICAL security issues that MUST be resolved before publishing.');
|
|
1266
|
-
lines.push('> Publishing with these issues could compromise every user who installs this package.');
|
|
1267
|
-
} else if (results.ready) {
|
|
1268
|
-
lines.push('## READY TO PUBLISH');
|
|
1269
|
-
} else {
|
|
1270
|
-
lines.push('## NOT READY — FIX ISSUES');
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
lines.push('');
|
|
1274
|
-
lines.push('---');
|
|
1275
|
-
lines.push('');
|
|
1276
|
-
lines.push('## Summary');
|
|
1277
|
-
lines.push('');
|
|
1278
|
-
lines.push('| Metric | Count |');
|
|
1279
|
-
lines.push('|--------|-------|');
|
|
1280
|
-
lines.push(`| Pass | ${results.summary.pass} |`);
|
|
1281
|
-
lines.push(`| Fail | ${results.summary.fail} |`);
|
|
1282
|
-
if (results.summary.critical > 0) lines.push(`| -- CRITICAL | ${results.summary.critical} |`);
|
|
1283
|
-
if (results.summary.high > 0) lines.push(`| -- HIGH | ${results.summary.high} |`);
|
|
1284
|
-
if (results.summary.medium > 0) lines.push(`| -- MEDIUM | ${results.summary.medium} |`);
|
|
1285
|
-
lines.push(`| Skip | ${results.summary.skip} |`);
|
|
1286
|
-
lines.push(`| Manual | ${results.summary.manual} |`);
|
|
1287
|
-
lines.push(`| **Score** | **${results.score}/100** |`);
|
|
1288
|
-
if (results.severityScore > 0) lines.push(`| **Severity Penalty** | **${results.severityScore}** |`);
|
|
1289
|
-
lines.push('');
|
|
1290
|
-
lines.push('---');
|
|
1291
|
-
lines.push('');
|
|
1292
|
-
lines.push('## Detailed Results');
|
|
1293
|
-
lines.push('');
|
|
1294
|
-
|
|
1295
|
-
// Group by category — supply-chain FIRST
|
|
1296
|
-
const byCategory = {};
|
|
1297
|
-
const categoryOrder = ['supply-chain', 'security', 'metadata', 'code', 'docs', 'test', 'files', 'ai-verify', 'build', 'content', 'reproducibility', 'ethics', 'format', 'release', 'regulatory', 'statistics', 'clinical', 'conflicts', 'methodology', 'data', 'claims', 'figures', 'literature', 'proofs', 'definitions', 'examples', 'legal'];
|
|
1298
|
-
|
|
1299
|
-
for (const check of results.checks) {
|
|
1300
|
-
if (!byCategory[check.cat]) byCategory[check.cat] = [];
|
|
1301
|
-
byCategory[check.cat].push(check);
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
// Sort categories: supply-chain first, then security, then rest
|
|
1305
|
-
const sortedCats = Object.keys(byCategory).sort((a, b) => {
|
|
1306
|
-
const aIdx = categoryOrder.indexOf(a);
|
|
1307
|
-
const bIdx = categoryOrder.indexOf(b);
|
|
1308
|
-
return (aIdx === -1 ? 999 : aIdx) - (bIdx === -1 ? 999 : bIdx);
|
|
1309
|
-
});
|
|
1310
|
-
|
|
1311
|
-
for (const cat of sortedCats) {
|
|
1312
|
-
const checks = byCategory[cat];
|
|
1313
|
-
const catLabel = cat === 'supply-chain' ? 'SUPPLY CHAIN SECURITY' : cat.toUpperCase();
|
|
1314
|
-
lines.push(`### ${catLabel}`);
|
|
1315
|
-
lines.push('');
|
|
1316
|
-
lines.push('| Status | Severity | Check | Message |');
|
|
1317
|
-
lines.push('|--------|----------|-------|---------|');
|
|
1318
|
-
for (const c of checks) {
|
|
1319
|
-
const icon = c.status === 'PASS' ? '✅' : c.status === 'FAIL' ? '❌' : c.status === 'SKIP' ? '⏭️' : '👁️';
|
|
1320
|
-
const sev = c.severity || '-';
|
|
1321
|
-
lines.push(`| ${icon} ${c.status} | ${sev} | ${c.desc} | ${(c.msg || '').substring(0, 60)} |`);
|
|
1322
|
-
}
|
|
1323
|
-
lines.push('');
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
// Cost estimate for AI checks
|
|
1327
|
-
const aiChecks = results.checks.filter(c => c.cat === 'ai-verify' && c.status !== 'SKIP');
|
|
1328
|
-
if (aiChecks.length > 0) {
|
|
1329
|
-
lines.push('---');
|
|
1330
|
-
lines.push('');
|
|
1331
|
-
lines.push('## AI Verification Cost');
|
|
1332
|
-
lines.push('');
|
|
1333
|
-
lines.push('| Tool | Est. Cost |');
|
|
1334
|
-
lines.push('|------|-----------|');
|
|
1335
|
-
for (const c of aiChecks) {
|
|
1336
|
-
const cost = c.id.includes('bcalc') ? '$0.15' :
|
|
1337
|
-
c.id.includes('genius') ? '$0.65' :
|
|
1338
|
-
c.id.includes('hints') ? '$0.10' : 'FREE';
|
|
1339
|
-
lines.push(`| ${c.id} | ${cost} |`);
|
|
1340
|
-
}
|
|
1341
|
-
lines.push('');
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
lines.push('---');
|
|
1345
|
-
lines.push('');
|
|
1346
|
-
lines.push('*Generated by 50c Security Suite — "Never let a Verdant happen again"*');
|
|
1347
|
-
lines.push('*Supply chain checks are FREE. AI verification costs noted above.*');
|
|
1348
|
-
|
|
1349
|
-
return lines.join('\n');
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
/**
|
|
1353
|
-
* Full verification with receipt generation
|
|
1354
|
-
*/
|
|
1355
|
-
async function verifyWithReceipt(profile, options = {}) {
|
|
1356
|
-
const results = await verify(profile, options);
|
|
1357
|
-
const receipt = generateReceipt(results, options);
|
|
1358
|
-
return { ...results, receipt };
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
module.exports = { verify, verifyWithReceipt, generateReceipt, PROFILES, EMPIRICAL_CHECKS, call50cTool, loadAllowlist };
|
|
1
|
+
/**
|
|
2
|
+
* 50c Pre-Publish Verification Tool
|
|
3
|
+
*
|
|
4
|
+
* Thorough 100-point checklist before publishing to:
|
|
5
|
+
* - npm (packages)
|
|
6
|
+
* - GitHub (releases)
|
|
7
|
+
* - arXiv (papers)
|
|
8
|
+
* - Medical journals
|
|
9
|
+
* - Scientific publications
|
|
10
|
+
*
|
|
11
|
+
* "Ask once, verify everything"
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Verification profiles for different publish targets
|
|
15
|
+
const PROFILES = {
|
|
16
|
+
npm: {
|
|
17
|
+
name: 'NPM Package',
|
|
18
|
+
checks: [
|
|
19
|
+
// Package basics
|
|
20
|
+
{ id: 'pkg_name', cat: 'metadata', desc: 'Package name valid and available', empirical: true },
|
|
21
|
+
{ id: 'pkg_version', cat: 'metadata', desc: 'Version bumped from npm registry', empirical: true },
|
|
22
|
+
{ id: 'pkg_desc', cat: 'metadata', desc: 'Description clear, no marketing fluff', empirical: false },
|
|
23
|
+
{ id: 'pkg_keywords', cat: 'metadata', desc: 'Keywords relevant and searchable', empirical: false },
|
|
24
|
+
{ id: 'pkg_license', cat: 'metadata', desc: 'LICENSE file exists and valid', empirical: true },
|
|
25
|
+
{ id: 'pkg_repo', cat: 'metadata', desc: 'Repository URL valid and accessible', empirical: true },
|
|
26
|
+
{ id: 'pkg_homepage', cat: 'metadata', desc: 'Homepage URL live and correct', empirical: true },
|
|
27
|
+
|
|
28
|
+
// Code quality
|
|
29
|
+
{ id: 'syntax_valid', cat: 'code', desc: 'All JS/TS files pass syntax check', empirical: true },
|
|
30
|
+
{ id: 'no_console_log', cat: 'code', desc: 'No debug console.log statements', empirical: true },
|
|
31
|
+
{ id: 'no_todo_fixme', cat: 'code', desc: 'No unresolved TODO/FIXME comments', empirical: true },
|
|
32
|
+
{ id: 'no_hardcoded_secrets', cat: 'security', desc: 'No hardcoded API keys/passwords/tokens', empirical: true },
|
|
33
|
+
{ id: 'no_localhost', cat: 'security', desc: 'No localhost/127.0.0.1 in production code', empirical: true },
|
|
34
|
+
{ id: 'deps_secure', cat: 'security', desc: 'No known vulnerabilities in dependencies', empirical: true },
|
|
35
|
+
|
|
36
|
+
// Documentation
|
|
37
|
+
{ id: 'readme_exists', cat: 'docs', desc: 'README.md exists', empirical: true },
|
|
38
|
+
{ id: 'readme_install', cat: 'docs', desc: 'Installation instructions present', empirical: true },
|
|
39
|
+
{ id: 'readme_usage', cat: 'docs', desc: 'Usage examples present', empirical: true },
|
|
40
|
+
{ id: 'readme_links_live', cat: 'docs', desc: 'All README links are live', empirical: true },
|
|
41
|
+
{ id: 'changelog_updated', cat: 'docs', desc: 'CHANGELOG updated for this version', empirical: true },
|
|
42
|
+
|
|
43
|
+
// Build/Test
|
|
44
|
+
{ id: 'tests_pass', cat: 'test', desc: 'All tests pass', empirical: true },
|
|
45
|
+
{ id: 'build_clean', cat: 'test', desc: 'Build completes without warnings', empirical: true },
|
|
46
|
+
{ id: 'entry_point_valid', cat: 'test', desc: 'Main/bin entry points resolve', empirical: true },
|
|
47
|
+
|
|
48
|
+
// Files
|
|
49
|
+
{ id: 'files_included', cat: 'files', desc: 'Only intended files in package', empirical: true },
|
|
50
|
+
{ id: 'no_env_files', cat: 'files', desc: 'No .env files included', empirical: true },
|
|
51
|
+
{ id: 'no_test_files', cat: 'files', desc: 'Test files excluded from package', empirical: true },
|
|
52
|
+
{ id: 'size_reasonable', cat: 'files', desc: 'Package size under 10MB', empirical: true },
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
github: {
|
|
57
|
+
name: 'GitHub Release',
|
|
58
|
+
checks: [
|
|
59
|
+
{ id: 'tag_format', cat: 'release', desc: 'Tag follows semver (vX.Y.Z)', empirical: true },
|
|
60
|
+
{ id: 'tag_unique', cat: 'release', desc: 'Tag does not exist yet', empirical: true },
|
|
61
|
+
{ id: 'branch_clean', cat: 'release', desc: 'No uncommitted changes', empirical: true },
|
|
62
|
+
{ id: 'branch_pushed', cat: 'release', desc: 'All commits pushed to remote', empirical: true },
|
|
63
|
+
{ id: 'ci_passing', cat: 'release', desc: 'CI/CD pipeline green', empirical: true },
|
|
64
|
+
{ id: 'release_notes', cat: 'docs', desc: 'Release notes written', empirical: false },
|
|
65
|
+
{ id: 'breaking_changes', cat: 'docs', desc: 'Breaking changes documented', empirical: false },
|
|
66
|
+
{ id: 'migration_guide', cat: 'docs', desc: 'Migration guide if needed', empirical: false },
|
|
67
|
+
{ id: 'no_secrets_in_history', cat: 'security', desc: 'No secrets in git history', empirical: true },
|
|
68
|
+
{ id: 'license_file', cat: 'legal', desc: 'LICENSE file present', empirical: true },
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
arxiv: {
|
|
73
|
+
name: 'arXiv Paper',
|
|
74
|
+
checks: [
|
|
75
|
+
// Compilation
|
|
76
|
+
{ id: 'latex_compiles', cat: 'build', desc: 'LaTeX compiles without errors', empirical: true },
|
|
77
|
+
{ id: 'no_overfull_hbox', cat: 'build', desc: 'No overfull hbox warnings', empirical: true },
|
|
78
|
+
{ id: 'figures_render', cat: 'build', desc: 'All figures render correctly', empirical: true },
|
|
79
|
+
{ id: 'refs_resolve', cat: 'build', desc: 'All references resolve', empirical: true },
|
|
80
|
+
{ id: 'citations_complete', cat: 'build', desc: 'No [?] citation markers', empirical: true },
|
|
81
|
+
|
|
82
|
+
// Content integrity
|
|
83
|
+
{ id: 'abstract_standalone', cat: 'content', desc: 'Abstract is self-contained', empirical: false },
|
|
84
|
+
{ id: 'claims_supported', cat: 'content', desc: 'All claims have citations or proofs', empirical: false },
|
|
85
|
+
{ id: 'math_verified', cat: 'content', desc: 'Mathematical derivations verified', empirical: false },
|
|
86
|
+
{ id: 'no_placeholder', cat: 'content', desc: 'No TODO/TBD/XXX placeholders', empirical: true },
|
|
87
|
+
{ id: 'consistent_notation', cat: 'content', desc: 'Notation consistent throughout', empirical: false },
|
|
88
|
+
|
|
89
|
+
// AI-POWERED VERIFICATION (50c tools)
|
|
90
|
+
{ id: 'math_verified_bcalc', cat: 'ai-verify', desc: '[bCalc] Math expressions validated', empirical: true },
|
|
91
|
+
{ id: 'proofs_verified_genius', cat: 'ai-verify', desc: '[genius+] Proofs checked for gaps', empirical: true },
|
|
92
|
+
{ id: 'claims_validated_search', cat: 'ai-verify', desc: '[web_search] Novelty claims validated', empirical: true },
|
|
93
|
+
|
|
94
|
+
// Reproducibility
|
|
95
|
+
{ id: 'code_available', cat: 'repro', desc: 'Code repository linked', empirical: true },
|
|
96
|
+
{ id: 'data_available', cat: 'repro', desc: 'Dataset accessible or described', empirical: false },
|
|
97
|
+
{ id: 'hyperparams_listed', cat: 'repro', desc: 'All hyperparameters specified', empirical: false },
|
|
98
|
+
{ id: 'compute_resources', cat: 'repro', desc: 'Compute requirements stated', empirical: false },
|
|
99
|
+
|
|
100
|
+
// Ethics
|
|
101
|
+
{ id: 'ethics_statement', cat: 'ethics', desc: 'Ethics statement if applicable', empirical: false },
|
|
102
|
+
{ id: 'limitations_discussed', cat: 'ethics', desc: 'Limitations acknowledged', empirical: false },
|
|
103
|
+
{ id: 'societal_impact', cat: 'ethics', desc: 'Broader impact discussed', empirical: false },
|
|
104
|
+
|
|
105
|
+
// Formatting
|
|
106
|
+
{ id: 'arxiv_format', cat: 'format', desc: 'Follows arXiv formatting guidelines', empirical: true },
|
|
107
|
+
{ id: 'page_limit', cat: 'format', desc: 'Within page limit for category', empirical: true },
|
|
108
|
+
{ id: 'anon_submission', cat: 'format', desc: 'Anonymized if required', empirical: true },
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
medical: {
|
|
113
|
+
name: 'Medical/Clinical Publication',
|
|
114
|
+
checks: [
|
|
115
|
+
// Regulatory
|
|
116
|
+
{ id: 'irb_approval', cat: 'regulatory', desc: 'IRB/Ethics approval documented', empirical: true },
|
|
117
|
+
{ id: 'trial_registration', cat: 'regulatory', desc: 'Trial registered (if applicable)', empirical: true },
|
|
118
|
+
{ id: 'consort_checklist', cat: 'regulatory', desc: 'CONSORT/STROBE checklist completed', empirical: true },
|
|
119
|
+
{ id: 'data_privacy', cat: 'regulatory', desc: 'Patient data de-identified', empirical: true },
|
|
120
|
+
|
|
121
|
+
// Statistical rigor
|
|
122
|
+
{ id: 'sample_size_justified', cat: 'stats', desc: 'Sample size calculation provided', empirical: false },
|
|
123
|
+
{ id: 'stats_methods_appropriate', cat: 'stats', desc: 'Statistical methods appropriate', empirical: false },
|
|
124
|
+
{ id: 'confidence_intervals', cat: 'stats', desc: 'Confidence intervals reported', empirical: true },
|
|
125
|
+
{ id: 'effect_sizes', cat: 'stats', desc: 'Effect sizes reported', empirical: true },
|
|
126
|
+
{ id: 'multiple_comparisons', cat: 'stats', desc: 'Multiple comparisons addressed', empirical: false },
|
|
127
|
+
|
|
128
|
+
// Clinical validity
|
|
129
|
+
{ id: 'endpoints_defined', cat: 'clinical', desc: 'Primary/secondary endpoints defined', empirical: true },
|
|
130
|
+
{ id: 'adverse_events', cat: 'clinical', desc: 'Adverse events reported', empirical: true },
|
|
131
|
+
{ id: 'limitations_stated', cat: 'clinical', desc: 'Study limitations stated', empirical: true },
|
|
132
|
+
{ id: 'generalizability', cat: 'clinical', desc: 'Generalizability discussed', empirical: false },
|
|
133
|
+
|
|
134
|
+
// Conflicts
|
|
135
|
+
{ id: 'coi_disclosed', cat: 'disclosure', desc: 'Conflicts of interest disclosed', empirical: true },
|
|
136
|
+
{ id: 'funding_disclosed', cat: 'disclosure', desc: 'Funding sources disclosed', empirical: true },
|
|
137
|
+
{ id: 'author_contributions', cat: 'disclosure', desc: 'Author contributions listed', empirical: true },
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
science: {
|
|
142
|
+
name: 'Scientific Publication',
|
|
143
|
+
checks: [
|
|
144
|
+
// Methodology
|
|
145
|
+
{ id: 'methods_reproducible', cat: 'methods', desc: 'Methods described in reproducible detail', empirical: false },
|
|
146
|
+
{ id: 'controls_appropriate', cat: 'methods', desc: 'Control experiments appropriate', empirical: false },
|
|
147
|
+
{ id: 'sample_size_adequate', cat: 'methods', desc: 'Sample size adequate for claims', empirical: false },
|
|
148
|
+
{ id: 'blinding_described', cat: 'methods', desc: 'Blinding procedures described', empirical: false },
|
|
149
|
+
|
|
150
|
+
// AI-POWERED VERIFICATION (50c tools)
|
|
151
|
+
{ id: 'methodology_hints', cat: 'ai-verify', desc: '[hints+] Methodology weaknesses', empirical: true },
|
|
152
|
+
{ id: 'claims_validated_search', cat: 'ai-verify', desc: '[web_search] Prior work check', empirical: true },
|
|
153
|
+
|
|
154
|
+
// Data integrity
|
|
155
|
+
{ id: 'raw_data_available', cat: 'data', desc: 'Raw data available/accessible', empirical: true },
|
|
156
|
+
{ id: 'data_processing_documented', cat: 'data', desc: 'Data processing steps documented', empirical: false },
|
|
157
|
+
{ id: 'outliers_handled', cat: 'data', desc: 'Outlier handling described', empirical: false },
|
|
158
|
+
{ id: 'error_bars_explained', cat: 'data', desc: 'Error bars/uncertainty quantified', empirical: true },
|
|
159
|
+
|
|
160
|
+
// Claims
|
|
161
|
+
{ id: 'claims_match_evidence', cat: 'claims', desc: 'Claims proportional to evidence', empirical: false },
|
|
162
|
+
{ id: 'alternative_explanations', cat: 'claims', desc: 'Alternative explanations addressed', empirical: false },
|
|
163
|
+
{ id: 'no_overclaiming', cat: 'claims', desc: 'No overclaiming in abstract/title', empirical: false },
|
|
164
|
+
{ id: 'novelty_justified', cat: 'claims', desc: 'Novelty claims justified vs prior work', empirical: false },
|
|
165
|
+
|
|
166
|
+
// Figures
|
|
167
|
+
{ id: 'figures_clear', cat: 'figures', desc: 'Figures clear at print resolution', empirical: true },
|
|
168
|
+
{ id: 'axes_labeled', cat: 'figures', desc: 'All axes labeled with units', empirical: true },
|
|
169
|
+
{ id: 'legends_complete', cat: 'figures', desc: 'Figure legends self-contained', empirical: false },
|
|
170
|
+
{ id: 'color_accessible', cat: 'figures', desc: 'Color-blind accessible', empirical: true },
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
math: {
|
|
175
|
+
name: 'Mathematics Paper',
|
|
176
|
+
checks: [
|
|
177
|
+
// Proofs
|
|
178
|
+
{ id: 'proofs_complete', cat: 'proofs', desc: 'All proofs complete (no gaps)', empirical: false },
|
|
179
|
+
{ id: 'proofs_verified', cat: 'proofs', desc: 'Proofs independently verified', empirical: false },
|
|
180
|
+
{ id: 'edge_cases_handled', cat: 'proofs', desc: 'Edge cases and degeneracies handled', empirical: false },
|
|
181
|
+
{ id: 'assumptions_explicit', cat: 'proofs', desc: 'All assumptions explicitly stated', empirical: false },
|
|
182
|
+
|
|
183
|
+
// AI-POWERED VERIFICATION (50c tools)
|
|
184
|
+
{ id: 'math_verified_bcalc', cat: 'ai-verify', desc: '[bCalc] Mathematical verification', empirical: true },
|
|
185
|
+
{ id: 'proofs_verified_genius', cat: 'ai-verify', desc: '[genius+] Proof gap detection', empirical: true },
|
|
186
|
+
{ id: 'claims_validated_search', cat: 'ai-verify', desc: '[web_search] Novelty validation', empirical: true },
|
|
187
|
+
|
|
188
|
+
// Definitions
|
|
189
|
+
{ id: 'terms_defined', cat: 'defs', desc: 'All terms defined before use', empirical: true },
|
|
190
|
+
{ id: 'notation_consistent', cat: 'defs', desc: 'Notation consistent with literature', empirical: false },
|
|
191
|
+
{ id: 'numbering_correct', cat: 'defs', desc: 'Theorem/lemma numbering correct', empirical: true },
|
|
192
|
+
|
|
193
|
+
// Examples
|
|
194
|
+
{ id: 'examples_verify_claims', cat: 'examples', desc: 'Examples verify main theorems', empirical: false },
|
|
195
|
+
{ id: 'counterexamples_checked', cat: 'examples', desc: 'Potential counterexamples addressed', empirical: false },
|
|
196
|
+
{ id: 'computational_verified', cat: 'examples', desc: 'Computational results verified', empirical: true },
|
|
197
|
+
|
|
198
|
+
// Literature
|
|
199
|
+
{ id: 'prior_work_cited', cat: 'refs', desc: 'Prior work appropriately cited', empirical: false },
|
|
200
|
+
{ id: 'no_overclaiming_novelty', cat: 'refs', desc: 'Novelty claims accurate', empirical: false },
|
|
201
|
+
{ id: 'comparison_to_existing', cat: 'refs', desc: 'Results compared to existing bounds', empirical: false },
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// 50c API integration for AI-powered verification
|
|
207
|
+
async function call50cTool(tool, params, timeout = 60000) {
|
|
208
|
+
const fs = require('fs');
|
|
209
|
+
const path = require('path');
|
|
210
|
+
|
|
211
|
+
// Try to get API key from mcp.json
|
|
212
|
+
let apiKey = process.env.FIFTYC_API_KEY;
|
|
213
|
+
if (!apiKey) {
|
|
214
|
+
try {
|
|
215
|
+
const homeDir = process.env.USERPROFILE || process.env.HOME;
|
|
216
|
+
const mcpPath = path.join(homeDir, '.verdent', 'mcp.json');
|
|
217
|
+
if (fs.existsSync(mcpPath)) {
|
|
218
|
+
const mcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
219
|
+
const server = mcp.mcpServers?.['50c'] || mcp.mcpServers?.['50c-ai'];
|
|
220
|
+
apiKey = server?.env?.FIFTYC_API_KEY;
|
|
221
|
+
}
|
|
222
|
+
} catch (e) {}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!apiKey) {
|
|
226
|
+
return { error: 'No 50c API key configured' };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const controller = new AbortController();
|
|
231
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
232
|
+
|
|
233
|
+
const res = await fetch('https://api.50c.ai/mcp', {
|
|
234
|
+
method: 'POST',
|
|
235
|
+
headers: {
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
'Authorization': `Bearer ${apiKey}`
|
|
238
|
+
},
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
jsonrpc: '2.0',
|
|
241
|
+
method: 'tools/call',
|
|
242
|
+
params: { name: tool, arguments: params },
|
|
243
|
+
id: Date.now()
|
|
244
|
+
}),
|
|
245
|
+
signal: controller.signal
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
clearTimeout(timeoutId);
|
|
249
|
+
const data = await res.json();
|
|
250
|
+
return data.result?.content?.[0]?.text || data.result || data;
|
|
251
|
+
} catch (e) {
|
|
252
|
+
return { error: e.message };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Empirical check implementations
|
|
257
|
+
const EMPIRICAL_CHECKS = {
|
|
258
|
+
// NPM checks
|
|
259
|
+
async pkg_version(ctx) {
|
|
260
|
+
const pkg = ctx.packageJson;
|
|
261
|
+
if (!pkg) return { pass: false, msg: 'No package.json found' };
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`);
|
|
265
|
+
if (res.status === 404) return { pass: true, msg: 'New package (not on npm yet)' };
|
|
266
|
+
const data = await res.json();
|
|
267
|
+
const npmVersion = data.version;
|
|
268
|
+
const localVersion = pkg.version;
|
|
269
|
+
|
|
270
|
+
if (localVersion === npmVersion) {
|
|
271
|
+
return { pass: false, msg: `Version ${localVersion} already published. Bump required.` };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check if local is higher
|
|
275
|
+
const [lMaj, lMin, lPatch] = localVersion.split('.').map(Number);
|
|
276
|
+
const [nMaj, nMin, nPatch] = npmVersion.split('.').map(Number);
|
|
277
|
+
|
|
278
|
+
if (lMaj > nMaj || (lMaj === nMaj && lMin > nMin) || (lMaj === nMaj && lMin === nMin && lPatch > nPatch)) {
|
|
279
|
+
return { pass: true, msg: `${localVersion} > ${npmVersion} (npm)` };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { pass: false, msg: `Local ${localVersion} <= npm ${npmVersion}` };
|
|
283
|
+
} catch (e) {
|
|
284
|
+
return { pass: false, msg: `npm check failed: ${e.message}` };
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
async pkg_homepage(ctx) {
|
|
289
|
+
const url = ctx.packageJson?.homepage;
|
|
290
|
+
if (!url) return { pass: false, msg: 'No homepage in package.json' };
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const res = await fetch(url, { method: 'HEAD' });
|
|
294
|
+
return res.ok
|
|
295
|
+
? { pass: true, msg: `${url} is live` }
|
|
296
|
+
: { pass: false, msg: `${url} returned ${res.status}` };
|
|
297
|
+
} catch (e) {
|
|
298
|
+
return { pass: false, msg: `${url} unreachable: ${e.message}` };
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
async pkg_repo(ctx) {
|
|
303
|
+
const repo = ctx.packageJson?.repository?.url || ctx.packageJson?.repository;
|
|
304
|
+
if (!repo) return { pass: null, msg: 'No repository URL (optional)' };
|
|
305
|
+
|
|
306
|
+
// Convert git URL to HTTPS for checking
|
|
307
|
+
let checkUrl = String(repo).replace(/^git\+/, '').replace(/\.git$/, '');
|
|
308
|
+
if (checkUrl.startsWith('git://')) {
|
|
309
|
+
checkUrl = checkUrl.replace('git://', 'https://');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const res = await fetch(checkUrl, { method: 'HEAD' });
|
|
314
|
+
return res.ok
|
|
315
|
+
? { pass: true, msg: `${checkUrl} accessible` }
|
|
316
|
+
: { pass: false, msg: `${checkUrl} returned ${res.status}` };
|
|
317
|
+
} catch (e) {
|
|
318
|
+
return { pass: false, msg: `${checkUrl} unreachable` };
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
async syntax_valid(ctx) {
|
|
323
|
+
const { execSync } = require('child_process');
|
|
324
|
+
const jsFiles = ctx.files.filter(f => f.endsWith('.js') || f.endsWith('.mjs'));
|
|
325
|
+
const errors = [];
|
|
326
|
+
|
|
327
|
+
for (const file of jsFiles.slice(0, 20)) { // Limit to 20 files
|
|
328
|
+
try {
|
|
329
|
+
execSync(`node -c "${file}"`, { stdio: 'pipe', cwd: ctx.cwd });
|
|
330
|
+
} catch (e) {
|
|
331
|
+
errors.push(file);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return errors.length === 0
|
|
336
|
+
? { pass: true, msg: `${jsFiles.length} JS files valid` }
|
|
337
|
+
: { pass: false, msg: `Syntax errors in: ${errors.join(', ')}` };
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
async no_hardcoded_secrets(ctx) {
|
|
341
|
+
const { execSync } = require('child_process');
|
|
342
|
+
const patterns = [
|
|
343
|
+
'api[_-]?key\\s*[=:]\\s*["\'][a-zA-Z0-9]{20,}',
|
|
344
|
+
'password\\s*[=:]\\s*["\'][^"\']{8,}',
|
|
345
|
+
'secret\\s*[=:]\\s*["\'][a-zA-Z0-9]{20,}',
|
|
346
|
+
'token\\s*[=:]\\s*["\'][a-zA-Z0-9]{20,}',
|
|
347
|
+
'AWS[_-]?ACCESS[_-]?KEY',
|
|
348
|
+
'PRIVATE[_-]?KEY',
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
const findings = [];
|
|
352
|
+
for (const pattern of patterns) {
|
|
353
|
+
try {
|
|
354
|
+
const result = execSync(
|
|
355
|
+
`findstr /R /I /S "${pattern}" *.js *.ts *.json 2>nul`,
|
|
356
|
+
{ cwd: ctx.cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
357
|
+
);
|
|
358
|
+
if (result.trim()) {
|
|
359
|
+
findings.push(pattern.substring(0, 20) + '...');
|
|
360
|
+
}
|
|
361
|
+
} catch (e) {
|
|
362
|
+
// No matches is good
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return findings.length === 0
|
|
367
|
+
? { pass: true, msg: 'No hardcoded secrets detected' }
|
|
368
|
+
: { pass: false, msg: `Potential secrets: ${findings.join(', ')}` };
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
async readme_links_live(ctx) {
|
|
372
|
+
const fs = require('fs');
|
|
373
|
+
const readmePath = ctx.files.find(f => f.toLowerCase().endsWith('readme.md'));
|
|
374
|
+
if (!readmePath) return { pass: false, msg: 'No README.md' };
|
|
375
|
+
|
|
376
|
+
const content = fs.readFileSync(readmePath, 'utf8');
|
|
377
|
+
const urlPattern = /https?:\/\/[^\s\)\"\']+/g;
|
|
378
|
+
const urls = [...new Set(content.match(urlPattern) || [])];
|
|
379
|
+
|
|
380
|
+
const deadLinks = [];
|
|
381
|
+
for (const url of urls.slice(0, 10)) { // Check first 10
|
|
382
|
+
try {
|
|
383
|
+
const res = await fetch(url, { method: 'HEAD', timeout: 5000 });
|
|
384
|
+
if (!res.ok) deadLinks.push(url);
|
|
385
|
+
} catch (e) {
|
|
386
|
+
deadLinks.push(url);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return deadLinks.length === 0
|
|
391
|
+
? { pass: true, msg: `${urls.length} links verified` }
|
|
392
|
+
: { pass: false, msg: `Dead links: ${deadLinks.join(', ')}` };
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
async no_localhost(ctx) {
|
|
396
|
+
const fs = require('fs');
|
|
397
|
+
const path = require('path');
|
|
398
|
+
const findings = [];
|
|
399
|
+
|
|
400
|
+
for (const file of ctx.files.filter(f => /\.(js|ts|json)$/.test(f)).slice(0, 30)) {
|
|
401
|
+
// Skip this file itself (it contains localhost patterns for checking)
|
|
402
|
+
if (path.basename(file) === 'pre-publish.js') continue;
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
406
|
+
if (/localhost|127\.0\.0\.1|0\.0\.0\.0/.test(content)) {
|
|
407
|
+
// Check it's not in a comment or config option
|
|
408
|
+
const lines = content.split('\n');
|
|
409
|
+
for (let i = 0; i < lines.length; i++) {
|
|
410
|
+
const line = lines[i];
|
|
411
|
+
if (/localhost|127\.0\.0\.1/.test(line) && !line.trim().startsWith('//') && !line.includes('||') && !line.includes('regex') && !line.includes('pattern')) {
|
|
412
|
+
findings.push(`${path.basename(file)}:${i+1}`);
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
} catch (e) {}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return findings.length === 0
|
|
421
|
+
? { pass: true, msg: 'No localhost references' }
|
|
422
|
+
: { pass: false, msg: `localhost found: ${findings.slice(0,3).join(', ')}` };
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
async size_reasonable(ctx) {
|
|
426
|
+
const { execSync } = require('child_process');
|
|
427
|
+
try {
|
|
428
|
+
const result = execSync('npm pack --dry-run 2>&1', { cwd: ctx.cwd, encoding: 'utf8' });
|
|
429
|
+
const sizeMatch = result.match(/total files.*?(\d+(?:\.\d+)?)\s*(kB|MB|B)/i);
|
|
430
|
+
if (sizeMatch) {
|
|
431
|
+
let sizeKB = parseFloat(sizeMatch[1]);
|
|
432
|
+
if (sizeMatch[2].toLowerCase() === 'mb') sizeKB *= 1024;
|
|
433
|
+
if (sizeMatch[2].toLowerCase() === 'b') sizeKB /= 1024;
|
|
434
|
+
|
|
435
|
+
return sizeKB < 10240 // 10MB limit
|
|
436
|
+
? { pass: true, msg: `Package size: ${sizeKB.toFixed(1)} KB` }
|
|
437
|
+
: { pass: false, msg: `Package too large: ${sizeKB.toFixed(1)} KB (limit 10MB)` };
|
|
438
|
+
}
|
|
439
|
+
} catch (e) {}
|
|
440
|
+
return { pass: null, msg: 'Could not determine package size' };
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
// arXiv checks
|
|
444
|
+
async latex_compiles(ctx) {
|
|
445
|
+
const { execSync } = require('child_process');
|
|
446
|
+
const texFile = ctx.files.find(f => f.endsWith('.tex') && !f.includes('preamble'));
|
|
447
|
+
if (!texFile) return { pass: false, msg: 'No .tex file found' };
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
execSync(`pdflatex -interaction=nonstopmode "${texFile}"`, { cwd: ctx.cwd, stdio: 'pipe' });
|
|
451
|
+
return { pass: true, msg: 'LaTeX compiles' };
|
|
452
|
+
} catch (e) {
|
|
453
|
+
return { pass: false, msg: 'LaTeX compilation failed' };
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
async no_placeholder(ctx) {
|
|
458
|
+
const fs = require('fs');
|
|
459
|
+
const findings = [];
|
|
460
|
+
const patterns = /\b(TODO|FIXME|XXX|TBD|PLACEHOLDER)\b/gi;
|
|
461
|
+
|
|
462
|
+
for (const file of ctx.files.slice(0, 50)) {
|
|
463
|
+
try {
|
|
464
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
465
|
+
const matches = content.match(patterns);
|
|
466
|
+
if (matches) {
|
|
467
|
+
findings.push(`${file}: ${matches.length} placeholders`);
|
|
468
|
+
}
|
|
469
|
+
} catch (e) {}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return findings.length === 0
|
|
473
|
+
? { pass: true, msg: 'No placeholders found' }
|
|
474
|
+
: { pass: false, msg: findings.slice(0, 3).join('; ') };
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
// AI-powered math verification using bCalc
|
|
478
|
+
async math_verified_bcalc(ctx) {
|
|
479
|
+
const fs = require('fs');
|
|
480
|
+
const texFiles = ctx.files.filter(f => f.endsWith('.tex'));
|
|
481
|
+
if (texFiles.length === 0) return { pass: null, msg: 'No .tex files' };
|
|
482
|
+
|
|
483
|
+
// Extract key mathematical claims/equations
|
|
484
|
+
const content = fs.readFileSync(texFiles[0], 'utf8');
|
|
485
|
+
const equations = content.match(/\$\$[^$]+\$\$/g) || [];
|
|
486
|
+
const claims = content.match(/\\begin\{theorem\}[\s\S]*?\\end\{theorem\}/g) || [];
|
|
487
|
+
|
|
488
|
+
if (equations.length === 0 && claims.length === 0) {
|
|
489
|
+
return { pass: null, msg: 'No equations/theorems found to verify' };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Sample first theorem or key equation for bCalc verification
|
|
493
|
+
const sample = claims[0] || equations[0];
|
|
494
|
+
const cleanSample = sample.replace(/\\[a-z]+\{[^}]*\}/g, '').substring(0, 500);
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
const result = await call50cTool('bcalc', {
|
|
498
|
+
expression: cleanSample,
|
|
499
|
+
mode: 'verify'
|
|
500
|
+
}, 30000);
|
|
501
|
+
|
|
502
|
+
if (result.error) {
|
|
503
|
+
return { pass: null, msg: `bCalc unavailable: ${result.error}` };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Check if bCalc found issues
|
|
507
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
508
|
+
const hasIssues = /error|invalid|incorrect|false/i.test(resultStr);
|
|
509
|
+
|
|
510
|
+
return hasIssues
|
|
511
|
+
? { pass: false, msg: `bCalc found issues: ${resultStr.substring(0, 100)}` }
|
|
512
|
+
: { pass: true, msg: 'bCalc verified mathematical expressions' };
|
|
513
|
+
} catch (e) {
|
|
514
|
+
return { pass: null, msg: `bCalc check failed: ${e.message}` };
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
// AI-powered proof verification using genius_plus
|
|
519
|
+
async proofs_verified_genius(ctx) {
|
|
520
|
+
const fs = require('fs');
|
|
521
|
+
const texFiles = ctx.files.filter(f => f.endsWith('.tex'));
|
|
522
|
+
if (texFiles.length === 0) return { pass: null, msg: 'No .tex files' };
|
|
523
|
+
|
|
524
|
+
const content = fs.readFileSync(texFiles[0], 'utf8');
|
|
525
|
+
|
|
526
|
+
// Extract proofs
|
|
527
|
+
const proofs = content.match(/\\begin\{proof\}[\s\S]*?\\end\{proof\}/g) || [];
|
|
528
|
+
if (proofs.length === 0) {
|
|
529
|
+
return { pass: null, msg: 'No formal proofs found' };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Take abstract + first proof for genius+ analysis
|
|
533
|
+
const abstractMatch = content.match(/\\begin\{abstract\}([\s\S]*?)\\end\{abstract\}/);
|
|
534
|
+
const abstract = abstractMatch ? abstractMatch[1] : '';
|
|
535
|
+
const firstProof = proofs[0].substring(0, 1000);
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const result = await call50cTool('genius_plus', {
|
|
539
|
+
problem: `Verify this mathematical proof for logical gaps, unstated assumptions, or errors:\n\nContext: ${abstract.substring(0, 300)}\n\nProof:\n${firstProof}`
|
|
540
|
+
}, 90000);
|
|
541
|
+
|
|
542
|
+
if (result.error) {
|
|
543
|
+
return { pass: null, msg: `genius+ unavailable: ${result.error}` };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
547
|
+
|
|
548
|
+
// Check for red flags
|
|
549
|
+
const redFlags = /gap|flaw|error|incorrect|missing|invalid|contradiction/i.test(resultStr);
|
|
550
|
+
|
|
551
|
+
return redFlags
|
|
552
|
+
? { pass: false, msg: `genius+ found issues: ${resultStr.substring(0, 150)}...` }
|
|
553
|
+
: { pass: true, msg: 'genius+ verified proof structure' };
|
|
554
|
+
} catch (e) {
|
|
555
|
+
return { pass: null, msg: `genius+ check failed: ${e.message}` };
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
// AI-powered claim validation using web_search
|
|
560
|
+
async claims_validated_search(ctx) {
|
|
561
|
+
const fs = require('fs');
|
|
562
|
+
const texFiles = ctx.files.filter(f => f.endsWith('.tex'));
|
|
563
|
+
if (texFiles.length === 0) return { pass: null, msg: 'No .tex files' };
|
|
564
|
+
|
|
565
|
+
const content = fs.readFileSync(texFiles[0], 'utf8');
|
|
566
|
+
|
|
567
|
+
// Extract title and key claims
|
|
568
|
+
const titleMatch = content.match(/\\title\{([^}]+)\}/);
|
|
569
|
+
const title = titleMatch ? titleMatch[1] : '';
|
|
570
|
+
|
|
571
|
+
// Find novelty claims
|
|
572
|
+
const noveltyPatterns = /first|novel|new|unprecedented|breakthrough|improve|better than/gi;
|
|
573
|
+
const hasNoveltyClaims = noveltyPatterns.test(content);
|
|
574
|
+
|
|
575
|
+
if (!hasNoveltyClaims || !title) {
|
|
576
|
+
return { pass: null, msg: 'No novelty claims to validate' };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
const result = await call50cTool('web_search', {
|
|
581
|
+
query: title.replace(/[\\{}]/g, ' ').trim(),
|
|
582
|
+
max_results: 5
|
|
583
|
+
}, 30000);
|
|
584
|
+
|
|
585
|
+
if (result.error) {
|
|
586
|
+
return { pass: null, msg: `web_search unavailable: ${result.error}` };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
590
|
+
|
|
591
|
+
// Check if similar work already exists
|
|
592
|
+
const duplicateSignals = /already|published|existing|prior art|known result/i.test(resultStr);
|
|
593
|
+
|
|
594
|
+
return duplicateSignals
|
|
595
|
+
? { pass: false, msg: `Potential prior work found - verify novelty: ${resultStr.substring(0, 100)}` }
|
|
596
|
+
: { pass: true, msg: 'No obvious conflicting prior work found' };
|
|
597
|
+
} catch (e) {
|
|
598
|
+
return { pass: null, msg: `Search check failed: ${e.message}` };
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
// Science-specific: validate methodology with hints+
|
|
603
|
+
async methodology_hints(ctx) {
|
|
604
|
+
const fs = require('fs');
|
|
605
|
+
const files = ctx.files.filter(f => /\.(tex|md|txt)$/.test(f));
|
|
606
|
+
if (files.length === 0) return { pass: null, msg: 'No text files' };
|
|
607
|
+
|
|
608
|
+
// Read first file
|
|
609
|
+
const content = fs.readFileSync(files[0], 'utf8').substring(0, 2000);
|
|
610
|
+
|
|
611
|
+
// Look for methods section
|
|
612
|
+
const methodsMatch = content.match(/method|experiment|procedure|approach/i);
|
|
613
|
+
if (!methodsMatch) {
|
|
614
|
+
return { pass: null, msg: 'No methods section detected' };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
try {
|
|
618
|
+
const result = await call50cTool('hints_plus', {
|
|
619
|
+
query: `Evaluate scientific methodology for weaknesses: ${content.substring(0, 1000)}`
|
|
620
|
+
}, 30000);
|
|
621
|
+
|
|
622
|
+
if (result.error) {
|
|
623
|
+
return { pass: null, msg: `hints+ unavailable: ${result.error}` };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// hints_plus returns brutal hints - just report them
|
|
627
|
+
return {
|
|
628
|
+
pass: null,
|
|
629
|
+
msg: `hints+: ${typeof result === 'string' ? result.substring(0, 200) : JSON.stringify(result).substring(0, 200)}`
|
|
630
|
+
};
|
|
631
|
+
} catch (e) {
|
|
632
|
+
return { pass: null, msg: `hints+ check failed: ${e.message}` };
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Main verification function
|
|
639
|
+
*/
|
|
640
|
+
async function verify(profile, options = {}) {
|
|
641
|
+
const config = PROFILES[profile];
|
|
642
|
+
if (!config) {
|
|
643
|
+
return { error: `Unknown profile: ${profile}. Available: ${Object.keys(PROFILES).join(', ')}` };
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const fs = require('fs');
|
|
647
|
+
const path = require('path');
|
|
648
|
+
const cwd = options.cwd || process.cwd();
|
|
649
|
+
|
|
650
|
+
// Build context
|
|
651
|
+
const ctx = {
|
|
652
|
+
cwd,
|
|
653
|
+
profile,
|
|
654
|
+
files: [],
|
|
655
|
+
packageJson: null,
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// Gather files
|
|
659
|
+
try {
|
|
660
|
+
const walk = (dir, depth = 0) => {
|
|
661
|
+
if (depth > 3) return; // Max depth
|
|
662
|
+
const items = fs.readdirSync(dir);
|
|
663
|
+
for (const item of items) {
|
|
664
|
+
if (item.startsWith('.') || item === 'node_modules') continue;
|
|
665
|
+
const full = path.join(dir, item);
|
|
666
|
+
const stat = fs.statSync(full);
|
|
667
|
+
if (stat.isDirectory()) {
|
|
668
|
+
walk(full, depth + 1);
|
|
669
|
+
} else {
|
|
670
|
+
ctx.files.push(full);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
walk(cwd);
|
|
675
|
+
} catch (e) {}
|
|
676
|
+
|
|
677
|
+
// Load package.json if exists
|
|
678
|
+
try {
|
|
679
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
680
|
+
if (fs.existsSync(pkgPath)) {
|
|
681
|
+
ctx.packageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
682
|
+
}
|
|
683
|
+
} catch (e) {}
|
|
684
|
+
|
|
685
|
+
// Run checks
|
|
686
|
+
const results = {
|
|
687
|
+
profile: config.name,
|
|
688
|
+
timestamp: new Date().toISOString(),
|
|
689
|
+
summary: { pass: 0, fail: 0, skip: 0, manual: 0 },
|
|
690
|
+
checks: [],
|
|
691
|
+
ready: false,
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
for (const check of config.checks) {
|
|
695
|
+
const result = { ...check, status: null, msg: '' };
|
|
696
|
+
|
|
697
|
+
if (check.empirical && EMPIRICAL_CHECKS[check.id]) {
|
|
698
|
+
try {
|
|
699
|
+
const r = await EMPIRICAL_CHECKS[check.id](ctx);
|
|
700
|
+
result.status = r.pass ? 'PASS' : (r.pass === false ? 'FAIL' : 'SKIP');
|
|
701
|
+
result.msg = r.msg;
|
|
702
|
+
} catch (e) {
|
|
703
|
+
result.status = 'SKIP';
|
|
704
|
+
result.msg = `Error: ${e.message}`;
|
|
705
|
+
}
|
|
706
|
+
} else if (check.empirical) {
|
|
707
|
+
result.status = 'SKIP';
|
|
708
|
+
result.msg = 'Check not implemented';
|
|
709
|
+
} else {
|
|
710
|
+
result.status = 'MANUAL';
|
|
711
|
+
result.msg = 'Requires manual review';
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
results.checks.push(result);
|
|
715
|
+
|
|
716
|
+
if (result.status === 'PASS') results.summary.pass++;
|
|
717
|
+
else if (result.status === 'FAIL') results.summary.fail++;
|
|
718
|
+
else if (result.status === 'SKIP') results.summary.skip++;
|
|
719
|
+
else results.summary.manual++;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
results.ready = results.summary.fail === 0;
|
|
723
|
+
results.score = Math.round((results.summary.pass / (results.summary.pass + results.summary.fail + results.summary.manual)) * 100);
|
|
724
|
+
|
|
725
|
+
return results;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Generate verification receipt as Markdown
|
|
730
|
+
*/
|
|
731
|
+
function generateReceipt(results, options = {}) {
|
|
732
|
+
const lines = [
|
|
733
|
+
'# 50c Pre-Publish Verification Receipt',
|
|
734
|
+
'',
|
|
735
|
+
`**Generated:** ${results.timestamp}`,
|
|
736
|
+
`**Profile:** ${results.profile}`,
|
|
737
|
+
`**Directory:** ${options.cwd || 'N/A'}`,
|
|
738
|
+
`**Verdict:** ${results.ready ? '✅ READY TO PUBLISH' : '❌ NOT READY - FIX ISSUES'}`,
|
|
739
|
+
'',
|
|
740
|
+
'---',
|
|
741
|
+
'',
|
|
742
|
+
'## Summary',
|
|
743
|
+
'',
|
|
744
|
+
'| Metric | Count |',
|
|
745
|
+
'|--------|-------|',
|
|
746
|
+
`| Pass | ${results.summary.pass} |`,
|
|
747
|
+
`| Fail | ${results.summary.fail} |`,
|
|
748
|
+
`| Skip | ${results.summary.skip} |`,
|
|
749
|
+
`| Manual | ${results.summary.manual} |`,
|
|
750
|
+
`| **Score** | **${results.score}/100** |`,
|
|
751
|
+
'',
|
|
752
|
+
'---',
|
|
753
|
+
'',
|
|
754
|
+
'## Detailed Results',
|
|
755
|
+
'',
|
|
756
|
+
];
|
|
757
|
+
|
|
758
|
+
// Group by category
|
|
759
|
+
const byCategory = {};
|
|
760
|
+
for (const check of results.checks) {
|
|
761
|
+
if (!byCategory[check.cat]) byCategory[check.cat] = [];
|
|
762
|
+
byCategory[check.cat].push(check);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
for (const [cat, checks] of Object.entries(byCategory)) {
|
|
766
|
+
lines.push(`### ${cat.toUpperCase()}`);
|
|
767
|
+
lines.push('');
|
|
768
|
+
lines.push('| Status | Check | Message |');
|
|
769
|
+
lines.push('|--------|-------|---------|');
|
|
770
|
+
for (const c of checks) {
|
|
771
|
+
const icon = c.status === 'PASS' ? '✅' : c.status === 'FAIL' ? '❌' : c.status === 'SKIP' ? '⏭️' : '👁️';
|
|
772
|
+
lines.push(`| ${icon} ${c.status} | ${c.desc} | ${(c.msg || '').substring(0, 50)} |`);
|
|
773
|
+
}
|
|
774
|
+
lines.push('');
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Cost estimate
|
|
778
|
+
const aiChecks = results.checks.filter(c => c.cat === 'ai-verify' && c.status !== 'SKIP');
|
|
779
|
+
if (aiChecks.length > 0) {
|
|
780
|
+
lines.push('---');
|
|
781
|
+
lines.push('');
|
|
782
|
+
lines.push('## AI Verification Cost');
|
|
783
|
+
lines.push('');
|
|
784
|
+
lines.push('| Tool | Est. Cost |');
|
|
785
|
+
lines.push('|------|-----------|');
|
|
786
|
+
for (const c of aiChecks) {
|
|
787
|
+
const cost = c.id.includes('bcalc') ? '$0.15' :
|
|
788
|
+
c.id.includes('genius') ? '$0.65' :
|
|
789
|
+
c.id.includes('hints') ? '$0.10' : 'FREE';
|
|
790
|
+
lines.push(`| ${c.id} | ${cost} |`);
|
|
791
|
+
}
|
|
792
|
+
lines.push('');
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
lines.push('---');
|
|
796
|
+
lines.push('');
|
|
797
|
+
lines.push('*Generated by 50c Pre-Publish Verification Tool*');
|
|
798
|
+
lines.push('*AI verification does not replace peer review*');
|
|
799
|
+
|
|
800
|
+
return lines.join('\n');
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Full verification with receipt generation
|
|
805
|
+
*/
|
|
806
|
+
async function verifyWithReceipt(profile, options = {}) {
|
|
807
|
+
const results = await verify(profile, options);
|
|
808
|
+
const receipt = generateReceipt(results, options);
|
|
809
|
+
return { ...results, receipt };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
module.exports = { verify, verifyWithReceipt, generateReceipt, PROFILES, EMPIRICAL_CHECKS, call50cTool };
|