50c 4.0.0 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,831 +0,0 @@
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
- // File-level exclusions: files whose entire purpose IS a local dev server
401
- const LEGIT_LOCAL_FILES = new Set(['pre-publish.js', 'mcp-tv.js', 'invent-ui.js']);
402
-
403
- for (const file of ctx.files.filter(f => /\.(js|ts|json)$/.test(f)).slice(0, 30)) {
404
- if (LEGIT_LOCAL_FILES.has(path.basename(file))) continue;
405
-
406
- try {
407
- const content = fs.readFileSync(file, 'utf8');
408
- if (/localhost|127\.0\.0\.1|0\.0\.0\.0/.test(content)) {
409
- const lines = content.split('\n');
410
- for (let i = 0; i < lines.length; i++) {
411
- const line = lines[i];
412
- const trimmed = line.trim();
413
- // Exclude legitimate patterns:
414
- // - Comments (// or *)
415
- // - env-fallback (|| 'localhost')
416
- // - regex/pattern keywords
417
- // - Template-literal dynamic ports (`http://localhost:${port}`)
418
- // - API protocol documentation in help text (POST/GET http://localhost)
419
- // - JSDoc protocol descriptions
420
- if (
421
- /localhost|127\.0\.0\.1/.test(line)
422
- && !trimmed.startsWith('//')
423
- && !trimmed.startsWith('*')
424
- && !line.includes('||')
425
- && !line.includes('regex')
426
- && !line.includes('pattern')
427
- && !line.includes('${')
428
- && !line.includes('POST http')
429
- && !line.includes('GET http')
430
- ) {
431
- findings.push(`${path.basename(file)}:${i+1}`);
432
- break;
433
- }
434
- }
435
- }
436
- } catch (e) {}
437
- }
438
-
439
- return findings.length === 0
440
- ? { pass: true, msg: 'No localhost references' }
441
- : { pass: false, msg: `localhost found: ${findings.slice(0,3).join(', ')}` };
442
- },
443
-
444
- async size_reasonable(ctx) {
445
- const { execSync } = require('child_process');
446
- try {
447
- const result = execSync('npm pack --dry-run 2>&1', { cwd: ctx.cwd, encoding: 'utf8' });
448
- const sizeMatch = result.match(/total files.*?(\d+(?:\.\d+)?)\s*(kB|MB|B)/i);
449
- if (sizeMatch) {
450
- let sizeKB = parseFloat(sizeMatch[1]);
451
- if (sizeMatch[2].toLowerCase() === 'mb') sizeKB *= 1024;
452
- if (sizeMatch[2].toLowerCase() === 'b') sizeKB /= 1024;
453
-
454
- return sizeKB < 10240 // 10MB limit
455
- ? { pass: true, msg: `Package size: ${sizeKB.toFixed(1)} KB` }
456
- : { pass: false, msg: `Package too large: ${sizeKB.toFixed(1)} KB (limit 10MB)` };
457
- }
458
- } catch (e) {}
459
- return { pass: null, msg: 'Could not determine package size' };
460
- },
461
-
462
- // arXiv checks
463
- async latex_compiles(ctx) {
464
- const { execSync } = require('child_process');
465
- const texFile = ctx.files.find(f => f.endsWith('.tex') && !f.includes('preamble'));
466
- if (!texFile) return { pass: false, msg: 'No .tex file found' };
467
-
468
- try {
469
- execSync(`pdflatex -interaction=nonstopmode "${texFile}"`, { cwd: ctx.cwd, stdio: 'pipe' });
470
- return { pass: true, msg: 'LaTeX compiles' };
471
- } catch (e) {
472
- return { pass: false, msg: 'LaTeX compilation failed' };
473
- }
474
- },
475
-
476
- async no_placeholder(ctx) {
477
- const fs = require('fs');
478
- const findings = [];
479
- const patterns = /\b(TODO|FIXME|XXX|TBD|PLACEHOLDER)\b/gi;
480
-
481
- for (const file of ctx.files.slice(0, 50)) {
482
- try {
483
- const content = fs.readFileSync(file, 'utf8');
484
- const matches = content.match(patterns);
485
- if (matches) {
486
- findings.push(`${file}: ${matches.length} placeholders`);
487
- }
488
- } catch (e) {}
489
- }
490
-
491
- return findings.length === 0
492
- ? { pass: true, msg: 'No placeholders found' }
493
- : { pass: false, msg: findings.slice(0, 3).join('; ') };
494
- },
495
-
496
- // AI-powered math verification using bCalc
497
- async math_verified_bcalc(ctx) {
498
- const fs = require('fs');
499
- const texFiles = ctx.files.filter(f => f.endsWith('.tex'));
500
- if (texFiles.length === 0) return { pass: null, msg: 'No .tex files' };
501
-
502
- // Extract key mathematical claims/equations
503
- const content = fs.readFileSync(texFiles[0], 'utf8');
504
- const equations = content.match(/\$\$[^$]+\$\$/g) || [];
505
- const claims = content.match(/\\begin\{theorem\}[\s\S]*?\\end\{theorem\}/g) || [];
506
-
507
- if (equations.length === 0 && claims.length === 0) {
508
- return { pass: null, msg: 'No equations/theorems found to verify' };
509
- }
510
-
511
- // Sample first theorem or key equation for bCalc verification
512
- const sample = claims[0] || equations[0];
513
- const cleanSample = sample.replace(/\\[a-z]+\{[^}]*\}/g, '').substring(0, 500);
514
-
515
- try {
516
- const result = await call50cTool('bcalc', {
517
- expression: cleanSample,
518
- mode: 'verify'
519
- }, 30000);
520
-
521
- if (result.error) {
522
- return { pass: null, msg: `bCalc unavailable: ${result.error}` };
523
- }
524
-
525
- // Check if bCalc found issues
526
- const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
527
- const hasIssues = /error|invalid|incorrect|false/i.test(resultStr);
528
-
529
- return hasIssues
530
- ? { pass: false, msg: `bCalc found issues: ${resultStr.substring(0, 100)}` }
531
- : { pass: true, msg: 'bCalc verified mathematical expressions' };
532
- } catch (e) {
533
- return { pass: null, msg: `bCalc check failed: ${e.message}` };
534
- }
535
- },
536
-
537
- // AI-powered proof verification using genius_plus
538
- async proofs_verified_genius(ctx) {
539
- const fs = require('fs');
540
- const texFiles = ctx.files.filter(f => f.endsWith('.tex'));
541
- if (texFiles.length === 0) return { pass: null, msg: 'No .tex files' };
542
-
543
- const content = fs.readFileSync(texFiles[0], 'utf8');
544
-
545
- // Extract proofs
546
- const proofs = content.match(/\\begin\{proof\}[\s\S]*?\\end\{proof\}/g) || [];
547
- if (proofs.length === 0) {
548
- return { pass: null, msg: 'No formal proofs found' };
549
- }
550
-
551
- // Take abstract + first proof for genius+ analysis
552
- const abstractMatch = content.match(/\\begin\{abstract\}([\s\S]*?)\\end\{abstract\}/);
553
- const abstract = abstractMatch ? abstractMatch[1] : '';
554
- const firstProof = proofs[0].substring(0, 1000);
555
-
556
- try {
557
- const result = await call50cTool('genius_plus', {
558
- problem: `Verify this mathematical proof for logical gaps, unstated assumptions, or errors:\n\nContext: ${abstract.substring(0, 300)}\n\nProof:\n${firstProof}`
559
- }, 90000);
560
-
561
- if (result.error) {
562
- return { pass: null, msg: `genius+ unavailable: ${result.error}` };
563
- }
564
-
565
- const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
566
-
567
- // Check for red flags
568
- const redFlags = /gap|flaw|error|incorrect|missing|invalid|contradiction/i.test(resultStr);
569
-
570
- return redFlags
571
- ? { pass: false, msg: `genius+ found issues: ${resultStr.substring(0, 150)}...` }
572
- : { pass: true, msg: 'genius+ verified proof structure' };
573
- } catch (e) {
574
- return { pass: null, msg: `genius+ check failed: ${e.message}` };
575
- }
576
- },
577
-
578
- // AI-powered claim validation using web_search
579
- async claims_validated_search(ctx) {
580
- const fs = require('fs');
581
- const texFiles = ctx.files.filter(f => f.endsWith('.tex'));
582
- if (texFiles.length === 0) return { pass: null, msg: 'No .tex files' };
583
-
584
- const content = fs.readFileSync(texFiles[0], 'utf8');
585
-
586
- // Extract title and key claims
587
- const titleMatch = content.match(/\\title\{([^}]+)\}/);
588
- const title = titleMatch ? titleMatch[1] : '';
589
-
590
- // Find novelty claims
591
- const noveltyPatterns = /first|novel|new|unprecedented|breakthrough|improve|better than/gi;
592
- const hasNoveltyClaims = noveltyPatterns.test(content);
593
-
594
- if (!hasNoveltyClaims || !title) {
595
- return { pass: null, msg: 'No novelty claims to validate' };
596
- }
597
-
598
- try {
599
- const result = await call50cTool('web_search', {
600
- query: title.replace(/[\\{}]/g, ' ').trim(),
601
- max_results: 5
602
- }, 30000);
603
-
604
- if (result.error) {
605
- return { pass: null, msg: `web_search unavailable: ${result.error}` };
606
- }
607
-
608
- const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
609
-
610
- // Check if similar work already exists
611
- const duplicateSignals = /already|published|existing|prior art|known result/i.test(resultStr);
612
-
613
- return duplicateSignals
614
- ? { pass: false, msg: `Potential prior work found - verify novelty: ${resultStr.substring(0, 100)}` }
615
- : { pass: true, msg: 'No obvious conflicting prior work found' };
616
- } catch (e) {
617
- return { pass: null, msg: `Search check failed: ${e.message}` };
618
- }
619
- },
620
-
621
- // Science-specific: validate methodology with hints+
622
- async methodology_hints(ctx) {
623
- const fs = require('fs');
624
- const files = ctx.files.filter(f => /\.(tex|md|txt)$/.test(f));
625
- if (files.length === 0) return { pass: null, msg: 'No text files' };
626
-
627
- // Read first file
628
- const content = fs.readFileSync(files[0], 'utf8').substring(0, 2000);
629
-
630
- // Look for methods section
631
- const methodsMatch = content.match(/method|experiment|procedure|approach/i);
632
- if (!methodsMatch) {
633
- return { pass: null, msg: 'No methods section detected' };
634
- }
635
-
636
- try {
637
- const result = await call50cTool('hints_plus', {
638
- query: `Evaluate scientific methodology for weaknesses: ${content.substring(0, 1000)}`
639
- }, 30000);
640
-
641
- if (result.error) {
642
- return { pass: null, msg: `hints+ unavailable: ${result.error}` };
643
- }
644
-
645
- // hints_plus returns brutal hints - just report them
646
- return {
647
- pass: null,
648
- msg: `hints+: ${typeof result === 'string' ? result.substring(0, 200) : JSON.stringify(result).substring(0, 200)}`
649
- };
650
- } catch (e) {
651
- return { pass: null, msg: `hints+ check failed: ${e.message}` };
652
- }
653
- },
654
- };
655
-
656
- /**
657
- * Main verification function
658
- */
659
- async function verify(profile, options = {}) {
660
- const config = PROFILES[profile];
661
- if (!config) {
662
- return { error: `Unknown profile: ${profile}. Available: ${Object.keys(PROFILES).join(', ')}` };
663
- }
664
-
665
- const fs = require('fs');
666
- const path = require('path');
667
- const cwd = options.cwd || process.cwd();
668
-
669
- // Build context
670
- const ctx = {
671
- cwd,
672
- profile,
673
- files: [],
674
- packageJson: null,
675
- };
676
-
677
- // Gather files
678
- try {
679
- const walk = (dir, depth = 0) => {
680
- if (depth > 3) return; // Max depth
681
- const items = fs.readdirSync(dir);
682
- for (const item of items) {
683
- if (item.startsWith('.') || item === 'node_modules') continue;
684
- const full = path.join(dir, item);
685
- const stat = fs.statSync(full);
686
- if (stat.isDirectory()) {
687
- walk(full, depth + 1);
688
- } else {
689
- ctx.files.push(full);
690
- }
691
- }
692
- };
693
- walk(cwd);
694
- } catch (e) {}
695
-
696
- // Load package.json if exists
697
- try {
698
- const pkgPath = path.join(cwd, 'package.json');
699
- if (fs.existsSync(pkgPath)) {
700
- ctx.packageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
701
- }
702
- } catch (e) {}
703
-
704
- // Run checks
705
- const results = {
706
- profile: config.name,
707
- timestamp: new Date().toISOString(),
708
- summary: { pass: 0, fail: 0, skip: 0, manual: 0 },
709
- checks: [],
710
- ready: false,
711
- };
712
-
713
- for (const check of config.checks) {
714
- const result = { ...check, status: null, msg: '' };
715
-
716
- if (check.empirical && EMPIRICAL_CHECKS[check.id]) {
717
- try {
718
- const r = await EMPIRICAL_CHECKS[check.id](ctx);
719
- result.status = r.pass ? 'PASS' : (r.pass === false ? 'FAIL' : 'SKIP');
720
- result.msg = r.msg;
721
- } catch (e) {
722
- result.status = 'SKIP';
723
- result.msg = `Error: ${e.message}`;
724
- }
725
- } else if (check.empirical) {
726
- result.status = 'SKIP';
727
- result.msg = 'Check not implemented';
728
- } else {
729
- result.status = 'MANUAL';
730
- result.msg = 'Requires manual review';
731
- }
732
-
733
- results.checks.push(result);
734
-
735
- if (result.status === 'PASS') results.summary.pass++;
736
- else if (result.status === 'FAIL') results.summary.fail++;
737
- else if (result.status === 'SKIP') results.summary.skip++;
738
- else results.summary.manual++;
739
- }
740
-
741
- results.ready = results.summary.fail === 0;
742
- results.score = Math.round((results.summary.pass / (results.summary.pass + results.summary.fail + results.summary.manual)) * 100);
743
-
744
- return results;
745
- }
746
-
747
- /**
748
- * Generate verification receipt as Markdown
749
- */
750
- function generateReceipt(results, options = {}) {
751
- const lines = [
752
- '# 50c Pre-Publish Verification Receipt',
753
- '',
754
- `**Generated:** ${results.timestamp}`,
755
- `**Profile:** ${results.profile}`,
756
- `**Directory:** ${options.cwd || 'N/A'}`,
757
- `**Verdict:** ${results.ready ? '✅ READY TO PUBLISH' : '❌ NOT READY - FIX ISSUES'}`,
758
- '',
759
- '---',
760
- '',
761
- '## Summary',
762
- '',
763
- '| Metric | Count |',
764
- '|--------|-------|',
765
- `| Pass | ${results.summary.pass} |`,
766
- `| Fail | ${results.summary.fail} |`,
767
- `| Skip | ${results.summary.skip} |`,
768
- `| Manual | ${results.summary.manual} |`,
769
- `| **Score** | **${results.score}/100** |`,
770
- '',
771
- '---',
772
- '',
773
- '## Detailed Results',
774
- '',
775
- ];
776
-
777
- // Group by category
778
- const byCategory = {};
779
- for (const check of results.checks) {
780
- if (!byCategory[check.cat]) byCategory[check.cat] = [];
781
- byCategory[check.cat].push(check);
782
- }
783
-
784
- for (const [cat, checks] of Object.entries(byCategory)) {
785
- lines.push(`### ${cat.toUpperCase()}`);
786
- lines.push('');
787
- lines.push('| Status | Check | Message |');
788
- lines.push('|--------|-------|---------|');
789
- for (const c of checks) {
790
- const icon = c.status === 'PASS' ? '✅' : c.status === 'FAIL' ? '❌' : c.status === 'SKIP' ? '⏭️' : '👁️';
791
- lines.push(`| ${icon} ${c.status} | ${c.desc} | ${(c.msg || '').substring(0, 50)} |`);
792
- }
793
- lines.push('');
794
- }
795
-
796
- // Cost estimate
797
- const aiChecks = results.checks.filter(c => c.cat === 'ai-verify' && c.status !== 'SKIP');
798
- if (aiChecks.length > 0) {
799
- lines.push('---');
800
- lines.push('');
801
- lines.push('## AI Verification Cost');
802
- lines.push('');
803
- lines.push('| Tool | Est. Cost |');
804
- lines.push('|------|-----------|');
805
- for (const c of aiChecks) {
806
- const cost = c.id.includes('bcalc') ? '$0.15' :
807
- c.id.includes('genius') ? '$0.65' :
808
- c.id.includes('hints') ? '$0.10' : 'FREE';
809
- lines.push(`| ${c.id} | ${cost} |`);
810
- }
811
- lines.push('');
812
- }
813
-
814
- lines.push('---');
815
- lines.push('');
816
- lines.push('*Generated by 50c Pre-Publish Verification Tool*');
817
- lines.push('*AI verification does not replace peer review*');
818
-
819
- return lines.join('\n');
820
- }
821
-
822
- /**
823
- * Full verification with receipt generation
824
- */
825
- async function verifyWithReceipt(profile, options = {}) {
826
- const results = await verify(profile, options);
827
- const receipt = generateReceipt(results, options);
828
- return { ...results, receipt };
829
- }
830
-
831
- module.exports = { verify, verifyWithReceipt, generateReceipt, PROFILES, EMPIRICAL_CHECKS, call50cTool };