@idlekit/cli 0.5.0 → 0.5.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.
package/dist/main.js CHANGED
@@ -19440,448 +19440,806 @@ function compareScenarios(args) {
19440
19440
  };
19441
19441
  }
19442
19442
 
19443
- // ../core/dist/index.js
19444
- var init_dist2 = __esm(() => {
19445
- init_types();
19446
- init_breakInfinity2();
19447
- init_breakEternity2();
19448
- init_types5();
19449
- init_types6();
19450
- init_emitter();
19451
- init_tickMoney2();
19452
- init_suffixer();
19453
- init_formatMoney2();
19454
- init_parseMoney2();
19455
- init_visibilityTracker2();
19456
- init_moneyState();
19457
- init_simState();
19458
- init_types7();
19459
- init_validate();
19460
- init_concreteValidator();
19461
- init_scenarioContract();
19462
- init_compile2();
19463
- init_simulator();
19464
- init_advance();
19465
- init_step();
19466
- init_stepTypes();
19467
- init_offline();
19468
- init_session();
19469
- init_monteCarlo();
19470
- init_executionBackend();
19471
- init_integrationMatrix();
19472
- init_runFactory();
19473
- init_constraints();
19474
- init_observation();
19475
- init_types8();
19476
- init_contracts();
19477
- init_stability();
19478
- init_scripted();
19479
- init_greedy();
19480
- init_planner();
19481
- init_params();
19482
- init_builtins();
19483
- init_tuneSpec();
19484
- init_validate2();
19485
- init_objective();
19486
- init_params2();
19487
- init_builtins2();
19488
- init_tuner();
19489
- init_randomSearch();
19490
- init_runner();
19491
- init_eta();
19492
- init_prestigeCycle();
19493
- init_resetCycle();
19494
- init_resetOptimization();
19495
- init_distribution();
19496
- init_experiment();
19497
- init_sensitivity();
19498
- init_growthFit();
19499
- init_contracts2();
19500
- init_metrics();
19501
- init_registry();
19502
- init_analyzers();
19503
- init_inspect();
19504
- init_checks3();
19505
- init_judge();
19506
- init_flow();
19507
- init_jevAdvisory();
19508
- });
19509
-
19510
- // src/runtime/bun.ts
19511
- var {$ } = globalThis.Bun;
19512
- import { dirname } from "path";
19513
- async function readTextFile(path) {
19514
- return Bun.file(path).text();
19515
- }
19516
- async function readJsonFile(path) {
19517
- return Bun.file(path).json();
19518
- }
19519
- async function writeTextFile(path, body) {
19520
- try {
19521
- await ensureDir(dirname(path));
19522
- await Bun.write(path, body);
19523
- } catch (error) {
19524
- throw cliError("OUTPUT_WRITE_FAILED", `Unable to write output file: ${path}`, {
19525
- detail: errorDetail(error),
19526
- hint: "Choose a writable output path and check that its parent is a directory.",
19527
- cause: error
19528
- });
19529
- }
19530
- }
19531
- function writeStdout(body) {
19532
- return new Promise((resolve, reject) => {
19533
- process.stdout.write(body, (error) => error ? reject(error) : resolve());
19534
- });
19535
- }
19536
- async function ensureDir(path) {
19537
- await $`mkdir -p ${path}`.quiet();
19538
- }
19539
- async function fileExists(path) {
19540
- return Bun.file(path).exists();
19541
- }
19542
- function sha256Hex(value) {
19543
- const hasher = new Bun.CryptoHasher("sha256");
19544
- hasher.update(value);
19545
- return hasher.digest("hex");
19546
- }
19547
- function runText(args, opts) {
19548
- const proc = Bun.spawnSync(args, {
19549
- cwd: opts?.cwd,
19550
- env: opts?.env,
19551
- stdout: "pipe",
19552
- stderr: "pipe",
19553
- maxBuffer: MAX_CAPTURE_BYTES
19554
- });
19555
- if (proc.exitCode !== 0) {
19556
- const stderr = proc.stderr.toString().trim();
19557
- const stdout = proc.stdout.toString().trim();
19558
- const detail = stderr || stdout || `command failed with exit code ${proc.exitCode}`;
19559
- throw new Error(detail);
19560
- }
19561
- return proc.stdout.toString();
19443
+ // ../core/dist/ltv/projection.js
19444
+ function clamp012(v) {
19445
+ if (v < 0)
19446
+ return 0;
19447
+ if (v > 1)
19448
+ return 1;
19449
+ return v;
19562
19450
  }
19563
- var MAX_CAPTURE_BYTES;
19564
- var init_bun = __esm(() => {
19565
- init_errors3();
19566
- MAX_CAPTURE_BYTES = 8 * 1024 * 1024;
19567
- });
19568
-
19569
- // package.json
19570
- var require_package = __commonJS(function(exports, module) {
19571
- module.exports = {
19572
- name: "@idlekit/cli",
19573
- version: "0.5.0",
19574
- description: "Bun-first idk CLI for idle game scenario validation, simulation, tuning, and reporting.",
19575
- license: "MIT",
19576
- type: "module",
19577
- files: [
19578
- "dist",
19579
- "README.md",
19580
- "LICENSE"
19581
- ],
19582
- bin: {
19583
- idk: "dist/main.js"
19584
- },
19585
- repository: {
19586
- type: "git",
19587
- url: "git+https://github.com/imjlk/idlekit.git",
19588
- directory: "packages/cli"
19589
- },
19590
- homepage: "https://github.com/imjlk/idlekit/tree/main/packages/cli#readme",
19591
- bugs: {
19592
- url: "https://github.com/imjlk/idlekit/issues"
19593
- },
19594
- keywords: [
19595
- "bun",
19596
- "idle-game",
19597
- "incremental-game",
19598
- "cli",
19599
- "economy-simulator",
19600
- "game-balancing",
19601
- "simulation"
19602
- ],
19603
- publishConfig: {
19604
- access: "public"
19451
+ function deriveMonetizationConfig(scenario) {
19452
+ const m = scenario.monetization;
19453
+ const q = (m?.uncertainty?.quantiles ?? [0.5, 0.9]).filter((x) => typeof x === "number");
19454
+ const quantiles = q.length > 0 ? [...new Set(q)].sort((a, b) => a - b) : [0.5, 0.9];
19455
+ return {
19456
+ cohorts: {
19457
+ baseUsers: m?.cohorts?.baseUsers ?? 1
19605
19458
  },
19606
- engines: {
19607
- bun: ">=1.4.2"
19459
+ retention: {
19460
+ d1: m?.retention?.d1 ?? 0.42,
19461
+ d7: m?.retention?.d7 ?? 0.2,
19462
+ d30: m?.retention?.d30 ?? 0.09,
19463
+ d90: m?.retention?.d90 ?? 0.04,
19464
+ longTailDailyDecay: m?.retention?.longTailDailyDecay ?? 0.02
19608
19465
  },
19609
- scripts: {
19610
- dev: "bun src/main.ts",
19611
- "dev:watch": "bun --watch src/main.ts",
19612
- "generate:check": "bun scripts/command-check.ts",
19613
- "doctor:completions": "bun scripts/command-check.ts",
19614
- "review:smoke": "bun scripts/review-smoke.ts",
19615
- build: "bun scripts/cli-bundle.ts --outdir dist --sourcemap",
19616
- "build:bin": "bun scripts/cli-bundle.ts --entry src/bin.ts --outdir dist/bin --targets native",
19617
- typecheck: "ttsc -p tsconfig.json --noEmit",
19618
- test: "bun scripts/cli-bundle.ts --outdir .test-bundle && IDLEKIT_CLI_ENTRY=.test-bundle/main.js bun test --timeout 90000 src",
19619
- prepack: "bun run build && cd ../.. && bun tools/package-manifest.ts prepare packages/cli",
19620
- postpack: "cd ../.. && bun tools/package-manifest.ts restore packages/cli"
19466
+ revenue: {
19467
+ payerConversion: m?.revenue?.payerConversion ?? 0.03,
19468
+ arppuDaily: m?.revenue?.arppuDaily ?? 0.7,
19469
+ adArpDau: m?.revenue?.adArpDau ?? 0.02,
19470
+ platformFeeRate: m?.revenue?.platformFeeRate ?? 0.3,
19471
+ grossMarginRate: m?.revenue?.grossMarginRate ?? 0.92,
19472
+ progressionRevenueLift: m?.revenue?.progressionRevenueLift ?? 0.4,
19473
+ progressionLogSpan: m?.revenue?.progressionLogSpan ?? 6
19621
19474
  },
19622
- dependencies: {
19623
- "@clack/prompts": "1.8.1",
19624
- "@gunshi/plugin-completion": "0.37.3",
19625
- "@gunshi/plugin-suggestion": "0.37.3",
19626
- "@idlekit/core": "0.4.1",
19627
- ajv: "^8.20.0",
19628
- gunshi: "0.37.3",
19629
- "proper-lockfile": "4.1.2",
19630
- yaml: "^2.9.1",
19631
- zod: "^4.6.5"
19475
+ acquisition: {
19476
+ cpi: m?.acquisition?.cpi ?? 0
19632
19477
  },
19633
- devDependencies: {
19634
- "@types/proper-lockfile": "4.1.4"
19478
+ uncertainty: {
19479
+ enabled: m?.uncertainty?.enabled ?? false,
19480
+ draws: m?.uncertainty?.draws ?? 300,
19481
+ quantiles,
19482
+ seed: m?.uncertainty?.seed,
19483
+ sigma: {
19484
+ retention: m?.uncertainty?.sigma?.retention ?? 0.08,
19485
+ conversion: m?.uncertainty?.sigma?.conversion ?? 0.12,
19486
+ arppu: m?.uncertainty?.sigma?.arppu ?? 0.2,
19487
+ ad: m?.uncertainty?.sigma?.ad ?? 0.15
19488
+ },
19489
+ correlation: {
19490
+ retentionConversion: m?.uncertainty?.correlation?.retentionConversion ?? 0,
19491
+ retentionArppu: m?.uncertainty?.correlation?.retentionArppu ?? 0,
19492
+ retentionAd: m?.uncertainty?.correlation?.retentionAd ?? 0,
19493
+ conversionArppu: m?.uncertainty?.correlation?.conversionArppu ?? 0,
19494
+ conversionAd: m?.uncertainty?.correlation?.conversionAd ?? 0,
19495
+ arppuAd: m?.uncertainty?.correlation?.arppuAd ?? 0
19496
+ }
19635
19497
  }
19636
19498
  };
19637
- });
19638
-
19639
- // src/runtime/selfCli.ts
19640
- import { existsSync } from "fs";
19641
- import { dirname as dirname3, resolve as resolve2 } from "path";
19642
- function currentEntry() {
19643
- return Bun.main;
19644
19499
  }
19645
- function isJavaScriptEntry(entry) {
19646
- return entry.endsWith(".js") || entry.endsWith(".mjs");
19500
+ function retentionAtDay(ret, day) {
19501
+ if (day <= 0)
19502
+ return 1;
19503
+ if (day <= 1)
19504
+ return ret.d1;
19505
+ if (day <= 7)
19506
+ return ret.d1 + (ret.d7 - ret.d1) * (day - 1) / 6;
19507
+ if (day <= 30)
19508
+ return ret.d7 + (ret.d30 - ret.d7) * (day - 7) / 23;
19509
+ if (day <= 90)
19510
+ return ret.d30 + (ret.d90 - ret.d30) * (day - 30) / 60;
19511
+ const extra = day - 90;
19512
+ return clamp012(ret.d90 * Math.exp(-ret.longTailDailyDecay * extra));
19647
19513
  }
19648
- function isNativeEntry(entry) {
19649
- return entry.includes("$bunfs") || entry.includes("~BUN");
19514
+ function progressionFactor(startWorthLog10, endWorthLog10, span) {
19515
+ if (!Number.isFinite(startWorthLog10) || !Number.isFinite(endWorthLog10) || !Number.isFinite(span) || span <= 0) {
19516
+ return 0;
19517
+ }
19518
+ return clamp012((endWorthLog10 - startWorthLog10) / span);
19650
19519
  }
19651
- function isBundledCliProcess(entry = currentEntry()) {
19652
- return isJavaScriptEntry(entry) || isNativeEntry(entry);
19520
+ function estimateLtvPerUser(args) {
19521
+ const { config, horizonDays } = args;
19522
+ const progression = clamp012(args.progression);
19523
+ const arppuDaily = config.revenue.arppuDaily * (1 + progression * config.revenue.progressionRevenueLift);
19524
+ const adArpDau = config.revenue.adArpDau * (1 + progression * config.revenue.progressionRevenueLift * 0.5);
19525
+ const payerConversion = config.revenue.payerConversion;
19526
+ const fullDays = Math.floor(Math.max(0, horizonDays));
19527
+ const remainder = Math.max(0, horizonDays - fullDays);
19528
+ let gross = 0;
19529
+ for (let day = 1;day <= fullDays; day++) {
19530
+ const active = retentionAtDay(config.retention, day);
19531
+ gross += active * (payerConversion * arppuDaily + adArpDau);
19532
+ }
19533
+ if (remainder > 0) {
19534
+ const active = retentionAtDay(config.retention, fullDays + 1);
19535
+ gross += remainder * active * (payerConversion * arppuDaily + adArpDau);
19536
+ }
19537
+ const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
19538
+ const ltv = net - config.acquisition.cpi;
19539
+ return {
19540
+ cumulativeGrossRevenuePerUser: gross,
19541
+ cumulativeNetRevenuePerUser: net,
19542
+ cumulativeLtvPerUser: ltv
19543
+ };
19653
19544
  }
19654
- function cliPackageRoot(entry = currentEntry()) {
19655
- if (isNativeEntry(entry))
19656
- return process.cwd();
19657
- if (isBundledCliProcess(entry))
19658
- return resolve2(dirname3(entry), "..");
19659
- return resolve2(import.meta.dir, "../..");
19545
+
19546
+ // ../core/dist/ltv/uncertainty.js
19547
+ function clamp013(v) {
19548
+ if (v < 0)
19549
+ return 0;
19550
+ if (v > 1)
19551
+ return 1;
19552
+ return v;
19660
19553
  }
19661
- function bundledCliConfig(entry) {
19662
- const config = resolve2(dirname3(entry), "../../../tools/bundled-cli-bunfig.toml");
19663
- if (!existsSync(config))
19664
- return;
19665
- return config;
19554
+ function clampCorrelation(v) {
19555
+ if (!Number.isFinite(v))
19556
+ return 0;
19557
+ if (v < -1)
19558
+ return -1;
19559
+ if (v > 1)
19560
+ return 1;
19561
+ return v;
19666
19562
  }
19667
- function selfCliCommand(args, entry = currentEntry()) {
19668
- if (isNativeEntry(entry))
19669
- return [process.execPath, ...args];
19670
- const bun = process.argv[0] ?? "bun";
19671
- if (isJavaScriptEntry(entry)) {
19672
- const config = bundledCliConfig(entry);
19673
- if (config)
19674
- return [bun, `--config=${config}`, entry, ...args];
19675
- return [bun, entry, ...args];
19563
+ function randNormal(rng) {
19564
+ const u1 = Math.max(0.000000000001, rng());
19565
+ const u2 = Math.max(0.000000000001, rng());
19566
+ return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
19567
+ }
19568
+ function buildCorrelationMatrix(base) {
19569
+ const rc = clampCorrelation(base.retentionConversion);
19570
+ const ra = clampCorrelation(base.retentionArppu);
19571
+ const rd = clampCorrelation(base.retentionAd);
19572
+ const ca = clampCorrelation(base.conversionArppu);
19573
+ const cd = clampCorrelation(base.conversionAd);
19574
+ const ad = clampCorrelation(base.arppuAd);
19575
+ return [
19576
+ [1, rc, ra, rd],
19577
+ [rc, 1, ca, cd],
19578
+ [ra, ca, 1, ad],
19579
+ [rd, cd, ad, 1]
19580
+ ];
19581
+ }
19582
+ function withOffDiagonalScale(matrix, alpha) {
19583
+ return matrix.map((row, i) => row.map((v, j) => {
19584
+ if (i === j)
19585
+ return 1;
19586
+ return v * alpha;
19587
+ }));
19588
+ }
19589
+ function cholesky4(matrix) {
19590
+ const n = 4;
19591
+ const l = Array.from({ length: n }, () => Array.from({ length: n }, () => 0));
19592
+ for (let i = 0;i < n; i++) {
19593
+ for (let j = 0;j <= i; j++) {
19594
+ let sum = 0;
19595
+ for (let k = 0;k < j; k++) {
19596
+ sum += (l[i]?.[k] ?? 0) * (l[j]?.[k] ?? 0);
19597
+ }
19598
+ if (i === j) {
19599
+ const val = (matrix[i]?.[i] ?? 0) - sum;
19600
+ if (val <= 0.000000001)
19601
+ return null;
19602
+ l[i][j] = Math.sqrt(val);
19603
+ } else {
19604
+ const denom = l[j]?.[j] ?? 0;
19605
+ if (Math.abs(denom) < 0.000000001)
19606
+ return null;
19607
+ l[i][j] = ((matrix[i]?.[j] ?? 0) - sum) / denom;
19608
+ }
19609
+ }
19676
19610
  }
19677
- const bunfigApplies = resolve2(process.cwd()) === CLI_ROOT;
19678
- if (entry.endsWith(".ts") && !bunfigApplies) {
19679
- return [bun, "--preload", "@ttsc/unplugin/bun-register", entry, ...args];
19611
+ return l;
19612
+ }
19613
+ function multiplyLowerTriangular(l, z) {
19614
+ const out = [0, 0, 0, 0];
19615
+ for (let i = 0;i < 4; i++) {
19616
+ let sum = 0;
19617
+ for (let j = 0;j <= i; j++) {
19618
+ sum += (l[i]?.[j] ?? 0) * (z[j] ?? 0);
19619
+ }
19620
+ out[i] = sum;
19680
19621
  }
19681
- return [bun, entry, ...args];
19622
+ return out;
19682
19623
  }
19683
- function runSelfCli(args) {
19684
- const proc = Bun.spawnSync(selfCliCommand(args), {
19685
- cwd: process.cwd(),
19686
- stdout: "pipe",
19687
- stderr: "pipe",
19688
- env: process.env,
19689
- maxBuffer: MAX_CAPTURE_BYTES2
19690
- });
19624
+ function sampleCorrelatedNormals(rng, corr) {
19625
+ const z = [randNormal(rng), randNormal(rng), randNormal(rng), randNormal(rng)];
19626
+ const matrix = buildCorrelationMatrix(corr);
19627
+ let l = cholesky4(matrix);
19628
+ let alpha = 0.99;
19629
+ while (!l && alpha > 0.000001) {
19630
+ l = cholesky4(withOffDiagonalScale(matrix, alpha));
19631
+ alpha *= 0.8;
19632
+ }
19633
+ if (!l) {
19634
+ return {
19635
+ retention: z[0] ?? 0,
19636
+ conversion: z[1] ?? 0,
19637
+ arppu: z[2] ?? 0,
19638
+ ad: z[3] ?? 0
19639
+ };
19640
+ }
19641
+ const c = multiplyLowerTriangular(l, z);
19691
19642
  return {
19692
- exitCode: proc.exitCode,
19693
- stdout: proc.stdout.toString(),
19694
- stderr: proc.stderr.toString()
19643
+ retention: c[0] ?? 0,
19644
+ conversion: c[1] ?? 0,
19645
+ arppu: c[2] ?? 0,
19646
+ ad: c[3] ?? 0
19695
19647
  };
19696
19648
  }
19697
- var CLI_ROOT, MAX_CAPTURE_BYTES2;
19698
- var init_selfCli = __esm(() => {
19699
- CLI_ROOT = resolve2(import.meta.dir, "../..");
19700
- MAX_CAPTURE_BYTES2 = 8 * 1024 * 1024;
19701
- });
19702
-
19703
- // src/io/outputMeta.ts
19704
- import { dirname as dirname4, resolve as resolve3 } from "path";
19705
- function repoRoot() {
19706
- if (isBundledCliProcess())
19707
- return resolve3(dirname4(Bun.main), "../..");
19708
- return resolve3(import.meta.dir, "../../..");
19649
+ function applyLogNormalShock(mean, sigma, z) {
19650
+ if (sigma <= 0)
19651
+ return mean;
19652
+ return mean * Math.exp(sigma * z - 0.5 * sigma * sigma);
19709
19653
  }
19710
- function stableStringify(value) {
19711
- if (value === null || typeof value !== "object") {
19712
- return JSON.stringify(value);
19654
+ function makeUncertainConfig(base, rng) {
19655
+ const shock = sampleCorrelatedNormals(rng, base.uncertainty.correlation);
19656
+ const d1 = clamp013(applyLogNormalShock(base.retention.d1, base.uncertainty.sigma.retention, shock.retention));
19657
+ const d7 = Math.min(d1, clamp013(applyLogNormalShock(base.retention.d7, base.uncertainty.sigma.retention, shock.retention)));
19658
+ const d30 = Math.min(d7, clamp013(applyLogNormalShock(base.retention.d30, base.uncertainty.sigma.retention, shock.retention)));
19659
+ const d90 = Math.min(d30, clamp013(applyLogNormalShock(base.retention.d90, base.uncertainty.sigma.retention, shock.retention)));
19660
+ return {
19661
+ ...base,
19662
+ retention: {
19663
+ ...base.retention,
19664
+ d1,
19665
+ d7,
19666
+ d30,
19667
+ d90
19668
+ },
19669
+ revenue: {
19670
+ ...base.revenue,
19671
+ payerConversion: clamp013(applyLogNormalShock(base.revenue.payerConversion, base.uncertainty.sigma.conversion, shock.conversion)),
19672
+ arppuDaily: Math.max(0, applyLogNormalShock(base.revenue.arppuDaily, base.uncertainty.sigma.arppu, shock.arppu)),
19673
+ adArpDau: Math.max(0, applyLogNormalShock(base.revenue.adArpDau, base.uncertainty.sigma.ad, shock.ad))
19674
+ }
19675
+ };
19676
+ }
19677
+ function quantileFromSorted(sorted, q) {
19678
+ if (sorted.length === 0)
19679
+ return 0;
19680
+ const idx = Math.max(0, Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * q)));
19681
+ return sorted[idx] ?? 0;
19682
+ }
19683
+ function estimateLtvDistribution(args) {
19684
+ const rng = mulberry32(args.seed);
19685
+ const grossSamples = [];
19686
+ const netSamples = [];
19687
+ const ltvSamples = [];
19688
+ const draws = Math.max(1, args.draws);
19689
+ for (let i = 0;i < draws; i++) {
19690
+ const sampled = makeUncertainConfig(args.config, rng);
19691
+ const point = estimateLtvPerUser({
19692
+ config: sampled,
19693
+ horizonDays: args.horizonDays,
19694
+ progression: args.progression
19695
+ });
19696
+ grossSamples.push(point.cumulativeGrossRevenuePerUser);
19697
+ netSamples.push(point.cumulativeNetRevenuePerUser);
19698
+ ltvSamples.push(point.cumulativeLtvPerUser);
19713
19699
  }
19714
- if (Array.isArray(value)) {
19715
- return `[${value.map((x) => stableStringify(x)).join(",")}]`;
19700
+ const avg = (arr) => arr.reduce((a, b) => a + b, 0) / Math.max(1, arr.length);
19701
+ const sortedLtv = [...ltvSamples].sort((a, b) => a - b);
19702
+ const byQuantile = {};
19703
+ for (const q of args.quantiles) {
19704
+ byQuantile[`q${Math.round(q * 100)}`] = quantileFromSorted(sortedLtv, q);
19716
19705
  }
19717
- const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
19718
- return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
19706
+ return {
19707
+ mean: {
19708
+ cumulativeGrossRevenuePerUser: avg(grossSamples),
19709
+ cumulativeNetRevenuePerUser: avg(netSamples),
19710
+ cumulativeLtvPerUser: avg(ltvSamples)
19711
+ },
19712
+ quantiles: byQuantile
19713
+ };
19719
19714
  }
19720
- function sha256(value) {
19721
- return sha256Hex(value);
19715
+ var init_uncertainty = () => {};
19716
+
19717
+ // ../core/dist/ltv/public.js
19718
+ function assertMonetization(monetization) {
19719
+ const valid = validateScenarioV1({
19720
+ schemaVersion: 1,
19721
+ unit: { code: "LTV" },
19722
+ policy: { mode: "drop" },
19723
+ model: { id: "ltv-input", version: 1 },
19724
+ initial: { wallet: { unit: "LTV", amount: "0" } },
19725
+ clock: { stepSec: 1, durationSec: 1 },
19726
+ monetization
19727
+ });
19728
+ if (!valid.ok)
19729
+ throw new RangeError(valid.issues.map((issue) => issue.message).join("; "));
19722
19730
  }
19723
- function resolveGitSha() {
19724
- if (hasResolvedGitSha)
19725
- return cachedGitSha ?? UNKNOWN_GIT_SHA;
19726
- hasResolvedGitSha = true;
19727
- const envSha = process.env.GITHUB_SHA?.trim();
19728
- if (envSha) {
19729
- cachedGitSha = envSha;
19730
- return cachedGitSha;
19731
+ function assertQuantileValues(quantiles) {
19732
+ if (quantiles.length === 0)
19733
+ throw new RangeError("quantiles must be nonempty and in 0..1");
19734
+ for (const q of quantiles) {
19735
+ if (!Number.isFinite(q) || q < 0 || q > 1)
19736
+ throw new RangeError("quantiles must be nonempty and in 0..1");
19731
19737
  }
19732
- try {
19733
- cachedGitSha = runText(["git", "rev-parse", "--short", "HEAD"], {
19734
- cwd: REPO_ROOT,
19735
- env: process.env
19736
- }).trim();
19737
- } catch {
19738
- cachedGitSha = UNKNOWN_GIT_SHA;
19738
+ }
19739
+ function assertConfig(config) {
19740
+ const { retention, ...other } = config;
19741
+ for (const day of ["d1", "d7", "d30", "d90"]) {
19742
+ if (!Number.isFinite(retention[day]) || retention[day] < 0 || retention[day] > 1) {
19743
+ throw new RangeError(`retention.${day} must be finite and in 0..1`);
19744
+ }
19739
19745
  }
19740
- return cachedGitSha ?? UNKNOWN_GIT_SHA;
19746
+ if (!Number.isFinite(retention.longTailDailyDecay) || retention.longTailDailyDecay < 0) {
19747
+ throw new RangeError("retention.longTailDailyDecay must be finite and >= 0");
19748
+ }
19749
+ const { quantiles, ...uncertainty } = other.uncertainty;
19750
+ assertQuantileValues(quantiles);
19751
+ assertMonetization({ ...other, uncertainty });
19741
19752
  }
19742
- function hashContent(value) {
19743
- return sha256(stableStringify(value));
19753
+ function deriveMonetizationConfig2(scenario) {
19754
+ assertMonetization(scenario.monetization ?? {});
19755
+ const config = deriveMonetizationConfig(scenario);
19756
+ assertConfig(config);
19757
+ return config;
19744
19758
  }
19745
- function deriveDeterministicSeed(value) {
19746
- const digest = hashContent(value);
19747
- return Number.parseInt(digest.slice(0, 8), 16) >>> 0;
19748
- }
19749
- function deriveDeterministicRunId(args) {
19750
- const suffix = hashContent({
19751
- command: args.command,
19752
- seed: args.seed,
19753
- scope: args.scope ?? null
19754
- }).slice(0, 8);
19755
- return `${args.command}-${args.seed.toString(36)}-${suffix}`;
19759
+ function assertPoint(args) {
19760
+ assertConfig(args.config);
19761
+ if (!Number.isFinite(args.horizonDays) || args.horizonDays < 0 || args.horizonDays > 36500) {
19762
+ throw new RangeError("horizonDays must be finite and in 0..36500");
19763
+ }
19764
+ if (!Number.isFinite(args.progression))
19765
+ throw new RangeError("progression must be finite");
19756
19766
  }
19757
- function outputSchemaRefForCommand(command) {
19758
- return OUTPUT_SCHEMA_REF[command];
19767
+ function finiteResult(result) {
19768
+ if (!Object.values(result).every((value) => typeof value === "number" && Number.isFinite(value))) {
19769
+ throw new RangeError("LTV calculation overflowed; reduce the input magnitudes");
19770
+ }
19771
+ return result;
19759
19772
  }
19760
- function buildOutputMeta(args) {
19761
- const scenarioHash = (() => {
19762
- if (args.scenario !== undefined)
19763
- return hashContent(args.scenario);
19764
- if (args.scenarios) {
19765
- const out = {};
19766
- for (const [k, v] of Object.entries(args.scenarios)) {
19767
- out[k] = hashContent(v);
19768
- }
19769
- return out;
19770
- }
19771
- return;
19772
- })();
19773
- return {
19774
- command: args.command,
19775
- generatedAt: new Date().toISOString(),
19776
- contractVersion: OUTPUT_CONTRACT_VERSION,
19777
- schemaRef: args.schemaRef ?? outputSchemaRefForCommand(args.command),
19778
- cliVersion,
19779
- gitSha: resolveGitSha(),
19780
- runId: args.runId,
19781
- seed: args.seed,
19782
- scenarioPath: args.scenarioPath,
19783
- scenarioHash,
19784
- tuneSpecHash: args.tuneSpec !== undefined ? hashContent(args.tuneSpec) : undefined,
19785
- telemetryHash: args.telemetry !== undefined ? hashContent(args.telemetry) : undefined,
19786
- pluginDigest: args.pluginDigest ?? {},
19787
- ...args.effectiveRunHash !== undefined ? { effectiveRunHash: args.effectiveRunHash } : {},
19788
- ...args.effectiveEngine !== undefined ? { effectiveEngine: args.effectiveEngine } : {},
19789
- ...args.stageScope !== undefined ? { stageScope: args.stageScope } : {}
19790
- };
19773
+ function estimateLtvPerUser2(args) {
19774
+ assertPoint(args);
19775
+ return finiteResult(estimateLtvPerUser(args));
19776
+ }
19777
+ function estimateLtvDistribution2(args) {
19778
+ assertPoint(args);
19779
+ if (!Number.isSafeInteger(args.draws) || args.draws < 1 || args.draws > 1e5 || args.draws * Math.max(1, Math.ceil(args.horizonDays)) > 1e7) {
19780
+ throw new RangeError("draws must be 1..100000 and draws \xD7 ceil(horizonDays) at most 10000000");
19781
+ }
19782
+ if (!Number.isFinite(args.seed))
19783
+ throw new RangeError("seed must be finite");
19784
+ if (args.quantiles.length > 101)
19785
+ throw new RangeError("quantiles exceed the 101 distinct percent keys");
19786
+ assertQuantileValues(args.quantiles);
19787
+ if (new Set(args.quantiles.map((q) => Math.round(q * 100))).size !== args.quantiles.length) {
19788
+ throw new RangeError("quantiles must be nonempty, in 0..1 and have distinct rounded percent keys");
19789
+ }
19790
+ const result = estimateLtvDistribution(args);
19791
+ finiteResult(result.mean);
19792
+ finiteResult(result.quantiles);
19793
+ return result;
19791
19794
  }
19792
- var import_package, REPO_ROOT, OUTPUT_CONTRACT_VERSION = "1.4.0", UNKNOWN_GIT_SHA = "unknown", OUTPUT_SCHEMA_REF, cachedGitSha, hasResolvedGitSha = false, cliVersion;
19793
- var init_outputMeta = __esm(() => {
19794
- init_bun();
19795
- init_selfCli();
19796
- import_package = __toESM(require_package(), 1);
19797
- REPO_ROOT = repoRoot();
19798
- OUTPUT_SCHEMA_REF = {
19799
- simulate: "docs/schemas/simulate.output.schema.json",
19800
- eta: "docs/schemas/eta.output.schema.json",
19801
- compare: "docs/schemas/compare.output.schema.json",
19802
- doctor: "docs/schemas/doctor.output.schema.json",
19803
- evaluate: "docs/schemas/evaluate.output.schema.json",
19804
- "judge.jev": "docs/schemas/jev.judge.output.schema.json",
19805
- "execution.batch": "docs/worker-execution.md",
19806
- experience: "docs/schemas/experience.output.schema.json",
19807
- "setup.completions": "docs/schemas/setup.output.schema.json",
19808
- "setup.plugin-trust": "docs/schemas/setup.output.schema.json",
19809
- tune: "docs/schemas/tune.output.schema.json",
19810
- ltv: "docs/schemas/ltv.output.schema.json",
19811
- calibrate: "docs/schemas/calibrate.output.schema.json",
19812
- "kpi.regress": "docs/schemas/kpi.regress.output.schema.json",
19813
- "replay.verify": "docs/schemas/replay.verify.output.schema.json"
19814
- };
19815
- cliVersion = import_package.default.version ?? "0.0.0";
19795
+ var init_public = __esm(() => {
19796
+ init_validate();
19797
+ init_uncertainty();
19816
19798
  });
19817
19799
 
19818
- // ../../node_modules/.bun/yaml@2.9.1/node_modules/yaml/dist/nodes/identity.js
19819
- var require_identity = __commonJS(function(exports) {
19820
- var ALIAS = Symbol.for("yaml.alias");
19821
- var DOC = Symbol.for("yaml.document");
19822
- var MAP = Symbol.for("yaml.map");
19823
- var PAIR = Symbol.for("yaml.pair");
19824
- var SCALAR = Symbol.for("yaml.scalar");
19825
- var SEQ = Symbol.for("yaml.seq");
19826
- var NODE_TYPE = Symbol.for("yaml.node.type");
19827
- var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
19828
- var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
19829
- var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
19830
- var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR;
19831
- var isScalar = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR;
19832
- var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ;
19833
- function isCollection(node) {
19834
- if (node && typeof node === "object")
19835
- switch (node[NODE_TYPE]) {
19836
- case MAP:
19837
- case SEQ:
19838
- return true;
19839
- }
19840
- return false;
19800
+ // ../core/dist/index.js
19801
+ var init_dist2 = __esm(() => {
19802
+ init_types();
19803
+ init_breakInfinity2();
19804
+ init_breakEternity2();
19805
+ init_types5();
19806
+ init_types6();
19807
+ init_emitter();
19808
+ init_tickMoney2();
19809
+ init_suffixer();
19810
+ init_formatMoney2();
19811
+ init_parseMoney2();
19812
+ init_visibilityTracker2();
19813
+ init_moneyState();
19814
+ init_simState();
19815
+ init_types7();
19816
+ init_validate();
19817
+ init_concreteValidator();
19818
+ init_scenarioContract();
19819
+ init_compile2();
19820
+ init_simulator();
19821
+ init_advance();
19822
+ init_step();
19823
+ init_stepTypes();
19824
+ init_offline();
19825
+ init_session();
19826
+ init_monteCarlo();
19827
+ init_executionBackend();
19828
+ init_integrationMatrix();
19829
+ init_runFactory();
19830
+ init_constraints();
19831
+ init_observation();
19832
+ init_types8();
19833
+ init_contracts();
19834
+ init_stability();
19835
+ init_scripted();
19836
+ init_greedy();
19837
+ init_planner();
19838
+ init_params();
19839
+ init_builtins();
19840
+ init_tuneSpec();
19841
+ init_validate2();
19842
+ init_objective();
19843
+ init_params2();
19844
+ init_builtins2();
19845
+ init_tuner();
19846
+ init_randomSearch();
19847
+ init_runner();
19848
+ init_eta();
19849
+ init_prestigeCycle();
19850
+ init_resetCycle();
19851
+ init_resetOptimization();
19852
+ init_distribution();
19853
+ init_experiment();
19854
+ init_sensitivity();
19855
+ init_growthFit();
19856
+ init_contracts2();
19857
+ init_metrics();
19858
+ init_registry();
19859
+ init_analyzers();
19860
+ init_inspect();
19861
+ init_checks3();
19862
+ init_judge();
19863
+ init_flow();
19864
+ init_jevAdvisory();
19865
+ init_public();
19866
+ });
19867
+
19868
+ // src/runtime/bun.ts
19869
+ var {$ } = globalThis.Bun;
19870
+ import { dirname } from "path";
19871
+ async function readTextFile(path) {
19872
+ return Bun.file(path).text();
19873
+ }
19874
+ async function readJsonFile(path) {
19875
+ return Bun.file(path).json();
19876
+ }
19877
+ async function writeTextFile(path, body) {
19878
+ try {
19879
+ await ensureDir(dirname(path));
19880
+ await Bun.write(path, body);
19881
+ } catch (error) {
19882
+ throw cliError("OUTPUT_WRITE_FAILED", `Unable to write output file: ${path}`, {
19883
+ detail: errorDetail(error),
19884
+ hint: "Choose a writable output path and check that its parent is a directory.",
19885
+ cause: error
19886
+ });
19841
19887
  }
19842
- function isNode(node) {
19843
- if (node && typeof node === "object")
19844
- switch (node[NODE_TYPE]) {
19845
- case ALIAS:
19846
- case MAP:
19847
- case SCALAR:
19848
- case SEQ:
19849
- return true;
19850
- }
19851
- return false;
19888
+ }
19889
+ function writeStdout(body) {
19890
+ return new Promise((resolve, reject) => {
19891
+ process.stdout.write(body, (error) => error ? reject(error) : resolve());
19892
+ });
19893
+ }
19894
+ async function ensureDir(path) {
19895
+ await $`mkdir -p ${path}`.quiet();
19896
+ }
19897
+ async function fileExists(path) {
19898
+ return Bun.file(path).exists();
19899
+ }
19900
+ function sha256Hex(value) {
19901
+ const hasher = new Bun.CryptoHasher("sha256");
19902
+ hasher.update(value);
19903
+ return hasher.digest("hex");
19904
+ }
19905
+ function runText(args, opts) {
19906
+ const proc = Bun.spawnSync(args, {
19907
+ cwd: opts?.cwd,
19908
+ env: opts?.env,
19909
+ stdout: "pipe",
19910
+ stderr: "pipe",
19911
+ maxBuffer: MAX_CAPTURE_BYTES
19912
+ });
19913
+ if (proc.exitCode !== 0) {
19914
+ const stderr = proc.stderr.toString().trim();
19915
+ const stdout = proc.stdout.toString().trim();
19916
+ const detail = stderr || stdout || `command failed with exit code ${proc.exitCode}`;
19917
+ throw new Error(detail);
19852
19918
  }
19853
- var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
19854
- exports.ALIAS = ALIAS;
19855
- exports.DOC = DOC;
19856
- exports.MAP = MAP;
19857
- exports.NODE_TYPE = NODE_TYPE;
19858
- exports.PAIR = PAIR;
19859
- exports.SCALAR = SCALAR;
19860
- exports.SEQ = SEQ;
19861
- exports.hasAnchor = hasAnchor;
19862
- exports.isAlias = isAlias;
19863
- exports.isCollection = isCollection;
19864
- exports.isDocument = isDocument;
19865
- exports.isMap = isMap;
19866
- exports.isNode = isNode;
19867
- exports.isPair = isPair;
19868
- exports.isScalar = isScalar;
19869
- exports.isSeq = isSeq;
19919
+ return proc.stdout.toString();
19920
+ }
19921
+ var MAX_CAPTURE_BYTES;
19922
+ var init_bun = __esm(() => {
19923
+ init_errors3();
19924
+ MAX_CAPTURE_BYTES = 8 * 1024 * 1024;
19870
19925
  });
19871
19926
 
19872
- // ../../node_modules/.bun/yaml@2.9.1/node_modules/yaml/dist/visit.js
19873
- var require_visit = __commonJS(function(exports) {
19874
- var identity = require_identity();
19875
- var BREAK = Symbol("break visit");
19876
- var SKIP = Symbol("skip children");
19877
- var REMOVE = Symbol("remove node");
19878
- function visit(node, visitor) {
19879
- const visitor_ = initVisitor(visitor);
19880
- if (identity.isDocument(node)) {
19881
- const cd = visit_(null, node.contents, visitor_, Object.freeze([node]));
19882
- if (cd === REMOVE)
19883
- node.contents = null;
19884
- } else
19927
+ // package.json
19928
+ var require_package = __commonJS(function(exports, module) {
19929
+ module.exports = {
19930
+ name: "@idlekit/cli",
19931
+ version: "0.5.1",
19932
+ description: "Bun-first idk CLI for idle game scenario validation, simulation, tuning, and reporting.",
19933
+ license: "MIT",
19934
+ type: "module",
19935
+ files: [
19936
+ "dist",
19937
+ "README.md",
19938
+ "LICENSE"
19939
+ ],
19940
+ bin: {
19941
+ idk: "dist/main.js"
19942
+ },
19943
+ repository: {
19944
+ type: "git",
19945
+ url: "git+https://github.com/imjlk/idlekit.git",
19946
+ directory: "packages/cli"
19947
+ },
19948
+ homepage: "https://github.com/imjlk/idlekit/tree/main/packages/cli#readme",
19949
+ bugs: {
19950
+ url: "https://github.com/imjlk/idlekit/issues"
19951
+ },
19952
+ keywords: [
19953
+ "bun",
19954
+ "idle-game",
19955
+ "incremental-game",
19956
+ "cli",
19957
+ "economy-simulator",
19958
+ "game-balancing",
19959
+ "simulation"
19960
+ ],
19961
+ publishConfig: {
19962
+ access: "public"
19963
+ },
19964
+ engines: {
19965
+ bun: ">=1.4.2"
19966
+ },
19967
+ scripts: {
19968
+ dev: "bun src/main.ts",
19969
+ "dev:watch": "bun --watch src/main.ts",
19970
+ "generate:check": "bun scripts/command-check.ts",
19971
+ "doctor:completions": "bun scripts/command-check.ts",
19972
+ "review:smoke": "bun scripts/review-smoke.ts",
19973
+ build: "bun scripts/cli-bundle.ts --outdir dist --sourcemap",
19974
+ "build:bin": "bun scripts/cli-bundle.ts --entry src/bin.ts --outdir dist/bin --targets native",
19975
+ typecheck: "ttsc -p tsconfig.json --noEmit",
19976
+ test: "bun scripts/cli-bundle.ts --outdir .test-bundle && IDLEKIT_CLI_ENTRY=.test-bundle/main.js bun test --timeout 90000 src",
19977
+ prepack: "bun run build && cd ../.. && bun tools/package-manifest.ts prepare packages/cli",
19978
+ postpack: "cd ../.. && bun tools/package-manifest.ts restore packages/cli"
19979
+ },
19980
+ dependencies: {
19981
+ "@clack/prompts": "1.8.1",
19982
+ "@gunshi/plugin-completion": "0.37.3",
19983
+ "@gunshi/plugin-suggestion": "0.37.3",
19984
+ "@idlekit/core": "0.5.0",
19985
+ ajv: "^8.20.0",
19986
+ gunshi: "0.37.3",
19987
+ "proper-lockfile": "4.1.2",
19988
+ yaml: "^2.9.1",
19989
+ zod: "^4.6.5"
19990
+ },
19991
+ devDependencies: {
19992
+ "@types/proper-lockfile": "4.1.4"
19993
+ }
19994
+ };
19995
+ });
19996
+
19997
+ // src/runtime/selfCli.ts
19998
+ import { existsSync } from "fs";
19999
+ import { dirname as dirname3, resolve as resolve2 } from "path";
20000
+ function currentEntry() {
20001
+ return Bun.main;
20002
+ }
20003
+ function isJavaScriptEntry(entry) {
20004
+ return entry.endsWith(".js") || entry.endsWith(".mjs");
20005
+ }
20006
+ function isNativeEntry(entry) {
20007
+ return entry.includes("$bunfs") || entry.includes("~BUN");
20008
+ }
20009
+ function isBundledCliProcess(entry = currentEntry()) {
20010
+ return isJavaScriptEntry(entry) || isNativeEntry(entry);
20011
+ }
20012
+ function cliPackageRoot(entry = currentEntry()) {
20013
+ if (isNativeEntry(entry))
20014
+ return process.cwd();
20015
+ if (isBundledCliProcess(entry))
20016
+ return resolve2(dirname3(entry), "..");
20017
+ return resolve2(import.meta.dir, "../..");
20018
+ }
20019
+ function bundledCliConfig(entry) {
20020
+ const config = resolve2(dirname3(entry), "../../../tools/bundled-cli-bunfig.toml");
20021
+ if (!existsSync(config))
20022
+ return;
20023
+ return config;
20024
+ }
20025
+ function selfCliCommand(args, entry = currentEntry()) {
20026
+ if (isNativeEntry(entry))
20027
+ return [process.execPath, ...args];
20028
+ const bun = process.argv[0] ?? "bun";
20029
+ if (isJavaScriptEntry(entry)) {
20030
+ const config = bundledCliConfig(entry);
20031
+ if (config)
20032
+ return [bun, `--config=${config}`, entry, ...args];
20033
+ return [bun, entry, ...args];
20034
+ }
20035
+ const bunfigApplies = resolve2(process.cwd()) === CLI_ROOT;
20036
+ if (entry.endsWith(".ts") && !bunfigApplies) {
20037
+ return [bun, "--preload", "@ttsc/unplugin/bun-register", entry, ...args];
20038
+ }
20039
+ return [bun, entry, ...args];
20040
+ }
20041
+ function runSelfCli(args) {
20042
+ const proc = Bun.spawnSync(selfCliCommand(args), {
20043
+ cwd: process.cwd(),
20044
+ stdout: "pipe",
20045
+ stderr: "pipe",
20046
+ env: process.env,
20047
+ maxBuffer: MAX_CAPTURE_BYTES2
20048
+ });
20049
+ return {
20050
+ exitCode: proc.exitCode,
20051
+ stdout: proc.stdout.toString(),
20052
+ stderr: proc.stderr.toString()
20053
+ };
20054
+ }
20055
+ var CLI_ROOT, MAX_CAPTURE_BYTES2;
20056
+ var init_selfCli = __esm(() => {
20057
+ CLI_ROOT = resolve2(import.meta.dir, "../..");
20058
+ MAX_CAPTURE_BYTES2 = 8 * 1024 * 1024;
20059
+ });
20060
+
20061
+ // src/io/outputMeta.ts
20062
+ import { dirname as dirname4, resolve as resolve3 } from "path";
20063
+ function repoRoot() {
20064
+ if (isBundledCliProcess())
20065
+ return resolve3(dirname4(Bun.main), "../..");
20066
+ return resolve3(import.meta.dir, "../../..");
20067
+ }
20068
+ function stableStringify(value) {
20069
+ if (value === null || typeof value !== "object") {
20070
+ return JSON.stringify(value);
20071
+ }
20072
+ if (Array.isArray(value)) {
20073
+ return `[${value.map((x) => stableStringify(x)).join(",")}]`;
20074
+ }
20075
+ const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
20076
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
20077
+ }
20078
+ function sha256(value) {
20079
+ return sha256Hex(value);
20080
+ }
20081
+ function resolveGitSha() {
20082
+ if (hasResolvedGitSha)
20083
+ return cachedGitSha ?? UNKNOWN_GIT_SHA;
20084
+ hasResolvedGitSha = true;
20085
+ const envSha = process.env.GITHUB_SHA?.trim();
20086
+ if (envSha) {
20087
+ cachedGitSha = envSha;
20088
+ return cachedGitSha;
20089
+ }
20090
+ try {
20091
+ cachedGitSha = runText(["git", "rev-parse", "--short", "HEAD"], {
20092
+ cwd: REPO_ROOT,
20093
+ env: process.env
20094
+ }).trim();
20095
+ } catch {
20096
+ cachedGitSha = UNKNOWN_GIT_SHA;
20097
+ }
20098
+ return cachedGitSha ?? UNKNOWN_GIT_SHA;
20099
+ }
20100
+ function hashContent(value) {
20101
+ return sha256(stableStringify(value));
20102
+ }
20103
+ function deriveDeterministicSeed(value) {
20104
+ const digest = hashContent(value);
20105
+ return Number.parseInt(digest.slice(0, 8), 16) >>> 0;
20106
+ }
20107
+ function deriveDeterministicRunId(args) {
20108
+ const suffix = hashContent({
20109
+ command: args.command,
20110
+ seed: args.seed,
20111
+ scope: args.scope ?? null
20112
+ }).slice(0, 8);
20113
+ return `${args.command}-${args.seed.toString(36)}-${suffix}`;
20114
+ }
20115
+ function outputSchemaRefForCommand(command) {
20116
+ return OUTPUT_SCHEMA_REF[command];
20117
+ }
20118
+ function buildOutputMeta(args) {
20119
+ const scenarioHash = (() => {
20120
+ if (args.scenario !== undefined)
20121
+ return hashContent(args.scenario);
20122
+ if (args.scenarios) {
20123
+ const out = {};
20124
+ for (const [k, v] of Object.entries(args.scenarios)) {
20125
+ out[k] = hashContent(v);
20126
+ }
20127
+ return out;
20128
+ }
20129
+ return;
20130
+ })();
20131
+ return {
20132
+ command: args.command,
20133
+ generatedAt: new Date().toISOString(),
20134
+ contractVersion: OUTPUT_CONTRACT_VERSION,
20135
+ schemaRef: args.schemaRef ?? outputSchemaRefForCommand(args.command),
20136
+ cliVersion,
20137
+ gitSha: resolveGitSha(),
20138
+ runId: args.runId,
20139
+ seed: args.seed,
20140
+ scenarioPath: args.scenarioPath,
20141
+ scenarioHash,
20142
+ tuneSpecHash: args.tuneSpec !== undefined ? hashContent(args.tuneSpec) : undefined,
20143
+ telemetryHash: args.telemetry !== undefined ? hashContent(args.telemetry) : undefined,
20144
+ pluginDigest: args.pluginDigest ?? {},
20145
+ ...args.effectiveRunHash !== undefined ? { effectiveRunHash: args.effectiveRunHash } : {},
20146
+ ...args.effectiveEngine !== undefined ? { effectiveEngine: args.effectiveEngine } : {},
20147
+ ...args.stageScope !== undefined ? { stageScope: args.stageScope } : {}
20148
+ };
20149
+ }
20150
+ var import_package, REPO_ROOT, OUTPUT_CONTRACT_VERSION = "1.4.0", UNKNOWN_GIT_SHA = "unknown", OUTPUT_SCHEMA_REF, cachedGitSha, hasResolvedGitSha = false, cliVersion;
20151
+ var init_outputMeta = __esm(() => {
20152
+ init_bun();
20153
+ init_selfCli();
20154
+ import_package = __toESM(require_package(), 1);
20155
+ REPO_ROOT = repoRoot();
20156
+ OUTPUT_SCHEMA_REF = {
20157
+ simulate: "docs/schemas/simulate.output.schema.json",
20158
+ eta: "docs/schemas/eta.output.schema.json",
20159
+ compare: "docs/schemas/compare.output.schema.json",
20160
+ doctor: "docs/schemas/doctor.output.schema.json",
20161
+ evaluate: "docs/schemas/evaluate.output.schema.json",
20162
+ "judge.jev": "docs/schemas/jev.judge.output.schema.json",
20163
+ "execution.batch": "docs/worker-execution.md",
20164
+ experience: "docs/schemas/experience.output.schema.json",
20165
+ "setup.completions": "docs/schemas/setup.output.schema.json",
20166
+ "setup.plugin-trust": "docs/schemas/setup.output.schema.json",
20167
+ tune: "docs/schemas/tune.output.schema.json",
20168
+ ltv: "docs/schemas/ltv.output.schema.json",
20169
+ calibrate: "docs/schemas/calibrate.output.schema.json",
20170
+ "kpi.regress": "docs/schemas/kpi.regress.output.schema.json",
20171
+ "replay.verify": "docs/schemas/replay.verify.output.schema.json"
20172
+ };
20173
+ cliVersion = import_package.default.version ?? "0.0.0";
20174
+ });
20175
+
20176
+ // ../../node_modules/.bun/yaml@2.9.1/node_modules/yaml/dist/nodes/identity.js
20177
+ var require_identity = __commonJS(function(exports) {
20178
+ var ALIAS = Symbol.for("yaml.alias");
20179
+ var DOC = Symbol.for("yaml.document");
20180
+ var MAP = Symbol.for("yaml.map");
20181
+ var PAIR = Symbol.for("yaml.pair");
20182
+ var SCALAR = Symbol.for("yaml.scalar");
20183
+ var SEQ = Symbol.for("yaml.seq");
20184
+ var NODE_TYPE = Symbol.for("yaml.node.type");
20185
+ var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
20186
+ var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
20187
+ var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
20188
+ var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR;
20189
+ var isScalar = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR;
20190
+ var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ;
20191
+ function isCollection(node) {
20192
+ if (node && typeof node === "object")
20193
+ switch (node[NODE_TYPE]) {
20194
+ case MAP:
20195
+ case SEQ:
20196
+ return true;
20197
+ }
20198
+ return false;
20199
+ }
20200
+ function isNode(node) {
20201
+ if (node && typeof node === "object")
20202
+ switch (node[NODE_TYPE]) {
20203
+ case ALIAS:
20204
+ case MAP:
20205
+ case SCALAR:
20206
+ case SEQ:
20207
+ return true;
20208
+ }
20209
+ return false;
20210
+ }
20211
+ var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
20212
+ exports.ALIAS = ALIAS;
20213
+ exports.DOC = DOC;
20214
+ exports.MAP = MAP;
20215
+ exports.NODE_TYPE = NODE_TYPE;
20216
+ exports.PAIR = PAIR;
20217
+ exports.SCALAR = SCALAR;
20218
+ exports.SEQ = SEQ;
20219
+ exports.hasAnchor = hasAnchor;
20220
+ exports.isAlias = isAlias;
20221
+ exports.isCollection = isCollection;
20222
+ exports.isDocument = isDocument;
20223
+ exports.isMap = isMap;
20224
+ exports.isNode = isNode;
20225
+ exports.isPair = isPair;
20226
+ exports.isScalar = isScalar;
20227
+ exports.isSeq = isSeq;
20228
+ });
20229
+
20230
+ // ../../node_modules/.bun/yaml@2.9.1/node_modules/yaml/dist/visit.js
20231
+ var require_visit = __commonJS(function(exports) {
20232
+ var identity = require_identity();
20233
+ var BREAK = Symbol("break visit");
20234
+ var SKIP = Symbol("skip children");
20235
+ var REMOVE = Symbol("remove node");
20236
+ function visit(node, visitor) {
20237
+ const visitor_ = initVisitor(visitor);
20238
+ if (identity.isDocument(node)) {
20239
+ const cd = visit_(null, node.contents, visitor_, Object.freeze([node]));
20240
+ if (cd === REMOVE)
20241
+ node.contents = null;
20242
+ } else
19885
20243
  visit_(null, node, visitor_, Object.freeze([]));
19886
20244
  }
19887
20245
  visit.BREAK = BREAK;
@@ -36322,532 +36680,165 @@ function judgeCsvRows(report) {
36322
36680
  unit: result.evidence.unit,
36323
36681
  operator: result.evidence.operator,
36324
36682
  threshold: result.evidence.threshold,
36325
- upperThreshold: result.evidence.upperThreshold,
36326
- diagnosticStatus: diagnostic?.status ?? null,
36327
- recommendationId: diagnostic?.recommendationId ?? null
36328
- };
36329
- });
36330
- }
36331
- async function runJudge(flags, analysisPath, hooks) {
36332
- const requestedModel = (flags.model ?? "jev-latest").trim();
36333
- if (flags.provider === "jev" && requestedModel.length === 0) {
36334
- throw usageError("--model requires a nonempty model name.");
36335
- }
36336
- if (flags.provider === "jev" && flags.format === "csv") {
36337
- throw usageError("--provider jev writes json or md. csv cannot hold the rules report and the advisory together.");
36338
- }
36339
- if (flags.provider === "jev" && flags.cache) {
36340
- if (flags.out && sameFile(flags.cache, flags.out)) {
36341
- throw usageError("--cache and --out name the same file. Give the Jev cache its own path.");
36342
- }
36343
- if (flags.out && (ancestorFile(flags.cache, flags.out) || ancestorFile(flags.out, flags.cache))) {
36344
- throw usageError("--cache and --out cannot be ancestor paths. Give each file a separate target.");
36345
- }
36346
- if (sameFile(flags.cache, analysisPath) || sameFile(flags.cache, flags.rubric)) {
36347
- throw usageError("--cache names an input file. Give the Jev cache its own path.");
36348
- }
36349
- }
36350
- if (flags.provider === "jev") {
36351
- if (flags.cache)
36352
- requireFileTarget(flags.cache, "cache");
36353
- if (flags.out)
36354
- requireFileTarget(flags.out, "out");
36355
- }
36356
- let artifactRaw;
36357
- let rubricRaw;
36358
- try {
36359
- artifactRaw = await readJsonFile(analysisPath);
36360
- rubricRaw = await readJsonFile(flags.rubric);
36361
- } catch (error) {
36362
- throw usageError(`Invalid judge input: ${error instanceof Error ? error.message : String(error)}`);
36363
- }
36364
- const parsed = readJudgeInputs(artifactRaw, rubricRaw);
36365
- if (!parsed.ok) {
36366
- throw usageError(`Invalid judge input: ${parsed.issues.join("; ")}`);
36367
- }
36368
- let report;
36369
- try {
36370
- report = evaluateRules(parsed);
36371
- } catch (error) {
36372
- const message = error instanceof Error ? error.message : String(error);
36373
- throw usageError(`Invalid judge input: ${message}`);
36374
- }
36375
- if (flags.provider === "rules") {
36376
- await writeOutput({
36377
- format: flags.format,
36378
- outPath: flags.out,
36379
- data: flags.format === "md" ? renderJudgeMarkdown(report) : flags.format === "csv" ? judgeCsvRows(report) : report
36380
- });
36381
- return report;
36382
- }
36383
- await Promise.resolve().then(() => init_jev());
36384
- const key = (hooks?.env ?? process.env).TYPESAFE_API_KEY?.trim() ?? "";
36385
- if (!isSafeJevReport(report, key)) {
36386
- throw usageError("Unsafe Jev numerical report metadata. Remove secrets and local paths from the judge inputs.");
36387
- }
36388
- const advisory = await adviseWithJev({
36389
- artifact: parsed.artifact,
36390
- rubric: parsed.rubric,
36391
- numerical: report,
36392
- requestedModel,
36393
- dryRun: flags["dry-run"] === true,
36394
- cachePath: flags.cache,
36395
- transport: hooks?.transport,
36396
- env: hooks?.env,
36397
- nowMs: hooks?.nowMs,
36398
- sleep: hooks?.sleep
36399
- });
36400
- const bundle = { numericalReport: report, advisory };
36401
- if (flags.format === "md") {
36402
- const markdown = [
36403
- renderJudgeMarkdown(report),
36404
- `- Jev: ${advisory.status} ${advisory.reason}`,
36405
- `- Endpoint: ${advisory.endpoint}`,
36406
- `- Planned requests: ${advisory.plannedRequests}`,
36407
- "",
36408
- "## Jev answers",
36409
- "",
36410
- ...advisory.answers.map((answer) => {
36411
- const value = answer.noul ?? answer.choice ?? answer.score ?? "none";
36412
- const confidence = answer.confidence === null ? "" : `; confidence=${answer.confidence}`;
36413
- return `- ${answer.id}: ${answer.type}=${value}${confidence}; ${answer.disposition}`;
36414
- }),
36415
- ...advisory.answers.length === 0 ? ["None."] : []
36416
- ].join(`
36417
- `);
36418
- if (flags.out) {
36419
- const outPath = resolve7(flags.out);
36420
- await writeTextFile(outPath, `${markdown}
36421
- `);
36422
- console.log(`Wrote md output to ${outPath}`);
36423
- } else {
36424
- await writeStdout(`${markdown}
36425
- `);
36426
- }
36427
- return bundle;
36428
- }
36429
- await writeOutput({
36430
- format: flags.format,
36431
- outPath: flags.out,
36432
- data: bundle,
36433
- meta: buildOutputMeta({
36434
- command: "judge",
36435
- pluginDigest: {},
36436
- schemaRef: outputSchemaRefForCommand("judge.jev")
36437
- })
36438
- });
36439
- return bundle;
36440
- }
36441
- var judge_default = defineCommand({
36442
- name: "judge",
36443
- description: "Advise on an analysis artifact with a versioned rules rubric",
36444
- options: {
36445
- rubric: option(string2(), { description: "Versioned rubric JSON path" }),
36446
- provider: option(_enum(["rules", "jev"]).default("rules"), {
36447
- description: "Advisory provider. rules is local. jev is an opt-in network provider."
36448
- }),
36449
- "dry-run": option(exports_coerce.boolean().default(false), {
36450
- description: "Show the Jev request plan without calling the network"
36451
- }),
36452
- model: option(string2().trim().min(1).default("jev-latest"), {
36453
- description: "Requested Jev model. jev-latest is an alias, not a pinned id."
36454
- }),
36455
- cache: option(string2().optional(), { description: "Optional Jev advisory cache path" }),
36456
- out: option(string2().optional(), { description: "Output path" }),
36457
- format: option(_enum(["json", "md", "csv"]).default("json"), { description: "Output format" })
36458
- },
36459
- async handler({ flags, positional }) {
36460
- const analysisPath = positional[0];
36461
- if (!analysisPath) {
36462
- throw usageError("Usage: idk judge <analysis.json> --rubric <rubric.json> --provider rules");
36463
- }
36464
- await runJudge(flags, analysisPath);
36465
- }
36466
- });
36467
-
36468
- // src/commands/calibrate.ts
36469
- init_zod();
36470
- init_errors3();
36471
- init_outputMeta();
36472
- import { extname as extname3, resolve as resolve8 } from "path";
36473
-
36474
- // src/lib/ltvCalibration.ts
36475
- function assertFiniteNumbers(value, path) {
36476
- if (typeof value === "number" && !Number.isFinite(value)) {
36477
- throw new RangeError(`Calibration requires finite numbers; numeric overflow at ${path}`);
36478
- }
36479
- if (typeof value === "object" && value !== null) {
36480
- for (const [key, child] of Object.entries(value))
36481
- assertFiniteNumbers(child, `${path}.${key}`);
36482
- }
36483
- }
36484
- function clamp012(v) {
36485
- if (v < 0)
36486
- return 0;
36487
- if (v > 1)
36488
- return 1;
36489
- return v;
36490
- }
36491
- function clampCorrelation(v) {
36492
- if (!Number.isFinite(v))
36493
- return 0;
36494
- if (v < -1)
36495
- return -1;
36496
- if (v > 1)
36497
- return 1;
36498
- return v;
36499
- }
36500
- function mean(values) {
36501
- if (values.length === 0)
36502
- return 0;
36503
- return values.reduce((a, b) => a + b, 0) / values.length;
36504
- }
36505
- function variance(values) {
36506
- if (values.length === 0)
36507
- return 0;
36508
- const m = mean(values);
36509
- let acc = 0;
36510
- for (const v of values) {
36511
- const d = v - m;
36512
- acc += d * d;
36513
- }
36514
- return acc / values.length;
36515
- }
36516
- function estimateCorrelation(x, y) {
36517
- const n = Math.min(x.length, y.length);
36518
- if (n < 3) {
36519
- return {
36520
- raw: 0,
36521
- value: 0,
36522
- confidence: 0,
36523
- shrinkage: 0,
36524
- sampleSize: n,
36525
- varianceX: 0,
36526
- varianceY: 0
36527
- };
36528
- }
36529
- const sx = x.slice(0, n);
36530
- const sy = y.slice(0, n);
36531
- const mx = mean(sx);
36532
- const my = mean(sy);
36533
- let cov = 0;
36534
- let vx = 0;
36535
- let vy = 0;
36536
- for (let i = 0;i < n; i++) {
36537
- const dx = (sx[i] ?? 0) - mx;
36538
- const dy = (sy[i] ?? 0) - my;
36539
- cov += dx * dy;
36540
- vx += dx * dx;
36541
- vy += dy * dy;
36542
- }
36543
- const varianceX = vx / n;
36544
- const varianceY = vy / n;
36545
- if (vx <= 0.000000000001 || vy <= 0.000000000001) {
36546
- return {
36547
- raw: 0,
36548
- value: 0,
36549
- confidence: 0,
36550
- shrinkage: 0,
36551
- sampleSize: n,
36552
- varianceX,
36553
- varianceY
36683
+ upperThreshold: result.evidence.upperThreshold,
36684
+ diagnosticStatus: diagnostic?.status ?? null,
36685
+ recommendationId: diagnostic?.recommendationId ?? null
36554
36686
  };
36555
- }
36556
- const raw = clampCorrelation(cov / Math.sqrt(vx) / Math.sqrt(vy));
36557
- const ci95 = 1.96 / Math.sqrt(Math.max(1, n - 3));
36558
- const sampleConfidence = clamp012(1 - ci95);
36559
- const varianceConfidenceX = varianceX / (varianceX + 0.01);
36560
- const varianceConfidenceY = varianceY / (varianceY + 0.01);
36561
- const varianceConfidence = Math.sqrt(varianceConfidenceX * varianceConfidenceY);
36562
- const confidence = clamp012(sampleConfidence * varianceConfidence);
36563
- const shrinkage = confidence;
36564
- const value = raw * shrinkage;
36565
- return {
36566
- raw,
36567
- value,
36568
- confidence,
36569
- shrinkage,
36570
- sampleSize: n,
36571
- varianceX,
36572
- varianceY
36573
- };
36687
+ });
36574
36688
  }
36575
- function calibrateMonetization(rows) {
36576
- if (rows.length === 0) {
36577
- throw new Error("calibration rows cannot be empty");
36689
+ async function runJudge(flags, analysisPath, hooks) {
36690
+ const requestedModel = (flags.model ?? "jev-latest").trim();
36691
+ if (flags.provider === "jev" && requestedModel.length === 0) {
36692
+ throw usageError("--model requires a nonempty model name.");
36578
36693
  }
36579
- const users = new Map;
36580
- for (const row of rows) {
36581
- const day = Math.max(0, Math.floor(row.day));
36582
- const u = users.get(row.userId) ?? { days: new Set, iap: 0, ad: 0, acq: undefined };
36583
- if (row.active ?? true)
36584
- u.days.add(day);
36585
- u.iap += Math.max(0, row.iapRevenue);
36586
- u.ad += Math.max(0, row.adRevenue);
36587
- if (row.acquisitionCost !== undefined && Number.isFinite(row.acquisitionCost)) {
36588
- u.acq = (u.acq ?? 0) + Math.max(0, row.acquisitionCost);
36589
- }
36590
- users.set(row.userId, u);
36694
+ if (flags.provider === "jev" && flags.format === "csv") {
36695
+ throw usageError("--provider jev writes json or md. csv cannot hold the rules report and the advisory together.");
36591
36696
  }
36592
- const cohortSize = Math.max(1, users.size);
36593
- const dayActiveCount = (targetDay) => {
36594
- let count = 0;
36595
- for (const user of users.values())
36596
- if (user.days.has(targetDay))
36597
- count += 1;
36598
- return count;
36599
- };
36600
- const d1 = dayActiveCount(1) / cohortSize;
36601
- const d7 = dayActiveCount(7) / cohortSize;
36602
- const d30 = dayActiveCount(30) / cohortSize;
36603
- const d90 = dayActiveCount(90) / cohortSize;
36604
- let totalIap = 0;
36605
- let totalAd = 0;
36606
- let payerUsers = 0;
36607
- let activeUserDays = 0;
36608
- let payerDays = 0;
36609
- let totalAcq = 0;
36610
- let acqUsers = 0;
36611
- for (const user of users.values()) {
36612
- totalIap += user.iap;
36613
- totalAd += user.ad;
36614
- if (user.iap > 0)
36615
- payerUsers += 1;
36616
- activeUserDays += user.days.size;
36617
- for (const day of user.days) {
36618
- if (day >= 0 && user.iap > 0)
36619
- payerDays += 1;
36697
+ if (flags.provider === "jev" && flags.cache) {
36698
+ if (flags.out && sameFile(flags.cache, flags.out)) {
36699
+ throw usageError("--cache and --out name the same file. Give the Jev cache its own path.");
36620
36700
  }
36621
- if (user.acq !== undefined) {
36622
- totalAcq += user.acq;
36623
- acqUsers += 1;
36701
+ if (flags.out && (ancestorFile(flags.cache, flags.out) || ancestorFile(flags.out, flags.cache))) {
36702
+ throw usageError("--cache and --out cannot be ancestor paths. Give each file a separate target.");
36703
+ }
36704
+ if (sameFile(flags.cache, analysisPath) || sameFile(flags.cache, flags.rubric)) {
36705
+ throw usageError("--cache names an input file. Give the Jev cache its own path.");
36624
36706
  }
36625
36707
  }
36626
- const payerConversion = payerUsers / cohortSize;
36627
- const arppuDaily = totalIap / Math.max(1, payerDays);
36628
- const adArpDau = totalAd / Math.max(1, activeUserDays);
36629
- const cpi = totalAcq / Math.max(1, acqUsers);
36630
- const userRetention = [];
36631
- const userConversion = [];
36632
- const userArppu = [];
36633
- const userAdArpDau = [];
36634
- for (const user of users.values()) {
36635
- const activeDays = Math.max(1, user.days.size);
36636
- const retentionScore = clamp012(activeDays / 30);
36637
- const conversionScore = user.iap > 0 ? 1 : 0;
36638
- const arppuScore = user.iap > 0 ? user.iap / activeDays : 0;
36639
- const adScore = user.ad / activeDays;
36640
- userRetention.push(retentionScore);
36641
- userConversion.push(conversionScore);
36642
- userArppu.push(arppuScore);
36643
- userAdArpDau.push(adScore);
36708
+ if (flags.provider === "jev") {
36709
+ if (flags.cache)
36710
+ requireFileTarget(flags.cache, "cache");
36711
+ if (flags.out)
36712
+ requireFileTarget(flags.out, "out");
36644
36713
  }
36645
- const estimatedCorrelationPair = {
36646
- retentionConversion: estimateCorrelation(userRetention, userConversion),
36647
- retentionArppu: estimateCorrelation(userRetention, userArppu),
36648
- retentionAd: estimateCorrelation(userRetention, userAdArpDau),
36649
- conversionArppu: estimateCorrelation(userConversion, userArppu),
36650
- conversionAd: estimateCorrelation(userConversion, userAdArpDau),
36651
- arppuAd: estimateCorrelation(userArppu, userAdArpDau)
36652
- };
36653
- const estimatedCorrelation = {
36654
- retentionConversion: estimatedCorrelationPair.retentionConversion.value,
36655
- retentionArppu: estimatedCorrelationPair.retentionArppu.value,
36656
- retentionAd: estimatedCorrelationPair.retentionAd.value,
36657
- conversionArppu: estimatedCorrelationPair.conversionArppu.value,
36658
- conversionAd: estimatedCorrelationPair.conversionAd.value,
36659
- arppuAd: estimatedCorrelationPair.arppuAd.value
36660
- };
36661
- const estimatedCorrelationRaw = {
36662
- retentionConversion: estimatedCorrelationPair.retentionConversion.raw,
36663
- retentionArppu: estimatedCorrelationPair.retentionArppu.raw,
36664
- retentionAd: estimatedCorrelationPair.retentionAd.raw,
36665
- conversionArppu: estimatedCorrelationPair.conversionArppu.raw,
36666
- conversionAd: estimatedCorrelationPair.conversionAd.raw,
36667
- arppuAd: estimatedCorrelationPair.arppuAd.raw
36668
- };
36669
- const correlationConfidence = {
36670
- retentionConversion: estimatedCorrelationPair.retentionConversion.confidence,
36671
- retentionArppu: estimatedCorrelationPair.retentionArppu.confidence,
36672
- retentionAd: estimatedCorrelationPair.retentionAd.confidence,
36673
- conversionArppu: estimatedCorrelationPair.conversionArppu.confidence,
36674
- conversionAd: estimatedCorrelationPair.conversionAd.confidence,
36675
- arppuAd: estimatedCorrelationPair.arppuAd.confidence
36676
- };
36677
- const result = {
36678
- monetization: {
36679
- cohorts: {
36680
- baseUsers: cohortSize
36681
- },
36682
- retention: {
36683
- d1: clamp012(d1),
36684
- d7: clamp012(Math.min(d1, d7)),
36685
- d30: clamp012(Math.min(d1, d7, d30)),
36686
- d90: clamp012(Math.min(d1, d7, d30, d90)),
36687
- longTailDailyDecay: 0.02
36688
- },
36689
- revenue: {
36690
- payerConversion: clamp012(payerConversion),
36691
- arppuDaily: Math.max(0, arppuDaily),
36692
- adArpDau: Math.max(0, adArpDau),
36693
- platformFeeRate: 0.3,
36694
- grossMarginRate: 0.92
36695
- },
36696
- acquisition: {
36697
- cpi: Math.max(0, cpi)
36698
- },
36699
- uncertainty: {
36700
- enabled: true,
36701
- draws: 300,
36702
- quantiles: [0.5, 0.9],
36703
- sigma: {
36704
- retention: 0.08,
36705
- conversion: 0.12,
36706
- arppu: 0.2,
36707
- ad: 0.15
36708
- },
36709
- correlation: {
36710
- retentionConversion: estimatedCorrelation.retentionConversion,
36711
- retentionArppu: estimatedCorrelation.retentionArppu,
36712
- retentionAd: estimatedCorrelation.retentionAd,
36713
- conversionArppu: estimatedCorrelation.conversionArppu,
36714
- conversionAd: estimatedCorrelation.conversionAd,
36715
- arppuAd: estimatedCorrelation.arppuAd
36716
- }
36717
- }
36718
- },
36719
- diagnostics: {
36720
- users: cohortSize,
36721
- totalRows: rows.length,
36722
- totalIap,
36723
- totalAd,
36724
- activeUserDays,
36725
- payerUsers,
36726
- payerDays,
36727
- retention: {
36728
- d1,
36729
- d7,
36730
- d30,
36731
- d90
36732
- },
36733
- userFeatureVariance: {
36734
- retention: variance(userRetention),
36735
- conversion: variance(userConversion),
36736
- arppu: variance(userArppu),
36737
- adArpDau: variance(userAdArpDau)
36738
- },
36739
- estimatedCorrelation,
36740
- estimatedCorrelationRaw,
36741
- correlationConfidence,
36742
- correlationDiagnostics: estimatedCorrelationPair
36714
+ let artifactRaw;
36715
+ let rubricRaw;
36716
+ try {
36717
+ artifactRaw = await readJsonFile(analysisPath);
36718
+ rubricRaw = await readJsonFile(flags.rubric);
36719
+ } catch (error) {
36720
+ throw usageError(`Invalid judge input: ${error instanceof Error ? error.message : String(error)}`);
36721
+ }
36722
+ const parsed = readJudgeInputs(artifactRaw, rubricRaw);
36723
+ if (!parsed.ok) {
36724
+ throw usageError(`Invalid judge input: ${parsed.issues.join("; ")}`);
36725
+ }
36726
+ let report;
36727
+ try {
36728
+ report = evaluateRules(parsed);
36729
+ } catch (error) {
36730
+ const message = error instanceof Error ? error.message : String(error);
36731
+ throw usageError(`Invalid judge input: ${message}`);
36732
+ }
36733
+ if (flags.provider === "rules") {
36734
+ await writeOutput({
36735
+ format: flags.format,
36736
+ outPath: flags.out,
36737
+ data: flags.format === "md" ? renderJudgeMarkdown(report) : flags.format === "csv" ? judgeCsvRows(report) : report
36738
+ });
36739
+ return report;
36740
+ }
36741
+ await Promise.resolve().then(() => init_jev());
36742
+ const key = (hooks?.env ?? process.env).TYPESAFE_API_KEY?.trim() ?? "";
36743
+ if (!isSafeJevReport(report, key)) {
36744
+ throw usageError("Unsafe Jev numerical report metadata. Remove secrets and local paths from the judge inputs.");
36745
+ }
36746
+ const advisory = await adviseWithJev({
36747
+ artifact: parsed.artifact,
36748
+ rubric: parsed.rubric,
36749
+ numerical: report,
36750
+ requestedModel,
36751
+ dryRun: flags["dry-run"] === true,
36752
+ cachePath: flags.cache,
36753
+ transport: hooks?.transport,
36754
+ env: hooks?.env,
36755
+ nowMs: hooks?.nowMs,
36756
+ sleep: hooks?.sleep
36757
+ });
36758
+ const bundle = { numericalReport: report, advisory };
36759
+ if (flags.format === "md") {
36760
+ const markdown = [
36761
+ renderJudgeMarkdown(report),
36762
+ `- Jev: ${advisory.status} ${advisory.reason}`,
36763
+ `- Endpoint: ${advisory.endpoint}`,
36764
+ `- Planned requests: ${advisory.plannedRequests}`,
36765
+ "",
36766
+ "## Jev answers",
36767
+ "",
36768
+ ...advisory.answers.map((answer) => {
36769
+ const value = answer.noul ?? answer.choice ?? answer.score ?? "none";
36770
+ const confidence = answer.confidence === null ? "" : `; confidence=${answer.confidence}`;
36771
+ return `- ${answer.id}: ${answer.type}=${value}${confidence}; ${answer.disposition}`;
36772
+ }),
36773
+ ...advisory.answers.length === 0 ? ["None."] : []
36774
+ ].join(`
36775
+ `);
36776
+ if (flags.out) {
36777
+ const outPath = resolve7(flags.out);
36778
+ await writeTextFile(outPath, `${markdown}
36779
+ `);
36780
+ console.log(`Wrote md output to ${outPath}`);
36781
+ } else {
36782
+ await writeStdout(`${markdown}
36783
+ `);
36743
36784
  }
36744
- };
36745
- assertFiniteNumbers(result, "calibration");
36746
- return result;
36747
- }
36748
- // src/lib/ltvProjection.ts
36749
- function clamp013(v) {
36750
- if (v < 0)
36751
- return 0;
36752
- if (v > 1)
36753
- return 1;
36754
- return v;
36785
+ return bundle;
36786
+ }
36787
+ await writeOutput({
36788
+ format: flags.format,
36789
+ outPath: flags.out,
36790
+ data: bundle,
36791
+ meta: buildOutputMeta({
36792
+ command: "judge",
36793
+ pluginDigest: {},
36794
+ schemaRef: outputSchemaRefForCommand("judge.jev")
36795
+ })
36796
+ });
36797
+ return bundle;
36755
36798
  }
36756
- function deriveMonetizationConfig(scenario) {
36757
- const m = scenario.monetization;
36758
- const q = (m?.uncertainty?.quantiles ?? [0.5, 0.9]).filter((x) => typeof x === "number");
36759
- const quantiles = q.length > 0 ? [...new Set(q)].sort((a, b) => a - b) : [0.5, 0.9];
36760
- return {
36761
- cohorts: {
36762
- baseUsers: m?.cohorts?.baseUsers ?? 1
36763
- },
36764
- retention: {
36765
- d1: m?.retention?.d1 ?? 0.42,
36766
- d7: m?.retention?.d7 ?? 0.2,
36767
- d30: m?.retention?.d30 ?? 0.09,
36768
- d90: m?.retention?.d90 ?? 0.04,
36769
- longTailDailyDecay: m?.retention?.longTailDailyDecay ?? 0.02
36770
- },
36771
- revenue: {
36772
- payerConversion: m?.revenue?.payerConversion ?? 0.03,
36773
- arppuDaily: m?.revenue?.arppuDaily ?? 0.7,
36774
- adArpDau: m?.revenue?.adArpDau ?? 0.02,
36775
- platformFeeRate: m?.revenue?.platformFeeRate ?? 0.3,
36776
- grossMarginRate: m?.revenue?.grossMarginRate ?? 0.92,
36777
- progressionRevenueLift: m?.revenue?.progressionRevenueLift ?? 0.4,
36778
- progressionLogSpan: m?.revenue?.progressionLogSpan ?? 6
36779
- },
36780
- acquisition: {
36781
- cpi: m?.acquisition?.cpi ?? 0
36782
- },
36783
- uncertainty: {
36784
- enabled: m?.uncertainty?.enabled ?? false,
36785
- draws: m?.uncertainty?.draws ?? 300,
36786
- quantiles,
36787
- seed: m?.uncertainty?.seed,
36788
- sigma: {
36789
- retention: m?.uncertainty?.sigma?.retention ?? 0.08,
36790
- conversion: m?.uncertainty?.sigma?.conversion ?? 0.12,
36791
- arppu: m?.uncertainty?.sigma?.arppu ?? 0.2,
36792
- ad: m?.uncertainty?.sigma?.ad ?? 0.15
36793
- },
36794
- correlation: {
36795
- retentionConversion: m?.uncertainty?.correlation?.retentionConversion ?? 0,
36796
- retentionArppu: m?.uncertainty?.correlation?.retentionArppu ?? 0,
36797
- retentionAd: m?.uncertainty?.correlation?.retentionAd ?? 0,
36798
- conversionArppu: m?.uncertainty?.correlation?.conversionArppu ?? 0,
36799
- conversionAd: m?.uncertainty?.correlation?.conversionAd ?? 0,
36800
- arppuAd: m?.uncertainty?.correlation?.arppuAd ?? 0
36801
- }
36799
+ var judge_default = defineCommand({
36800
+ name: "judge",
36801
+ description: "Advise on an analysis artifact with a versioned rules rubric",
36802
+ options: {
36803
+ rubric: option(string2(), { description: "Versioned rubric JSON path" }),
36804
+ provider: option(_enum(["rules", "jev"]).default("rules"), {
36805
+ description: "Advisory provider. rules is local. jev is an opt-in network provider."
36806
+ }),
36807
+ "dry-run": option(exports_coerce.boolean().default(false), {
36808
+ description: "Show the Jev request plan without calling the network"
36809
+ }),
36810
+ model: option(string2().trim().min(1).default("jev-latest"), {
36811
+ description: "Requested Jev model. jev-latest is an alias, not a pinned id."
36812
+ }),
36813
+ cache: option(string2().optional(), { description: "Optional Jev advisory cache path" }),
36814
+ out: option(string2().optional(), { description: "Output path" }),
36815
+ format: option(_enum(["json", "md", "csv"]).default("json"), { description: "Output format" })
36816
+ },
36817
+ async handler({ flags, positional }) {
36818
+ const analysisPath = positional[0];
36819
+ if (!analysisPath) {
36820
+ throw usageError("Usage: idk judge <analysis.json> --rubric <rubric.json> --provider rules");
36802
36821
  }
36803
- };
36804
- }
36805
- function retentionAtDay(ret, day) {
36806
- if (day <= 0)
36807
- return 1;
36808
- if (day <= 1)
36809
- return ret.d1;
36810
- if (day <= 7)
36811
- return ret.d1 + (ret.d7 - ret.d1) * (day - 1) / 6;
36812
- if (day <= 30)
36813
- return ret.d7 + (ret.d30 - ret.d7) * (day - 7) / 23;
36814
- if (day <= 90)
36815
- return ret.d30 + (ret.d90 - ret.d30) * (day - 30) / 60;
36816
- const extra = day - 90;
36817
- return clamp013(ret.d90 * Math.exp(-ret.longTailDailyDecay * extra));
36818
- }
36819
- function progressionFactor(startWorthLog10, endWorthLog10, span) {
36820
- if (!Number.isFinite(startWorthLog10) || !Number.isFinite(endWorthLog10) || !Number.isFinite(span) || span <= 0) {
36821
- return 0;
36822
+ await runJudge(flags, analysisPath);
36822
36823
  }
36823
- return clamp013((endWorthLog10 - startWorthLog10) / span);
36824
- }
36825
- function estimateLtvPerUser(args) {
36826
- const { config, horizonDays } = args;
36827
- const progression = clamp013(args.progression);
36828
- const arppuDaily = config.revenue.arppuDaily * (1 + progression * config.revenue.progressionRevenueLift);
36829
- const adArpDau = config.revenue.adArpDau * (1 + progression * config.revenue.progressionRevenueLift * 0.5);
36830
- const payerConversion = config.revenue.payerConversion;
36831
- const fullDays = Math.floor(Math.max(0, horizonDays));
36832
- const remainder = Math.max(0, horizonDays - fullDays);
36833
- let gross = 0;
36834
- for (let day = 1;day <= fullDays; day++) {
36835
- const active = retentionAtDay(config.retention, day);
36836
- gross += active * (payerConversion * arppuDaily + adArpDau);
36824
+ });
36825
+
36826
+ // src/commands/calibrate.ts
36827
+ init_zod();
36828
+ init_errors3();
36829
+ init_outputMeta();
36830
+ import { extname as extname3, resolve as resolve8 } from "path";
36831
+
36832
+ // src/lib/ltvCalibration.ts
36833
+ function assertFiniteNumbers(value, path) {
36834
+ if (typeof value === "number" && !Number.isFinite(value)) {
36835
+ throw new RangeError(`Calibration requires finite numbers; numeric overflow at ${path}`);
36837
36836
  }
36838
- if (remainder > 0) {
36839
- const active = retentionAtDay(config.retention, fullDays + 1);
36840
- gross += remainder * active * (payerConversion * arppuDaily + adArpDau);
36837
+ if (typeof value === "object" && value !== null) {
36838
+ for (const [key, child] of Object.entries(value))
36839
+ assertFiniteNumbers(child, `${path}.${key}`);
36841
36840
  }
36842
- const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
36843
- const ltv = net - config.acquisition.cpi;
36844
- return {
36845
- cumulativeGrossRevenuePerUser: gross,
36846
- cumulativeNetRevenuePerUser: net,
36847
- cumulativeLtvPerUser: ltv
36848
- };
36849
36841
  }
36850
- // src/lib/ltvUncertainty.ts
36851
36842
  function clamp014(v) {
36852
36843
  if (v < 0)
36853
36844
  return 0;
@@ -36864,168 +36855,258 @@ function clampCorrelation2(v) {
36864
36855
  return 1;
36865
36856
  return v;
36866
36857
  }
36867
- function mulberry323(seed) {
36868
- let a = seed >>> 0;
36869
- return () => {
36870
- a |= 0;
36871
- a = a + 1831565813 | 0;
36872
- let t = Math.imul(a ^ a >>> 15, 1 | a);
36873
- t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
36874
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
36875
- };
36876
- }
36877
- function randNormal(rng) {
36878
- const u1 = Math.max(0.000000000001, rng());
36879
- const u2 = Math.max(0.000000000001, rng());
36880
- return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
36881
- }
36882
- function buildCorrelationMatrix(base) {
36883
- const rc = clampCorrelation2(base.retentionConversion);
36884
- const ra = clampCorrelation2(base.retentionArppu);
36885
- const rd = clampCorrelation2(base.retentionAd);
36886
- const ca = clampCorrelation2(base.conversionArppu);
36887
- const cd = clampCorrelation2(base.conversionAd);
36888
- const ad = clampCorrelation2(base.arppuAd);
36889
- return [
36890
- [1, rc, ra, rd],
36891
- [rc, 1, ca, cd],
36892
- [ra, ca, 1, ad],
36893
- [rd, cd, ad, 1]
36894
- ];
36895
- }
36896
- function withOffDiagonalScale(matrix, alpha) {
36897
- return matrix.map((row, i) => row.map((v, j) => {
36898
- if (i === j)
36899
- return 1;
36900
- return v * alpha;
36901
- }));
36858
+ function mean(values) {
36859
+ if (values.length === 0)
36860
+ return 0;
36861
+ return values.reduce((a, b) => a + b, 0) / values.length;
36902
36862
  }
36903
- function cholesky4(matrix) {
36904
- const n = 4;
36905
- const l = Array.from({ length: n }, () => Array.from({ length: n }, () => 0));
36906
- for (let i = 0;i < n; i++) {
36907
- for (let j = 0;j <= i; j++) {
36908
- let sum = 0;
36909
- for (let k = 0;k < j; k++) {
36910
- sum += (l[i]?.[k] ?? 0) * (l[j]?.[k] ?? 0);
36911
- }
36912
- if (i === j) {
36913
- const val = (matrix[i]?.[i] ?? 0) - sum;
36914
- if (val <= 0.000000001)
36915
- return null;
36916
- l[i][j] = Math.sqrt(val);
36917
- } else {
36918
- const denom = l[j]?.[j] ?? 0;
36919
- if (Math.abs(denom) < 0.000000001)
36920
- return null;
36921
- l[i][j] = ((matrix[i]?.[j] ?? 0) - sum) / denom;
36922
- }
36923
- }
36863
+ function variance(values) {
36864
+ if (values.length === 0)
36865
+ return 0;
36866
+ const m = mean(values);
36867
+ let acc = 0;
36868
+ for (const v of values) {
36869
+ const d = v - m;
36870
+ acc += d * d;
36924
36871
  }
36925
- return l;
36872
+ return acc / values.length;
36926
36873
  }
36927
- function multiplyLowerTriangular(l, z) {
36928
- const out = [0, 0, 0, 0];
36929
- for (let i = 0;i < 4; i++) {
36930
- let sum = 0;
36931
- for (let j = 0;j <= i; j++) {
36932
- sum += (l[i]?.[j] ?? 0) * (z[j] ?? 0);
36933
- }
36934
- out[i] = sum;
36874
+ function estimateCorrelation(x, y) {
36875
+ const n = Math.min(x.length, y.length);
36876
+ if (n < 3) {
36877
+ return {
36878
+ raw: 0,
36879
+ value: 0,
36880
+ confidence: 0,
36881
+ shrinkage: 0,
36882
+ sampleSize: n,
36883
+ varianceX: 0,
36884
+ varianceY: 0
36885
+ };
36935
36886
  }
36936
- return out;
36937
- }
36938
- function sampleCorrelatedNormals(rng, corr) {
36939
- const z = [randNormal(rng), randNormal(rng), randNormal(rng), randNormal(rng)];
36940
- const matrix = buildCorrelationMatrix(corr);
36941
- let l = cholesky4(matrix);
36942
- let alpha = 0.99;
36943
- while (!l && alpha > 0.000001) {
36944
- l = cholesky4(withOffDiagonalScale(matrix, alpha));
36945
- alpha *= 0.8;
36887
+ const sx = x.slice(0, n);
36888
+ const sy = y.slice(0, n);
36889
+ const mx = mean(sx);
36890
+ const my = mean(sy);
36891
+ let cov = 0;
36892
+ let vx = 0;
36893
+ let vy = 0;
36894
+ for (let i = 0;i < n; i++) {
36895
+ const dx = (sx[i] ?? 0) - mx;
36896
+ const dy = (sy[i] ?? 0) - my;
36897
+ cov += dx * dy;
36898
+ vx += dx * dx;
36899
+ vy += dy * dy;
36946
36900
  }
36947
- if (!l) {
36901
+ const varianceX = vx / n;
36902
+ const varianceY = vy / n;
36903
+ if (vx <= 0.000000000001 || vy <= 0.000000000001) {
36948
36904
  return {
36949
- retention: z[0] ?? 0,
36950
- conversion: z[1] ?? 0,
36951
- arppu: z[2] ?? 0,
36952
- ad: z[3] ?? 0
36905
+ raw: 0,
36906
+ value: 0,
36907
+ confidence: 0,
36908
+ shrinkage: 0,
36909
+ sampleSize: n,
36910
+ varianceX,
36911
+ varianceY
36953
36912
  };
36954
36913
  }
36955
- const c = multiplyLowerTriangular(l, z);
36914
+ const raw = clampCorrelation2(cov / Math.sqrt(vx) / Math.sqrt(vy));
36915
+ const ci95 = 1.96 / Math.sqrt(Math.max(1, n - 3));
36916
+ const sampleConfidence = clamp014(1 - ci95);
36917
+ const varianceConfidenceX = varianceX / (varianceX + 0.01);
36918
+ const varianceConfidenceY = varianceY / (varianceY + 0.01);
36919
+ const varianceConfidence = Math.sqrt(varianceConfidenceX * varianceConfidenceY);
36920
+ const confidence = clamp014(sampleConfidence * varianceConfidence);
36921
+ const shrinkage = confidence;
36922
+ const value = raw * shrinkage;
36956
36923
  return {
36957
- retention: c[0] ?? 0,
36958
- conversion: c[1] ?? 0,
36959
- arppu: c[2] ?? 0,
36960
- ad: c[3] ?? 0
36924
+ raw,
36925
+ value,
36926
+ confidence,
36927
+ shrinkage,
36928
+ sampleSize: n,
36929
+ varianceX,
36930
+ varianceY
36961
36931
  };
36962
36932
  }
36963
- function applyLogNormalShock(mean, sigma, z) {
36964
- if (sigma <= 0)
36965
- return mean;
36966
- return mean * Math.exp(sigma * z - 0.5 * sigma * sigma);
36967
- }
36968
- function makeUncertainConfig(base, rng) {
36969
- const shock = sampleCorrelatedNormals(rng, base.uncertainty.correlation);
36970
- const d1 = clamp014(applyLogNormalShock(base.retention.d1, base.uncertainty.sigma.retention, shock.retention));
36971
- const d7 = Math.min(d1, clamp014(applyLogNormalShock(base.retention.d7, base.uncertainty.sigma.retention, shock.retention)));
36972
- const d30 = Math.min(d7, clamp014(applyLogNormalShock(base.retention.d30, base.uncertainty.sigma.retention, shock.retention)));
36973
- const d90 = Math.min(d30, clamp014(applyLogNormalShock(base.retention.d90, base.uncertainty.sigma.retention, shock.retention)));
36974
- return {
36975
- ...base,
36976
- retention: {
36977
- ...base.retention,
36978
- d1,
36979
- d7,
36980
- d30,
36981
- d90
36982
- },
36983
- revenue: {
36984
- ...base.revenue,
36985
- payerConversion: clamp014(applyLogNormalShock(base.revenue.payerConversion, base.uncertainty.sigma.conversion, shock.conversion)),
36986
- arppuDaily: Math.max(0, applyLogNormalShock(base.revenue.arppuDaily, base.uncertainty.sigma.arppu, shock.arppu)),
36987
- adArpDau: Math.max(0, applyLogNormalShock(base.revenue.adArpDau, base.uncertainty.sigma.ad, shock.ad))
36933
+ function calibrateMonetization(rows) {
36934
+ if (rows.length === 0) {
36935
+ throw new Error("calibration rows cannot be empty");
36936
+ }
36937
+ const users = new Map;
36938
+ for (const row of rows) {
36939
+ const day = Math.max(0, Math.floor(row.day));
36940
+ const u = users.get(row.userId) ?? { days: new Set, iap: 0, ad: 0, acq: undefined };
36941
+ if (row.active ?? true)
36942
+ u.days.add(day);
36943
+ u.iap += Math.max(0, row.iapRevenue);
36944
+ u.ad += Math.max(0, row.adRevenue);
36945
+ if (row.acquisitionCost !== undefined && Number.isFinite(row.acquisitionCost)) {
36946
+ u.acq = (u.acq ?? 0) + Math.max(0, row.acquisitionCost);
36988
36947
  }
36948
+ users.set(row.userId, u);
36949
+ }
36950
+ const cohortSize = Math.max(1, users.size);
36951
+ const dayActiveCount = (targetDay) => {
36952
+ let count = 0;
36953
+ for (const user of users.values())
36954
+ if (user.days.has(targetDay))
36955
+ count += 1;
36956
+ return count;
36989
36957
  };
36990
- }
36991
- function quantile2(values, q) {
36992
- if (values.length === 0)
36993
- return 0;
36994
- const sorted = [...values].sort((a, b) => a - b);
36995
- const idx = Math.max(0, Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * q)));
36996
- return sorted[idx] ?? 0;
36997
- }
36998
- function estimateLtvDistribution(args) {
36999
- const rng = mulberry323(args.seed);
37000
- const grossSamples = [];
37001
- const netSamples = [];
37002
- const ltvSamples = [];
37003
- const draws = Math.max(1, args.draws);
37004
- for (let i = 0;i < draws; i++) {
37005
- const sampled = makeUncertainConfig(args.config, rng);
37006
- const point = estimateLtvPerUser({
37007
- config: sampled,
37008
- horizonDays: args.horizonDays,
37009
- progression: args.progression
37010
- });
37011
- grossSamples.push(point.cumulativeGrossRevenuePerUser);
37012
- netSamples.push(point.cumulativeNetRevenuePerUser);
37013
- ltvSamples.push(point.cumulativeLtvPerUser);
36958
+ const d1 = dayActiveCount(1) / cohortSize;
36959
+ const d7 = dayActiveCount(7) / cohortSize;
36960
+ const d30 = dayActiveCount(30) / cohortSize;
36961
+ const d90 = dayActiveCount(90) / cohortSize;
36962
+ let totalIap = 0;
36963
+ let totalAd = 0;
36964
+ let payerUsers = 0;
36965
+ let activeUserDays = 0;
36966
+ let payerDays = 0;
36967
+ let totalAcq = 0;
36968
+ let acqUsers = 0;
36969
+ for (const user of users.values()) {
36970
+ totalIap += user.iap;
36971
+ totalAd += user.ad;
36972
+ if (user.iap > 0)
36973
+ payerUsers += 1;
36974
+ activeUserDays += user.days.size;
36975
+ for (const day of user.days) {
36976
+ if (day >= 0 && user.iap > 0)
36977
+ payerDays += 1;
36978
+ }
36979
+ if (user.acq !== undefined) {
36980
+ totalAcq += user.acq;
36981
+ acqUsers += 1;
36982
+ }
37014
36983
  }
37015
- const avg = (arr) => arr.reduce((a, b) => a + b, 0) / Math.max(1, arr.length);
37016
- const byQuantile = {};
37017
- for (const q of args.quantiles) {
37018
- byQuantile[`q${Math.round(q * 100)}`] = quantile2(ltvSamples, q);
36984
+ const payerConversion = payerUsers / cohortSize;
36985
+ const arppuDaily = totalIap / Math.max(1, payerDays);
36986
+ const adArpDau = totalAd / Math.max(1, activeUserDays);
36987
+ const cpi = totalAcq / Math.max(1, acqUsers);
36988
+ const userRetention = [];
36989
+ const userConversion = [];
36990
+ const userArppu = [];
36991
+ const userAdArpDau = [];
36992
+ for (const user of users.values()) {
36993
+ const activeDays = Math.max(1, user.days.size);
36994
+ const retentionScore = clamp014(activeDays / 30);
36995
+ const conversionScore = user.iap > 0 ? 1 : 0;
36996
+ const arppuScore = user.iap > 0 ? user.iap / activeDays : 0;
36997
+ const adScore = user.ad / activeDays;
36998
+ userRetention.push(retentionScore);
36999
+ userConversion.push(conversionScore);
37000
+ userArppu.push(arppuScore);
37001
+ userAdArpDau.push(adScore);
37019
37002
  }
37020
- return {
37021
- mean: {
37022
- cumulativeGrossRevenuePerUser: avg(grossSamples),
37023
- cumulativeNetRevenuePerUser: avg(netSamples),
37024
- cumulativeLtvPerUser: avg(ltvSamples)
37003
+ const estimatedCorrelationPair = {
37004
+ retentionConversion: estimateCorrelation(userRetention, userConversion),
37005
+ retentionArppu: estimateCorrelation(userRetention, userArppu),
37006
+ retentionAd: estimateCorrelation(userRetention, userAdArpDau),
37007
+ conversionArppu: estimateCorrelation(userConversion, userArppu),
37008
+ conversionAd: estimateCorrelation(userConversion, userAdArpDau),
37009
+ arppuAd: estimateCorrelation(userArppu, userAdArpDau)
37010
+ };
37011
+ const estimatedCorrelation = {
37012
+ retentionConversion: estimatedCorrelationPair.retentionConversion.value,
37013
+ retentionArppu: estimatedCorrelationPair.retentionArppu.value,
37014
+ retentionAd: estimatedCorrelationPair.retentionAd.value,
37015
+ conversionArppu: estimatedCorrelationPair.conversionArppu.value,
37016
+ conversionAd: estimatedCorrelationPair.conversionAd.value,
37017
+ arppuAd: estimatedCorrelationPair.arppuAd.value
37018
+ };
37019
+ const estimatedCorrelationRaw = {
37020
+ retentionConversion: estimatedCorrelationPair.retentionConversion.raw,
37021
+ retentionArppu: estimatedCorrelationPair.retentionArppu.raw,
37022
+ retentionAd: estimatedCorrelationPair.retentionAd.raw,
37023
+ conversionArppu: estimatedCorrelationPair.conversionArppu.raw,
37024
+ conversionAd: estimatedCorrelationPair.conversionAd.raw,
37025
+ arppuAd: estimatedCorrelationPair.arppuAd.raw
37026
+ };
37027
+ const correlationConfidence = {
37028
+ retentionConversion: estimatedCorrelationPair.retentionConversion.confidence,
37029
+ retentionArppu: estimatedCorrelationPair.retentionArppu.confidence,
37030
+ retentionAd: estimatedCorrelationPair.retentionAd.confidence,
37031
+ conversionArppu: estimatedCorrelationPair.conversionArppu.confidence,
37032
+ conversionAd: estimatedCorrelationPair.conversionAd.confidence,
37033
+ arppuAd: estimatedCorrelationPair.arppuAd.confidence
37034
+ };
37035
+ const result = {
37036
+ monetization: {
37037
+ cohorts: {
37038
+ baseUsers: cohortSize
37039
+ },
37040
+ retention: {
37041
+ d1: clamp014(d1),
37042
+ d7: clamp014(Math.min(d1, d7)),
37043
+ d30: clamp014(Math.min(d1, d7, d30)),
37044
+ d90: clamp014(Math.min(d1, d7, d30, d90)),
37045
+ longTailDailyDecay: 0.02
37046
+ },
37047
+ revenue: {
37048
+ payerConversion: clamp014(payerConversion),
37049
+ arppuDaily: Math.max(0, arppuDaily),
37050
+ adArpDau: Math.max(0, adArpDau),
37051
+ platformFeeRate: 0.3,
37052
+ grossMarginRate: 0.92
37053
+ },
37054
+ acquisition: {
37055
+ cpi: Math.max(0, cpi)
37056
+ },
37057
+ uncertainty: {
37058
+ enabled: true,
37059
+ draws: 300,
37060
+ quantiles: [0.5, 0.9],
37061
+ sigma: {
37062
+ retention: 0.08,
37063
+ conversion: 0.12,
37064
+ arppu: 0.2,
37065
+ ad: 0.15
37066
+ },
37067
+ correlation: {
37068
+ retentionConversion: estimatedCorrelation.retentionConversion,
37069
+ retentionArppu: estimatedCorrelation.retentionArppu,
37070
+ retentionAd: estimatedCorrelation.retentionAd,
37071
+ conversionArppu: estimatedCorrelation.conversionArppu,
37072
+ conversionAd: estimatedCorrelation.conversionAd,
37073
+ arppuAd: estimatedCorrelation.arppuAd
37074
+ }
37075
+ }
37025
37076
  },
37026
- quantiles: byQuantile
37077
+ diagnostics: {
37078
+ users: cohortSize,
37079
+ totalRows: rows.length,
37080
+ totalIap,
37081
+ totalAd,
37082
+ activeUserDays,
37083
+ payerUsers,
37084
+ payerDays,
37085
+ retention: {
37086
+ d1,
37087
+ d7,
37088
+ d30,
37089
+ d90
37090
+ },
37091
+ userFeatureVariance: {
37092
+ retention: variance(userRetention),
37093
+ conversion: variance(userConversion),
37094
+ arppu: variance(userArppu),
37095
+ adArpDau: variance(userAdArpDau)
37096
+ },
37097
+ estimatedCorrelation,
37098
+ estimatedCorrelationRaw,
37099
+ correlationConfidence,
37100
+ correlationDiagnostics: estimatedCorrelationPair
37101
+ }
37027
37102
  };
37103
+ assertFiniteNumbers(result, "calibration");
37104
+ return result;
37028
37105
  }
37106
+ // src/lib/ltvProjection.ts
37107
+ init_dist2();
37108
+ // src/lib/ltvUncertainty.ts
37109
+ init_dist2();
37029
37110
  // src/lib/cohortObservation.ts
37030
37111
  var RETENTION_DAYS = [1, 7, 30, 90];
37031
37112
  function observeCohort(rows, observedThroughDay) {
@@ -40329,7 +40410,7 @@ function parseHorizons(raw) {
40329
40410
  }
40330
40411
  var DEFAULT_LTV_HORIZONS = "30m,2h,24h,7d,30d,90d";
40331
40412
  function ltvUncertaintyDraws(scenario, draws) {
40332
- const uncertainty = deriveMonetizationConfig(scenario).uncertainty;
40413
+ const uncertainty = deriveMonetizationConfig2(scenario).uncertainty;
40333
40414
  return uncertainty.enabled || draws !== undefined ? draws ?? uncertainty.draws : null;
40334
40415
  }
40335
40416
  function ltvStageInputs(scenario, horizons, draws) {
@@ -40391,7 +40472,7 @@ function runLtvAnalysis(args) {
40391
40472
  const horizons = parseHorizons(args.horizonsRaw);
40392
40473
  const stepSec = args.step ?? args.compiled.run.stepSec;
40393
40474
  const runFast = resolveFastMode(args.fast, args.compiled.run.fast);
40394
- const monetizationConfig = deriveMonetizationConfig(args.scenario);
40475
+ const monetizationConfig = deriveMonetizationConfig2(args.scenario);
40395
40476
  const uncertainEnabled = monetizationConfig.uncertainty.enabled || args.draws !== undefined;
40396
40477
  const draws = args.draws ?? monetizationConfig.uncertainty.draws;
40397
40478
  let state = args.compiled.initial;
@@ -40451,12 +40532,12 @@ function runLtvAnalysis(args) {
40451
40532
  const endWorthLog10 = args.compiled.ctx.E.absLog10(worth.amount);
40452
40533
  const progression = progressionFactor(startWorthLog10, endWorthLog10, monetizationConfig.revenue.progressionLogSpan);
40453
40534
  const horizonDays = h.seconds / 86400;
40454
- const point = estimateLtvPerUser({
40535
+ const point = estimateLtvPerUser2({
40455
40536
  config: monetizationConfig,
40456
40537
  horizonDays,
40457
40538
  progression
40458
40539
  });
40459
- const distribution = uncertainEnabled && draws > 1 ? estimateLtvDistribution({
40540
+ const distribution = uncertainEnabled && draws > 1 ? estimateLtvDistribution2({
40460
40541
  config: monetizationConfig,
40461
40542
  horizonDays,
40462
40543
  progression,
@@ -44105,4 +44186,4 @@ export {
44105
44186
  cli2 as cli
44106
44187
  };
44107
44188
 
44108
- //# debugId=4AAADDD1336AF66164756E2164756E21
44189
+ //# debugId=A3CAAFDF1955C7F064756E2164756E21