@idlekit/cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/main.js +1768 -1425
- package/dist/main.js.map +26 -18
- package/dist/runtime/workers/entry.js +655 -25
- package/dist/runtime/workers/entry.js.map +15 -8
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -11605,6 +11605,63 @@ function logicalDrawId(drawIndex) {
|
|
|
11605
11605
|
return String(drawIndex);
|
|
11606
11606
|
}
|
|
11607
11607
|
|
|
11608
|
+
// ../core/dist/sim/sessionOffline.js
|
|
11609
|
+
function validateCreditPolicy(policy) {
|
|
11610
|
+
if (!policy || typeof policy !== "object" || Array.isArray(policy) || "actions" in policy) {
|
|
11611
|
+
throw new RangeError("prepareOffline policy must be a credit policy; set actions on scenario.run.offline");
|
|
11612
|
+
}
|
|
11613
|
+
if (policy.maxSec !== undefined && (typeof policy.maxSec !== "number" || !Number.isFinite(policy.maxSec) || policy.maxSec < 0)) {
|
|
11614
|
+
throw new RangeError("prepareOffline policy.maxSec must be finite and nonnegative");
|
|
11615
|
+
}
|
|
11616
|
+
if (policy.overflowPolicy !== undefined && policy.overflowPolicy !== "clamp" && policy.overflowPolicy !== "reject") {
|
|
11617
|
+
throw new RangeError("prepareOffline policy.overflowPolicy must be clamp or reject");
|
|
11618
|
+
}
|
|
11619
|
+
const decay = policy.decay;
|
|
11620
|
+
if (decay !== undefined) {
|
|
11621
|
+
if (!decay || typeof decay !== "object" || Array.isArray(decay) || decay.kind !== "none" && decay.kind !== "linear") {
|
|
11622
|
+
throw new RangeError("prepareOffline policy.decay must select none or linear");
|
|
11623
|
+
}
|
|
11624
|
+
if (decay.floorRatio !== undefined && (typeof decay.floorRatio !== "number" || !Number.isFinite(decay.floorRatio) || decay.floorRatio < 0 || decay.floorRatio > 1)) {
|
|
11625
|
+
throw new RangeError("prepareOffline policy.decay.floorRatio must be finite and in 0..1");
|
|
11626
|
+
}
|
|
11627
|
+
}
|
|
11628
|
+
}
|
|
11629
|
+
function prepareOfflineSegment(args) {
|
|
11630
|
+
const { scenario, prepare } = args;
|
|
11631
|
+
if (!prepare)
|
|
11632
|
+
return { model: scenario.model, policy: scenario.run.offline, stepSec: scenario.run.stepSec };
|
|
11633
|
+
const prepared = prepare({
|
|
11634
|
+
ctx: scenario.ctx,
|
|
11635
|
+
model: scenario.model,
|
|
11636
|
+
state: deepClonePreservingPrototype(args.state),
|
|
11637
|
+
seconds: args.seconds,
|
|
11638
|
+
wallStartT: args.wallStartT,
|
|
11639
|
+
wallEndT: args.wallEndT,
|
|
11640
|
+
...scenario.run.offline ? { policy: deepClonePreservingPrototype(scenario.run.offline) } : {}
|
|
11641
|
+
});
|
|
11642
|
+
if (!prepared || typeof prepared !== "object" || Array.isArray(prepared) || "then" in prepared) {
|
|
11643
|
+
throw new RangeError("prepareOffline must synchronously return a preparation object");
|
|
11644
|
+
}
|
|
11645
|
+
if (prepared.stepSec !== undefined && (typeof prepared.stepSec !== "number" || !Number.isFinite(prepared.stepSec) || prepared.stepSec <= 0)) {
|
|
11646
|
+
throw new RangeError("prepareOffline stepSec must be finite and positive");
|
|
11647
|
+
}
|
|
11648
|
+
if (prepared.policy !== undefined)
|
|
11649
|
+
validateCreditPolicy(prepared.policy);
|
|
11650
|
+
const model = prepared.model === undefined ? scenario.model : prepared.model;
|
|
11651
|
+
if (!model || typeof model !== "object" || typeof model.income !== "function" || typeof model.actions !== "function") {
|
|
11652
|
+
throw new RangeError("prepareOffline model must provide income and actions");
|
|
11653
|
+
}
|
|
11654
|
+
return {
|
|
11655
|
+
model,
|
|
11656
|
+
stepSec: prepared.stepSec ?? scenario.run.stepSec,
|
|
11657
|
+
policy: prepared.policy === undefined ? scenario.run.offline : {
|
|
11658
|
+
...deepClonePreservingPrototype(prepared.policy),
|
|
11659
|
+
actions: scenario.run.offline?.actions
|
|
11660
|
+
}
|
|
11661
|
+
};
|
|
11662
|
+
}
|
|
11663
|
+
var init_sessionOffline = () => {};
|
|
11664
|
+
|
|
11608
11665
|
// ../core/dist/sim/session.js
|
|
11609
11666
|
function buildBlocks(pattern) {
|
|
11610
11667
|
const blocks = [];
|
|
@@ -11700,6 +11757,9 @@ function withClocks(scenario, wallT, wallEndT, rewardT, activeT) {
|
|
|
11700
11757
|
};
|
|
11701
11758
|
}
|
|
11702
11759
|
function simulateSessionPattern(args) {
|
|
11760
|
+
if (args.prepareOffline !== undefined && typeof args.prepareOffline !== "function") {
|
|
11761
|
+
throw new TypeError("prepareOffline must be a function");
|
|
11762
|
+
}
|
|
11703
11763
|
const sc = args.seed === undefined ? args.scenario : { ...args.scenario, ctx: { ...args.scenario.ctx, seed: args.seed } };
|
|
11704
11764
|
const start = sc.initial;
|
|
11705
11765
|
const horizonSec = args.pattern.days * 86400;
|
|
@@ -11786,7 +11846,19 @@ function simulateSessionPattern(args) {
|
|
|
11786
11846
|
return;
|
|
11787
11847
|
const requested = wallEnd - wallT;
|
|
11788
11848
|
const wallStart = wallT;
|
|
11789
|
-
|
|
11849
|
+
let segment = segmentScenario(wallStart, wallEnd);
|
|
11850
|
+
const prepared = prepareOfflineSegment({
|
|
11851
|
+
scenario: segment,
|
|
11852
|
+
state,
|
|
11853
|
+
seconds: requested,
|
|
11854
|
+
wallStartT: wallStart,
|
|
11855
|
+
wallEndT: wallEnd,
|
|
11856
|
+
prepare: args.prepareOffline
|
|
11857
|
+
});
|
|
11858
|
+
if (prepared.model !== segment.model) {
|
|
11859
|
+
const { clocks: _clocks, ...ctx } = segment.ctx;
|
|
11860
|
+
segment = withClocks({ ...segment, ctx, model: prepared.model }, wallStart, wallEnd, state.t, activeSec);
|
|
11861
|
+
}
|
|
11790
11862
|
const offlineRun = applyOfflineSeconds({
|
|
11791
11863
|
scenario: segment,
|
|
11792
11864
|
seconds: requested,
|
|
@@ -11798,15 +11870,16 @@ function simulateSessionPattern(args) {
|
|
|
11798
11870
|
enabled: sc.run.eventLog?.enabled ?? true,
|
|
11799
11871
|
maxEvents: sc.run.eventLog?.maxEvents
|
|
11800
11872
|
},
|
|
11801
|
-
|
|
11873
|
+
stepSec: prepared.stepSec,
|
|
11874
|
+
policy: prepared.policy,
|
|
11802
11875
|
...stopFn ? { until: stopFn } : {}
|
|
11803
11876
|
}
|
|
11804
11877
|
});
|
|
11805
11878
|
const credited = offlineRun.offline.simulatedSec;
|
|
11806
11879
|
const reason = classify();
|
|
11807
|
-
const absence = reason ? offlineAbsenceForCredit(credited, requested,
|
|
11880
|
+
const absence = reason ? offlineAbsenceForCredit(credited, requested, prepared.policy) : requested;
|
|
11808
11881
|
const gapEnd = wallStart + absence;
|
|
11809
|
-
const effective = absence === requested ? offlineRun.offline.effectiveSec : resolveOfflineSeconds(absence,
|
|
11882
|
+
const effective = absence === requested ? offlineRun.offline.effectiveSec : resolveOfflineSeconds(absence, prepared.policy).effectiveSec;
|
|
11810
11883
|
const lost = Math.max(0, absence - effective);
|
|
11811
11884
|
offlineElapsedSec += absence;
|
|
11812
11885
|
offlineCreditedSec += credited;
|
|
@@ -11977,6 +12050,7 @@ var init_session = __esm(() => {
|
|
|
11977
12050
|
init_goalRead();
|
|
11978
12051
|
init_observation();
|
|
11979
12052
|
init_offline();
|
|
12053
|
+
init_sessionOffline();
|
|
11980
12054
|
init_simulator();
|
|
11981
12055
|
});
|
|
11982
12056
|
|
|
@@ -12972,11 +13046,19 @@ var init_params2 = __esm(() => {
|
|
|
12972
13046
|
}));
|
|
12973
13047
|
});
|
|
12974
13048
|
|
|
12975
|
-
// ../core/dist/sim/strategy/opt/
|
|
12976
|
-
function
|
|
12977
|
-
|
|
12978
|
-
|
|
13049
|
+
// ../core/dist/sim/strategy/opt/finiteAmountLog10.js
|
|
13050
|
+
function finiteAmountLog10(E, amount) {
|
|
13051
|
+
if (!E.isFinite(amount))
|
|
13052
|
+
throw new RangeError("Tuning requires finite monetary amounts in the selected engine");
|
|
13053
|
+
const value = E.absLog10(amount);
|
|
13054
|
+
if (value === Number.NEGATIVE_INFINITY)
|
|
13055
|
+
return -300;
|
|
13056
|
+
if (!Number.isFinite(value))
|
|
13057
|
+
throw new RangeError("Tuning requires a finite monetary logarithm");
|
|
13058
|
+
return value;
|
|
12979
13059
|
}
|
|
13060
|
+
|
|
13061
|
+
// ../core/dist/sim/strategy/opt/objectives/builtins.js
|
|
12980
13062
|
var builtinObjectiveFactories;
|
|
12981
13063
|
var init_builtins2 = __esm(() => {
|
|
12982
13064
|
init_parseMoney2();
|
|
@@ -12990,7 +13072,7 @@ var init_builtins2 = __esm(() => {
|
|
|
12990
13072
|
id: "endMoneyLog10",
|
|
12991
13073
|
score: ({ scenario, run }) => {
|
|
12992
13074
|
const { E } = scenario.ctx;
|
|
12993
|
-
return
|
|
13075
|
+
return finiteAmountLog10(E, run.end.wallet.money.amount);
|
|
12994
13076
|
}
|
|
12995
13077
|
})
|
|
12996
13078
|
},
|
|
@@ -13003,7 +13085,7 @@ var init_builtins2 = __esm(() => {
|
|
|
13003
13085
|
score: ({ scenario, run }) => {
|
|
13004
13086
|
const { E } = scenario.ctx;
|
|
13005
13087
|
const worth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
|
|
13006
|
-
return
|
|
13088
|
+
return finiteAmountLog10(E, worth.amount);
|
|
13007
13089
|
}
|
|
13008
13090
|
})
|
|
13009
13091
|
},
|
|
@@ -13018,7 +13100,7 @@ var init_builtins2 = __esm(() => {
|
|
|
13018
13100
|
const sec = Math.max(1, runElapsedSec(run));
|
|
13019
13101
|
const hours = sec / 3600;
|
|
13020
13102
|
const worth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
|
|
13021
|
-
return
|
|
13103
|
+
return finiteAmountLog10(E, worth.amount) - Math.log10(Math.max(0.000000000001, hours));
|
|
13022
13104
|
}
|
|
13023
13105
|
})
|
|
13024
13106
|
},
|
|
@@ -13032,7 +13114,7 @@ var init_builtins2 = __esm(() => {
|
|
|
13032
13114
|
const { E } = scenario.ctx;
|
|
13033
13115
|
const sec = Math.max(1, runElapsedSec(run));
|
|
13034
13116
|
const hours = sec / 3600;
|
|
13035
|
-
return
|
|
13117
|
+
return finiteAmountLog10(E, run.end.prestige.points) - Math.log10(Math.max(0.000000000001, hours));
|
|
13036
13118
|
}
|
|
13037
13119
|
})
|
|
13038
13120
|
},
|
|
@@ -13047,7 +13129,7 @@ var init_builtins2 = __esm(() => {
|
|
|
13047
13129
|
const sec = Math.max(1, runElapsedSec(run));
|
|
13048
13130
|
const startWorth = scenario.model.netWorth?.(scenario.ctx, run.start) ?? run.start.wallet.money;
|
|
13049
13131
|
const endWorth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
|
|
13050
|
-
const deltaLog =
|
|
13132
|
+
const deltaLog = finiteAmountLog10(E, endWorth.amount) - finiteAmountLog10(E, startWorth.amount);
|
|
13051
13133
|
return deltaLog / (sec / 3600);
|
|
13052
13134
|
}
|
|
13053
13135
|
})
|
|
@@ -13073,6 +13155,9 @@ var init_builtins2 = __esm(() => {
|
|
|
13073
13155
|
suffix: { kind: "alphaInfinite", minLen: 2 }
|
|
13074
13156
|
}).amount;
|
|
13075
13157
|
const endWorth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
|
|
13158
|
+
if (!E.isFinite(threshold) || !E.isFinite(endWorth.amount)) {
|
|
13159
|
+
throw new RangeError("ETA objective requires finite target and net worth in the selected engine");
|
|
13160
|
+
}
|
|
13076
13161
|
const reached = E.cmp(endWorth.amount, threshold) >= 0;
|
|
13077
13162
|
if (!reached)
|
|
13078
13163
|
return -(params.unreachedPenaltySec ?? 1e9);
|
|
@@ -13102,7 +13187,7 @@ var init_builtins2 = __esm(() => {
|
|
|
13102
13187
|
const sec = Math.max(1, runElapsedSec(run));
|
|
13103
13188
|
const hours = sec / 3600;
|
|
13104
13189
|
const worth = scenario.model.netWorth?.(scenario.ctx, run.end) ?? run.end.wallet.money;
|
|
13105
|
-
const worthLog =
|
|
13190
|
+
const worthLog = finiteAmountLog10(E, worth.amount);
|
|
13106
13191
|
const stats = run.stats;
|
|
13107
13192
|
if (!stats || stats.money.droppedRate === null || stats.actions.status === "missing") {
|
|
13108
13193
|
throw new Error("pacingBalancedLog10 needs observed money and action counters, but this run has none (run.observation.enabled is false)");
|
|
@@ -13301,8 +13386,45 @@ function cloneCandidateInitial(state) {
|
|
|
13301
13386
|
}
|
|
13302
13387
|
var init_freshRun = () => {};
|
|
13303
13388
|
|
|
13389
|
+
// ../core/dist/sim/strategy/opt/finiteMean.js
|
|
13390
|
+
function finiteMean(values) {
|
|
13391
|
+
if (values.length === 0)
|
|
13392
|
+
throw new RangeError("A mean requires at least one value");
|
|
13393
|
+
const bits = new DataView(new ArrayBuffer(8));
|
|
13394
|
+
let total = 0n;
|
|
13395
|
+
for (const value of values) {
|
|
13396
|
+
if (!Number.isFinite(value))
|
|
13397
|
+
throw new RangeError("A mean requires finite values");
|
|
13398
|
+
bits.setFloat64(0, value, false);
|
|
13399
|
+
const high = bits.getUint32(0, false);
|
|
13400
|
+
const low = bits.getUint32(4, false);
|
|
13401
|
+
const exponent = high >>> 20 & 2047;
|
|
13402
|
+
const fraction = BigInt(high & 1048575) << 32n | BigInt(low);
|
|
13403
|
+
const significand = exponent === 0 ? fraction : 1n << 52n | fraction;
|
|
13404
|
+
const units = significand << BigInt(Math.max(0, exponent - 1));
|
|
13405
|
+
total += high >>> 31 ? -units : units;
|
|
13406
|
+
}
|
|
13407
|
+
if (total === 0n)
|
|
13408
|
+
return 0;
|
|
13409
|
+
const negative = total < 0n;
|
|
13410
|
+
const magnitude = negative ? -total : total;
|
|
13411
|
+
const count = BigInt(values.length);
|
|
13412
|
+
const integerMean = magnitude / count;
|
|
13413
|
+
const shift = Math.max(0, integerMean.toString(2).length - 53);
|
|
13414
|
+
const denominator = count << BigInt(shift);
|
|
13415
|
+
let significand = magnitude / denominator;
|
|
13416
|
+
const twiceRemainder = 2n * (magnitude % denominator);
|
|
13417
|
+
if (twiceRemainder > denominator || twiceRemainder === denominator && (significand & 1n) !== 0n) {
|
|
13418
|
+
significand++;
|
|
13419
|
+
}
|
|
13420
|
+
const mean = Number(significand) * 2 ** (shift - 1074);
|
|
13421
|
+
return negative ? -mean : mean;
|
|
13422
|
+
}
|
|
13423
|
+
|
|
13304
13424
|
// ../core/dist/sim/strategy/opt/runner.js
|
|
13305
13425
|
function runCandidateAndScore(args) {
|
|
13426
|
+
if (args.seeds.length === 0)
|
|
13427
|
+
throw new RangeError("Tuning requires at least one seed");
|
|
13306
13428
|
const stratFactory = args.strategyRegistry.get(args.strategyId);
|
|
13307
13429
|
if (!stratFactory)
|
|
13308
13430
|
throw new Error(`Unknown strategy: ${args.strategyId}`);
|
|
@@ -13349,24 +13471,28 @@ function runCandidateAndScore(args) {
|
|
|
13349
13471
|
const sc = openScenario();
|
|
13350
13472
|
const run = runScenario(sc);
|
|
13351
13473
|
assertHorizonReached(run, "runCandidateAndScore");
|
|
13474
|
+
const worth = sc.model.netWorth?.(sc.ctx, run.end) ?? run.end.wallet.money;
|
|
13475
|
+
const endMoneyLog10 = finiteAmountLog10(sc.ctx.E, run.end.wallet.money.amount);
|
|
13476
|
+
const endNetWorthLog10 = finiteAmountLog10(sc.ctx.E, worth.amount);
|
|
13352
13477
|
const s = objective.score({ scenario: sc, run, evaluation: {
|
|
13353
13478
|
open: openScenario,
|
|
13354
13479
|
registries: { models: args.modelRegistry, strategies: args.strategyRegistry },
|
|
13355
13480
|
isolation: { model: args.model, statefulModel: args.statefulModel, strategy: { id: args.strategyId, params: candidateParams } }
|
|
13356
13481
|
} });
|
|
13482
|
+
if (!Number.isFinite(s))
|
|
13483
|
+
throw new RangeError(`Objective ${args.objectiveId} must return a finite score (seed ${seed})`);
|
|
13357
13484
|
seedScores.push(s);
|
|
13358
|
-
const worth = sc.model.netWorth?.(sc.ctx, run.end) ?? run.end.wallet.money;
|
|
13359
13485
|
seedResults.push({
|
|
13360
13486
|
seed,
|
|
13361
13487
|
score: s,
|
|
13362
13488
|
durationSec: Math.max(0, runElapsedSec(run)),
|
|
13363
|
-
endMoneyLog10
|
|
13364
|
-
endNetWorthLog10
|
|
13489
|
+
endMoneyLog10,
|
|
13490
|
+
endNetWorthLog10,
|
|
13365
13491
|
droppedRate: run.stats?.money.droppedRate ?? null,
|
|
13366
13492
|
actionsApplied: run.stats && run.stats.actions.status === "observed" ? run.stats.actions.applied : null
|
|
13367
13493
|
});
|
|
13368
13494
|
}
|
|
13369
|
-
const score =
|
|
13495
|
+
const score = finiteMean(seedScores);
|
|
13370
13496
|
return { score, seedScores, seedResults };
|
|
13371
13497
|
}
|
|
13372
13498
|
var init_runner = __esm(() => {
|
|
@@ -14353,7 +14479,7 @@ function compensatedSum(values, scale) {
|
|
|
14353
14479
|
}
|
|
14354
14480
|
return sum + compensation;
|
|
14355
14481
|
}
|
|
14356
|
-
function
|
|
14482
|
+
function finiteMean2(values) {
|
|
14357
14483
|
let smallest = Number.POSITIVE_INFINITY;
|
|
14358
14484
|
let largest = Number.NEGATIVE_INFINITY;
|
|
14359
14485
|
for (const value of values) {
|
|
@@ -14503,7 +14629,7 @@ function analyzeDistribution(request) {
|
|
|
14503
14629
|
}
|
|
14504
14630
|
const eligible = observed.length + rightCensored;
|
|
14505
14631
|
const completionRate = eligible > 0 ? observed.length / eligible : null;
|
|
14506
|
-
const arithmetic = observed.length > 0 ?
|
|
14632
|
+
const arithmetic = observed.length > 0 ? finiteMean2(observed) : null;
|
|
14507
14633
|
const identical = observed.every((value) => value === observed[0]);
|
|
14508
14634
|
const rawVariance = arithmetic === null ? null : finiteVariance(observed, arithmetic);
|
|
14509
14635
|
const variance = rawVariance === 0 && !identical ? null : rawVariance;
|
|
@@ -19353,32 +19479,46 @@ function buildTimeline(args) {
|
|
|
19353
19479
|
}
|
|
19354
19480
|
|
|
19355
19481
|
// ../core/dist/report/compare.js
|
|
19356
|
-
function
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
const
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19482
|
+
function decimalParts(input) {
|
|
19483
|
+
const match = /^([+-]?)(?:(\d+)(?:\.(\d+))?|\.(\d+))(?:e([+-]?\d+))?([a-z]{2,})?$/i.exec(input.trim());
|
|
19484
|
+
if (!match)
|
|
19485
|
+
throw new RangeError("Static monetary comparison requires decimal amounts");
|
|
19486
|
+
let suffixExponent = 0n;
|
|
19487
|
+
if (match[6]) {
|
|
19488
|
+
let index = 0n;
|
|
19489
|
+
for (const ch of match[6].toLowerCase())
|
|
19490
|
+
index = index * 26n + BigInt(ch.charCodeAt(0) - 96);
|
|
19491
|
+
suffixExponent = (index - 26n) * 3n;
|
|
19492
|
+
}
|
|
19493
|
+
const fraction = match[3] ?? match[4] ?? "";
|
|
19494
|
+
const digits = `${match[2] ?? ""}${fraction}`.replace(/^0+/, "");
|
|
19495
|
+
let end = digits.length;
|
|
19496
|
+
while (end > 0 && digits[end - 1] === "0")
|
|
19497
|
+
end--;
|
|
19498
|
+
return {
|
|
19499
|
+
sign: digits.length === 0 ? 0 : match[1] === "-" ? -1 : 1,
|
|
19500
|
+
digits: digits.slice(0, end),
|
|
19501
|
+
magnitude: BigInt(match[5] ?? "0") + suffixExponent - BigInt(fraction.length) + BigInt(digits.length)
|
|
19502
|
+
};
|
|
19503
|
+
}
|
|
19504
|
+
function compareStaticMoney(left, right) {
|
|
19505
|
+
const a = decimalParts(left), b = decimalParts(right);
|
|
19506
|
+
if (a.sign !== b.sign)
|
|
19507
|
+
return a.sign < b.sign ? -1 : 1;
|
|
19508
|
+
if (a.sign === 0)
|
|
19509
|
+
return 0;
|
|
19510
|
+
if (a.magnitude !== b.magnitude)
|
|
19511
|
+
return (a.magnitude < b.magnitude ? -1 : 1) * a.sign;
|
|
19512
|
+
const length = Math.max(a.digits.length, b.digits.length);
|
|
19513
|
+
const ad = a.digits.padEnd(length, "0"), bd = b.digits.padEnd(length, "0");
|
|
19514
|
+
return (ad < bd ? -1 : ad > bd ? 1 : 0) * a.sign;
|
|
19375
19515
|
}
|
|
19376
19516
|
function score(s, metric) {
|
|
19377
19517
|
switch (metric) {
|
|
19378
19518
|
case "endMoney":
|
|
19379
|
-
return
|
|
19519
|
+
return s.initial.wallet.amount;
|
|
19380
19520
|
case "endNetWorth":
|
|
19381
|
-
return
|
|
19521
|
+
return s.initial.maxMoneyEver ?? s.initial.wallet.amount;
|
|
19382
19522
|
case "droppedRate":
|
|
19383
19523
|
return -(s.policy.maxLogGap ?? 0);
|
|
19384
19524
|
case "etaToTargetWorth":
|
|
@@ -19417,6 +19557,13 @@ function compareScenarios(args) {
|
|
|
19417
19557
|
const useMeasured = measuredA !== undefined && measuredB !== undefined;
|
|
19418
19558
|
const aScore = useMeasured ? measuredA : score(args.a, args.metric);
|
|
19419
19559
|
const bScore = useMeasured ? measuredB : score(args.b, args.metric);
|
|
19560
|
+
if (!useMeasured && (args.metric === "endMoney" || args.metric === "endNetWorth")) {
|
|
19561
|
+
const order = compareStaticMoney(String(aScore), String(bScore));
|
|
19562
|
+
return {
|
|
19563
|
+
better: order === 0 ? "tie" : order > 0 ? "a" : "b",
|
|
19564
|
+
detail: { aScore, bScore, metric: args.metric, source: "static" }
|
|
19565
|
+
};
|
|
19566
|
+
}
|
|
19420
19567
|
const higherBetter = args.metric === "endMoney" || args.metric === "endNetWorth" || args.metric === "visibleChangesPerMinute";
|
|
19421
19568
|
if (aScore === bScore) {
|
|
19422
19569
|
return {
|
|
@@ -19440,420 +19587,924 @@ function compareScenarios(args) {
|
|
|
19440
19587
|
};
|
|
19441
19588
|
}
|
|
19442
19589
|
|
|
19443
|
-
// ../core/dist/
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
19449
|
-
|
|
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();
|
|
19590
|
+
// ../core/dist/ltv/projection.js
|
|
19591
|
+
function clamp012(v) {
|
|
19592
|
+
if (v < 0)
|
|
19593
|
+
return 0;
|
|
19594
|
+
if (v > 1)
|
|
19595
|
+
return 1;
|
|
19596
|
+
return v;
|
|
19515
19597
|
}
|
|
19516
|
-
|
|
19517
|
-
|
|
19598
|
+
function deriveMonetizationConfig(scenario) {
|
|
19599
|
+
const m = scenario.monetization;
|
|
19600
|
+
const q = (m?.uncertainty?.quantiles ?? [0.5, 0.9]).filter((x) => typeof x === "number");
|
|
19601
|
+
const quantiles = q.length > 0 ? [...new Set(q)].sort((a, b) => a - b) : [0.5, 0.9];
|
|
19602
|
+
return {
|
|
19603
|
+
cohorts: {
|
|
19604
|
+
baseUsers: m?.cohorts?.baseUsers ?? 1
|
|
19605
|
+
},
|
|
19606
|
+
retention: {
|
|
19607
|
+
d1: m?.retention?.d1 ?? 0.42,
|
|
19608
|
+
d7: m?.retention?.d7 ?? 0.2,
|
|
19609
|
+
d30: m?.retention?.d30 ?? 0.09,
|
|
19610
|
+
d90: m?.retention?.d90 ?? 0.04,
|
|
19611
|
+
longTailDailyDecay: m?.retention?.longTailDailyDecay ?? 0.02
|
|
19612
|
+
},
|
|
19613
|
+
revenue: {
|
|
19614
|
+
payerConversion: m?.revenue?.payerConversion ?? 0.03,
|
|
19615
|
+
arppuDaily: m?.revenue?.arppuDaily ?? 0.7,
|
|
19616
|
+
adArpDau: m?.revenue?.adArpDau ?? 0.02,
|
|
19617
|
+
platformFeeRate: m?.revenue?.platformFeeRate ?? 0.3,
|
|
19618
|
+
grossMarginRate: m?.revenue?.grossMarginRate ?? 0.92,
|
|
19619
|
+
progressionRevenueLift: m?.revenue?.progressionRevenueLift ?? 0.4,
|
|
19620
|
+
progressionLogSpan: m?.revenue?.progressionLogSpan ?? 6
|
|
19621
|
+
},
|
|
19622
|
+
acquisition: {
|
|
19623
|
+
cpi: m?.acquisition?.cpi ?? 0
|
|
19624
|
+
},
|
|
19625
|
+
uncertainty: {
|
|
19626
|
+
enabled: m?.uncertainty?.enabled ?? false,
|
|
19627
|
+
draws: m?.uncertainty?.draws ?? 300,
|
|
19628
|
+
quantiles,
|
|
19629
|
+
seed: m?.uncertainty?.seed,
|
|
19630
|
+
sigma: {
|
|
19631
|
+
retention: m?.uncertainty?.sigma?.retention ?? 0.08,
|
|
19632
|
+
conversion: m?.uncertainty?.sigma?.conversion ?? 0.12,
|
|
19633
|
+
arppu: m?.uncertainty?.sigma?.arppu ?? 0.2,
|
|
19634
|
+
ad: m?.uncertainty?.sigma?.ad ?? 0.15
|
|
19635
|
+
},
|
|
19636
|
+
correlation: {
|
|
19637
|
+
retentionConversion: m?.uncertainty?.correlation?.retentionConversion ?? 0,
|
|
19638
|
+
retentionArppu: m?.uncertainty?.correlation?.retentionArppu ?? 0,
|
|
19639
|
+
retentionAd: m?.uncertainty?.correlation?.retentionAd ?? 0,
|
|
19640
|
+
conversionArppu: m?.uncertainty?.correlation?.conversionArppu ?? 0,
|
|
19641
|
+
conversionAd: m?.uncertainty?.correlation?.conversionAd ?? 0,
|
|
19642
|
+
arppuAd: m?.uncertainty?.correlation?.arppuAd ?? 0
|
|
19643
|
+
}
|
|
19644
|
+
}
|
|
19645
|
+
};
|
|
19518
19646
|
}
|
|
19519
|
-
|
|
19520
|
-
|
|
19521
|
-
|
|
19522
|
-
|
|
19523
|
-
|
|
19524
|
-
|
|
19525
|
-
|
|
19526
|
-
|
|
19527
|
-
|
|
19528
|
-
|
|
19647
|
+
function retentionAtDay(ret, day) {
|
|
19648
|
+
if (day <= 0)
|
|
19649
|
+
return 1;
|
|
19650
|
+
if (day <= 1)
|
|
19651
|
+
return ret.d1;
|
|
19652
|
+
if (day <= 7)
|
|
19653
|
+
return ret.d1 + (ret.d7 - ret.d1) * (day - 1) / 6;
|
|
19654
|
+
if (day <= 30)
|
|
19655
|
+
return ret.d7 + (ret.d30 - ret.d7) * (day - 7) / 23;
|
|
19656
|
+
if (day <= 90)
|
|
19657
|
+
return ret.d30 + (ret.d90 - ret.d30) * (day - 30) / 60;
|
|
19658
|
+
const extra = day - 90;
|
|
19659
|
+
return clamp012(ret.d90 * Math.exp(-ret.longTailDailyDecay * extra));
|
|
19660
|
+
}
|
|
19661
|
+
function progressionFactor(startWorthLog10, endWorthLog10, span) {
|
|
19662
|
+
if (!Number.isFinite(startWorthLog10) || !Number.isFinite(endWorthLog10) || !Number.isFinite(span) || span <= 0) {
|
|
19663
|
+
return 0;
|
|
19529
19664
|
}
|
|
19665
|
+
return clamp012((endWorthLog10 - startWorthLog10) / span);
|
|
19530
19666
|
}
|
|
19531
|
-
function
|
|
19532
|
-
|
|
19533
|
-
|
|
19534
|
-
|
|
19667
|
+
function estimateLtvPerUser(args) {
|
|
19668
|
+
const { config, horizonDays } = args;
|
|
19669
|
+
const progression = clamp012(args.progression);
|
|
19670
|
+
const arppuDaily = config.revenue.arppuDaily * (1 + progression * config.revenue.progressionRevenueLift);
|
|
19671
|
+
const adArpDau = config.revenue.adArpDau * (1 + progression * config.revenue.progressionRevenueLift * 0.5);
|
|
19672
|
+
const payerConversion = config.revenue.payerConversion;
|
|
19673
|
+
const fullDays = Math.floor(Math.max(0, horizonDays));
|
|
19674
|
+
const remainder = Math.max(0, horizonDays - fullDays);
|
|
19675
|
+
let gross = 0;
|
|
19676
|
+
for (let day = 1;day <= fullDays; day++) {
|
|
19677
|
+
const active = retentionAtDay(config.retention, day);
|
|
19678
|
+
gross += active * (payerConversion * arppuDaily + adArpDau);
|
|
19679
|
+
}
|
|
19680
|
+
if (remainder > 0) {
|
|
19681
|
+
const active = retentionAtDay(config.retention, fullDays + 1);
|
|
19682
|
+
gross += remainder * active * (payerConversion * arppuDaily + adArpDau);
|
|
19683
|
+
}
|
|
19684
|
+
const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
|
|
19685
|
+
const ltv = net - config.acquisition.cpi;
|
|
19686
|
+
return {
|
|
19687
|
+
cumulativeGrossRevenuePerUser: gross,
|
|
19688
|
+
cumulativeNetRevenuePerUser: net,
|
|
19689
|
+
cumulativeLtvPerUser: ltv
|
|
19690
|
+
};
|
|
19535
19691
|
}
|
|
19536
|
-
|
|
19537
|
-
|
|
19692
|
+
|
|
19693
|
+
// ../core/dist/ltv/uncertainty.js
|
|
19694
|
+
function clamp013(v) {
|
|
19695
|
+
if (v < 0)
|
|
19696
|
+
return 0;
|
|
19697
|
+
if (v > 1)
|
|
19698
|
+
return 1;
|
|
19699
|
+
return v;
|
|
19538
19700
|
}
|
|
19539
|
-
|
|
19540
|
-
|
|
19701
|
+
function clampCorrelation(v) {
|
|
19702
|
+
if (!Number.isFinite(v))
|
|
19703
|
+
return 0;
|
|
19704
|
+
if (v < -1)
|
|
19705
|
+
return -1;
|
|
19706
|
+
if (v > 1)
|
|
19707
|
+
return 1;
|
|
19708
|
+
return v;
|
|
19541
19709
|
}
|
|
19542
|
-
function
|
|
19543
|
-
const
|
|
19544
|
-
|
|
19545
|
-
return
|
|
19710
|
+
function randNormal(rng) {
|
|
19711
|
+
const u1 = Math.max(0.000000000001, rng());
|
|
19712
|
+
const u2 = Math.max(0.000000000001, rng());
|
|
19713
|
+
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
19546
19714
|
}
|
|
19547
|
-
function
|
|
19548
|
-
const
|
|
19549
|
-
|
|
19550
|
-
|
|
19551
|
-
|
|
19552
|
-
|
|
19553
|
-
|
|
19554
|
-
|
|
19555
|
-
|
|
19556
|
-
|
|
19557
|
-
|
|
19558
|
-
|
|
19559
|
-
|
|
19715
|
+
function buildCorrelationMatrix(base) {
|
|
19716
|
+
const rc = clampCorrelation(base.retentionConversion);
|
|
19717
|
+
const ra = clampCorrelation(base.retentionArppu);
|
|
19718
|
+
const rd = clampCorrelation(base.retentionAd);
|
|
19719
|
+
const ca = clampCorrelation(base.conversionArppu);
|
|
19720
|
+
const cd = clampCorrelation(base.conversionAd);
|
|
19721
|
+
const ad = clampCorrelation(base.arppuAd);
|
|
19722
|
+
return [
|
|
19723
|
+
[1, rc, ra, rd],
|
|
19724
|
+
[rc, 1, ca, cd],
|
|
19725
|
+
[ra, ca, 1, ad],
|
|
19726
|
+
[rd, cd, ad, 1]
|
|
19727
|
+
];
|
|
19728
|
+
}
|
|
19729
|
+
function withOffDiagonalScale(matrix, alpha) {
|
|
19730
|
+
return matrix.map((row, i) => row.map((v, j) => {
|
|
19731
|
+
if (i === j)
|
|
19732
|
+
return 1;
|
|
19733
|
+
return v * alpha;
|
|
19734
|
+
}));
|
|
19735
|
+
}
|
|
19736
|
+
function cholesky4(matrix) {
|
|
19737
|
+
const n = 4;
|
|
19738
|
+
const l = Array.from({ length: n }, () => Array.from({ length: n }, () => 0));
|
|
19739
|
+
for (let i = 0;i < n; i++) {
|
|
19740
|
+
for (let j = 0;j <= i; j++) {
|
|
19741
|
+
let sum = 0;
|
|
19742
|
+
for (let k = 0;k < j; k++) {
|
|
19743
|
+
sum += (l[i]?.[k] ?? 0) * (l[j]?.[k] ?? 0);
|
|
19744
|
+
}
|
|
19745
|
+
if (i === j) {
|
|
19746
|
+
const val = (matrix[i]?.[i] ?? 0) - sum;
|
|
19747
|
+
if (val <= 0.000000001)
|
|
19748
|
+
return null;
|
|
19749
|
+
l[i][j] = Math.sqrt(val);
|
|
19750
|
+
} else {
|
|
19751
|
+
const denom = l[j]?.[j] ?? 0;
|
|
19752
|
+
if (Math.abs(denom) < 0.000000001)
|
|
19753
|
+
return null;
|
|
19754
|
+
l[i][j] = ((matrix[i]?.[j] ?? 0) - sum) / denom;
|
|
19755
|
+
}
|
|
19756
|
+
}
|
|
19560
19757
|
}
|
|
19561
|
-
return
|
|
19758
|
+
return l;
|
|
19562
19759
|
}
|
|
19563
|
-
|
|
19564
|
-
|
|
19565
|
-
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
|
|
19571
|
-
|
|
19572
|
-
|
|
19573
|
-
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
|
|
19583
|
-
|
|
19584
|
-
|
|
19585
|
-
|
|
19586
|
-
|
|
19587
|
-
|
|
19588
|
-
|
|
19589
|
-
}
|
|
19590
|
-
|
|
19591
|
-
|
|
19592
|
-
|
|
19593
|
-
|
|
19594
|
-
|
|
19595
|
-
|
|
19596
|
-
|
|
19597
|
-
|
|
19598
|
-
|
|
19599
|
-
|
|
19600
|
-
|
|
19601
|
-
|
|
19602
|
-
|
|
19603
|
-
|
|
19604
|
-
|
|
19605
|
-
|
|
19606
|
-
|
|
19607
|
-
|
|
19608
|
-
|
|
19609
|
-
|
|
19610
|
-
|
|
19611
|
-
|
|
19612
|
-
|
|
19613
|
-
|
|
19614
|
-
|
|
19615
|
-
|
|
19616
|
-
|
|
19617
|
-
|
|
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"
|
|
19621
|
-
},
|
|
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"
|
|
19760
|
+
function multiplyLowerTriangular(l, z) {
|
|
19761
|
+
const out = [0, 0, 0, 0];
|
|
19762
|
+
for (let i = 0;i < 4; i++) {
|
|
19763
|
+
let sum = 0;
|
|
19764
|
+
for (let j = 0;j <= i; j++) {
|
|
19765
|
+
sum += (l[i]?.[j] ?? 0) * (z[j] ?? 0);
|
|
19766
|
+
}
|
|
19767
|
+
out[i] = sum;
|
|
19768
|
+
}
|
|
19769
|
+
return out;
|
|
19770
|
+
}
|
|
19771
|
+
function sampleCorrelatedNormals(rng, corr) {
|
|
19772
|
+
const z = [randNormal(rng), randNormal(rng), randNormal(rng), randNormal(rng)];
|
|
19773
|
+
const matrix = buildCorrelationMatrix(corr);
|
|
19774
|
+
let l = cholesky4(matrix);
|
|
19775
|
+
let alpha = 0.99;
|
|
19776
|
+
while (!l && alpha > 0.000001) {
|
|
19777
|
+
l = cholesky4(withOffDiagonalScale(matrix, alpha));
|
|
19778
|
+
alpha *= 0.8;
|
|
19779
|
+
}
|
|
19780
|
+
if (!l) {
|
|
19781
|
+
return {
|
|
19782
|
+
retention: z[0] ?? 0,
|
|
19783
|
+
conversion: z[1] ?? 0,
|
|
19784
|
+
arppu: z[2] ?? 0,
|
|
19785
|
+
ad: z[3] ?? 0
|
|
19786
|
+
};
|
|
19787
|
+
}
|
|
19788
|
+
const c = multiplyLowerTriangular(l, z);
|
|
19789
|
+
return {
|
|
19790
|
+
retention: c[0] ?? 0,
|
|
19791
|
+
conversion: c[1] ?? 0,
|
|
19792
|
+
arppu: c[2] ?? 0,
|
|
19793
|
+
ad: c[3] ?? 0
|
|
19794
|
+
};
|
|
19795
|
+
}
|
|
19796
|
+
function applyLogNormalShock(mean, sigma, z) {
|
|
19797
|
+
if (sigma <= 0)
|
|
19798
|
+
return mean;
|
|
19799
|
+
return mean * Math.exp(sigma * z - 0.5 * sigma * sigma);
|
|
19800
|
+
}
|
|
19801
|
+
function makeUncertainConfig(base, rng) {
|
|
19802
|
+
const shock = sampleCorrelatedNormals(rng, base.uncertainty.correlation);
|
|
19803
|
+
const d1 = clamp013(applyLogNormalShock(base.retention.d1, base.uncertainty.sigma.retention, shock.retention));
|
|
19804
|
+
const d7 = Math.min(d1, clamp013(applyLogNormalShock(base.retention.d7, base.uncertainty.sigma.retention, shock.retention)));
|
|
19805
|
+
const d30 = Math.min(d7, clamp013(applyLogNormalShock(base.retention.d30, base.uncertainty.sigma.retention, shock.retention)));
|
|
19806
|
+
const d90 = Math.min(d30, clamp013(applyLogNormalShock(base.retention.d90, base.uncertainty.sigma.retention, shock.retention)));
|
|
19807
|
+
return {
|
|
19808
|
+
...base,
|
|
19809
|
+
retention: {
|
|
19810
|
+
...base.retention,
|
|
19811
|
+
d1,
|
|
19812
|
+
d7,
|
|
19813
|
+
d30,
|
|
19814
|
+
d90
|
|
19632
19815
|
},
|
|
19633
|
-
|
|
19634
|
-
|
|
19816
|
+
revenue: {
|
|
19817
|
+
...base.revenue,
|
|
19818
|
+
payerConversion: clamp013(applyLogNormalShock(base.revenue.payerConversion, base.uncertainty.sigma.conversion, shock.conversion)),
|
|
19819
|
+
arppuDaily: Math.max(0, applyLogNormalShock(base.revenue.arppuDaily, base.uncertainty.sigma.arppu, shock.arppu)),
|
|
19820
|
+
adArpDau: Math.max(0, applyLogNormalShock(base.revenue.adArpDau, base.uncertainty.sigma.ad, shock.ad))
|
|
19635
19821
|
}
|
|
19636
19822
|
};
|
|
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
19823
|
}
|
|
19645
|
-
function
|
|
19646
|
-
|
|
19824
|
+
function quantileFromSorted(sorted, q) {
|
|
19825
|
+
if (sorted.length === 0)
|
|
19826
|
+
return 0;
|
|
19827
|
+
const idx = Math.max(0, Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * q)));
|
|
19828
|
+
return sorted[idx] ?? 0;
|
|
19647
19829
|
}
|
|
19648
|
-
function
|
|
19649
|
-
|
|
19830
|
+
function estimateLtvDistribution(args) {
|
|
19831
|
+
const rng = mulberry32(args.seed);
|
|
19832
|
+
const grossSamples = [];
|
|
19833
|
+
const netSamples = [];
|
|
19834
|
+
const ltvSamples = [];
|
|
19835
|
+
const draws = Math.max(1, args.draws);
|
|
19836
|
+
for (let i = 0;i < draws; i++) {
|
|
19837
|
+
const sampled = makeUncertainConfig(args.config, rng);
|
|
19838
|
+
const point = estimateLtvPerUser({
|
|
19839
|
+
config: sampled,
|
|
19840
|
+
horizonDays: args.horizonDays,
|
|
19841
|
+
progression: args.progression
|
|
19842
|
+
});
|
|
19843
|
+
grossSamples.push(point.cumulativeGrossRevenuePerUser);
|
|
19844
|
+
netSamples.push(point.cumulativeNetRevenuePerUser);
|
|
19845
|
+
ltvSamples.push(point.cumulativeLtvPerUser);
|
|
19846
|
+
}
|
|
19847
|
+
const avg = (arr) => arr.reduce((a, b) => a + b, 0) / Math.max(1, arr.length);
|
|
19848
|
+
const sortedLtv = [...ltvSamples].sort((a, b) => a - b);
|
|
19849
|
+
const byQuantile = {};
|
|
19850
|
+
for (const q of args.quantiles) {
|
|
19851
|
+
byQuantile[`q${Math.round(q * 100)}`] = quantileFromSorted(sortedLtv, q);
|
|
19852
|
+
}
|
|
19853
|
+
return {
|
|
19854
|
+
mean: {
|
|
19855
|
+
cumulativeGrossRevenuePerUser: avg(grossSamples),
|
|
19856
|
+
cumulativeNetRevenuePerUser: avg(netSamples),
|
|
19857
|
+
cumulativeLtvPerUser: avg(ltvSamples)
|
|
19858
|
+
},
|
|
19859
|
+
quantiles: byQuantile
|
|
19860
|
+
};
|
|
19650
19861
|
}
|
|
19651
|
-
|
|
19652
|
-
|
|
19862
|
+
var init_uncertainty = () => {};
|
|
19863
|
+
|
|
19864
|
+
// ../core/dist/ltv/public.js
|
|
19865
|
+
function assertMonetization(monetization) {
|
|
19866
|
+
const valid = validateScenarioV1({
|
|
19867
|
+
schemaVersion: 1,
|
|
19868
|
+
unit: { code: "LTV" },
|
|
19869
|
+
policy: { mode: "drop" },
|
|
19870
|
+
model: { id: "ltv-input", version: 1 },
|
|
19871
|
+
initial: { wallet: { unit: "LTV", amount: "0" } },
|
|
19872
|
+
clock: { stepSec: 1, durationSec: 1 },
|
|
19873
|
+
monetization
|
|
19874
|
+
});
|
|
19875
|
+
if (!valid.ok)
|
|
19876
|
+
throw new RangeError(valid.issues.map((issue) => issue.message).join("; "));
|
|
19653
19877
|
}
|
|
19654
|
-
function
|
|
19655
|
-
if (
|
|
19656
|
-
|
|
19657
|
-
|
|
19658
|
-
|
|
19659
|
-
|
|
19878
|
+
function assertQuantileValues(quantiles) {
|
|
19879
|
+
if (quantiles.length === 0)
|
|
19880
|
+
throw new RangeError("quantiles must be nonempty and in 0..1");
|
|
19881
|
+
for (const q of quantiles) {
|
|
19882
|
+
if (!Number.isFinite(q) || q < 0 || q > 1)
|
|
19883
|
+
throw new RangeError("quantiles must be nonempty and in 0..1");
|
|
19884
|
+
}
|
|
19660
19885
|
}
|
|
19661
|
-
function
|
|
19662
|
-
const
|
|
19663
|
-
|
|
19664
|
-
|
|
19886
|
+
function assertConfig(config) {
|
|
19887
|
+
const { retention, ...other } = config;
|
|
19888
|
+
for (const day of ["d1", "d7", "d30", "d90"]) {
|
|
19889
|
+
if (!Number.isFinite(retention[day]) || retention[day] < 0 || retention[day] > 1) {
|
|
19890
|
+
throw new RangeError(`retention.${day} must be finite and in 0..1`);
|
|
19891
|
+
}
|
|
19892
|
+
}
|
|
19893
|
+
if (!Number.isFinite(retention.longTailDailyDecay) || retention.longTailDailyDecay < 0) {
|
|
19894
|
+
throw new RangeError("retention.longTailDailyDecay must be finite and >= 0");
|
|
19895
|
+
}
|
|
19896
|
+
const { quantiles, ...uncertainty } = other.uncertainty;
|
|
19897
|
+
assertQuantileValues(quantiles);
|
|
19898
|
+
assertMonetization({ ...other, uncertainty });
|
|
19899
|
+
}
|
|
19900
|
+
function deriveMonetizationConfig2(scenario) {
|
|
19901
|
+
assertMonetization(scenario.monetization ?? {});
|
|
19902
|
+
const config = deriveMonetizationConfig(scenario);
|
|
19903
|
+
assertConfig(config);
|
|
19665
19904
|
return config;
|
|
19666
19905
|
}
|
|
19667
|
-
function
|
|
19668
|
-
|
|
19669
|
-
|
|
19670
|
-
|
|
19671
|
-
if (isJavaScriptEntry(entry)) {
|
|
19672
|
-
const config = bundledCliConfig(entry);
|
|
19673
|
-
if (config)
|
|
19674
|
-
return [bun, `--config=${config}`, entry, ...args];
|
|
19675
|
-
return [bun, entry, ...args];
|
|
19906
|
+
function assertPoint(args) {
|
|
19907
|
+
assertConfig(args.config);
|
|
19908
|
+
if (!Number.isFinite(args.horizonDays) || args.horizonDays < 0 || args.horizonDays > 36500) {
|
|
19909
|
+
throw new RangeError("horizonDays must be finite and in 0..36500");
|
|
19676
19910
|
}
|
|
19677
|
-
|
|
19678
|
-
|
|
19679
|
-
|
|
19911
|
+
if (!Number.isFinite(args.progression))
|
|
19912
|
+
throw new RangeError("progression must be finite");
|
|
19913
|
+
}
|
|
19914
|
+
function finiteResult(result) {
|
|
19915
|
+
if (!Object.values(result).every((value) => typeof value === "number" && Number.isFinite(value))) {
|
|
19916
|
+
throw new RangeError("LTV calculation overflowed; reduce the input magnitudes");
|
|
19680
19917
|
}
|
|
19681
|
-
return
|
|
19918
|
+
return result;
|
|
19682
19919
|
}
|
|
19683
|
-
function
|
|
19684
|
-
|
|
19685
|
-
|
|
19686
|
-
|
|
19687
|
-
|
|
19688
|
-
|
|
19689
|
-
|
|
19690
|
-
|
|
19691
|
-
|
|
19692
|
-
|
|
19693
|
-
|
|
19694
|
-
|
|
19695
|
-
|
|
19920
|
+
function estimateLtvPerUser2(args) {
|
|
19921
|
+
assertPoint(args);
|
|
19922
|
+
return finiteResult(estimateLtvPerUser(args));
|
|
19923
|
+
}
|
|
19924
|
+
function estimateLtvDistribution2(args) {
|
|
19925
|
+
assertPoint(args);
|
|
19926
|
+
if (!Number.isSafeInteger(args.draws) || args.draws < 1 || args.draws > 1e5 || args.draws * Math.max(1, Math.ceil(args.horizonDays)) > 1e7) {
|
|
19927
|
+
throw new RangeError("draws must be 1..100000 and draws \xD7 ceil(horizonDays) at most 10000000");
|
|
19928
|
+
}
|
|
19929
|
+
if (!Number.isFinite(args.seed))
|
|
19930
|
+
throw new RangeError("seed must be finite");
|
|
19931
|
+
if (args.quantiles.length > 101)
|
|
19932
|
+
throw new RangeError("quantiles exceed the 101 distinct percent keys");
|
|
19933
|
+
assertQuantileValues(args.quantiles);
|
|
19934
|
+
if (new Set(args.quantiles.map((q) => Math.round(q * 100))).size !== args.quantiles.length) {
|
|
19935
|
+
throw new RangeError("quantiles must be nonempty, in 0..1 and have distinct rounded percent keys");
|
|
19936
|
+
}
|
|
19937
|
+
const result = estimateLtvDistribution(args);
|
|
19938
|
+
finiteResult(result.mean);
|
|
19939
|
+
finiteResult(result.quantiles);
|
|
19940
|
+
return result;
|
|
19696
19941
|
}
|
|
19697
|
-
var
|
|
19698
|
-
|
|
19699
|
-
|
|
19700
|
-
MAX_CAPTURE_BYTES2 = 8 * 1024 * 1024;
|
|
19942
|
+
var init_public = __esm(() => {
|
|
19943
|
+
init_validate();
|
|
19944
|
+
init_uncertainty();
|
|
19701
19945
|
});
|
|
19702
19946
|
|
|
19703
|
-
//
|
|
19704
|
-
|
|
19705
|
-
|
|
19706
|
-
|
|
19707
|
-
|
|
19708
|
-
|
|
19947
|
+
// ../core/dist/ltv/cohort.js
|
|
19948
|
+
function wilsonInterval(activeUsers, users) {
|
|
19949
|
+
const rate = activeUsers / users;
|
|
19950
|
+
const z2 = 1.959963984540054 ** 2;
|
|
19951
|
+
const denominator = 1 + z2 / users;
|
|
19952
|
+
const center = (rate + z2 / (2 * users)) / denominator;
|
|
19953
|
+
const radius = Math.sqrt(z2 * (rate * (1 - rate) / users + z2 / (4 * users * users))) / denominator;
|
|
19954
|
+
return { min: activeUsers === 0 ? 0 : Math.max(0, center - radius), max: activeUsers === users ? 1 : Math.min(1, center + radius) };
|
|
19709
19955
|
}
|
|
19710
|
-
function
|
|
19711
|
-
if (
|
|
19712
|
-
|
|
19956
|
+
function observeCohort(rows, observedThroughDay) {
|
|
19957
|
+
if (!Number.isSafeInteger(observedThroughDay) || observedThroughDay < 0)
|
|
19958
|
+
throw new Error("observed-through-day must be a nonnegative safe integer");
|
|
19959
|
+
if (!Array.isArray(rows) || rows.length === 0)
|
|
19960
|
+
throw new Error("Observed cohort requires at least one install record");
|
|
19961
|
+
const users = new Map;
|
|
19962
|
+
for (const row of rows) {
|
|
19963
|
+
if (!row || typeof row.userId !== "string" || !row.userId.trim() || !Number.isSafeInteger(row.day) || row.day < 0 || row.day > observedThroughDay) {
|
|
19964
|
+
throw new Error("Observed cohort requires a user ID and integer days within observed-through-day");
|
|
19965
|
+
}
|
|
19966
|
+
if (![row.iapRevenue, row.adRevenue, row.acquisitionCost].every((v) => v === undefined || typeof v === "number" && Number.isFinite(v) && v >= 0)) {
|
|
19967
|
+
throw new Error("Observed cohort revenue and acquisition costs must be finite and nonnegative");
|
|
19968
|
+
}
|
|
19969
|
+
if (row.active !== undefined && typeof row.active !== "boolean")
|
|
19970
|
+
throw new Error("Observed cohort active must be boolean");
|
|
19971
|
+
const user = users.get(row.userId) ?? { days: new Set, active: new Set };
|
|
19972
|
+
if (user.days.has(row.day))
|
|
19973
|
+
throw new Error("Observed cohort requires one aggregated row per user and day");
|
|
19974
|
+
user.days.add(row.day);
|
|
19975
|
+
if (row.active ?? true)
|
|
19976
|
+
user.active.add(row.day);
|
|
19977
|
+
users.set(row.userId, user);
|
|
19713
19978
|
}
|
|
19714
|
-
|
|
19715
|
-
|
|
19979
|
+
for (const user of users.values()) {
|
|
19980
|
+
if (!user.days.has(0))
|
|
19981
|
+
throw new Error("Observed cohort requires a day 0 install record for every user, including inactive users");
|
|
19716
19982
|
}
|
|
19717
|
-
const
|
|
19718
|
-
|
|
19983
|
+
const n = users.size;
|
|
19984
|
+
const observation = {
|
|
19985
|
+
observedThroughDay,
|
|
19986
|
+
users: n,
|
|
19987
|
+
retention: RETENTION_DAYS.map((day) => {
|
|
19988
|
+
if (day > observedThroughDay)
|
|
19989
|
+
return { day, status: "not-observed", users: n, activeUsers: null, rate: null, interval95: null };
|
|
19990
|
+
const activeUsers = [...users.values()].filter((user) => user.active.has(day)).length;
|
|
19991
|
+
const rate = activeUsers / n;
|
|
19992
|
+
return { day, status: "observed", users: n, activeUsers, rate, interval95: wilsonInterval(activeUsers, n) };
|
|
19993
|
+
})
|
|
19994
|
+
};
|
|
19995
|
+
assertCohortObservation(observation);
|
|
19996
|
+
return observation;
|
|
19719
19997
|
}
|
|
19720
|
-
function
|
|
19721
|
-
return
|
|
19998
|
+
function object2(value) {
|
|
19999
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19722
20000
|
}
|
|
19723
|
-
function
|
|
19724
|
-
if (
|
|
19725
|
-
|
|
19726
|
-
hasResolvedGitSha = true;
|
|
19727
|
-
const envSha = process.env.GITHUB_SHA?.trim();
|
|
19728
|
-
if (envSha) {
|
|
19729
|
-
cachedGitSha = envSha;
|
|
19730
|
-
return cachedGitSha;
|
|
20001
|
+
function assertCohortObservation(observation) {
|
|
20002
|
+
if (!object2(observation) || typeof observation.users !== "number" || !Number.isSafeInteger(observation.users) || observation.users < 1 || typeof observation.observedThroughDay !== "number" || !Number.isSafeInteger(observation.observedThroughDay) || observation.observedThroughDay < 0) {
|
|
20003
|
+
throw new Error("Invalid cohort population or observation cutoff");
|
|
19731
20004
|
}
|
|
19732
|
-
|
|
19733
|
-
|
|
19734
|
-
|
|
19735
|
-
|
|
19736
|
-
|
|
19737
|
-
|
|
19738
|
-
|
|
20005
|
+
if (!Array.isArray(observation.retention) || observation.retention.length !== RETENTION_DAYS.length)
|
|
20006
|
+
throw new Error("Cohort retention must contain D1, D7, D30 and D90");
|
|
20007
|
+
for (const [index, value] of observation.retention.entries()) {
|
|
20008
|
+
const point = value;
|
|
20009
|
+
if (!object2(point) || typeof point.day !== "number" || point.day !== RETENTION_DAYS[index] || point.users !== observation.users)
|
|
20010
|
+
throw new Error("Cohort retention days and population must match the observation");
|
|
20011
|
+
const observed = point.day <= observation.observedThroughDay;
|
|
20012
|
+
if (point.status !== (observed ? "observed" : "not-observed"))
|
|
20013
|
+
throw new Error("Cohort retention status must match the observation cutoff");
|
|
20014
|
+
if (!observed) {
|
|
20015
|
+
if (point.activeUsers !== null || point.rate !== null || point.interval95 !== null)
|
|
20016
|
+
throw new Error("Future retention must remain unobserved");
|
|
20017
|
+
continue;
|
|
20018
|
+
}
|
|
20019
|
+
if (typeof point.activeUsers !== "number" || !Number.isSafeInteger(point.activeUsers) || point.activeUsers < 0 || point.activeUsers > observation.users || point.rate !== point.activeUsers / observation.users) {
|
|
20020
|
+
throw new Error("Retained users and retention rate must match the installed population");
|
|
20021
|
+
}
|
|
20022
|
+
const interval = point.interval95;
|
|
20023
|
+
if (typeof point.rate !== "number" || !object2(interval) || typeof interval.min !== "number" || typeof interval.max !== "number" || !Number.isFinite(interval.min) || !Number.isFinite(interval.max) || interval.min < 0 || interval.max > 1 || interval.min > point.rate || interval.max < point.rate) {
|
|
20024
|
+
throw new Error("Retention confidence interval must be finite and contain the observed rate");
|
|
20025
|
+
}
|
|
20026
|
+
const expected = wilsonInterval(point.activeUsers, observation.users);
|
|
20027
|
+
if (interval.min !== expected.min || interval.max !== expected.max) {
|
|
20028
|
+
throw new Error("Retention confidence interval must match the Wilson 95% bounds for the observed population");
|
|
20029
|
+
}
|
|
19739
20030
|
}
|
|
19740
|
-
return cachedGitSha ?? UNKNOWN_GIT_SHA;
|
|
19741
|
-
}
|
|
19742
|
-
function hashContent(value) {
|
|
19743
|
-
return sha256(stableStringify(value));
|
|
19744
20031
|
}
|
|
19745
|
-
function
|
|
19746
|
-
|
|
19747
|
-
|
|
20032
|
+
function observedRetentionRates(observation) {
|
|
20033
|
+
assertCohortObservation(observation);
|
|
20034
|
+
const rates = {};
|
|
20035
|
+
for (const row of observation.retention) {
|
|
20036
|
+
if (row.rate !== null)
|
|
20037
|
+
rates[`d${row.day}`] = row.rate;
|
|
20038
|
+
}
|
|
20039
|
+
return rates;
|
|
19748
20040
|
}
|
|
19749
|
-
function
|
|
19750
|
-
|
|
19751
|
-
|
|
19752
|
-
|
|
19753
|
-
|
|
19754
|
-
|
|
19755
|
-
|
|
20041
|
+
function assertRetentionAssumptions(assumptions) {
|
|
20042
|
+
if (!object2(assumptions))
|
|
20043
|
+
throw new RangeError("Retention assumptions must be an object");
|
|
20044
|
+
for (const day of RETENTION_DAYS) {
|
|
20045
|
+
const rate = assumptions[`d${day}`];
|
|
20046
|
+
if (rate !== undefined && (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0 || rate > 1)) {
|
|
20047
|
+
throw new RangeError(`Retention assumption d${day} must be finite and in 0..1`);
|
|
20048
|
+
}
|
|
20049
|
+
}
|
|
19756
20050
|
}
|
|
19757
|
-
function
|
|
19758
|
-
|
|
20051
|
+
function planRetentionPatch(observation, assumptions = {}) {
|
|
20052
|
+
assertRetentionAssumptions(assumptions);
|
|
20053
|
+
const rates = observedRetentionRates(observation);
|
|
20054
|
+
if (Object.keys(rates).length === 0)
|
|
20055
|
+
return { status: "unavailable", retention: {}, warnings: ["No retention day has been fully observed."] };
|
|
20056
|
+
const merged = { ...assumptions, ...rates };
|
|
20057
|
+
const warnings = [];
|
|
20058
|
+
for (const day of RETENTION_DAYS) {
|
|
20059
|
+
if (merged[`d${day}`] === undefined)
|
|
20060
|
+
warnings.push(`Unobserved D${day} has no explicit retained assumption. An LTV default could conflict with the observations; supply monetization.retention.d${day} in the scenario or observe this day before applying a patch.`);
|
|
20061
|
+
}
|
|
20062
|
+
let previous;
|
|
20063
|
+
for (const day of RETENTION_DAYS) {
|
|
20064
|
+
const rate = merged[`d${day}`];
|
|
20065
|
+
if (rate === undefined)
|
|
20066
|
+
continue;
|
|
20067
|
+
if (previous && previous.rate < rate)
|
|
20068
|
+
warnings.push(`D${previous.day}=${previous.rate} is below D${day}=${rate}. LTV requires d1 >= d7 >= d30 >= d90; review observations and retained assumptions before applying a patch.`);
|
|
20069
|
+
previous = { day, rate };
|
|
20070
|
+
}
|
|
20071
|
+
return warnings.length ? { status: "requires-review", retention: {}, warnings } : { status: "ready", retention: rates, warnings };
|
|
19759
20072
|
}
|
|
19760
|
-
function
|
|
19761
|
-
|
|
19762
|
-
|
|
19763
|
-
|
|
19764
|
-
|
|
19765
|
-
|
|
19766
|
-
|
|
19767
|
-
|
|
19768
|
-
|
|
19769
|
-
|
|
19770
|
-
|
|
19771
|
-
|
|
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
|
-
};
|
|
20073
|
+
function compareRetention(observation, assumptions) {
|
|
20074
|
+
assertCohortObservation(observation);
|
|
20075
|
+
assertRetentionAssumptions(assumptions);
|
|
20076
|
+
return observation.retention.map((row) => {
|
|
20077
|
+
const assumption = assumptions[`d${row.day}`] ?? null;
|
|
20078
|
+
return {
|
|
20079
|
+
day: row.day,
|
|
20080
|
+
assumption,
|
|
20081
|
+
observed: row.rate,
|
|
20082
|
+
differencePercentagePoints: row.rate === null || assumption === null ? null : (row.rate - assumption) * 100,
|
|
20083
|
+
status: row.rate === null ? "not-observed" : assumption === null ? "unconfigured" : "compared"
|
|
20084
|
+
};
|
|
20085
|
+
});
|
|
19791
20086
|
}
|
|
19792
|
-
var
|
|
19793
|
-
var
|
|
19794
|
-
|
|
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";
|
|
20087
|
+
var RETENTION_DAYS;
|
|
20088
|
+
var init_cohort = __esm(() => {
|
|
20089
|
+
RETENTION_DAYS = Object.freeze([1, 7, 30, 90]);
|
|
19816
20090
|
});
|
|
19817
20091
|
|
|
19818
|
-
//
|
|
19819
|
-
var
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
19825
|
-
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
19829
|
-
|
|
19830
|
-
|
|
19831
|
-
|
|
19832
|
-
|
|
19833
|
-
|
|
19834
|
-
|
|
19835
|
-
|
|
19836
|
-
|
|
19837
|
-
|
|
19838
|
-
|
|
19839
|
-
|
|
19840
|
-
|
|
19841
|
-
|
|
19842
|
-
|
|
19843
|
-
|
|
19844
|
-
|
|
19845
|
-
|
|
19846
|
-
|
|
19847
|
-
|
|
19848
|
-
|
|
19849
|
-
|
|
19850
|
-
|
|
19851
|
-
|
|
19852
|
-
|
|
19853
|
-
|
|
19854
|
-
|
|
19855
|
-
|
|
19856
|
-
|
|
20092
|
+
// ../core/dist/index.js
|
|
20093
|
+
var init_dist2 = __esm(() => {
|
|
20094
|
+
init_types();
|
|
20095
|
+
init_breakInfinity2();
|
|
20096
|
+
init_breakEternity2();
|
|
20097
|
+
init_types5();
|
|
20098
|
+
init_types6();
|
|
20099
|
+
init_emitter();
|
|
20100
|
+
init_tickMoney2();
|
|
20101
|
+
init_suffixer();
|
|
20102
|
+
init_formatMoney2();
|
|
20103
|
+
init_parseMoney2();
|
|
20104
|
+
init_visibilityTracker2();
|
|
20105
|
+
init_moneyState();
|
|
20106
|
+
init_simState();
|
|
20107
|
+
init_types7();
|
|
20108
|
+
init_validate();
|
|
20109
|
+
init_concreteValidator();
|
|
20110
|
+
init_scenarioContract();
|
|
20111
|
+
init_compile2();
|
|
20112
|
+
init_simulator();
|
|
20113
|
+
init_advance();
|
|
20114
|
+
init_step();
|
|
20115
|
+
init_stepTypes();
|
|
20116
|
+
init_offline();
|
|
20117
|
+
init_session();
|
|
20118
|
+
init_monteCarlo();
|
|
20119
|
+
init_executionBackend();
|
|
20120
|
+
init_integrationMatrix();
|
|
20121
|
+
init_runFactory();
|
|
20122
|
+
init_constraints();
|
|
20123
|
+
init_observation();
|
|
20124
|
+
init_types8();
|
|
20125
|
+
init_contracts();
|
|
20126
|
+
init_stability();
|
|
20127
|
+
init_scripted();
|
|
20128
|
+
init_greedy();
|
|
20129
|
+
init_planner();
|
|
20130
|
+
init_params();
|
|
20131
|
+
init_builtins();
|
|
20132
|
+
init_tuneSpec();
|
|
20133
|
+
init_validate2();
|
|
20134
|
+
init_objective();
|
|
20135
|
+
init_params2();
|
|
20136
|
+
init_builtins2();
|
|
20137
|
+
init_tuner();
|
|
20138
|
+
init_randomSearch();
|
|
20139
|
+
init_runner();
|
|
20140
|
+
init_eta();
|
|
20141
|
+
init_prestigeCycle();
|
|
20142
|
+
init_resetCycle();
|
|
20143
|
+
init_resetOptimization();
|
|
20144
|
+
init_distribution();
|
|
20145
|
+
init_experiment();
|
|
20146
|
+
init_sensitivity();
|
|
20147
|
+
init_growthFit();
|
|
20148
|
+
init_contracts2();
|
|
20149
|
+
init_metrics();
|
|
20150
|
+
init_registry();
|
|
20151
|
+
init_analyzers();
|
|
20152
|
+
init_inspect();
|
|
20153
|
+
init_checks3();
|
|
20154
|
+
init_judge();
|
|
20155
|
+
init_flow();
|
|
20156
|
+
init_jevAdvisory();
|
|
20157
|
+
init_public();
|
|
20158
|
+
init_cohort();
|
|
20159
|
+
});
|
|
20160
|
+
|
|
20161
|
+
// src/runtime/bun.ts
|
|
20162
|
+
var {$ } = globalThis.Bun;
|
|
20163
|
+
import { dirname } from "path";
|
|
20164
|
+
async function readTextFile(path) {
|
|
20165
|
+
return Bun.file(path).text();
|
|
20166
|
+
}
|
|
20167
|
+
async function readJsonFile(path) {
|
|
20168
|
+
return Bun.file(path).json();
|
|
20169
|
+
}
|
|
20170
|
+
async function writeTextFile(path, body) {
|
|
20171
|
+
try {
|
|
20172
|
+
await ensureDir(dirname(path));
|
|
20173
|
+
await Bun.write(path, body);
|
|
20174
|
+
} catch (error) {
|
|
20175
|
+
throw cliError("OUTPUT_WRITE_FAILED", `Unable to write output file: ${path}`, {
|
|
20176
|
+
detail: errorDetail(error),
|
|
20177
|
+
hint: "Choose a writable output path and check that its parent is a directory.",
|
|
20178
|
+
cause: error
|
|
20179
|
+
});
|
|
20180
|
+
}
|
|
20181
|
+
}
|
|
20182
|
+
function writeStdout(body) {
|
|
20183
|
+
return new Promise((resolve, reject) => {
|
|
20184
|
+
process.stdout.write(body, (error) => error ? reject(error) : resolve());
|
|
20185
|
+
});
|
|
20186
|
+
}
|
|
20187
|
+
async function ensureDir(path) {
|
|
20188
|
+
await $`mkdir -p ${path}`.quiet();
|
|
20189
|
+
}
|
|
20190
|
+
async function fileExists(path) {
|
|
20191
|
+
return Bun.file(path).exists();
|
|
20192
|
+
}
|
|
20193
|
+
function sha256Hex(value) {
|
|
20194
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
20195
|
+
hasher.update(value);
|
|
20196
|
+
return hasher.digest("hex");
|
|
20197
|
+
}
|
|
20198
|
+
function runText(args, opts) {
|
|
20199
|
+
const proc = Bun.spawnSync(args, {
|
|
20200
|
+
cwd: opts?.cwd,
|
|
20201
|
+
env: opts?.env,
|
|
20202
|
+
stdout: "pipe",
|
|
20203
|
+
stderr: "pipe",
|
|
20204
|
+
maxBuffer: MAX_CAPTURE_BYTES
|
|
20205
|
+
});
|
|
20206
|
+
if (proc.exitCode !== 0) {
|
|
20207
|
+
const stderr = proc.stderr.toString().trim();
|
|
20208
|
+
const stdout = proc.stdout.toString().trim();
|
|
20209
|
+
const detail = stderr || stdout || `command failed with exit code ${proc.exitCode}`;
|
|
20210
|
+
throw new Error(detail);
|
|
20211
|
+
}
|
|
20212
|
+
return proc.stdout.toString();
|
|
20213
|
+
}
|
|
20214
|
+
var MAX_CAPTURE_BYTES;
|
|
20215
|
+
var init_bun = __esm(() => {
|
|
20216
|
+
init_errors3();
|
|
20217
|
+
MAX_CAPTURE_BYTES = 8 * 1024 * 1024;
|
|
20218
|
+
});
|
|
20219
|
+
|
|
20220
|
+
// package.json
|
|
20221
|
+
var require_package = __commonJS(function(exports, module) {
|
|
20222
|
+
module.exports = {
|
|
20223
|
+
name: "@idlekit/cli",
|
|
20224
|
+
version: "0.6.0",
|
|
20225
|
+
description: "Bun-first idk CLI for idle game scenario validation, simulation, tuning, and reporting.",
|
|
20226
|
+
license: "MIT",
|
|
20227
|
+
type: "module",
|
|
20228
|
+
files: [
|
|
20229
|
+
"dist",
|
|
20230
|
+
"README.md",
|
|
20231
|
+
"LICENSE"
|
|
20232
|
+
],
|
|
20233
|
+
bin: {
|
|
20234
|
+
idk: "dist/main.js"
|
|
20235
|
+
},
|
|
20236
|
+
repository: {
|
|
20237
|
+
type: "git",
|
|
20238
|
+
url: "git+https://github.com/imjlk/idlekit.git",
|
|
20239
|
+
directory: "packages/cli"
|
|
20240
|
+
},
|
|
20241
|
+
homepage: "https://github.com/imjlk/idlekit/tree/main/packages/cli#readme",
|
|
20242
|
+
bugs: {
|
|
20243
|
+
url: "https://github.com/imjlk/idlekit/issues"
|
|
20244
|
+
},
|
|
20245
|
+
keywords: [
|
|
20246
|
+
"bun",
|
|
20247
|
+
"idle-game",
|
|
20248
|
+
"incremental-game",
|
|
20249
|
+
"cli",
|
|
20250
|
+
"economy-simulator",
|
|
20251
|
+
"game-balancing",
|
|
20252
|
+
"simulation"
|
|
20253
|
+
],
|
|
20254
|
+
publishConfig: {
|
|
20255
|
+
access: "public"
|
|
20256
|
+
},
|
|
20257
|
+
engines: {
|
|
20258
|
+
bun: ">=1.4.2"
|
|
20259
|
+
},
|
|
20260
|
+
scripts: {
|
|
20261
|
+
dev: "bun src/main.ts",
|
|
20262
|
+
"dev:watch": "bun --watch src/main.ts",
|
|
20263
|
+
"generate:check": "bun scripts/command-check.ts",
|
|
20264
|
+
"doctor:completions": "bun scripts/command-check.ts",
|
|
20265
|
+
"review:smoke": "bun scripts/review-smoke.ts",
|
|
20266
|
+
build: "bun scripts/cli-bundle.ts --outdir dist --sourcemap",
|
|
20267
|
+
"build:bin": "bun scripts/cli-bundle.ts --entry src/bin.ts --outdir dist/bin --targets native",
|
|
20268
|
+
typecheck: "ttsc -p tsconfig.json --noEmit",
|
|
20269
|
+
test: "bun scripts/cli-bundle.ts --outdir .test-bundle && IDLEKIT_CLI_ENTRY=.test-bundle/main.js bun test --timeout 90000 src",
|
|
20270
|
+
prepack: "bun run build && cd ../.. && bun tools/package-manifest.ts prepare packages/cli",
|
|
20271
|
+
postpack: "cd ../.. && bun tools/package-manifest.ts restore packages/cli"
|
|
20272
|
+
},
|
|
20273
|
+
dependencies: {
|
|
20274
|
+
"@clack/prompts": "1.8.1",
|
|
20275
|
+
"@gunshi/plugin-completion": "0.37.3",
|
|
20276
|
+
"@gunshi/plugin-suggestion": "0.37.3",
|
|
20277
|
+
"@idlekit/core": "0.6.0",
|
|
20278
|
+
ajv: "^8.20.0",
|
|
20279
|
+
gunshi: "0.37.3",
|
|
20280
|
+
"proper-lockfile": "4.1.2",
|
|
20281
|
+
yaml: "^2.9.1",
|
|
20282
|
+
zod: "^4.6.5"
|
|
20283
|
+
},
|
|
20284
|
+
devDependencies: {
|
|
20285
|
+
"@types/proper-lockfile": "4.1.4"
|
|
20286
|
+
}
|
|
20287
|
+
};
|
|
20288
|
+
});
|
|
20289
|
+
|
|
20290
|
+
// src/runtime/selfCli.ts
|
|
20291
|
+
import { existsSync } from "fs";
|
|
20292
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
20293
|
+
function currentEntry() {
|
|
20294
|
+
return Bun.main;
|
|
20295
|
+
}
|
|
20296
|
+
function isJavaScriptEntry(entry) {
|
|
20297
|
+
return entry.endsWith(".js") || entry.endsWith(".mjs");
|
|
20298
|
+
}
|
|
20299
|
+
function isNativeEntry(entry) {
|
|
20300
|
+
return entry.includes("$bunfs") || entry.includes("~BUN");
|
|
20301
|
+
}
|
|
20302
|
+
function isBundledCliProcess(entry = currentEntry()) {
|
|
20303
|
+
return isJavaScriptEntry(entry) || isNativeEntry(entry);
|
|
20304
|
+
}
|
|
20305
|
+
function cliPackageRoot(entry = currentEntry()) {
|
|
20306
|
+
if (isNativeEntry(entry))
|
|
20307
|
+
return process.cwd();
|
|
20308
|
+
if (isBundledCliProcess(entry))
|
|
20309
|
+
return resolve2(dirname3(entry), "..");
|
|
20310
|
+
return resolve2(import.meta.dir, "../..");
|
|
20311
|
+
}
|
|
20312
|
+
function bundledCliConfig(entry) {
|
|
20313
|
+
const config = resolve2(dirname3(entry), "../../../tools/bundled-cli-bunfig.toml");
|
|
20314
|
+
if (!existsSync(config))
|
|
20315
|
+
return;
|
|
20316
|
+
return config;
|
|
20317
|
+
}
|
|
20318
|
+
function selfCliCommand(args, entry = currentEntry()) {
|
|
20319
|
+
if (isNativeEntry(entry))
|
|
20320
|
+
return [process.execPath, ...args];
|
|
20321
|
+
const bun = process.argv[0] ?? "bun";
|
|
20322
|
+
if (isJavaScriptEntry(entry)) {
|
|
20323
|
+
const config = bundledCliConfig(entry);
|
|
20324
|
+
if (config)
|
|
20325
|
+
return [bun, `--config=${config}`, entry, ...args];
|
|
20326
|
+
return [bun, entry, ...args];
|
|
20327
|
+
}
|
|
20328
|
+
const bunfigApplies = resolve2(process.cwd()) === CLI_ROOT;
|
|
20329
|
+
if (entry.endsWith(".ts") && !bunfigApplies) {
|
|
20330
|
+
return [bun, "--preload", "@ttsc/unplugin/bun-register", entry, ...args];
|
|
20331
|
+
}
|
|
20332
|
+
return [bun, entry, ...args];
|
|
20333
|
+
}
|
|
20334
|
+
function runSelfCli(args) {
|
|
20335
|
+
const proc = Bun.spawnSync(selfCliCommand(args), {
|
|
20336
|
+
cwd: process.cwd(),
|
|
20337
|
+
stdout: "pipe",
|
|
20338
|
+
stderr: "pipe",
|
|
20339
|
+
env: process.env,
|
|
20340
|
+
maxBuffer: MAX_CAPTURE_BYTES2
|
|
20341
|
+
});
|
|
20342
|
+
return {
|
|
20343
|
+
exitCode: proc.exitCode,
|
|
20344
|
+
stdout: proc.stdout.toString(),
|
|
20345
|
+
stderr: proc.stderr.toString()
|
|
20346
|
+
};
|
|
20347
|
+
}
|
|
20348
|
+
var CLI_ROOT, MAX_CAPTURE_BYTES2;
|
|
20349
|
+
var init_selfCli = __esm(() => {
|
|
20350
|
+
CLI_ROOT = resolve2(import.meta.dir, "../..");
|
|
20351
|
+
MAX_CAPTURE_BYTES2 = 8 * 1024 * 1024;
|
|
20352
|
+
});
|
|
20353
|
+
|
|
20354
|
+
// src/io/outputMeta.ts
|
|
20355
|
+
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
20356
|
+
function repoRoot() {
|
|
20357
|
+
if (isBundledCliProcess())
|
|
20358
|
+
return resolve3(dirname4(Bun.main), "../..");
|
|
20359
|
+
return resolve3(import.meta.dir, "../../..");
|
|
20360
|
+
}
|
|
20361
|
+
function stableStringify(value) {
|
|
20362
|
+
if (value === null || typeof value !== "object") {
|
|
20363
|
+
return JSON.stringify(value);
|
|
20364
|
+
}
|
|
20365
|
+
if (Array.isArray(value)) {
|
|
20366
|
+
return `[${value.map((x) => stableStringify(x)).join(",")}]`;
|
|
20367
|
+
}
|
|
20368
|
+
const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
20369
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
20370
|
+
}
|
|
20371
|
+
function sha256(value) {
|
|
20372
|
+
return sha256Hex(value);
|
|
20373
|
+
}
|
|
20374
|
+
function resolveGitSha() {
|
|
20375
|
+
if (hasResolvedGitSha)
|
|
20376
|
+
return cachedGitSha ?? UNKNOWN_GIT_SHA;
|
|
20377
|
+
hasResolvedGitSha = true;
|
|
20378
|
+
const envSha = process.env.GITHUB_SHA?.trim();
|
|
20379
|
+
if (envSha) {
|
|
20380
|
+
cachedGitSha = envSha;
|
|
20381
|
+
return cachedGitSha;
|
|
20382
|
+
}
|
|
20383
|
+
try {
|
|
20384
|
+
cachedGitSha = runText(["git", "rev-parse", "--short", "HEAD"], {
|
|
20385
|
+
cwd: REPO_ROOT,
|
|
20386
|
+
env: process.env
|
|
20387
|
+
}).trim();
|
|
20388
|
+
} catch {
|
|
20389
|
+
cachedGitSha = UNKNOWN_GIT_SHA;
|
|
20390
|
+
}
|
|
20391
|
+
return cachedGitSha ?? UNKNOWN_GIT_SHA;
|
|
20392
|
+
}
|
|
20393
|
+
function hashContent(value) {
|
|
20394
|
+
return sha256(stableStringify(value));
|
|
20395
|
+
}
|
|
20396
|
+
function deriveDeterministicSeed(value) {
|
|
20397
|
+
const digest = hashContent(value);
|
|
20398
|
+
return Number.parseInt(digest.slice(0, 8), 16) >>> 0;
|
|
20399
|
+
}
|
|
20400
|
+
function deriveDeterministicRunId(args) {
|
|
20401
|
+
const suffix = hashContent({
|
|
20402
|
+
command: args.command,
|
|
20403
|
+
seed: args.seed,
|
|
20404
|
+
scope: args.scope ?? null
|
|
20405
|
+
}).slice(0, 8);
|
|
20406
|
+
return `${args.command}-${args.seed.toString(36)}-${suffix}`;
|
|
20407
|
+
}
|
|
20408
|
+
function outputSchemaRefForCommand(command) {
|
|
20409
|
+
return OUTPUT_SCHEMA_REF[command];
|
|
20410
|
+
}
|
|
20411
|
+
function buildOutputMeta(args) {
|
|
20412
|
+
const scenarioHash = (() => {
|
|
20413
|
+
if (args.scenario !== undefined)
|
|
20414
|
+
return hashContent(args.scenario);
|
|
20415
|
+
if (args.scenarios) {
|
|
20416
|
+
const out = {};
|
|
20417
|
+
for (const [k, v] of Object.entries(args.scenarios)) {
|
|
20418
|
+
out[k] = hashContent(v);
|
|
20419
|
+
}
|
|
20420
|
+
return out;
|
|
20421
|
+
}
|
|
20422
|
+
return;
|
|
20423
|
+
})();
|
|
20424
|
+
return {
|
|
20425
|
+
command: args.command,
|
|
20426
|
+
generatedAt: new Date().toISOString(),
|
|
20427
|
+
contractVersion: OUTPUT_CONTRACT_VERSION,
|
|
20428
|
+
schemaRef: args.schemaRef ?? outputSchemaRefForCommand(args.command),
|
|
20429
|
+
cliVersion,
|
|
20430
|
+
gitSha: resolveGitSha(),
|
|
20431
|
+
runId: args.runId,
|
|
20432
|
+
seed: args.seed,
|
|
20433
|
+
scenarioPath: args.scenarioPath,
|
|
20434
|
+
scenarioHash,
|
|
20435
|
+
tuneSpecHash: args.tuneSpec !== undefined ? hashContent(args.tuneSpec) : undefined,
|
|
20436
|
+
telemetryHash: args.telemetry !== undefined ? hashContent(args.telemetry) : undefined,
|
|
20437
|
+
pluginDigest: args.pluginDigest ?? {},
|
|
20438
|
+
...args.effectiveRunHash !== undefined ? { effectiveRunHash: args.effectiveRunHash } : {},
|
|
20439
|
+
...args.effectiveEngine !== undefined ? { effectiveEngine: args.effectiveEngine } : {},
|
|
20440
|
+
...args.stageScope !== undefined ? { stageScope: args.stageScope } : {}
|
|
20441
|
+
};
|
|
20442
|
+
}
|
|
20443
|
+
var import_package, REPO_ROOT, OUTPUT_CONTRACT_VERSION = "1.4.0", UNKNOWN_GIT_SHA = "unknown", OUTPUT_SCHEMA_REF, cachedGitSha, hasResolvedGitSha = false, cliVersion;
|
|
20444
|
+
var init_outputMeta = __esm(() => {
|
|
20445
|
+
init_bun();
|
|
20446
|
+
init_selfCli();
|
|
20447
|
+
import_package = __toESM(require_package(), 1);
|
|
20448
|
+
REPO_ROOT = repoRoot();
|
|
20449
|
+
OUTPUT_SCHEMA_REF = {
|
|
20450
|
+
simulate: "docs/schemas/simulate.output.schema.json",
|
|
20451
|
+
eta: "docs/schemas/eta.output.schema.json",
|
|
20452
|
+
compare: "docs/schemas/compare.output.schema.json",
|
|
20453
|
+
doctor: "docs/schemas/doctor.output.schema.json",
|
|
20454
|
+
evaluate: "docs/schemas/evaluate.output.schema.json",
|
|
20455
|
+
"judge.jev": "docs/schemas/jev.judge.output.schema.json",
|
|
20456
|
+
"execution.batch": "docs/worker-execution.md",
|
|
20457
|
+
experience: "docs/schemas/experience.output.schema.json",
|
|
20458
|
+
"setup.completions": "docs/schemas/setup.output.schema.json",
|
|
20459
|
+
"setup.plugin-trust": "docs/schemas/setup.output.schema.json",
|
|
20460
|
+
tune: "docs/schemas/tune.output.schema.json",
|
|
20461
|
+
ltv: "docs/schemas/ltv.output.schema.json",
|
|
20462
|
+
calibrate: "docs/schemas/calibrate.output.schema.json",
|
|
20463
|
+
"kpi.regress": "docs/schemas/kpi.regress.output.schema.json",
|
|
20464
|
+
"replay.verify": "docs/schemas/replay.verify.output.schema.json"
|
|
20465
|
+
};
|
|
20466
|
+
cliVersion = import_package.default.version ?? "0.0.0";
|
|
20467
|
+
});
|
|
20468
|
+
|
|
20469
|
+
// ../../node_modules/.bun/yaml@2.9.1/node_modules/yaml/dist/nodes/identity.js
|
|
20470
|
+
var require_identity = __commonJS(function(exports) {
|
|
20471
|
+
var ALIAS = Symbol.for("yaml.alias");
|
|
20472
|
+
var DOC = Symbol.for("yaml.document");
|
|
20473
|
+
var MAP = Symbol.for("yaml.map");
|
|
20474
|
+
var PAIR = Symbol.for("yaml.pair");
|
|
20475
|
+
var SCALAR = Symbol.for("yaml.scalar");
|
|
20476
|
+
var SEQ = Symbol.for("yaml.seq");
|
|
20477
|
+
var NODE_TYPE = Symbol.for("yaml.node.type");
|
|
20478
|
+
var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
|
|
20479
|
+
var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
|
|
20480
|
+
var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
|
|
20481
|
+
var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR;
|
|
20482
|
+
var isScalar = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR;
|
|
20483
|
+
var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ;
|
|
20484
|
+
function isCollection(node) {
|
|
20485
|
+
if (node && typeof node === "object")
|
|
20486
|
+
switch (node[NODE_TYPE]) {
|
|
20487
|
+
case MAP:
|
|
20488
|
+
case SEQ:
|
|
20489
|
+
return true;
|
|
20490
|
+
}
|
|
20491
|
+
return false;
|
|
20492
|
+
}
|
|
20493
|
+
function isNode(node) {
|
|
20494
|
+
if (node && typeof node === "object")
|
|
20495
|
+
switch (node[NODE_TYPE]) {
|
|
20496
|
+
case ALIAS:
|
|
20497
|
+
case MAP:
|
|
20498
|
+
case SCALAR:
|
|
20499
|
+
case SEQ:
|
|
20500
|
+
return true;
|
|
20501
|
+
}
|
|
20502
|
+
return false;
|
|
20503
|
+
}
|
|
20504
|
+
var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
|
|
20505
|
+
exports.ALIAS = ALIAS;
|
|
20506
|
+
exports.DOC = DOC;
|
|
20507
|
+
exports.MAP = MAP;
|
|
19857
20508
|
exports.NODE_TYPE = NODE_TYPE;
|
|
19858
20509
|
exports.PAIR = PAIR;
|
|
19859
20510
|
exports.SCALAR = SCALAR;
|
|
@@ -34562,8 +35213,8 @@ function ownedCount(state) {
|
|
|
34562
35213
|
}
|
|
34563
35214
|
function finiteLinearAmount(E, value, label) {
|
|
34564
35215
|
if (!E.isFinite(value)) {
|
|
34565
|
-
throw cliError("SCENARIO_INVALID", `linear ${label} exceeds the selected engine's finite range. Use --engine breakInfinity with simulate/evaluate/experience/ltv for a large economy, or reduce the inputs.`, {
|
|
34566
|
-
hint: "
|
|
35216
|
+
throw cliError("SCENARIO_INVALID", `linear ${label} exceeds the selected engine's finite range. Use --engine breakInfinity with simulate/evaluate/experience/ltv/compare/tune/balance for a large economy, or reduce the inputs.`, {
|
|
35217
|
+
hint: "The report command uses the number engine; reduce its inputs."
|
|
34567
35218
|
});
|
|
34568
35219
|
}
|
|
34569
35220
|
return value;
|
|
@@ -36087,767 +36738,400 @@ var inspect_default = defineCommand({
|
|
|
36087
36738
|
if (skipping) {
|
|
36088
36739
|
try {
|
|
36089
36740
|
resolveStrategySelection({ scenario, strategyRegistry: loaded.strategyRegistry, overrideId: flags.strategy });
|
|
36090
|
-
} catch {
|
|
36091
|
-
strategyUnchecked = true;
|
|
36092
|
-
}
|
|
36093
|
-
}
|
|
36094
|
-
if (strategyUnchecked && strategyId !== undefined) {
|
|
36095
|
-
const report = buildScenarioInspect({
|
|
36096
|
-
...base,
|
|
36097
|
-
decimals: strategyDecimals(flags.strategy?.trim() ? [] : [scenario.strategy?.params]),
|
|
36098
|
-
engine: {
|
|
36099
|
-
effectiveId: engine.effectiveId,
|
|
36100
|
-
source: engine.source,
|
|
36101
|
-
accepted: true,
|
|
36102
|
-
...engine.scenarioEngine ? { scenarioEngine: engine.scenarioEngine } : {}
|
|
36103
|
-
},
|
|
36104
|
-
stages: [],
|
|
36105
|
-
planner: null,
|
|
36106
|
-
plugins: { ...base.plugins, unresolvedStrategy: strategyId }
|
|
36107
|
-
});
|
|
36108
|
-
await emit2(flags, report, scenarioPath, scenario, pluginDigest);
|
|
36109
|
-
return;
|
|
36110
|
-
}
|
|
36111
|
-
const prepared = planResolvedRun(planArgs);
|
|
36112
|
-
const simulate = prepared.planFor("simulate");
|
|
36113
|
-
const experience = prepared.planFor("experience");
|
|
36114
|
-
const ltv = prepared.planFor("ltv");
|
|
36115
|
-
const plans = [simulate, experience, ltv];
|
|
36116
|
-
const report = buildScenarioInspect({
|
|
36117
|
-
...base,
|
|
36118
|
-
decimals: strategyDecimals(plans.map((plan) => plan.strategy.params)),
|
|
36119
|
-
engine: {
|
|
36120
|
-
effectiveId: prepared.engine.effectiveId,
|
|
36121
|
-
source: prepared.engine.source,
|
|
36122
|
-
accepted: true,
|
|
36123
|
-
...prepared.engine.scenarioEngine ? { scenarioEngine: prepared.engine.scenarioEngine } : {}
|
|
36124
|
-
},
|
|
36125
|
-
stages: plans.map((plan) => stageInput(plan, scenario)),
|
|
36126
|
-
planner: skipping ? null : plannerParams(simulate, pluginStrategyIds)
|
|
36127
|
-
});
|
|
36128
|
-
await emit2(flags, report, scenarioPath, scenario, pluginDigest);
|
|
36129
|
-
}
|
|
36130
|
-
});
|
|
36131
|
-
async function emit2(flags, report, scenarioPath, scenario, pluginDigest) {
|
|
36132
|
-
if (flags.format === "md") {
|
|
36133
|
-
const body = renderInspectMarkdown(report);
|
|
36134
|
-
if (!flags.out) {
|
|
36135
|
-
console.log(body);
|
|
36136
|
-
return;
|
|
36137
|
-
}
|
|
36138
|
-
}
|
|
36139
|
-
await writeOutput({
|
|
36140
|
-
format: flags.format === "md" ? "md" : "json",
|
|
36141
|
-
outPath: flags.out,
|
|
36142
|
-
data: flags.format === "md" ? renderInspectMarkdown(report) : report,
|
|
36143
|
-
meta: flags.format === "json" ? buildOutputMeta({ command: "inspect", scenarioPath, scenario, pluginDigest }) : undefined
|
|
36144
|
-
});
|
|
36145
|
-
}
|
|
36146
|
-
|
|
36147
|
-
// src/commands/judge.ts
|
|
36148
|
-
init_zod();
|
|
36149
|
-
init_errors3();
|
|
36150
|
-
init_outputMeta();
|
|
36151
|
-
init_bun();
|
|
36152
|
-
import { closeSync as closeSync2, lstatSync as lstatSync2, openSync as openSync2, readlinkSync as readlinkSync2, realpathSync as realpathSync3, statSync, unlinkSync as unlinkSync2 } from "fs";
|
|
36153
|
-
import { basename as basename3, dirname as dirname6, resolve as resolve7 } from "path";
|
|
36154
|
-
|
|
36155
|
-
// src/lib/evaluation/input.ts
|
|
36156
|
-
init_dist2();
|
|
36157
|
-
function readJudgeInputs(artifactRaw, rubricRaw) {
|
|
36158
|
-
const artifact = validateAnalysisArtifact(artifactRaw);
|
|
36159
|
-
const rubric = parseJudgeRubric(rubricRaw);
|
|
36160
|
-
const issues = [];
|
|
36161
|
-
if (!artifact.success) {
|
|
36162
|
-
issues.push(...artifact.errors.map((error) => `artifact ${error.path} ${error.expected}`));
|
|
36163
|
-
}
|
|
36164
|
-
if (!rubric.ok)
|
|
36165
|
-
issues.push(...rubric.issues);
|
|
36166
|
-
if (!artifact.success || !rubric.ok)
|
|
36167
|
-
return { ok: false, issues };
|
|
36168
|
-
return { ok: true, artifact: artifact.data, rubric: rubric.rubric };
|
|
36169
|
-
}
|
|
36170
|
-
|
|
36171
|
-
// src/lib/evaluation/rules.ts
|
|
36172
|
-
init_dist2();
|
|
36173
|
-
function evaluateRules(input) {
|
|
36174
|
-
const judged = judgeArtifact(input.artifact, input.rubric);
|
|
36175
|
-
if (!judged.ok) {
|
|
36176
|
-
throw new Error(judged.issues.join("; "));
|
|
36177
|
-
}
|
|
36178
|
-
return judged.report;
|
|
36179
|
-
}
|
|
36180
|
-
|
|
36181
|
-
// src/commands/judge.ts
|
|
36182
|
-
function canonical(path, depth = 0) {
|
|
36183
|
-
const absolute = resolve7(path);
|
|
36184
|
-
try {
|
|
36185
|
-
return realpathSync3(absolute);
|
|
36186
|
-
} catch {
|
|
36187
|
-
try {
|
|
36188
|
-
if (depth < 32 && lstatSync2(absolute).isSymbolicLink()) {
|
|
36189
|
-
return canonical(resolve7(dirname6(absolute), readlinkSync2(absolute)), depth + 1);
|
|
36190
|
-
}
|
|
36191
|
-
} catch {}
|
|
36192
|
-
const parent = dirname6(absolute);
|
|
36193
|
-
return parent === absolute ? absolute : resolve7(canonical(parent, depth), basename3(absolute));
|
|
36194
|
-
}
|
|
36195
|
-
}
|
|
36196
|
-
function existingDirectory(path) {
|
|
36197
|
-
let directory = dirname6(path);
|
|
36198
|
-
for (;; ) {
|
|
36199
|
-
try {
|
|
36200
|
-
const stat = statSync(directory);
|
|
36201
|
-
return stat.isDirectory() ? { path: directory, dev: stat.dev, ino: stat.ino } : null;
|
|
36202
|
-
} catch {
|
|
36203
|
-
const parent = dirname6(directory);
|
|
36204
|
-
if (parent === directory)
|
|
36205
|
-
return null;
|
|
36206
|
-
directory = parent;
|
|
36207
|
-
}
|
|
36208
|
-
}
|
|
36209
|
-
}
|
|
36210
|
-
function ignoresCase(directory) {
|
|
36211
|
-
const name = `.idlekit-case-${crypto.randomUUID()}`;
|
|
36212
|
-
const path = resolve7(directory, name);
|
|
36213
|
-
let fd;
|
|
36214
|
-
try {
|
|
36215
|
-
fd = openSync2(path, "wx");
|
|
36216
|
-
const original = statSync(path);
|
|
36217
|
-
const folded = statSync(resolve7(directory, name.toUpperCase()));
|
|
36218
|
-
return original.dev === folded.dev && original.ino === folded.ino;
|
|
36219
|
-
} catch {
|
|
36220
|
-
return fd === undefined && process.platform === "win32";
|
|
36221
|
-
} finally {
|
|
36222
|
-
if (fd !== undefined) {
|
|
36223
|
-
try {
|
|
36224
|
-
closeSync2(fd);
|
|
36225
|
-
} finally {
|
|
36226
|
-
unlinkSync2(path);
|
|
36227
|
-
}
|
|
36228
|
-
}
|
|
36229
|
-
}
|
|
36230
|
-
}
|
|
36231
|
-
function sameFile(left, right) {
|
|
36232
|
-
const aPath = canonical(left);
|
|
36233
|
-
const bPath = canonical(right);
|
|
36234
|
-
if (aPath === bPath)
|
|
36235
|
-
return true;
|
|
36236
|
-
try {
|
|
36237
|
-
const a = statSync(left);
|
|
36238
|
-
const b = statSync(right);
|
|
36239
|
-
return a.dev === b.dev && a.ino === b.ino;
|
|
36240
|
-
} catch {
|
|
36241
|
-
if (aPath.toLowerCase() !== bPath.toLowerCase())
|
|
36242
|
-
return false;
|
|
36243
|
-
const a = existingDirectory(aPath);
|
|
36244
|
-
const b = existingDirectory(bPath);
|
|
36245
|
-
return a !== null && b !== null && a.dev === b.dev && a.ino === b.ino && ignoresCase(a.path);
|
|
36246
|
-
}
|
|
36247
|
-
}
|
|
36248
|
-
function ancestorFile(parent, child) {
|
|
36249
|
-
let directory = dirname6(canonical(child));
|
|
36250
|
-
for (;; ) {
|
|
36251
|
-
if (sameFile(parent, directory))
|
|
36252
|
-
return true;
|
|
36253
|
-
const next = dirname6(directory);
|
|
36254
|
-
if (next === directory)
|
|
36255
|
-
return false;
|
|
36256
|
-
directory = next;
|
|
36257
|
-
}
|
|
36258
|
-
}
|
|
36259
|
-
function requireFileTarget(path, flag) {
|
|
36260
|
-
const target = canonical(path);
|
|
36261
|
-
let current = target;
|
|
36262
|
-
for (;; ) {
|
|
36263
|
-
let stat;
|
|
36264
|
-
try {
|
|
36265
|
-
stat = statSync(current);
|
|
36266
|
-
} catch (error) {
|
|
36267
|
-
if (error.code !== "ENOENT") {
|
|
36268
|
-
throw usageError(`--${flag} requires a regular file target with directory parents.`);
|
|
36269
|
-
}
|
|
36270
|
-
}
|
|
36271
|
-
if (stat) {
|
|
36272
|
-
const valid = current === target ? stat.isFile() : stat.isDirectory();
|
|
36273
|
-
if (!valid)
|
|
36274
|
-
throw usageError(`--${flag} requires a regular file target with directory parents.`);
|
|
36275
|
-
return;
|
|
36276
|
-
}
|
|
36277
|
-
const parent = dirname6(current);
|
|
36278
|
-
if (parent === current)
|
|
36279
|
-
throw usageError(`--${flag} has no usable parent directory.`);
|
|
36280
|
-
current = parent;
|
|
36281
|
-
}
|
|
36282
|
-
}
|
|
36283
|
-
function renderJudgeMarkdown(report) {
|
|
36284
|
-
const lines = [
|
|
36285
|
-
"# Rules judge",
|
|
36286
|
-
"",
|
|
36287
|
-
"This is not doctor. The numerical verdict is not rewritten.",
|
|
36288
|
-
"",
|
|
36289
|
-
`- Provider: ${report.provider}`,
|
|
36290
|
-
`- Rubric: ${report.rubricVersion}`,
|
|
36291
|
-
`- Digest: ${report.advisoryDigest}`,
|
|
36292
|
-
"",
|
|
36293
|
-
"## Numerical",
|
|
36294
|
-
""
|
|
36295
|
-
];
|
|
36296
|
-
for (const result of report.numerical.results) {
|
|
36297
|
-
lines.push(`- ${result.metricId}: ${result.verdict} ${result.evidence.value ?? "none"} ${result.evidence.unit}`);
|
|
36298
|
-
}
|
|
36299
|
-
lines.push("", "## Estimates", "");
|
|
36300
|
-
if (report.estimatedObservations.length === 0)
|
|
36301
|
-
lines.push("None.");
|
|
36302
|
-
for (const estimate of report.estimatedObservations) {
|
|
36303
|
-
lines.push(`- ${estimate.metricId}: ${estimate.value ?? "none"}`);
|
|
36304
|
-
}
|
|
36305
|
-
lines.push("", "## Semantic", "");
|
|
36306
|
-
for (const diagnostic of report.diagnostics) {
|
|
36307
|
-
lines.push(`- ${diagnostic.status} ${diagnostic.recommendationId}: ${diagnostic.text.en}`);
|
|
36308
|
-
}
|
|
36309
|
-
return lines.join(`
|
|
36310
|
-
`);
|
|
36311
|
-
}
|
|
36312
|
-
function judgeCsvRows(report) {
|
|
36313
|
-
return report.numerical.results.map((result, index) => {
|
|
36314
|
-
const diagnostic = report.diagnostics[index];
|
|
36315
|
-
return {
|
|
36316
|
-
id: result.id,
|
|
36317
|
-
metricId: result.metricId,
|
|
36318
|
-
verdict: result.verdict,
|
|
36319
|
-
severity: result.severity,
|
|
36320
|
-
evidenceStatus: result.evidence.status,
|
|
36321
|
-
value: result.evidence.value,
|
|
36322
|
-
unit: result.evidence.unit,
|
|
36323
|
-
operator: result.evidence.operator,
|
|
36324
|
-
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.");
|
|
36741
|
+
} catch {
|
|
36742
|
+
strategyUnchecked = true;
|
|
36743
|
+
}
|
|
36345
36744
|
}
|
|
36346
|
-
if (
|
|
36347
|
-
|
|
36745
|
+
if (strategyUnchecked && strategyId !== undefined) {
|
|
36746
|
+
const report = buildScenarioInspect({
|
|
36747
|
+
...base,
|
|
36748
|
+
decimals: strategyDecimals(flags.strategy?.trim() ? [] : [scenario.strategy?.params]),
|
|
36749
|
+
engine: {
|
|
36750
|
+
effectiveId: engine.effectiveId,
|
|
36751
|
+
source: engine.source,
|
|
36752
|
+
accepted: true,
|
|
36753
|
+
...engine.scenarioEngine ? { scenarioEngine: engine.scenarioEngine } : {}
|
|
36754
|
+
},
|
|
36755
|
+
stages: [],
|
|
36756
|
+
planner: null,
|
|
36757
|
+
plugins: { ...base.plugins, unresolvedStrategy: strategyId }
|
|
36758
|
+
});
|
|
36759
|
+
await emit2(flags, report, scenarioPath, scenario, pluginDigest);
|
|
36760
|
+
return;
|
|
36348
36761
|
}
|
|
36349
|
-
|
|
36350
|
-
|
|
36351
|
-
|
|
36352
|
-
|
|
36353
|
-
|
|
36354
|
-
|
|
36355
|
-
|
|
36356
|
-
|
|
36357
|
-
|
|
36358
|
-
|
|
36359
|
-
|
|
36360
|
-
|
|
36361
|
-
|
|
36362
|
-
|
|
36363
|
-
|
|
36364
|
-
|
|
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
|
|
36762
|
+
const prepared = planResolvedRun(planArgs);
|
|
36763
|
+
const simulate = prepared.planFor("simulate");
|
|
36764
|
+
const experience = prepared.planFor("experience");
|
|
36765
|
+
const ltv = prepared.planFor("ltv");
|
|
36766
|
+
const plans = [simulate, experience, ltv];
|
|
36767
|
+
const report = buildScenarioInspect({
|
|
36768
|
+
...base,
|
|
36769
|
+
decimals: strategyDecimals(plans.map((plan) => plan.strategy.params)),
|
|
36770
|
+
engine: {
|
|
36771
|
+
effectiveId: prepared.engine.effectiveId,
|
|
36772
|
+
source: prepared.engine.source,
|
|
36773
|
+
accepted: true,
|
|
36774
|
+
...prepared.engine.scenarioEngine ? { scenarioEngine: prepared.engine.scenarioEngine } : {}
|
|
36775
|
+
},
|
|
36776
|
+
stages: plans.map((plan) => stageInput(plan, scenario)),
|
|
36777
|
+
planner: skipping ? null : plannerParams(simulate, pluginStrategyIds)
|
|
36380
36778
|
});
|
|
36381
|
-
|
|
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.");
|
|
36779
|
+
await emit2(flags, report, scenarioPath, scenario, pluginDigest);
|
|
36387
36780
|
}
|
|
36388
|
-
|
|
36389
|
-
|
|
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 };
|
|
36781
|
+
});
|
|
36782
|
+
async function emit2(flags, report, scenarioPath, scenario, pluginDigest) {
|
|
36401
36783
|
if (flags.format === "md") {
|
|
36402
|
-
const
|
|
36403
|
-
|
|
36404
|
-
|
|
36405
|
-
|
|
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
|
-
`);
|
|
36784
|
+
const body = renderInspectMarkdown(report);
|
|
36785
|
+
if (!flags.out) {
|
|
36786
|
+
console.log(body);
|
|
36787
|
+
return;
|
|
36426
36788
|
}
|
|
36427
|
-
return bundle;
|
|
36428
36789
|
}
|
|
36429
36790
|
await writeOutput({
|
|
36430
|
-
format: flags.format,
|
|
36791
|
+
format: flags.format === "md" ? "md" : "json",
|
|
36431
36792
|
outPath: flags.out,
|
|
36432
|
-
data:
|
|
36433
|
-
meta: buildOutputMeta({
|
|
36434
|
-
command: "judge",
|
|
36435
|
-
pluginDigest: {},
|
|
36436
|
-
schemaRef: outputSchemaRefForCommand("judge.jev")
|
|
36437
|
-
})
|
|
36793
|
+
data: flags.format === "md" ? renderInspectMarkdown(report) : report,
|
|
36794
|
+
meta: flags.format === "json" ? buildOutputMeta({ command: "inspect", scenarioPath, scenario, pluginDigest }) : undefined
|
|
36438
36795
|
});
|
|
36439
|
-
return bundle;
|
|
36440
36796
|
}
|
|
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
36797
|
|
|
36468
|
-
// src/commands/
|
|
36798
|
+
// src/commands/judge.ts
|
|
36469
36799
|
init_zod();
|
|
36470
36800
|
init_errors3();
|
|
36471
36801
|
init_outputMeta();
|
|
36472
|
-
|
|
36802
|
+
init_bun();
|
|
36803
|
+
import { closeSync as closeSync2, lstatSync as lstatSync2, openSync as openSync2, readlinkSync as readlinkSync2, realpathSync as realpathSync3, statSync, unlinkSync as unlinkSync2 } from "fs";
|
|
36804
|
+
import { basename as basename3, dirname as dirname6, resolve as resolve7 } from "path";
|
|
36473
36805
|
|
|
36474
|
-
// src/lib/
|
|
36475
|
-
|
|
36476
|
-
|
|
36477
|
-
|
|
36478
|
-
|
|
36479
|
-
|
|
36480
|
-
|
|
36481
|
-
|
|
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;
|
|
36806
|
+
// src/lib/evaluation/input.ts
|
|
36807
|
+
init_dist2();
|
|
36808
|
+
function readJudgeInputs(artifactRaw, rubricRaw) {
|
|
36809
|
+
const artifact = validateAnalysisArtifact(artifactRaw);
|
|
36810
|
+
const rubric = parseJudgeRubric(rubricRaw);
|
|
36811
|
+
const issues = [];
|
|
36812
|
+
if (!artifact.success) {
|
|
36813
|
+
issues.push(...artifact.errors.map((error) => `artifact ${error.path} ${error.expected}`));
|
|
36513
36814
|
}
|
|
36514
|
-
|
|
36815
|
+
if (!rubric.ok)
|
|
36816
|
+
issues.push(...rubric.issues);
|
|
36817
|
+
if (!artifact.success || !rubric.ok)
|
|
36818
|
+
return { ok: false, issues };
|
|
36819
|
+
return { ok: true, artifact: artifact.data, rubric: rubric.rubric };
|
|
36515
36820
|
}
|
|
36516
|
-
|
|
36517
|
-
|
|
36518
|
-
|
|
36519
|
-
|
|
36520
|
-
|
|
36521
|
-
|
|
36522
|
-
|
|
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
|
|
36554
|
-
};
|
|
36821
|
+
|
|
36822
|
+
// src/lib/evaluation/rules.ts
|
|
36823
|
+
init_dist2();
|
|
36824
|
+
function evaluateRules(input) {
|
|
36825
|
+
const judged = judgeArtifact(input.artifact, input.rubric);
|
|
36826
|
+
if (!judged.ok) {
|
|
36827
|
+
throw new Error(judged.issues.join("; "));
|
|
36555
36828
|
}
|
|
36556
|
-
|
|
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
|
-
};
|
|
36829
|
+
return judged.report;
|
|
36574
36830
|
}
|
|
36575
|
-
|
|
36576
|
-
|
|
36577
|
-
|
|
36831
|
+
|
|
36832
|
+
// src/commands/judge.ts
|
|
36833
|
+
function canonical(path, depth = 0) {
|
|
36834
|
+
const absolute = resolve7(path);
|
|
36835
|
+
try {
|
|
36836
|
+
return realpathSync3(absolute);
|
|
36837
|
+
} catch {
|
|
36838
|
+
try {
|
|
36839
|
+
if (depth < 32 && lstatSync2(absolute).isSymbolicLink()) {
|
|
36840
|
+
return canonical(resolve7(dirname6(absolute), readlinkSync2(absolute)), depth + 1);
|
|
36841
|
+
}
|
|
36842
|
+
} catch {}
|
|
36843
|
+
const parent = dirname6(absolute);
|
|
36844
|
+
return parent === absolute ? absolute : resolve7(canonical(parent, depth), basename3(absolute));
|
|
36578
36845
|
}
|
|
36579
|
-
|
|
36580
|
-
|
|
36581
|
-
|
|
36582
|
-
|
|
36583
|
-
|
|
36584
|
-
|
|
36585
|
-
|
|
36586
|
-
|
|
36587
|
-
|
|
36588
|
-
|
|
36846
|
+
}
|
|
36847
|
+
function existingDirectory(path) {
|
|
36848
|
+
let directory = dirname6(path);
|
|
36849
|
+
for (;; ) {
|
|
36850
|
+
try {
|
|
36851
|
+
const stat = statSync(directory);
|
|
36852
|
+
return stat.isDirectory() ? { path: directory, dev: stat.dev, ino: stat.ino } : null;
|
|
36853
|
+
} catch {
|
|
36854
|
+
const parent = dirname6(directory);
|
|
36855
|
+
if (parent === directory)
|
|
36856
|
+
return null;
|
|
36857
|
+
directory = parent;
|
|
36589
36858
|
}
|
|
36590
|
-
users.set(row.userId, u);
|
|
36591
36859
|
}
|
|
36592
|
-
|
|
36593
|
-
|
|
36594
|
-
|
|
36595
|
-
|
|
36596
|
-
|
|
36597
|
-
|
|
36598
|
-
|
|
36599
|
-
|
|
36600
|
-
|
|
36601
|
-
|
|
36602
|
-
|
|
36603
|
-
|
|
36604
|
-
|
|
36605
|
-
|
|
36606
|
-
|
|
36607
|
-
|
|
36608
|
-
|
|
36609
|
-
|
|
36610
|
-
|
|
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;
|
|
36620
|
-
}
|
|
36621
|
-
if (user.acq !== undefined) {
|
|
36622
|
-
totalAcq += user.acq;
|
|
36623
|
-
acqUsers += 1;
|
|
36860
|
+
}
|
|
36861
|
+
function ignoresCase(directory) {
|
|
36862
|
+
const name = `.idlekit-case-${crypto.randomUUID()}`;
|
|
36863
|
+
const path = resolve7(directory, name);
|
|
36864
|
+
let fd;
|
|
36865
|
+
try {
|
|
36866
|
+
fd = openSync2(path, "wx");
|
|
36867
|
+
const original = statSync(path);
|
|
36868
|
+
const folded = statSync(resolve7(directory, name.toUpperCase()));
|
|
36869
|
+
return original.dev === folded.dev && original.ino === folded.ino;
|
|
36870
|
+
} catch {
|
|
36871
|
+
return fd === undefined && process.platform === "win32";
|
|
36872
|
+
} finally {
|
|
36873
|
+
if (fd !== undefined) {
|
|
36874
|
+
try {
|
|
36875
|
+
closeSync2(fd);
|
|
36876
|
+
} finally {
|
|
36877
|
+
unlinkSync2(path);
|
|
36878
|
+
}
|
|
36624
36879
|
}
|
|
36625
36880
|
}
|
|
36626
|
-
|
|
36627
|
-
|
|
36628
|
-
const
|
|
36629
|
-
const
|
|
36630
|
-
|
|
36631
|
-
|
|
36632
|
-
|
|
36633
|
-
|
|
36634
|
-
|
|
36635
|
-
|
|
36636
|
-
|
|
36637
|
-
|
|
36638
|
-
|
|
36639
|
-
const
|
|
36640
|
-
|
|
36641
|
-
|
|
36642
|
-
userArppu.push(arppuScore);
|
|
36643
|
-
userAdArpDau.push(adScore);
|
|
36881
|
+
}
|
|
36882
|
+
function sameFile(left, right) {
|
|
36883
|
+
const aPath = canonical(left);
|
|
36884
|
+
const bPath = canonical(right);
|
|
36885
|
+
if (aPath === bPath)
|
|
36886
|
+
return true;
|
|
36887
|
+
try {
|
|
36888
|
+
const a = statSync(left);
|
|
36889
|
+
const b = statSync(right);
|
|
36890
|
+
return a.dev === b.dev && a.ino === b.ino;
|
|
36891
|
+
} catch {
|
|
36892
|
+
if (aPath.toLowerCase() !== bPath.toLowerCase())
|
|
36893
|
+
return false;
|
|
36894
|
+
const a = existingDirectory(aPath);
|
|
36895
|
+
const b = existingDirectory(bPath);
|
|
36896
|
+
return a !== null && b !== null && a.dev === b.dev && a.ino === b.ino && ignoresCase(a.path);
|
|
36644
36897
|
}
|
|
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
|
|
36743
|
-
}
|
|
36744
|
-
};
|
|
36745
|
-
assertFiniteNumbers(result, "calibration");
|
|
36746
|
-
return result;
|
|
36747
36898
|
}
|
|
36748
|
-
|
|
36749
|
-
|
|
36750
|
-
|
|
36751
|
-
|
|
36752
|
-
|
|
36753
|
-
|
|
36754
|
-
|
|
36899
|
+
function ancestorFile(parent, child) {
|
|
36900
|
+
let directory = dirname6(canonical(child));
|
|
36901
|
+
for (;; ) {
|
|
36902
|
+
if (sameFile(parent, directory))
|
|
36903
|
+
return true;
|
|
36904
|
+
const next = dirname6(directory);
|
|
36905
|
+
if (next === directory)
|
|
36906
|
+
return false;
|
|
36907
|
+
directory = next;
|
|
36908
|
+
}
|
|
36755
36909
|
}
|
|
36756
|
-
function
|
|
36757
|
-
const
|
|
36758
|
-
|
|
36759
|
-
|
|
36760
|
-
|
|
36761
|
-
|
|
36762
|
-
|
|
36763
|
-
}
|
|
36764
|
-
|
|
36765
|
-
|
|
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
|
|
36910
|
+
function requireFileTarget(path, flag) {
|
|
36911
|
+
const target = canonical(path);
|
|
36912
|
+
let current = target;
|
|
36913
|
+
for (;; ) {
|
|
36914
|
+
let stat;
|
|
36915
|
+
try {
|
|
36916
|
+
stat = statSync(current);
|
|
36917
|
+
} catch (error) {
|
|
36918
|
+
if (error.code !== "ENOENT") {
|
|
36919
|
+
throw usageError(`--${flag} requires a regular file target with directory parents.`);
|
|
36801
36920
|
}
|
|
36802
36921
|
}
|
|
36803
|
-
|
|
36922
|
+
if (stat) {
|
|
36923
|
+
const valid = current === target ? stat.isFile() : stat.isDirectory();
|
|
36924
|
+
if (!valid)
|
|
36925
|
+
throw usageError(`--${flag} requires a regular file target with directory parents.`);
|
|
36926
|
+
return;
|
|
36927
|
+
}
|
|
36928
|
+
const parent = dirname6(current);
|
|
36929
|
+
if (parent === current)
|
|
36930
|
+
throw usageError(`--${flag} has no usable parent directory.`);
|
|
36931
|
+
current = parent;
|
|
36932
|
+
}
|
|
36804
36933
|
}
|
|
36805
|
-
function
|
|
36806
|
-
|
|
36807
|
-
|
|
36808
|
-
|
|
36809
|
-
|
|
36810
|
-
|
|
36811
|
-
|
|
36812
|
-
|
|
36813
|
-
|
|
36814
|
-
|
|
36815
|
-
|
|
36816
|
-
|
|
36817
|
-
|
|
36934
|
+
function renderJudgeMarkdown(report) {
|
|
36935
|
+
const lines = [
|
|
36936
|
+
"# Rules judge",
|
|
36937
|
+
"",
|
|
36938
|
+
"This is not doctor. The numerical verdict is not rewritten.",
|
|
36939
|
+
"",
|
|
36940
|
+
`- Provider: ${report.provider}`,
|
|
36941
|
+
`- Rubric: ${report.rubricVersion}`,
|
|
36942
|
+
`- Digest: ${report.advisoryDigest}`,
|
|
36943
|
+
"",
|
|
36944
|
+
"## Numerical",
|
|
36945
|
+
""
|
|
36946
|
+
];
|
|
36947
|
+
for (const result of report.numerical.results) {
|
|
36948
|
+
lines.push(`- ${result.metricId}: ${result.verdict} ${result.evidence.value ?? "none"} ${result.evidence.unit}`);
|
|
36949
|
+
}
|
|
36950
|
+
lines.push("", "## Estimates", "");
|
|
36951
|
+
if (report.estimatedObservations.length === 0)
|
|
36952
|
+
lines.push("None.");
|
|
36953
|
+
for (const estimate of report.estimatedObservations) {
|
|
36954
|
+
lines.push(`- ${estimate.metricId}: ${estimate.value ?? "none"}`);
|
|
36955
|
+
}
|
|
36956
|
+
lines.push("", "## Semantic", "");
|
|
36957
|
+
for (const diagnostic of report.diagnostics) {
|
|
36958
|
+
lines.push(`- ${diagnostic.status} ${diagnostic.recommendationId}: ${diagnostic.text.en}`);
|
|
36959
|
+
}
|
|
36960
|
+
return lines.join(`
|
|
36961
|
+
`);
|
|
36818
36962
|
}
|
|
36819
|
-
function
|
|
36820
|
-
|
|
36821
|
-
|
|
36963
|
+
function judgeCsvRows(report) {
|
|
36964
|
+
return report.numerical.results.map((result, index) => {
|
|
36965
|
+
const diagnostic = report.diagnostics[index];
|
|
36966
|
+
return {
|
|
36967
|
+
id: result.id,
|
|
36968
|
+
metricId: result.metricId,
|
|
36969
|
+
verdict: result.verdict,
|
|
36970
|
+
severity: result.severity,
|
|
36971
|
+
evidenceStatus: result.evidence.status,
|
|
36972
|
+
value: result.evidence.value,
|
|
36973
|
+
unit: result.evidence.unit,
|
|
36974
|
+
operator: result.evidence.operator,
|
|
36975
|
+
threshold: result.evidence.threshold,
|
|
36976
|
+
upperThreshold: result.evidence.upperThreshold,
|
|
36977
|
+
diagnosticStatus: diagnostic?.status ?? null,
|
|
36978
|
+
recommendationId: diagnostic?.recommendationId ?? null
|
|
36979
|
+
};
|
|
36980
|
+
});
|
|
36981
|
+
}
|
|
36982
|
+
async function runJudge(flags, analysisPath, hooks) {
|
|
36983
|
+
const requestedModel = (flags.model ?? "jev-latest").trim();
|
|
36984
|
+
if (flags.provider === "jev" && requestedModel.length === 0) {
|
|
36985
|
+
throw usageError("--model requires a nonempty model name.");
|
|
36986
|
+
}
|
|
36987
|
+
if (flags.provider === "jev" && flags.format === "csv") {
|
|
36988
|
+
throw usageError("--provider jev writes json or md. csv cannot hold the rules report and the advisory together.");
|
|
36989
|
+
}
|
|
36990
|
+
if (flags.provider === "jev" && flags.cache) {
|
|
36991
|
+
if (flags.out && sameFile(flags.cache, flags.out)) {
|
|
36992
|
+
throw usageError("--cache and --out name the same file. Give the Jev cache its own path.");
|
|
36993
|
+
}
|
|
36994
|
+
if (flags.out && (ancestorFile(flags.cache, flags.out) || ancestorFile(flags.out, flags.cache))) {
|
|
36995
|
+
throw usageError("--cache and --out cannot be ancestor paths. Give each file a separate target.");
|
|
36996
|
+
}
|
|
36997
|
+
if (sameFile(flags.cache, analysisPath) || sameFile(flags.cache, flags.rubric)) {
|
|
36998
|
+
throw usageError("--cache names an input file. Give the Jev cache its own path.");
|
|
36999
|
+
}
|
|
37000
|
+
}
|
|
37001
|
+
if (flags.provider === "jev") {
|
|
37002
|
+
if (flags.cache)
|
|
37003
|
+
requireFileTarget(flags.cache, "cache");
|
|
37004
|
+
if (flags.out)
|
|
37005
|
+
requireFileTarget(flags.out, "out");
|
|
37006
|
+
}
|
|
37007
|
+
let artifactRaw;
|
|
37008
|
+
let rubricRaw;
|
|
37009
|
+
try {
|
|
37010
|
+
artifactRaw = await readJsonFile(analysisPath);
|
|
37011
|
+
rubricRaw = await readJsonFile(flags.rubric);
|
|
37012
|
+
} catch (error) {
|
|
37013
|
+
throw usageError(`Invalid judge input: ${error instanceof Error ? error.message : String(error)}`);
|
|
37014
|
+
}
|
|
37015
|
+
const parsed = readJudgeInputs(artifactRaw, rubricRaw);
|
|
37016
|
+
if (!parsed.ok) {
|
|
37017
|
+
throw usageError(`Invalid judge input: ${parsed.issues.join("; ")}`);
|
|
37018
|
+
}
|
|
37019
|
+
let report;
|
|
37020
|
+
try {
|
|
37021
|
+
report = evaluateRules(parsed);
|
|
37022
|
+
} catch (error) {
|
|
37023
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37024
|
+
throw usageError(`Invalid judge input: ${message}`);
|
|
37025
|
+
}
|
|
37026
|
+
if (flags.provider === "rules") {
|
|
37027
|
+
await writeOutput({
|
|
37028
|
+
format: flags.format,
|
|
37029
|
+
outPath: flags.out,
|
|
37030
|
+
data: flags.format === "md" ? renderJudgeMarkdown(report) : flags.format === "csv" ? judgeCsvRows(report) : report
|
|
37031
|
+
});
|
|
37032
|
+
return report;
|
|
37033
|
+
}
|
|
37034
|
+
await Promise.resolve().then(() => init_jev());
|
|
37035
|
+
const key = (hooks?.env ?? process.env).TYPESAFE_API_KEY?.trim() ?? "";
|
|
37036
|
+
if (!isSafeJevReport(report, key)) {
|
|
37037
|
+
throw usageError("Unsafe Jev numerical report metadata. Remove secrets and local paths from the judge inputs.");
|
|
37038
|
+
}
|
|
37039
|
+
const advisory = await adviseWithJev({
|
|
37040
|
+
artifact: parsed.artifact,
|
|
37041
|
+
rubric: parsed.rubric,
|
|
37042
|
+
numerical: report,
|
|
37043
|
+
requestedModel,
|
|
37044
|
+
dryRun: flags["dry-run"] === true,
|
|
37045
|
+
cachePath: flags.cache,
|
|
37046
|
+
transport: hooks?.transport,
|
|
37047
|
+
env: hooks?.env,
|
|
37048
|
+
nowMs: hooks?.nowMs,
|
|
37049
|
+
sleep: hooks?.sleep
|
|
37050
|
+
});
|
|
37051
|
+
const bundle = { numericalReport: report, advisory };
|
|
37052
|
+
if (flags.format === "md") {
|
|
37053
|
+
const markdown = [
|
|
37054
|
+
renderJudgeMarkdown(report),
|
|
37055
|
+
`- Jev: ${advisory.status} ${advisory.reason}`,
|
|
37056
|
+
`- Endpoint: ${advisory.endpoint}`,
|
|
37057
|
+
`- Planned requests: ${advisory.plannedRequests}`,
|
|
37058
|
+
"",
|
|
37059
|
+
"## Jev answers",
|
|
37060
|
+
"",
|
|
37061
|
+
...advisory.answers.map((answer) => {
|
|
37062
|
+
const value = answer.noul ?? answer.choice ?? answer.score ?? "none";
|
|
37063
|
+
const confidence = answer.confidence === null ? "" : `; confidence=${answer.confidence}`;
|
|
37064
|
+
return `- ${answer.id}: ${answer.type}=${value}${confidence}; ${answer.disposition}`;
|
|
37065
|
+
}),
|
|
37066
|
+
...advisory.answers.length === 0 ? ["None."] : []
|
|
37067
|
+
].join(`
|
|
37068
|
+
`);
|
|
37069
|
+
if (flags.out) {
|
|
37070
|
+
const outPath = resolve7(flags.out);
|
|
37071
|
+
await writeTextFile(outPath, `${markdown}
|
|
37072
|
+
`);
|
|
37073
|
+
console.log(`Wrote md output to ${outPath}`);
|
|
37074
|
+
} else {
|
|
37075
|
+
await writeStdout(`${markdown}
|
|
37076
|
+
`);
|
|
37077
|
+
}
|
|
37078
|
+
return bundle;
|
|
37079
|
+
}
|
|
37080
|
+
await writeOutput({
|
|
37081
|
+
format: flags.format,
|
|
37082
|
+
outPath: flags.out,
|
|
37083
|
+
data: bundle,
|
|
37084
|
+
meta: buildOutputMeta({
|
|
37085
|
+
command: "judge",
|
|
37086
|
+
pluginDigest: {},
|
|
37087
|
+
schemaRef: outputSchemaRefForCommand("judge.jev")
|
|
37088
|
+
})
|
|
37089
|
+
});
|
|
37090
|
+
return bundle;
|
|
37091
|
+
}
|
|
37092
|
+
var judge_default = defineCommand({
|
|
37093
|
+
name: "judge",
|
|
37094
|
+
description: "Advise on an analysis artifact with a versioned rules rubric",
|
|
37095
|
+
options: {
|
|
37096
|
+
rubric: option(string2(), { description: "Versioned rubric JSON path" }),
|
|
37097
|
+
provider: option(_enum(["rules", "jev"]).default("rules"), {
|
|
37098
|
+
description: "Advisory provider. rules is local. jev is an opt-in network provider."
|
|
37099
|
+
}),
|
|
37100
|
+
"dry-run": option(exports_coerce.boolean().default(false), {
|
|
37101
|
+
description: "Show the Jev request plan without calling the network"
|
|
37102
|
+
}),
|
|
37103
|
+
model: option(string2().trim().min(1).default("jev-latest"), {
|
|
37104
|
+
description: "Requested Jev model. jev-latest is an alias, not a pinned id."
|
|
37105
|
+
}),
|
|
37106
|
+
cache: option(string2().optional(), { description: "Optional Jev advisory cache path" }),
|
|
37107
|
+
out: option(string2().optional(), { description: "Output path" }),
|
|
37108
|
+
format: option(_enum(["json", "md", "csv"]).default("json"), { description: "Output format" })
|
|
37109
|
+
},
|
|
37110
|
+
async handler({ flags, positional }) {
|
|
37111
|
+
const analysisPath = positional[0];
|
|
37112
|
+
if (!analysisPath) {
|
|
37113
|
+
throw usageError("Usage: idk judge <analysis.json> --rubric <rubric.json> --provider rules");
|
|
37114
|
+
}
|
|
37115
|
+
await runJudge(flags, analysisPath);
|
|
36822
37116
|
}
|
|
36823
|
-
|
|
36824
|
-
|
|
36825
|
-
|
|
36826
|
-
|
|
36827
|
-
|
|
36828
|
-
|
|
36829
|
-
|
|
36830
|
-
|
|
36831
|
-
|
|
36832
|
-
|
|
36833
|
-
|
|
36834
|
-
|
|
36835
|
-
const active = retentionAtDay(config.retention, day);
|
|
36836
|
-
gross += active * (payerConversion * arppuDaily + adArpDau);
|
|
37117
|
+
});
|
|
37118
|
+
|
|
37119
|
+
// src/commands/calibrate.ts
|
|
37120
|
+
init_zod();
|
|
37121
|
+
init_errors3();
|
|
37122
|
+
init_outputMeta();
|
|
37123
|
+
import { extname as extname3, resolve as resolve8 } from "path";
|
|
37124
|
+
|
|
37125
|
+
// src/lib/ltvCalibration.ts
|
|
37126
|
+
function assertFiniteNumbers(value, path) {
|
|
37127
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
37128
|
+
throw new RangeError(`Calibration requires finite numbers; numeric overflow at ${path}`);
|
|
36837
37129
|
}
|
|
36838
|
-
if (
|
|
36839
|
-
const
|
|
36840
|
-
|
|
37130
|
+
if (typeof value === "object" && value !== null) {
|
|
37131
|
+
for (const [key, child] of Object.entries(value))
|
|
37132
|
+
assertFiniteNumbers(child, `${path}.${key}`);
|
|
36841
37133
|
}
|
|
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
37134
|
}
|
|
36850
|
-
// src/lib/ltvUncertainty.ts
|
|
36851
37135
|
function clamp014(v) {
|
|
36852
37136
|
if (v < 0)
|
|
36853
37137
|
return 0;
|
|
@@ -36864,278 +37148,260 @@ function clampCorrelation2(v) {
|
|
|
36864
37148
|
return 1;
|
|
36865
37149
|
return v;
|
|
36866
37150
|
}
|
|
36867
|
-
function
|
|
36868
|
-
|
|
36869
|
-
|
|
36870
|
-
|
|
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
|
-
}));
|
|
36902
|
-
}
|
|
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
|
-
}
|
|
36924
|
-
}
|
|
36925
|
-
return l;
|
|
37151
|
+
function mean(values) {
|
|
37152
|
+
if (values.length === 0)
|
|
37153
|
+
return 0;
|
|
37154
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
36926
37155
|
}
|
|
36927
|
-
function
|
|
36928
|
-
|
|
36929
|
-
|
|
36930
|
-
|
|
36931
|
-
|
|
36932
|
-
|
|
36933
|
-
|
|
36934
|
-
|
|
37156
|
+
function variance(values) {
|
|
37157
|
+
if (values.length === 0)
|
|
37158
|
+
return 0;
|
|
37159
|
+
const m = mean(values);
|
|
37160
|
+
let acc = 0;
|
|
37161
|
+
for (const v of values) {
|
|
37162
|
+
const d = v - m;
|
|
37163
|
+
acc += d * d;
|
|
36935
37164
|
}
|
|
36936
|
-
return
|
|
37165
|
+
return acc / values.length;
|
|
36937
37166
|
}
|
|
36938
|
-
function
|
|
36939
|
-
const
|
|
36940
|
-
|
|
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;
|
|
36946
|
-
}
|
|
36947
|
-
if (!l) {
|
|
37167
|
+
function estimateCorrelation(x, y) {
|
|
37168
|
+
const n = Math.min(x.length, y.length);
|
|
37169
|
+
if (n < 3) {
|
|
36948
37170
|
return {
|
|
36949
|
-
|
|
36950
|
-
|
|
36951
|
-
|
|
36952
|
-
|
|
37171
|
+
raw: 0,
|
|
37172
|
+
value: 0,
|
|
37173
|
+
confidence: 0,
|
|
37174
|
+
shrinkage: 0,
|
|
37175
|
+
sampleSize: n,
|
|
37176
|
+
varianceX: 0,
|
|
37177
|
+
varianceY: 0
|
|
36953
37178
|
};
|
|
36954
37179
|
}
|
|
36955
|
-
const
|
|
36956
|
-
|
|
36957
|
-
|
|
36958
|
-
|
|
36959
|
-
|
|
36960
|
-
|
|
36961
|
-
|
|
36962
|
-
|
|
36963
|
-
|
|
36964
|
-
|
|
36965
|
-
|
|
36966
|
-
|
|
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))
|
|
36988
|
-
}
|
|
36989
|
-
};
|
|
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);
|
|
37014
|
-
}
|
|
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);
|
|
37019
|
-
}
|
|
37020
|
-
return {
|
|
37021
|
-
mean: {
|
|
37022
|
-
cumulativeGrossRevenuePerUser: avg(grossSamples),
|
|
37023
|
-
cumulativeNetRevenuePerUser: avg(netSamples),
|
|
37024
|
-
cumulativeLtvPerUser: avg(ltvSamples)
|
|
37025
|
-
},
|
|
37026
|
-
quantiles: byQuantile
|
|
37027
|
-
};
|
|
37028
|
-
}
|
|
37029
|
-
// src/lib/cohortObservation.ts
|
|
37030
|
-
var RETENTION_DAYS = [1, 7, 30, 90];
|
|
37031
|
-
function observeCohort(rows, observedThroughDay) {
|
|
37032
|
-
if (!Number.isSafeInteger(observedThroughDay) || observedThroughDay < 0)
|
|
37033
|
-
throw new Error("observed-through-day must be a nonnegative safe integer");
|
|
37034
|
-
if (rows.length === 0)
|
|
37035
|
-
throw new Error("Observed cohort requires at least one install record");
|
|
37036
|
-
const users = new Map;
|
|
37037
|
-
for (const row of rows) {
|
|
37038
|
-
if (!row.userId.trim() || !Number.isSafeInteger(row.day) || row.day < 0 || row.day > observedThroughDay) {
|
|
37039
|
-
throw new Error("Observed cohort requires a user ID and integer days within observed-through-day");
|
|
37040
|
-
}
|
|
37041
|
-
if (![row.iapRevenue, row.adRevenue, ...row.acquisitionCost === undefined ? [] : [row.acquisitionCost]].every((v) => Number.isFinite(v) && v >= 0)) {
|
|
37042
|
-
throw new Error("Observed cohort revenue and acquisition costs must be finite and nonnegative");
|
|
37043
|
-
}
|
|
37044
|
-
if (row.active !== undefined && typeof row.active !== "boolean")
|
|
37045
|
-
throw new Error("Observed cohort active must be boolean");
|
|
37046
|
-
const user = users.get(row.userId) ?? { days: new Set, active: new Set };
|
|
37047
|
-
if (user.days.has(row.day))
|
|
37048
|
-
throw new Error("Observed cohort requires one aggregated row per user and day");
|
|
37049
|
-
user.days.add(row.day);
|
|
37050
|
-
if (row.active ?? true)
|
|
37051
|
-
user.active.add(row.day);
|
|
37052
|
-
users.set(row.userId, user);
|
|
37180
|
+
const sx = x.slice(0, n);
|
|
37181
|
+
const sy = y.slice(0, n);
|
|
37182
|
+
const mx = mean(sx);
|
|
37183
|
+
const my = mean(sy);
|
|
37184
|
+
let cov = 0;
|
|
37185
|
+
let vx = 0;
|
|
37186
|
+
let vy = 0;
|
|
37187
|
+
for (let i = 0;i < n; i++) {
|
|
37188
|
+
const dx = (sx[i] ?? 0) - mx;
|
|
37189
|
+
const dy = (sy[i] ?? 0) - my;
|
|
37190
|
+
cov += dx * dy;
|
|
37191
|
+
vx += dx * dx;
|
|
37192
|
+
vy += dy * dy;
|
|
37053
37193
|
}
|
|
37054
|
-
|
|
37055
|
-
|
|
37056
|
-
|
|
37194
|
+
const varianceX = vx / n;
|
|
37195
|
+
const varianceY = vy / n;
|
|
37196
|
+
if (vx <= 0.000000000001 || vy <= 0.000000000001) {
|
|
37197
|
+
return {
|
|
37198
|
+
raw: 0,
|
|
37199
|
+
value: 0,
|
|
37200
|
+
confidence: 0,
|
|
37201
|
+
shrinkage: 0,
|
|
37202
|
+
sampleSize: n,
|
|
37203
|
+
varianceX,
|
|
37204
|
+
varianceY
|
|
37205
|
+
};
|
|
37057
37206
|
}
|
|
37058
|
-
const
|
|
37059
|
-
const
|
|
37060
|
-
|
|
37061
|
-
|
|
37062
|
-
|
|
37063
|
-
|
|
37064
|
-
|
|
37065
|
-
|
|
37066
|
-
|
|
37067
|
-
|
|
37068
|
-
|
|
37069
|
-
|
|
37070
|
-
|
|
37071
|
-
|
|
37072
|
-
|
|
37207
|
+
const raw = clampCorrelation2(cov / Math.sqrt(vx) / Math.sqrt(vy));
|
|
37208
|
+
const ci95 = 1.96 / Math.sqrt(Math.max(1, n - 3));
|
|
37209
|
+
const sampleConfidence = clamp014(1 - ci95);
|
|
37210
|
+
const varianceConfidenceX = varianceX / (varianceX + 0.01);
|
|
37211
|
+
const varianceConfidenceY = varianceY / (varianceY + 0.01);
|
|
37212
|
+
const varianceConfidence = Math.sqrt(varianceConfidenceX * varianceConfidenceY);
|
|
37213
|
+
const confidence = clamp014(sampleConfidence * varianceConfidence);
|
|
37214
|
+
const shrinkage = confidence;
|
|
37215
|
+
const value = raw * shrinkage;
|
|
37216
|
+
return {
|
|
37217
|
+
raw,
|
|
37218
|
+
value,
|
|
37219
|
+
confidence,
|
|
37220
|
+
shrinkage,
|
|
37221
|
+
sampleSize: n,
|
|
37222
|
+
varianceX,
|
|
37223
|
+
varianceY
|
|
37073
37224
|
};
|
|
37074
|
-
assertCohortObservation(observation);
|
|
37075
|
-
return observation;
|
|
37076
37225
|
}
|
|
37077
|
-
function
|
|
37078
|
-
if (
|
|
37079
|
-
throw new Error("
|
|
37226
|
+
function calibrateMonetization(rows) {
|
|
37227
|
+
if (rows.length === 0) {
|
|
37228
|
+
throw new Error("calibration rows cannot be empty");
|
|
37080
37229
|
}
|
|
37081
|
-
|
|
37082
|
-
|
|
37083
|
-
|
|
37084
|
-
|
|
37085
|
-
|
|
37086
|
-
|
|
37087
|
-
|
|
37088
|
-
|
|
37089
|
-
if (
|
|
37090
|
-
|
|
37091
|
-
throw new Error("Future retention must remain unobserved");
|
|
37092
|
-
continue;
|
|
37230
|
+
const users = new Map;
|
|
37231
|
+
for (const row of rows) {
|
|
37232
|
+
const day = Math.max(0, Math.floor(row.day));
|
|
37233
|
+
const u = users.get(row.userId) ?? { days: new Set, iap: 0, ad: 0, acq: undefined };
|
|
37234
|
+
if (row.active ?? true)
|
|
37235
|
+
u.days.add(day);
|
|
37236
|
+
u.iap += Math.max(0, row.iapRevenue);
|
|
37237
|
+
u.ad += Math.max(0, row.adRevenue);
|
|
37238
|
+
if (row.acquisitionCost !== undefined && Number.isFinite(row.acquisitionCost)) {
|
|
37239
|
+
u.acq = (u.acq ?? 0) + Math.max(0, row.acquisitionCost);
|
|
37093
37240
|
}
|
|
37094
|
-
|
|
37095
|
-
|
|
37241
|
+
users.set(row.userId, u);
|
|
37242
|
+
}
|
|
37243
|
+
const cohortSize = Math.max(1, users.size);
|
|
37244
|
+
const dayActiveCount = (targetDay) => {
|
|
37245
|
+
let count = 0;
|
|
37246
|
+
for (const user of users.values())
|
|
37247
|
+
if (user.days.has(targetDay))
|
|
37248
|
+
count += 1;
|
|
37249
|
+
return count;
|
|
37250
|
+
};
|
|
37251
|
+
const d1 = dayActiveCount(1) / cohortSize;
|
|
37252
|
+
const d7 = dayActiveCount(7) / cohortSize;
|
|
37253
|
+
const d30 = dayActiveCount(30) / cohortSize;
|
|
37254
|
+
const d90 = dayActiveCount(90) / cohortSize;
|
|
37255
|
+
let totalIap = 0;
|
|
37256
|
+
let totalAd = 0;
|
|
37257
|
+
let payerUsers = 0;
|
|
37258
|
+
let activeUserDays = 0;
|
|
37259
|
+
let payerDays = 0;
|
|
37260
|
+
let totalAcq = 0;
|
|
37261
|
+
let acqUsers = 0;
|
|
37262
|
+
for (const user of users.values()) {
|
|
37263
|
+
totalIap += user.iap;
|
|
37264
|
+
totalAd += user.ad;
|
|
37265
|
+
if (user.iap > 0)
|
|
37266
|
+
payerUsers += 1;
|
|
37267
|
+
activeUserDays += user.days.size;
|
|
37268
|
+
for (const day of user.days) {
|
|
37269
|
+
if (day >= 0 && user.iap > 0)
|
|
37270
|
+
payerDays += 1;
|
|
37096
37271
|
}
|
|
37097
|
-
|
|
37098
|
-
|
|
37099
|
-
|
|
37272
|
+
if (user.acq !== undefined) {
|
|
37273
|
+
totalAcq += user.acq;
|
|
37274
|
+
acqUsers += 1;
|
|
37100
37275
|
}
|
|
37101
37276
|
}
|
|
37102
|
-
|
|
37103
|
-
|
|
37104
|
-
|
|
37105
|
-
|
|
37106
|
-
|
|
37107
|
-
const
|
|
37108
|
-
|
|
37109
|
-
|
|
37110
|
-
const
|
|
37111
|
-
|
|
37112
|
-
|
|
37113
|
-
|
|
37114
|
-
|
|
37115
|
-
|
|
37116
|
-
|
|
37117
|
-
|
|
37118
|
-
|
|
37119
|
-
|
|
37120
|
-
continue;
|
|
37121
|
-
if (previous && previous.rate < rate)
|
|
37122
|
-
warnings.push(`D${previous.day}=${previous.rate} is below D${day}=${rate}. LTV requires d1 >= d7 >= d30 >= d90; review observations and retained assumptions before applying a patch.`);
|
|
37123
|
-
previous = { day, rate };
|
|
37277
|
+
const payerConversion = payerUsers / cohortSize;
|
|
37278
|
+
const arppuDaily = totalIap / Math.max(1, payerDays);
|
|
37279
|
+
const adArpDau = totalAd / Math.max(1, activeUserDays);
|
|
37280
|
+
const cpi = totalAcq / Math.max(1, acqUsers);
|
|
37281
|
+
const userRetention = [];
|
|
37282
|
+
const userConversion = [];
|
|
37283
|
+
const userArppu = [];
|
|
37284
|
+
const userAdArpDau = [];
|
|
37285
|
+
for (const user of users.values()) {
|
|
37286
|
+
const activeDays = Math.max(1, user.days.size);
|
|
37287
|
+
const retentionScore = clamp014(activeDays / 30);
|
|
37288
|
+
const conversionScore = user.iap > 0 ? 1 : 0;
|
|
37289
|
+
const arppuScore = user.iap > 0 ? user.iap / activeDays : 0;
|
|
37290
|
+
const adScore = user.ad / activeDays;
|
|
37291
|
+
userRetention.push(retentionScore);
|
|
37292
|
+
userConversion.push(conversionScore);
|
|
37293
|
+
userArppu.push(arppuScore);
|
|
37294
|
+
userAdArpDau.push(adScore);
|
|
37124
37295
|
}
|
|
37125
|
-
|
|
37126
|
-
|
|
37127
|
-
|
|
37128
|
-
|
|
37129
|
-
|
|
37130
|
-
|
|
37131
|
-
|
|
37132
|
-
|
|
37133
|
-
|
|
37134
|
-
|
|
37135
|
-
|
|
37136
|
-
|
|
37137
|
-
|
|
37296
|
+
const estimatedCorrelationPair = {
|
|
37297
|
+
retentionConversion: estimateCorrelation(userRetention, userConversion),
|
|
37298
|
+
retentionArppu: estimateCorrelation(userRetention, userArppu),
|
|
37299
|
+
retentionAd: estimateCorrelation(userRetention, userAdArpDau),
|
|
37300
|
+
conversionArppu: estimateCorrelation(userConversion, userArppu),
|
|
37301
|
+
conversionAd: estimateCorrelation(userConversion, userAdArpDau),
|
|
37302
|
+
arppuAd: estimateCorrelation(userArppu, userAdArpDau)
|
|
37303
|
+
};
|
|
37304
|
+
const estimatedCorrelation = {
|
|
37305
|
+
retentionConversion: estimatedCorrelationPair.retentionConversion.value,
|
|
37306
|
+
retentionArppu: estimatedCorrelationPair.retentionArppu.value,
|
|
37307
|
+
retentionAd: estimatedCorrelationPair.retentionAd.value,
|
|
37308
|
+
conversionArppu: estimatedCorrelationPair.conversionArppu.value,
|
|
37309
|
+
conversionAd: estimatedCorrelationPair.conversionAd.value,
|
|
37310
|
+
arppuAd: estimatedCorrelationPair.arppuAd.value
|
|
37311
|
+
};
|
|
37312
|
+
const estimatedCorrelationRaw = {
|
|
37313
|
+
retentionConversion: estimatedCorrelationPair.retentionConversion.raw,
|
|
37314
|
+
retentionArppu: estimatedCorrelationPair.retentionArppu.raw,
|
|
37315
|
+
retentionAd: estimatedCorrelationPair.retentionAd.raw,
|
|
37316
|
+
conversionArppu: estimatedCorrelationPair.conversionArppu.raw,
|
|
37317
|
+
conversionAd: estimatedCorrelationPair.conversionAd.raw,
|
|
37318
|
+
arppuAd: estimatedCorrelationPair.arppuAd.raw
|
|
37319
|
+
};
|
|
37320
|
+
const correlationConfidence = {
|
|
37321
|
+
retentionConversion: estimatedCorrelationPair.retentionConversion.confidence,
|
|
37322
|
+
retentionArppu: estimatedCorrelationPair.retentionArppu.confidence,
|
|
37323
|
+
retentionAd: estimatedCorrelationPair.retentionAd.confidence,
|
|
37324
|
+
conversionArppu: estimatedCorrelationPair.conversionArppu.confidence,
|
|
37325
|
+
conversionAd: estimatedCorrelationPair.conversionAd.confidence,
|
|
37326
|
+
arppuAd: estimatedCorrelationPair.arppuAd.confidence
|
|
37327
|
+
};
|
|
37328
|
+
const result = {
|
|
37329
|
+
monetization: {
|
|
37330
|
+
cohorts: {
|
|
37331
|
+
baseUsers: cohortSize
|
|
37332
|
+
},
|
|
37333
|
+
retention: {
|
|
37334
|
+
d1: clamp014(d1),
|
|
37335
|
+
d7: clamp014(Math.min(d1, d7)),
|
|
37336
|
+
d30: clamp014(Math.min(d1, d7, d30)),
|
|
37337
|
+
d90: clamp014(Math.min(d1, d7, d30, d90)),
|
|
37338
|
+
longTailDailyDecay: 0.02
|
|
37339
|
+
},
|
|
37340
|
+
revenue: {
|
|
37341
|
+
payerConversion: clamp014(payerConversion),
|
|
37342
|
+
arppuDaily: Math.max(0, arppuDaily),
|
|
37343
|
+
adArpDau: Math.max(0, adArpDau),
|
|
37344
|
+
platformFeeRate: 0.3,
|
|
37345
|
+
grossMarginRate: 0.92
|
|
37346
|
+
},
|
|
37347
|
+
acquisition: {
|
|
37348
|
+
cpi: Math.max(0, cpi)
|
|
37349
|
+
},
|
|
37350
|
+
uncertainty: {
|
|
37351
|
+
enabled: true,
|
|
37352
|
+
draws: 300,
|
|
37353
|
+
quantiles: [0.5, 0.9],
|
|
37354
|
+
sigma: {
|
|
37355
|
+
retention: 0.08,
|
|
37356
|
+
conversion: 0.12,
|
|
37357
|
+
arppu: 0.2,
|
|
37358
|
+
ad: 0.15
|
|
37359
|
+
},
|
|
37360
|
+
correlation: {
|
|
37361
|
+
retentionConversion: estimatedCorrelation.retentionConversion,
|
|
37362
|
+
retentionArppu: estimatedCorrelation.retentionArppu,
|
|
37363
|
+
retentionAd: estimatedCorrelation.retentionAd,
|
|
37364
|
+
conversionArppu: estimatedCorrelation.conversionArppu,
|
|
37365
|
+
conversionAd: estimatedCorrelation.conversionAd,
|
|
37366
|
+
arppuAd: estimatedCorrelation.arppuAd
|
|
37367
|
+
}
|
|
37368
|
+
}
|
|
37369
|
+
},
|
|
37370
|
+
diagnostics: {
|
|
37371
|
+
users: cohortSize,
|
|
37372
|
+
totalRows: rows.length,
|
|
37373
|
+
totalIap,
|
|
37374
|
+
totalAd,
|
|
37375
|
+
activeUserDays,
|
|
37376
|
+
payerUsers,
|
|
37377
|
+
payerDays,
|
|
37378
|
+
retention: {
|
|
37379
|
+
d1,
|
|
37380
|
+
d7,
|
|
37381
|
+
d30,
|
|
37382
|
+
d90
|
|
37383
|
+
},
|
|
37384
|
+
userFeatureVariance: {
|
|
37385
|
+
retention: variance(userRetention),
|
|
37386
|
+
conversion: variance(userConversion),
|
|
37387
|
+
arppu: variance(userArppu),
|
|
37388
|
+
adArpDau: variance(userAdArpDau)
|
|
37389
|
+
},
|
|
37390
|
+
estimatedCorrelation,
|
|
37391
|
+
estimatedCorrelationRaw,
|
|
37392
|
+
correlationConfidence,
|
|
37393
|
+
correlationDiagnostics: estimatedCorrelationPair
|
|
37394
|
+
}
|
|
37395
|
+
};
|
|
37396
|
+
assertFiniteNumbers(result, "calibration");
|
|
37397
|
+
return result;
|
|
37138
37398
|
}
|
|
37399
|
+
// src/lib/ltvProjection.ts
|
|
37400
|
+
init_dist2();
|
|
37401
|
+
// src/lib/ltvUncertainty.ts
|
|
37402
|
+
init_dist2();
|
|
37403
|
+
// src/lib/cohortObservation.ts
|
|
37404
|
+
init_dist2();
|
|
37139
37405
|
function renderCohortMarkdown(observation, comparison, patch) {
|
|
37140
37406
|
const percent = (value) => value === null ? "unavailable" : `${(value * 100).toFixed(2)}%`;
|
|
37141
37407
|
const lines = [
|
|
@@ -37460,6 +37726,42 @@ var CLI_DESCRIPTION = import_package2.default.description ?? "Bun-first idle gam
|
|
|
37460
37726
|
// src/balance/workflow.ts
|
|
37461
37727
|
init_bun();
|
|
37462
37728
|
|
|
37729
|
+
// src/balance/pacingAmount.ts
|
|
37730
|
+
var MAX_DECIMAL_LENGTH = 200;
|
|
37731
|
+
var DECIMAL = /^([+-]?)(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:[eE]([+-]?\d+))?$/;
|
|
37732
|
+
function pacingValue(value) {
|
|
37733
|
+
if (typeof value === "number")
|
|
37734
|
+
return Number.isFinite(value) ? Object.is(value, -0) ? 0 : value : undefined;
|
|
37735
|
+
return typeof value === "string" && value.length <= MAX_DECIMAL_LENGTH && DECIMAL.test(value) ? value : undefined;
|
|
37736
|
+
}
|
|
37737
|
+
function parts(value) {
|
|
37738
|
+
if (pacingValue(value) === undefined)
|
|
37739
|
+
throw new RangeError("Pacing value must be finite or bounded decimal text");
|
|
37740
|
+
const match = DECIMAL.exec(String(value));
|
|
37741
|
+
const fraction = match[3] ?? match[4] ?? "";
|
|
37742
|
+
const digits = `${match[2] ?? ""}${fraction}`.replace(/^0+/, "");
|
|
37743
|
+
const sign = digits.length === 0 ? 0 : match[1] === "-" ? -1 : 1;
|
|
37744
|
+
return {
|
|
37745
|
+
sign,
|
|
37746
|
+
digits: digits.replace(/0+$/, ""),
|
|
37747
|
+
magnitude: BigInt(match[5] ?? "0") - BigInt(fraction.length) + BigInt(digits.length)
|
|
37748
|
+
};
|
|
37749
|
+
}
|
|
37750
|
+
function comparePacingValues(left, right) {
|
|
37751
|
+
if (typeof left === "number" && typeof right === "number")
|
|
37752
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
37753
|
+
const a = parts(left), b = parts(right);
|
|
37754
|
+
if (a.sign !== b.sign)
|
|
37755
|
+
return a.sign < b.sign ? -1 : 1;
|
|
37756
|
+
if (a.sign === 0)
|
|
37757
|
+
return 0;
|
|
37758
|
+
if (a.magnitude !== b.magnitude)
|
|
37759
|
+
return (a.magnitude < b.magnitude ? -1 : 1) * a.sign;
|
|
37760
|
+
const length = Math.max(a.digits.length, b.digits.length);
|
|
37761
|
+
const ad = a.digits.padEnd(length, "0"), bd = b.digits.padEnd(length, "0");
|
|
37762
|
+
return (ad < bd ? -1 : ad > bd ? 1 : 0) * a.sign;
|
|
37763
|
+
}
|
|
37764
|
+
|
|
37463
37765
|
// src/balance/pacing.ts
|
|
37464
37766
|
var PACING_LIMITS = Object.freeze({
|
|
37465
37767
|
maxRuns: 1000,
|
|
@@ -37501,6 +37803,12 @@ function finite2(value, label) {
|
|
|
37501
37803
|
invalid(`${label} must be finite`);
|
|
37502
37804
|
return Object.is(value, -0) ? 0 : value;
|
|
37503
37805
|
}
|
|
37806
|
+
function bound(value, label) {
|
|
37807
|
+
const parsed = pacingValue(value);
|
|
37808
|
+
if (parsed === undefined)
|
|
37809
|
+
invalid(`${label} must be finite or decimal text of at most 200 characters`);
|
|
37810
|
+
return parsed;
|
|
37811
|
+
}
|
|
37504
37812
|
function limit(value, maximum, label) {
|
|
37505
37813
|
if (value === undefined)
|
|
37506
37814
|
return maximum;
|
|
@@ -37559,11 +37867,11 @@ function planPacingRuns(input) {
|
|
|
37559
37867
|
if (metrics.has(metric))
|
|
37560
37868
|
invalid(`duplicate target metric ${metric}`);
|
|
37561
37869
|
metrics.add(metric);
|
|
37562
|
-
const min = raw.min === undefined ? undefined :
|
|
37563
|
-
const max = raw.max === undefined ? undefined :
|
|
37870
|
+
const min = raw.min === undefined ? undefined : bound(raw.min, `targets[${index}].min`);
|
|
37871
|
+
const max = raw.max === undefined ? undefined : bound(raw.max, `targets[${index}].max`);
|
|
37564
37872
|
if (min === undefined && max === undefined)
|
|
37565
37873
|
invalid(`target ${metric} needs min or max`);
|
|
37566
|
-
if (min !== undefined && max !== undefined && min >
|
|
37874
|
+
if (min !== undefined && max !== undefined && comparePacingValues(min, max) > 0)
|
|
37567
37875
|
invalid(`target ${metric} min exceeds max`);
|
|
37568
37876
|
return Object.freeze({ metric, unit, ...min !== undefined ? { min } : {}, ...max !== undefined ? { max } : {} });
|
|
37569
37877
|
}).sort((left, right) => compareText(left.metric, right.metric));
|
|
@@ -37642,11 +37950,12 @@ function checkTarget(target, metrics) {
|
|
|
37642
37950
|
const value = metrics[target.metric];
|
|
37643
37951
|
if (value === null)
|
|
37644
37952
|
return { ...target, value, status: "unreached", message: "Metric not reached within the run horizon" };
|
|
37645
|
-
|
|
37646
|
-
|
|
37953
|
+
const parsed = pacingValue(value);
|
|
37954
|
+
if (parsed === undefined) {
|
|
37955
|
+
return { ...target, value: null, status: "error", message: "Evaluator metric must be a finite number, bounded decimal text or null" };
|
|
37647
37956
|
}
|
|
37648
|
-
const breached = target.min !== undefined &&
|
|
37649
|
-
return { ...target, value:
|
|
37957
|
+
const breached = target.min !== undefined && comparePacingValues(parsed, target.min) < 0 || target.max !== undefined && comparePacingValues(parsed, target.max) > 0;
|
|
37958
|
+
return { ...target, value: parsed, status: breached ? "breach" : "pass" };
|
|
37650
37959
|
}
|
|
37651
37960
|
async function runPacingChecks(input, evaluator) {
|
|
37652
37961
|
const plan = planPacingRuns(input);
|
|
@@ -38410,7 +38719,8 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
|
|
|
38410
38719
|
const loaded = await loadRegistriesFromFlags(flags);
|
|
38411
38720
|
for (const [index, path] of Object.keys(loaded.pluginDigest).entries())
|
|
38412
38721
|
inputFiles[`plugin${index}`] = path;
|
|
38413
|
-
const
|
|
38722
|
+
const engine = resolveEffectiveEngine({ requested: flags.engine });
|
|
38723
|
+
const context = { cliVersion: CLI_VERSION, bunVersion: Bun.version, pluginDigests: JSON.stringify(loaded.pluginDigest), engine: engine.effectiveId };
|
|
38414
38724
|
const outputDir = resolve10(base, config.outputDir);
|
|
38415
38725
|
if (check)
|
|
38416
38726
|
return readCurrentBundle(outputDir, inputFiles, { context });
|
|
@@ -38447,7 +38757,7 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
|
|
|
38447
38757
|
const metricIds = new Set(config.metrics.map((metric) => metric.id));
|
|
38448
38758
|
if (metricIds.size !== config.metrics.length)
|
|
38449
38759
|
throw new Error("Metric IDs must be unique");
|
|
38450
|
-
const targets =
|
|
38760
|
+
const targets = plan.targets;
|
|
38451
38761
|
for (const target of targets)
|
|
38452
38762
|
if (!metricIds.has(target.metric))
|
|
38453
38763
|
throw new Error(`Unknown target metric: ${target.metric}`);
|
|
@@ -38469,6 +38779,9 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
|
|
|
38469
38779
|
const unit = metric.kind === "firstAction" || metric.kind === "firstActionPrefix" ? "seconds" : metric.kind === "endWallet" || metric.kind === "endNetWorth" ? baselineDocument.unit.code : "count";
|
|
38470
38780
|
if (target.unit !== unit)
|
|
38471
38781
|
throw new Error(`Target ${target.metric} must use unit '${unit}'`);
|
|
38782
|
+
if (metric.kind !== "endWallet" && metric.kind !== "endNetWorth" && (typeof target.min === "string" || typeof target.max === "string")) {
|
|
38783
|
+
throw new Error(`Target ${target.metric} requires finite numeric time or count bounds`);
|
|
38784
|
+
}
|
|
38472
38785
|
}
|
|
38473
38786
|
const materialize = (patches) => {
|
|
38474
38787
|
const candidate = applySheetCsv(template, schema, overlayCsv(canonicalCsv, patches));
|
|
@@ -38486,7 +38799,7 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
|
|
|
38486
38799
|
modelRegistry: loaded.modelRegistry,
|
|
38487
38800
|
strategyRegistry: loaded.strategyRegistry,
|
|
38488
38801
|
pluginDigest: loaded.pluginDigest,
|
|
38489
|
-
engineRequest:
|
|
38802
|
+
engineRequest: engine.effectiveId,
|
|
38490
38803
|
seed: descriptor.seed
|
|
38491
38804
|
});
|
|
38492
38805
|
const opened = prepared.open("simulate", `simulate:${descriptor.seed}`, { durationSec: descriptor.horizonSec });
|
|
@@ -38508,6 +38821,13 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
|
|
|
38508
38821
|
throw new Error("Action history exceeded the result budget; pacing cannot be inferred from truncated history");
|
|
38509
38822
|
const actions = run.actionsLog ?? [];
|
|
38510
38823
|
const result = {};
|
|
38824
|
+
const monetary = (amount) => {
|
|
38825
|
+
const E = opened.scenario.ctx.E;
|
|
38826
|
+
if (!E.isFinite(amount))
|
|
38827
|
+
throw new Error("Monetary metric overflowed the selected engine");
|
|
38828
|
+
const decimal = E.toString(amount);
|
|
38829
|
+
return engine.effectiveId === "number" ? Number(decimal) : decimal;
|
|
38830
|
+
};
|
|
38511
38831
|
for (const metric of config.metrics) {
|
|
38512
38832
|
if (metric.kind === "firstAction" || metric.kind === "firstActionPrefix") {
|
|
38513
38833
|
const action = actions.find((item) => metric.kind === "firstAction" ? item.actionId === metric.actionId : item.actionId.startsWith(metric.prefix));
|
|
@@ -38517,12 +38837,12 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
|
|
|
38517
38837
|
else if (metric.kind === "actionCount")
|
|
38518
38838
|
result[metric.id] = actions.length;
|
|
38519
38839
|
else if (metric.kind === "endWallet")
|
|
38520
|
-
result[metric.id] =
|
|
38840
|
+
result[metric.id] = monetary(run.end.wallet.money.amount);
|
|
38521
38841
|
else {
|
|
38522
38842
|
const worth = opened.scenario.model.netWorth?.(opened.scenario.ctx, run.end);
|
|
38523
38843
|
if (!worth)
|
|
38524
38844
|
throw new Error("endNetWorth requires model.netWorth; wallet is not a substitute");
|
|
38525
|
-
result[metric.id] =
|
|
38845
|
+
result[metric.id] = monetary(worth.amount);
|
|
38526
38846
|
}
|
|
38527
38847
|
}
|
|
38528
38848
|
return result;
|
|
@@ -38580,7 +38900,7 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
|
|
|
38580
38900
|
`);
|
|
38581
38901
|
return { id: variant.id, scenario: name };
|
|
38582
38902
|
});
|
|
38583
|
-
addArtifact("variants.json", JSON.stringify({ version: 1, engine:
|
|
38903
|
+
addArtifact("variants.json", JSON.stringify({ version: 1, engine: engine.effectiveId, seeds: plan.seeds, horizonSec: horizon, strategy: plan.strategy, variants }, null, 2) + `
|
|
38584
38904
|
`);
|
|
38585
38905
|
}
|
|
38586
38906
|
return artifacts;
|
|
@@ -38595,10 +38915,10 @@ function cell(value) {
|
|
|
38595
38915
|
}
|
|
38596
38916
|
function explain(result) {
|
|
38597
38917
|
if (result.status === "breach" && result.value !== null) {
|
|
38598
|
-
if (result.min !== undefined && result.value
|
|
38599
|
-
return `below the minimum by ${result.min - result.value} ${result.unit}`;
|
|
38600
|
-
if (result.max !== undefined && result.value
|
|
38601
|
-
return `above the maximum by ${result.value - result.max} ${result.unit}`;
|
|
38918
|
+
if (result.min !== undefined && comparePacingValues(result.value, result.min) < 0)
|
|
38919
|
+
return typeof result.min === "number" && typeof result.value === "number" ? `below the minimum by ${result.min - result.value} ${result.unit}` : `below the minimum ${result.min} ${result.unit}`;
|
|
38920
|
+
if (result.max !== undefined && comparePacingValues(result.value, result.max) > 0)
|
|
38921
|
+
return typeof result.max === "number" && typeof result.value === "number" ? `above the maximum by ${result.value - result.max} ${result.unit}` : `above the maximum ${result.max} ${result.unit}`;
|
|
38602
38922
|
}
|
|
38603
38923
|
if (result.status === "pass")
|
|
38604
38924
|
return "within the inclusive target bounds";
|
|
@@ -38637,6 +38957,7 @@ var balance_default = defineCommand({
|
|
|
38637
38957
|
description: "Refresh a typed CSV balance sheet into scenario, pacing checks, and result sheets",
|
|
38638
38958
|
options: {
|
|
38639
38959
|
...pluginOptions(),
|
|
38960
|
+
engine: option(string2().min(1).optional(), { description: "Numeric engine (default: number; explicit: breakInfinity)" }),
|
|
38640
38961
|
check: option(exports_coerce.boolean().default(false), { description: "Check result freshness without rerunning simulations" }),
|
|
38641
38962
|
format: option(_enum(["json", "md"]).default("json"), { description: "JSON manifest or human-readable target explanations" })
|
|
38642
38963
|
},
|
|
@@ -39093,6 +39414,7 @@ function compareIdentity2(args) {
|
|
|
39093
39414
|
return {
|
|
39094
39415
|
a: side(args.scenarios.a),
|
|
39095
39416
|
b: side(args.scenarios.b),
|
|
39417
|
+
...args.engineId && args.engineId !== "number" ? { engineId: args.engineId } : {},
|
|
39096
39418
|
pluginDigests: pluginDigestValues(args.loaded.pluginDigest),
|
|
39097
39419
|
metrics,
|
|
39098
39420
|
bundle: flags.bundle ?? null,
|
|
@@ -39101,9 +39423,16 @@ function compareIdentity2(args) {
|
|
|
39101
39423
|
};
|
|
39102
39424
|
}
|
|
39103
39425
|
function measureScenario(args) {
|
|
39426
|
+
const requireFinite = (amount, label) => {
|
|
39427
|
+
if (!args.E.isFinite(amount))
|
|
39428
|
+
throw new RangeError(`compare ${label} must be finite in the selected engine`);
|
|
39429
|
+
return amount;
|
|
39430
|
+
};
|
|
39104
39431
|
const runInput = args.compiled.fresh("economy");
|
|
39105
39432
|
const run = runScenario(runInput);
|
|
39106
39433
|
const endWorth = runInput.model.netWorth?.(runInput.ctx, run.end) ?? run.end.wallet.money;
|
|
39434
|
+
requireFinite(run.end.wallet.money.amount, "end money");
|
|
39435
|
+
requireFinite(endWorth.amount, "end net worth");
|
|
39107
39436
|
let etaSeconds;
|
|
39108
39437
|
let etaReached;
|
|
39109
39438
|
if (args.targetWorth) {
|
|
@@ -39112,7 +39441,12 @@ function measureScenario(args) {
|
|
|
39112
39441
|
unit: etaInput.ctx.unit,
|
|
39113
39442
|
suffix: { kind: "alphaInfinite", minLen: 2 }
|
|
39114
39443
|
}).amount;
|
|
39115
|
-
|
|
39444
|
+
requireFinite(target, "ETA target");
|
|
39445
|
+
const reachedFn = (s) => {
|
|
39446
|
+
requireFinite(s.wallet.money.amount, "ETA money");
|
|
39447
|
+
const worth = etaInput.model.netWorth?.(etaInput.ctx, s) ?? s.wallet.money;
|
|
39448
|
+
return args.E.cmp(requireFinite(worth.amount, "ETA net worth"), target) >= 0;
|
|
39449
|
+
};
|
|
39116
39450
|
const etaRun = runScenario({
|
|
39117
39451
|
...etaInput,
|
|
39118
39452
|
run: {
|
|
@@ -39394,6 +39728,7 @@ var compare_default = defineCommand({
|
|
|
39394
39728
|
description: "Compare two scenarios via measured simulation metrics",
|
|
39395
39729
|
options: {
|
|
39396
39730
|
...pluginOptions(),
|
|
39731
|
+
engine: option(string2().min(1).optional(), { description: "Numeric engine (default: number; explicit: breakInfinity)" }),
|
|
39397
39732
|
duration: option(exports_coerce.number().optional(), { description: "Override durationSec" }),
|
|
39398
39733
|
step: option(exports_coerce.number().optional(), { description: "Override stepSec" }),
|
|
39399
39734
|
strategy: option(strategySchema, { description: "Override strategy id (greedy|planner|scripted)" }),
|
|
@@ -39449,7 +39784,8 @@ var compare_default = defineCommand({
|
|
|
39449
39784
|
throw usageError("metric=timeToMilestone requires --milestone-key <key>");
|
|
39450
39785
|
}
|
|
39451
39786
|
const scenarios = { a: aScenario, b: bScenario };
|
|
39452
|
-
const
|
|
39787
|
+
const engine = resolveEffectiveEngine({ requested: flags.engine });
|
|
39788
|
+
const identity = compareIdentity2({ scenarios, flags, loaded, engineId: engine.effectiveId });
|
|
39453
39789
|
const effectiveSeed = flags.seed ?? defaultRunSeed({
|
|
39454
39790
|
base: {
|
|
39455
39791
|
command: "compare",
|
|
@@ -39469,9 +39805,12 @@ var compare_default = defineCommand({
|
|
|
39469
39805
|
milestoneKey: undefined
|
|
39470
39806
|
}
|
|
39471
39807
|
},
|
|
39472
|
-
runs: [{ identity, defaults:
|
|
39808
|
+
runs: [{ identity, defaults: {
|
|
39809
|
+
...compareIdentity2({ scenarios, flags: { "max-duration": DEFAULT_MAX_DURATION }, loaded }),
|
|
39810
|
+
engineId: "number"
|
|
39811
|
+
} }]
|
|
39473
39812
|
});
|
|
39474
|
-
const E =
|
|
39813
|
+
const E = engine.engine;
|
|
39475
39814
|
const aCompiled = compileComparableScenario({
|
|
39476
39815
|
scenario: aScenario,
|
|
39477
39816
|
E,
|
|
@@ -40329,7 +40668,7 @@ function parseHorizons(raw) {
|
|
|
40329
40668
|
}
|
|
40330
40669
|
var DEFAULT_LTV_HORIZONS = "30m,2h,24h,7d,30d,90d";
|
|
40331
40670
|
function ltvUncertaintyDraws(scenario, draws) {
|
|
40332
|
-
const uncertainty =
|
|
40671
|
+
const uncertainty = deriveMonetizationConfig2(scenario).uncertainty;
|
|
40333
40672
|
return uncertainty.enabled || draws !== undefined ? draws ?? uncertainty.draws : null;
|
|
40334
40673
|
}
|
|
40335
40674
|
function ltvStageInputs(scenario, horizons, draws) {
|
|
@@ -40391,7 +40730,7 @@ function runLtvAnalysis(args) {
|
|
|
40391
40730
|
const horizons = parseHorizons(args.horizonsRaw);
|
|
40392
40731
|
const stepSec = args.step ?? args.compiled.run.stepSec;
|
|
40393
40732
|
const runFast = resolveFastMode(args.fast, args.compiled.run.fast);
|
|
40394
|
-
const monetizationConfig =
|
|
40733
|
+
const monetizationConfig = deriveMonetizationConfig2(args.scenario);
|
|
40395
40734
|
const uncertainEnabled = monetizationConfig.uncertainty.enabled || args.draws !== undefined;
|
|
40396
40735
|
const draws = args.draws ?? monetizationConfig.uncertainty.draws;
|
|
40397
40736
|
let state = args.compiled.initial;
|
|
@@ -40451,12 +40790,12 @@ function runLtvAnalysis(args) {
|
|
|
40451
40790
|
const endWorthLog10 = args.compiled.ctx.E.absLog10(worth.amount);
|
|
40452
40791
|
const progression = progressionFactor(startWorthLog10, endWorthLog10, monetizationConfig.revenue.progressionLogSpan);
|
|
40453
40792
|
const horizonDays = h.seconds / 86400;
|
|
40454
|
-
const point =
|
|
40793
|
+
const point = estimateLtvPerUser2({
|
|
40455
40794
|
config: monetizationConfig,
|
|
40456
40795
|
horizonDays,
|
|
40457
40796
|
progression
|
|
40458
40797
|
});
|
|
40459
|
-
const distribution = uncertainEnabled && draws > 1 ?
|
|
40798
|
+
const distribution = uncertainEnabled && draws > 1 ? estimateLtvDistribution2({
|
|
40460
40799
|
config: monetizationConfig,
|
|
40461
40800
|
horizonDays,
|
|
40462
40801
|
progression,
|
|
@@ -42109,6 +42448,7 @@ var tune_default = defineCommand({
|
|
|
42109
42448
|
description: "Tune strategy parameters with objective scoring",
|
|
42110
42449
|
options: {
|
|
42111
42450
|
...pluginOptions(),
|
|
42451
|
+
engine: option(string2().min(1).optional(), { description: "Numeric engine (default: number; explicit: breakInfinity)" }),
|
|
42112
42452
|
tune: option(string2().optional(), { description: "TuneSpec file path (.json|.yaml)" }),
|
|
42113
42453
|
wizard: option(exports_coerce.boolean().default(false), {
|
|
42114
42454
|
description: "Interactively generate a TuneSpec before tuning"
|
|
@@ -42164,11 +42504,14 @@ var tune_default = defineCommand({
|
|
|
42164
42504
|
const tuneSpecInput = wizardResult?.tuneSpec ?? await readScenarioFile(tunePath);
|
|
42165
42505
|
const baselinePath = flags["baseline-artifact"] ? resolve21(flags["baseline-artifact"]) : undefined;
|
|
42166
42506
|
const baselineRaw = baselinePath ? await readJsonFile(baselinePath) : undefined;
|
|
42507
|
+
const engine = resolveEffectiveEngine({ requested: flags.engine });
|
|
42508
|
+
const engineIdentity = engine.effectiveId === "number" ? {} : { engineId: engine.effectiveId };
|
|
42167
42509
|
const seed = flags.seed ?? deriveDeterministicSeed({
|
|
42168
42510
|
command: "tune",
|
|
42169
42511
|
scenario: scenarioInput,
|
|
42170
42512
|
tuneSpec: tuneSpecInput,
|
|
42171
42513
|
options: {
|
|
42514
|
+
...engineIdentity,
|
|
42172
42515
|
baselineArtifact: baselineRaw === undefined ? undefined : hashContent(baselineRaw),
|
|
42173
42516
|
regressionTolerance: flags["regression-tolerance"]
|
|
42174
42517
|
}
|
|
@@ -42176,7 +42519,7 @@ var tune_default = defineCommand({
|
|
|
42176
42519
|
const runId = flags["run-id"] ?? deriveDeterministicRunId({
|
|
42177
42520
|
command: "tune",
|
|
42178
42521
|
seed,
|
|
42179
|
-
scope: { scenario: scenarioInput, tuneSpec: tuneSpecInput, pluginDigests: Object.values(loaded.pluginDigest) }
|
|
42522
|
+
scope: { scenario: scenarioInput, tuneSpec: tuneSpecInput, pluginDigests: Object.values(loaded.pluginDigest), ...engineIdentity }
|
|
42180
42523
|
});
|
|
42181
42524
|
const outputMeta = buildOutputMeta({
|
|
42182
42525
|
command: "tune",
|
|
@@ -42188,7 +42531,7 @@ var tune_default = defineCommand({
|
|
|
42188
42531
|
pluginDigest: loaded.pluginDigest
|
|
42189
42532
|
});
|
|
42190
42533
|
const result = await cmdTune({
|
|
42191
|
-
E:
|
|
42534
|
+
E: engine.engine,
|
|
42192
42535
|
scenarioInput,
|
|
42193
42536
|
tuneSpecInput,
|
|
42194
42537
|
modelRegistry: loaded.modelRegistry,
|
|
@@ -44105,4 +44448,4 @@ export {
|
|
|
44105
44448
|
cli2 as cli
|
|
44106
44449
|
};
|
|
44107
44450
|
|
|
44108
|
-
//# debugId=
|
|
44451
|
+
//# debugId=0936659FB5366DD764756E2164756E21
|