@automagik/genie 4.260424.5 → 4.260424.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260424.5",
3
+ "version": "4.260424.6",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260424.5",
3
+ "version": "4.260424.6",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260424.5",
3
+ "version": "4.260424.6",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -259,6 +259,10 @@ const MAX_TEXT_SNIPPETS = 6;
259
259
  const MAX_SNIPPET_CHARS = 240;
260
260
  const MAX_TEMP_WALK_ENTRIES = 25000;
261
261
  const MAX_TEMP_FINDINGS = 200;
262
+ const DEFAULT_TEMP_FILES_BUDGET = 500;
263
+ const DEFAULT_TEMP_BYTES_BUDGET = 32 * 1024 * 1024;
264
+ const DEFAULT_TEMP_WALL_BUDGET_MS = 2000;
265
+ const TEMP_YIELD_INTERVAL = 128;
262
266
  const MAX_TIMELINE_EVENTS = 120;
263
267
 
264
268
  const TEMP_ARTIFACT_NAME_REGEX =
@@ -322,32 +326,32 @@ const TEXT_MATCHERS = [
322
326
  {
323
327
  label: 'install:npm @automagik/genie',
324
328
  category: 'install',
325
- regex: /\bnpm\b[^\n]*\b(?:install|i|add|update|exec|ci)\b[^\n]*@automagik\/genie(?:@[0-9.]+)?/i,
329
+ regex: /\bnpm\b[^\n]{0,200}\b(?:install|i|add|update|exec|ci)\b[^\n]{0,200}@automagik\/genie(?:@[0-9.]+)?/i,
326
330
  },
327
331
  {
328
332
  label: 'install:pnpm @automagik/genie',
329
333
  category: 'install',
330
- regex: /\bpnpm\b[^\n]*\b(?:add|install|update|up)\b[^\n]*@automagik\/genie(?:@[0-9.]+)?/i,
334
+ regex: /\bpnpm\b[^\n]{0,200}\b(?:add|install|update|up)\b[^\n]{0,200}@automagik\/genie(?:@[0-9.]+)?/i,
331
335
  },
332
336
  {
333
337
  label: 'install:yarn @automagik/genie',
334
338
  category: 'install',
335
- regex: /\byarn\b[^\n]*\b(?:add|install|up|upgrade)\b[^\n]*@automagik\/genie(?:@[0-9.]+)?/i,
339
+ regex: /\byarn\b[^\n]{0,200}\b(?:add|install|up|upgrade)\b[^\n]{0,200}@automagik\/genie(?:@[0-9.]+)?/i,
336
340
  },
337
341
  {
338
342
  label: 'install:bun @automagik/genie',
339
343
  category: 'install',
340
- regex: /\bbun\b[^\n]*\b(?:add|install|pm add)\b[^\n]*@automagik\/genie(?:@[0-9.]+)?/i,
344
+ regex: /\bbun\b[^\n]{0,200}\b(?:add|install|pm add)\b[^\n]{0,200}@automagik\/genie(?:@[0-9.]+)?/i,
341
345
  },
342
346
  {
343
347
  label: 'exec:npx @automagik/genie',
344
348
  category: 'execution',
345
- regex: /\bnpx\b[^\n]*@automagik\/genie(?:@[0-9.]+)?/i,
349
+ regex: /\bnpx\b[^\n]{0,200}@automagik\/genie(?:@[0-9.]+)?/i,
346
350
  },
347
351
  {
348
352
  label: 'exec:bunx @automagik/genie',
349
353
  category: 'execution',
350
- regex: /\bbunx\b[^\n]*@automagik\/genie(?:@[0-9.]+)?/i,
354
+ regex: /\bbunx\b[^\n]{0,200}@automagik\/genie(?:@[0-9.]+)?/i,
351
355
  },
352
356
  {
353
357
  label: 'exec:node_modules/@automagik/genie',
@@ -357,12 +361,12 @@ const TEXT_MATCHERS = [
357
361
  {
358
362
  label: 'exec:env-compat',
359
363
  category: 'execution',
360
- regex: /\b(?:node|bun|bash|sh)\b[^\n]*env-compat\.(?:cjs|js)\b/i,
364
+ regex: /\b(?:node|bun|bash|sh)\b[^\n]{0,200}env-compat\.(?:cjs|js)\b/i,
361
365
  },
362
366
  {
363
367
  label: 'network:curl-wget IOC',
364
368
  category: 'network',
365
- regex: /\b(?:curl|wget|fetch|Invoke-WebRequest)\b[^\n]*(?:telemetry\.api-monitor\.com|raw\.icp0\.io\/drop)/i,
369
+ regex: /\b(?:curl|wget|fetch|Invoke-WebRequest)\b[^\n]{0,200}(?:telemetry\.api-monitor\.com|raw\.icp0\.io\/drop)/i,
366
370
  },
367
371
  ];
368
372
 
@@ -577,6 +581,31 @@ function createRuntime({
577
581
  });
578
582
  }
579
583
 
584
+ function recordPhaseCapHit(reason, detail = {}) {
585
+ const phaseId = currentPhase ? currentPhase.id : null;
586
+ const ts = nowFn();
587
+ const breachedAtMs = currentPhase ? ts - currentPhase.startMs : null;
588
+ const entry = {
589
+ kind: 'phase.cap_hit',
590
+ phase: phaseId,
591
+ ts_ms: ts,
592
+ reason,
593
+ breached_at_ms: breachedAtMs,
594
+ ...detail,
595
+ };
596
+ capEvents.push(entry);
597
+ if (currentPhase) currentPhase.caps += 1;
598
+ walkEvents.push({
599
+ event: 'phase.cap_hit',
600
+ phase: phaseId,
601
+ ts_ms: ts,
602
+ reason,
603
+ breached_at_ms: breachedAtMs,
604
+ ...detail,
605
+ });
606
+ emit({ kind: 'phase.cap_hit', phase: phaseId, reason, ...detail });
607
+ }
608
+
580
609
  function recordSkip(kind, detail = {}) {
581
610
  walkEvents.push({
582
611
  event: 'walk.skipped',
@@ -673,6 +702,7 @@ function createRuntime({
673
702
  endPhase,
674
703
  emit,
675
704
  recordCap,
705
+ recordPhaseCapHit,
676
706
  recordSkip,
677
707
  recordSymlinkCycle,
678
708
  recordReaddirError,
@@ -1375,7 +1405,7 @@ function collectTextIndicators(text) {
1375
1405
  const escapedName = escapeRegex(trackedPackage.name);
1376
1406
  if (
1377
1407
  new RegExp(
1378
- `\\b(?:npm|pnpm|yarn|bun)\\b[^\\n]*(?:install|i|add|update|up|upgrade|exec|ci|pm add)?[^\\n]*${escapedName}(?:@[0-9.]+)?`,
1408
+ `\\b(?:npm|pnpm|yarn|bun)\\b[^\\n]{0,200}(?:install|i|add|update|up|upgrade|exec|ci|pm add)?[^\\n]{0,200}${escapedName}(?:@[0-9.]+)?`,
1379
1409
  'i',
1380
1410
  ).test(text)
1381
1411
  ) {
@@ -1383,8 +1413,8 @@ function collectTextIndicators(text) {
1383
1413
  }
1384
1414
 
1385
1415
  if (
1386
- new RegExp(`\\b(?:npx|bunx)\\b[^\\n]*${escapedName}(?:@[0-9.]+)?`, 'i').test(text) ||
1387
- new RegExp(`${escapedName}[^\\n]*node_modules`, 'i').test(text)
1416
+ new RegExp(`\\b(?:npx|bunx)\\b[^\\n]{0,200}${escapedName}(?:@[0-9.]+)?`, 'i').test(text) ||
1417
+ new RegExp(`${escapedName}[^\\n]{0,200}node_modules`, 'i').test(text)
1388
1418
  ) {
1389
1419
  indicators.executionCommands.push(`exec:${trackedPackage.name}`);
1390
1420
  }
@@ -2554,11 +2584,11 @@ function collectTempRoots(platformInfo, homes, roots) {
2554
2584
  }
2555
2585
 
2556
2586
  for (const homePath of homes) {
2587
+ // ~/.npm, ~/.npm/_npx, ~/.cache, ~/.bun intentionally omitted: dedicated
2588
+ // scanNpmCache / scanBunCache phases cover them with tighter caps; walking
2589
+ // them here surfaces hundreds of MB of content scans and bypasses
2590
+ // WALK_SKIP_DIRS (skip-set only filters sub-entries, not chosen roots).
2557
2591
  for (const candidate of [
2558
- join(homePath, '.npm'),
2559
- join(homePath, '.npm', '_npx'),
2560
- join(homePath, '.cache'),
2561
- join(homePath, '.bun'),
2562
2592
  join(homePath, 'Library', 'Caches'),
2563
2593
  join(homePath, 'AppData', 'Local', 'Temp'),
2564
2594
  join(homePath, 'AppData', 'Local', 'npm-cache'),
@@ -2579,8 +2609,185 @@ function collectTempRoots(platformInfo, homes, roots) {
2579
2609
  });
2580
2610
  }
2581
2611
 
2582
- function scanTempArtifacts(platformInfo, homes, roots, report, runtime) {
2612
+ function resolveTempBudgets(runtime) {
2613
+ const budgets = runtime?.options?.phaseBudgets || {};
2614
+ const filesOverride = Number(budgets['scanTempArtifacts.files']);
2615
+ const bytesOverride = Number(budgets['scanTempArtifacts.bytes']);
2616
+ const wallOverride = Number(budgets['scanTempArtifacts.wall_ms']);
2617
+ // Back-compat: `--phase-budget scanTempArtifacts=N` is interpreted as the
2618
+ // files-count cap (dominant failure mode for this phase).
2619
+ const shorthandOverride = Number(budgets.scanTempArtifacts);
2620
+ const files =
2621
+ Number.isFinite(filesOverride) && filesOverride >= 0
2622
+ ? filesOverride
2623
+ : Number.isFinite(shorthandOverride) && shorthandOverride >= 0
2624
+ ? shorthandOverride
2625
+ : DEFAULT_TEMP_FILES_BUDGET;
2626
+ const bytes = Number.isFinite(bytesOverride) && bytesOverride >= 0 ? bytesOverride : DEFAULT_TEMP_BYTES_BUDGET;
2627
+ const wallMs = Number.isFinite(wallOverride) && wallOverride >= 0 ? wallOverride : DEFAULT_TEMP_WALL_BUDGET_MS;
2628
+ return { files, bytes, wallMs };
2629
+ }
2630
+
2631
+ function pushSizeCappedTempFinding(report, fullPath, stat, namedHits) {
2632
+ const finding = {
2633
+ path: fullPath,
2634
+ realpath: safeRealpath(fullPath),
2635
+ size: stat.size,
2636
+ modifiedAt: isoTime(stat.mtimeMs),
2637
+ nameMatches: namedHits,
2638
+ sha256: null,
2639
+ expectedSha256: expectedMalwareHashForBasename(basename(fullPath)),
2640
+ knownMalwareHash: false,
2641
+ iocMatches: [],
2642
+ versions: [],
2643
+ packageRefs: [],
2644
+ executionCommands: [],
2645
+ networkCommands: [],
2646
+ snippets: [],
2647
+ size_capped_not_hashed: true,
2648
+ };
2649
+ report.tempArtifactFindings.push(finding);
2650
+ addTimeline(report, {
2651
+ time: finding.modifiedAt,
2652
+ category: 'temp-artifact',
2653
+ severity: namedHits.some((value) => /env-compat\.(?:cjs|js)/i.test(value)) ? 'compromised' : 'affected',
2654
+ summary: 'temp or cache artifact matched known-bad basename; size-capped, content not scanned',
2655
+ path: fullPath,
2656
+ });
2657
+ }
2658
+
2659
+ function inspectTempFileSync(fullPath, report) {
2660
+ const stat = safeStat(fullPath);
2661
+ if (!stat || !stat.isFile()) return { bytesRead: 0, skipped: true };
2662
+
2663
+ const namedHits = collectNamedArtifactHits(fullPath);
2664
+
2665
+ // Size guard applies universally (hotfix: close DoS via name-regex fast path).
2666
+ // Basename hits still surface a finding but content is NOT read/hashed.
2667
+ if (stat.size > MAX_TEMP_CONTENT_SCAN_SIZE) {
2668
+ if (namedHits.length > 0 && report.tempArtifactFindings.length < MAX_TEMP_FINDINGS) {
2669
+ pushSizeCappedTempFinding(report, fullPath, stat, namedHits);
2670
+ }
2671
+ return { bytesRead: 0, skipped: true };
2672
+ }
2673
+
2674
+ const buffer = safeReadFile(fullPath);
2675
+ if (!buffer) return { bytesRead: 0, skipped: true };
2676
+
2677
+ const bytesRead = buffer.length;
2678
+ const expanded = maybeGunzip(buffer);
2679
+ const iocMatches = searchBufferForIocs(expanded);
2680
+ const fileSha256 = sha256(expanded);
2681
+ const expectedSha256 = expectedMalwareHashForBasename(basename(fullPath));
2682
+
2683
+ const text = expanded.toString('utf8');
2684
+ const indicators = collectTextIndicators(text);
2685
+ const versions = indicators.versions;
2686
+ const packageRefs = indicators.packageRefs;
2687
+ const executionCommands = indicators.executionCommands;
2688
+ const networkCommands = indicators.networkCommands;
2689
+ const snippets = extractInterestingSnippets(text);
2690
+
2691
+ if (
2692
+ namedHits.length === 0 &&
2693
+ iocMatches.length === 0 &&
2694
+ versions.length === 0 &&
2695
+ packageRefs.length === 0 &&
2696
+ executionCommands.length === 0 &&
2697
+ networkCommands.length === 0
2698
+ ) {
2699
+ return { bytesRead, skipped: false };
2700
+ }
2701
+
2702
+ const finding = {
2703
+ path: fullPath,
2704
+ realpath: safeRealpath(fullPath),
2705
+ size: stat.size,
2706
+ modifiedAt: isoTime(stat.mtimeMs),
2707
+ nameMatches: namedHits,
2708
+ sha256: fileSha256,
2709
+ expectedSha256,
2710
+ knownMalwareHash: Boolean(expectedSha256 && expectedSha256 === fileSha256),
2711
+ iocMatches,
2712
+ versions,
2713
+ packageRefs,
2714
+ executionCommands,
2715
+ networkCommands,
2716
+ snippets,
2717
+ };
2718
+
2719
+ report.tempArtifactFindings.push(finding);
2720
+ addTimeline(report, {
2721
+ time: finding.modifiedAt,
2722
+ category: 'temp-artifact',
2723
+ severity:
2724
+ iocMatches.length > 0 ||
2725
+ namedHits.some((value) => /env-compat\.(?:cjs|js)/i.test(value)) ||
2726
+ Boolean(expectedSha256 && expectedSha256 === fileSha256)
2727
+ ? 'compromised'
2728
+ : 'affected',
2729
+ summary: 'temp or cache artifact retained suspicious package evidence',
2730
+ path: fullPath,
2731
+ });
2732
+ return { bytesRead, skipped: false };
2733
+ }
2734
+
2735
+ async function processTempArtifactQueue(pending, report, runtime) {
2736
+ const { files: filesBudget, bytes: bytesBudget, wallMs: wallBudget } = resolveTempBudgets(runtime);
2737
+ let filesProcessed = 0;
2738
+ let bytesProcessed = 0;
2739
+ let capRecorded = false;
2740
+ const startMs = Date.now();
2741
+
2742
+ const recordCapOnce = (reason, limit) => {
2743
+ if (capRecorded) return;
2744
+ capRecorded = true;
2745
+ if (runtime && typeof runtime.recordPhaseCapHit === 'function') {
2746
+ runtime.recordPhaseCapHit(reason, {
2747
+ // `root` is what `envelopeFromReport` greps for in `coverage.cappedRoots`;
2748
+ // without it a phase-budget breach does not surface in the coverage banner.
2749
+ root: 'scanTempArtifacts',
2750
+ limit,
2751
+ entries_processed: filesProcessed,
2752
+ bytes_processed: bytesProcessed,
2753
+ });
2754
+ }
2755
+ };
2756
+
2757
+ for (const fullPath of pending) {
2758
+ if (runtime && typeof runtime.isInterrupted === 'function' && runtime.isInterrupted()) break;
2759
+ if (report.tempArtifactFindings.length >= MAX_TEMP_FINDINGS) break;
2760
+ // Wall first — a single slow `readFileSync` (spinning disk, NFS, large
2761
+ // near-limit file) can blow the other budgets' theoretical bounds; the
2762
+ // wall-clock check is the definitive "stop" signal.
2763
+ if (wallBudget > 0 && Date.now() - startMs >= wallBudget) {
2764
+ recordCapOnce('wall_budget', wallBudget);
2765
+ break;
2766
+ }
2767
+ if (filesProcessed >= filesBudget) {
2768
+ recordCapOnce('files_budget', filesBudget);
2769
+ break;
2770
+ }
2771
+ if (bytesProcessed >= bytesBudget) {
2772
+ recordCapOnce('bytes_budget', bytesBudget);
2773
+ break;
2774
+ }
2775
+
2776
+ const { bytesRead } = inspectTempFileSync(fullPath, report);
2777
+ filesProcessed += 1;
2778
+ if (bytesRead > 0) bytesProcessed += bytesRead;
2779
+ if (runtime && typeof runtime.addBytes === 'function' && bytesRead > 0) runtime.addBytes(bytesRead);
2780
+
2781
+ if (filesProcessed % TEMP_YIELD_INTERVAL === 0) {
2782
+ await new Promise((resolvePromise) => setImmediate(resolvePromise));
2783
+ }
2784
+ }
2785
+ return { filesProcessed, bytesProcessed, capRecorded };
2786
+ }
2787
+
2788
+ async function scanTempArtifacts(platformInfo, homes, roots, report, runtime) {
2583
2789
  const tempRoots = collectTempRoots(platformInfo, homes, roots);
2790
+ const pending = [];
2584
2791
 
2585
2792
  walkTreeFiles(
2586
2793
  tempRoots,
@@ -2592,80 +2799,11 @@ function scanTempArtifacts(platformInfo, homes, roots, report, runtime) {
2592
2799
  scope: 'temp-artifacts',
2593
2800
  },
2594
2801
  (fullPath) => {
2595
- if (report.tempArtifactFindings.length >= MAX_TEMP_FINDINGS) return;
2596
-
2597
- const stat = safeStat(fullPath);
2598
- if (!stat || !stat.isFile()) return;
2599
-
2600
- const namedHits = collectNamedArtifactHits(fullPath);
2601
- let iocMatches = [];
2602
- let versions = [];
2603
- let packageRefs = [];
2604
- let executionCommands = [];
2605
- let networkCommands = [];
2606
- let snippets = [];
2607
-
2608
- if (namedHits.length === 0 && stat.size > MAX_TEMP_CONTENT_SCAN_SIZE) return;
2609
-
2610
- const buffer = safeReadFile(fullPath);
2611
- if (!buffer) return;
2612
-
2613
- const expanded = maybeGunzip(buffer);
2614
- iocMatches = searchBufferForIocs(expanded);
2615
- const fileSha256 = sha256(expanded);
2616
- const expectedSha256 = expectedMalwareHashForBasename(basename(fullPath));
2617
-
2618
- const text = expanded.toString('utf8');
2619
- const indicators = collectTextIndicators(text);
2620
- versions = indicators.versions;
2621
- packageRefs = indicators.packageRefs;
2622
- executionCommands = indicators.executionCommands;
2623
- networkCommands = indicators.networkCommands;
2624
- snippets = extractInterestingSnippets(text);
2625
-
2626
- if (
2627
- namedHits.length === 0 &&
2628
- iocMatches.length === 0 &&
2629
- versions.length === 0 &&
2630
- packageRefs.length === 0 &&
2631
- executionCommands.length === 0 &&
2632
- networkCommands.length === 0
2633
- ) {
2634
- return;
2635
- }
2636
-
2637
- const finding = {
2638
- path: fullPath,
2639
- realpath: safeRealpath(fullPath),
2640
- size: stat.size,
2641
- modifiedAt: isoTime(stat.mtimeMs),
2642
- nameMatches: namedHits,
2643
- sha256: fileSha256,
2644
- expectedSha256,
2645
- knownMalwareHash: Boolean(expectedSha256 && expectedSha256 === fileSha256),
2646
- iocMatches,
2647
- versions,
2648
- packageRefs,
2649
- executionCommands,
2650
- networkCommands,
2651
- snippets,
2652
- };
2653
-
2654
- report.tempArtifactFindings.push(finding);
2655
- addTimeline(report, {
2656
- time: finding.modifiedAt,
2657
- category: 'temp-artifact',
2658
- severity:
2659
- iocMatches.length > 0 ||
2660
- namedHits.some((value) => /env-compat\.(?:cjs|js)/i.test(value)) ||
2661
- Boolean(expectedSha256 && expectedSha256 === fileSha256)
2662
- ? 'compromised'
2663
- : 'affected',
2664
- summary: 'temp or cache artifact retained suspicious package evidence',
2665
- path: fullPath,
2666
- });
2802
+ pending.push(fullPath);
2667
2803
  },
2668
2804
  );
2805
+
2806
+ await processTempArtifactQueue(pending, report, runtime);
2669
2807
  }
2670
2808
 
2671
2809
  function scanLiveProcesses(report) {
@@ -3217,10 +3355,10 @@ function emitSlowestRootsReport(envelope, options, stderr) {
3217
3355
  }
3218
3356
  }
3219
3357
 
3220
- function runPhase(runtime, id, scope, path, fn, report) {
3358
+ async function runPhase(runtime, id, scope, path, fn, report) {
3221
3359
  runtime.startPhase(id);
3222
3360
  try {
3223
- fn();
3361
+ await fn();
3224
3362
  } catch (error) {
3225
3363
  addError(report, scope, path, error);
3226
3364
  } finally {
@@ -3228,7 +3366,7 @@ function runPhase(runtime, id, scope, path, fn, report) {
3228
3366
  }
3229
3367
  }
3230
3368
 
3231
- function main() {
3369
+ async function main() {
3232
3370
  const options = parseArgs(process.argv);
3233
3371
 
3234
3372
  if (options.help) {
@@ -3298,11 +3436,11 @@ function main() {
3298
3436
  runtime.startTicker();
3299
3437
 
3300
3438
  for (const homePath of homes) {
3301
- runPhase(runtime, 'scanNpmCache', 'npm-cache', homePath, () => scanNpmCache(homePath, report), report);
3302
- runPhase(runtime, 'scanBunCache', 'bun-cache', homePath, () => scanBunCache(homePath, report), report);
3439
+ await runPhase(runtime, 'scanNpmCache', 'npm-cache', homePath, () => scanNpmCache(homePath, report), report);
3440
+ await runPhase(runtime, 'scanBunCache', 'bun-cache', homePath, () => scanBunCache(homePath, report), report);
3303
3441
  }
3304
3442
 
3305
- runPhase(
3443
+ await runPhase(
3306
3444
  runtime,
3307
3445
  'scanGlobalInstallCandidates',
3308
3446
  'global-installs',
@@ -3310,7 +3448,7 @@ function main() {
3310
3448
  () => scanGlobalInstallCandidates(homes, report),
3311
3449
  report,
3312
3450
  );
3313
- runPhase(
3451
+ await runPhase(
3314
3452
  runtime,
3315
3453
  'scanProjectRoots',
3316
3454
  'project-roots',
@@ -3318,7 +3456,7 @@ function main() {
3318
3456
  () => scanProjectRoots(roots, report, runtime),
3319
3457
  report,
3320
3458
  );
3321
- runPhase(
3459
+ await runPhase(
3322
3460
  runtime,
3323
3461
  'scanShellHistories',
3324
3462
  'shell-histories',
@@ -3326,7 +3464,7 @@ function main() {
3326
3464
  () => scanShellHistories(homes, report),
3327
3465
  report,
3328
3466
  );
3329
- runPhase(
3467
+ await runPhase(
3330
3468
  runtime,
3331
3469
  'scanShellProfiles',
3332
3470
  'shell-profiles',
@@ -3334,7 +3472,7 @@ function main() {
3334
3472
  () => scanShellProfiles(homes, report, runtime),
3335
3473
  report,
3336
3474
  );
3337
- runPhase(
3475
+ await runPhase(
3338
3476
  runtime,
3339
3477
  'scanPersistenceLocations',
3340
3478
  'persistence',
@@ -3342,7 +3480,7 @@ function main() {
3342
3480
  () => scanPersistenceLocations(platformInfo, homes, report, runtime),
3343
3481
  report,
3344
3482
  );
3345
- runPhase(
3483
+ await runPhase(
3346
3484
  runtime,
3347
3485
  'scanPythonPthArtifacts',
3348
3486
  'python-pth',
@@ -3350,7 +3488,7 @@ function main() {
3350
3488
  () => scanPythonPthArtifacts(homes, roots, report, runtime),
3351
3489
  report,
3352
3490
  );
3353
- runPhase(
3491
+ await runPhase(
3354
3492
  runtime,
3355
3493
  'scanTempArtifacts',
3356
3494
  'temp-artifacts',
@@ -3358,7 +3496,7 @@ function main() {
3358
3496
  () => scanTempArtifacts(platformInfo, homes, roots, report, runtime),
3359
3497
  report,
3360
3498
  );
3361
- runPhase(
3499
+ await runPhase(
3362
3500
  runtime,
3363
3501
  'scanImpactSurface',
3364
3502
  'impact-surface',
@@ -3366,7 +3504,14 @@ function main() {
3366
3504
  () => scanImpactSurface(homes, roots, report, runtime),
3367
3505
  report,
3368
3506
  );
3369
- runPhase(runtime, 'scanLiveProcesses', 'live-processes', '(process table)', () => scanLiveProcesses(report), report);
3507
+ await runPhase(
3508
+ runtime,
3509
+ 'scanLiveProcesses',
3510
+ 'live-processes',
3511
+ '(process table)',
3512
+ () => scanLiveProcesses(report),
3513
+ report,
3514
+ );
3370
3515
 
3371
3516
  report.timeline = sortTimeline(report.timeline);
3372
3517
  report.summary = summarize(report);
@@ -3382,12 +3527,10 @@ function main() {
3382
3527
  }
3383
3528
 
3384
3529
  if (require.main === module) {
3385
- try {
3386
- main();
3387
- } catch (error) {
3388
- process.stderr.write(`sec-scan.cjs failed: ${error.message}\n`);
3530
+ main().catch((error) => {
3531
+ process.stderr.write(`sec-scan.cjs failed: ${error?.message ? error.message : error}\n`);
3389
3532
  process.exit(3);
3390
- }
3533
+ });
3391
3534
  } else {
3392
3535
  module.exports = {
3393
3536
  REPORT_VERSION,
@@ -3423,5 +3566,17 @@ if (require.main === module) {
3423
3566
  printCoverageBanner,
3424
3567
  printHumanReport,
3425
3568
  emitSlowestRootsReport,
3569
+ collectTempRoots,
3570
+ scanTempArtifacts,
3571
+ processTempArtifactQueue,
3572
+ inspectTempFileSync,
3573
+ resolveTempBudgets,
3574
+ runPhase,
3575
+ MAX_TEMP_CONTENT_SCAN_SIZE,
3576
+ MAX_TEMP_WALK_ENTRIES,
3577
+ MAX_TEMP_FINDINGS,
3578
+ DEFAULT_TEMP_FILES_BUDGET,
3579
+ DEFAULT_TEMP_BYTES_BUDGET,
3580
+ TEMP_YIELD_INTERVAL,
3426
3581
  };
3427
3582
  }