textopt 0.0.0 → 0.2.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.
Files changed (65) hide show
  1. package/README.md +65 -25
  2. package/dist/bootstrap-search/index.cjs +159 -73
  3. package/dist/bootstrap-search/index.d.cts +32 -10
  4. package/dist/bootstrap-search/index.d.mts +32 -10
  5. package/dist/bootstrap-search/index.mjs +150 -66
  6. package/dist/demos-9v5ts7F3.cjs +244 -0
  7. package/dist/{demos-B0pVQjYC.d.mts → demos-ASsSXYXA.d.mts} +10 -3
  8. package/dist/demos-Brobjfuc.mjs +215 -0
  9. package/dist/{demos-BTuzFNsp.d.cts → demos-ByaLZy-Z.d.cts} +10 -3
  10. package/dist/file-cache.cjs +27 -8
  11. package/dist/file-cache.d.cts +13 -0
  12. package/dist/file-cache.d.mts +13 -0
  13. package/dist/file-cache.mjs +27 -8
  14. package/dist/gepa/index.cjs +128 -80
  15. package/dist/gepa/index.d.cts +15 -7
  16. package/dist/gepa/index.d.mts +15 -7
  17. package/dist/gepa/index.mjs +101 -55
  18. package/dist/index.cjs +157 -30
  19. package/dist/index.d.cts +177 -7
  20. package/dist/index.d.mts +177 -7
  21. package/dist/index.mjs +139 -18
  22. package/dist/{math-COOofUyv.cjs → math-BhlziRPc.cjs} +60 -9
  23. package/dist/math-Dqme4rYz.mjs +123 -0
  24. package/dist/mipro/index.cjs +104 -70
  25. package/dist/mipro/index.d.cts +17 -14
  26. package/dist/mipro/index.d.mts +17 -14
  27. package/dist/mipro/index.mjs +90 -58
  28. package/dist/opro/index.cjs +136 -51
  29. package/dist/opro/index.d.cts +17 -9
  30. package/dist/opro/index.d.mts +17 -9
  31. package/dist/opro/index.mjs +121 -38
  32. package/dist/{optimizer-B7SpRwl7.d.cts → optimizer-4Zv-Zt2t.d.cts} +90 -5
  33. package/dist/{optimizer-DqCoth_w.d.mts → optimizer-Ds5mzYjz.d.mts} +90 -5
  34. package/dist/random-search/index.cjs +99 -49
  35. package/dist/random-search/index.d.cts +15 -13
  36. package/dist/random-search/index.d.mts +15 -13
  37. package/dist/random-search/index.mjs +89 -41
  38. package/dist/{reflection-Cr_upzU0.d.mts → reflection-CMezGu6u.d.mts} +38 -14
  39. package/dist/{reflection-CQToe-5B.d.cts → reflection-D0A7eahD.d.cts} +38 -14
  40. package/dist/reporting-bq007_2z.d.cts +294 -0
  41. package/dist/reporting-bq007_2z.d.mts +294 -0
  42. package/dist/simba/index.cjs +216 -83
  43. package/dist/simba/index.d.cts +53 -13
  44. package/dist/simba/index.d.mts +53 -13
  45. package/dist/simba/index.mjs +206 -75
  46. package/dist/testing.cjs +1 -0
  47. package/dist/testing.d.cts +5 -3
  48. package/dist/testing.d.mts +5 -3
  49. package/dist/testing.mjs +1 -1
  50. package/dist/{evaluation-OZOp6TB7.cjs → warnings-CWRJF-jA.cjs} +228 -5
  51. package/dist/{evaluation-BV0nSZVx.mjs → warnings-OxvDi9kN.mjs} +175 -6
  52. package/docs/adapters.md +169 -0
  53. package/docs/benchmark.md +90 -0
  54. package/docs/data-prep.md +113 -0
  55. package/docs/distillation.md +128 -0
  56. package/docs/evaluation.md +87 -0
  57. package/docs/metric-preflight.md +132 -0
  58. package/docs/optimizers.md +293 -0
  59. package/docs/tuning.md +130 -0
  60. package/package.json +6 -4
  61. package/dist/demos-B9BJiNKz.cjs +0 -143
  62. package/dist/demos-Degx6UmP.mjs +0 -126
  63. package/dist/math-DhrDmpFS.mjs +0 -78
  64. package/dist/types-CWv4IQFF.d.cts +0 -129
  65. package/dist/types-CWv4IQFF.d.mts +0 -129
@@ -30,6 +30,8 @@ function createBudget(args) {
30
30
  }
31
31
  //#endregion
32
32
  //#region src/cache.ts
33
+ /** What `stableHash` returns for any value that serializes to `{}`. */
34
+ const EMPTY_OBJECT_HASH = stableHash({});
33
35
  /**
34
36
  * The loop re-evaluates unchanged candidates against the same validation
35
37
  * instances constantly (every accepted child inherits most of its parent's
@@ -65,6 +67,20 @@ function stableHash(value) {
65
67
  }
66
68
  return `${hash32(serialized, 2166136261)}${hash32(serialized, 16777619)}`;
67
69
  }
70
+ /**
71
+ * Names one data instance for the evaluation cache: a content hash, so the same
72
+ * row is the same instance wherever it appears in a run.
73
+ *
74
+ * Falls back to the row's position when the datum carries nothing the hash can
75
+ * read. A Map, a Set and a class instance holding its state privately all
76
+ * serialize to `{}`, and an id two rows share serves each of them the score the
77
+ * other measured. Position is a weaker id — it is only stable while the data is
78
+ * — but it is one instance per row, which is what the cache needs to be sound.
79
+ */
80
+ function defaultInstanceId(args) {
81
+ const hash = stableHash(args.datum);
82
+ return hash === "" || hash === EMPTY_OBJECT_HASH ? String(args.index) : hash;
83
+ }
68
84
  function createMemoryCache(args = {}) {
69
85
  const { maxEntries = 1e5, entries: initial = [] } = args;
70
86
  const entries = new Map(initial);
@@ -172,25 +188,39 @@ const DEFAULT_RETRY = {
172
188
  */
173
189
  var BudgetExhausted = class extends Error {};
174
190
  function createEvaluator(args) {
175
- const { adapter, budget, cache, trackOutputs = false, onEvaluation, signal, cacheHits: initialCacheHits = 0, retry, cacheNamespace } = args;
191
+ const { adapter, budget, cache, trackOutputs = false, onEvaluation, signal, cacheHits: initialCacheHits = 0, usage: initialUsage, retry, cacheNamespace } = args;
176
192
  const { attempts: retryAttempts, delayMs: retryDelayMs } = {
177
193
  ...DEFAULT_RETRY,
178
194
  ...retry
179
195
  };
196
+ if (initialUsage !== void 0) assertUsage({
197
+ reading: initialUsage,
198
+ source: "Checkpoint carries"
199
+ });
180
200
  let cacheHits = initialCacheHits;
181
201
  let unchargedCalls = 0;
182
202
  const usage = {
203
+ inputTokens: 0,
204
+ outputTokens: 0,
205
+ totalTokens: 0,
206
+ costUsd: 0,
207
+ rollouts: 0,
208
+ ...initialUsage
209
+ };
210
+ /** What `charge: false` bought, kept out of the totals a ceiling reads. */
211
+ const unchargedUsage = {
183
212
  inputTokens: 0,
184
213
  outputTokens: 0,
185
214
  totalTokens: 0,
186
215
  costUsd: 0,
187
216
  rollouts: 0
188
217
  };
189
- /** Folds one adapter call's reported usage into the run's totals. */
218
+ /** Folds one adapter call's reported usage into the totals that bought it. */
190
219
  function recordUsage(args) {
191
- usage.rollouts += args.rollouts;
220
+ const totals = args.charge ? usage : unchargedUsage;
221
+ totals.rollouts += args.rollouts;
192
222
  for (const rollout of args.evaluation.usage ?? []) addUsage({
193
- totals: usage,
223
+ totals,
194
224
  rollout
195
225
  });
196
226
  }
@@ -252,7 +282,8 @@ function createEvaluator(args) {
252
282
  });
253
283
  recordUsage({
254
284
  evaluation,
255
- rollouts: rows.length
285
+ rollouts: rows.length,
286
+ charge
256
287
  });
257
288
  return evaluation;
258
289
  } catch (err) {
@@ -395,6 +426,18 @@ function createEvaluator(args) {
395
426
  cacheHits: () => cacheHits,
396
427
  unchargedCalls: () => unchargedCalls,
397
428
  usage: () => ({ ...usage }),
429
+ unchargedUsage: () => ({ ...unchargedUsage }),
430
+ absorbUsage: (spent) => {
431
+ assertUsage({
432
+ reading: spent,
433
+ source: "Absorbed usage carries"
434
+ });
435
+ usage.inputTokens += spent.inputTokens;
436
+ usage.outputTokens += spent.outputTokens;
437
+ usage.totalTokens += spent.totalTokens;
438
+ usage.costUsd += spent.costUsd;
439
+ usage.rollouts += spent.rollouts;
440
+ },
398
441
  entries: () => cache?.entries?.(),
399
442
  restore: (entries) => {
400
443
  for (const [key, cached] of entries) cache?.set(key, cached);
@@ -471,11 +514,29 @@ function requireMeasuredMean(args) {
471
514
  function addUsage(args) {
472
515
  const { totals, rollout } = args;
473
516
  const { inputTokens = 0, outputTokens = 0, costUsd = 0 } = rollout;
517
+ assertUsage({
518
+ reading: rollout,
519
+ source: "Adapter reported"
520
+ });
474
521
  totals.inputTokens += inputTokens;
475
522
  totals.outputTokens += outputTokens;
476
523
  totals.totalTokens += rollout.totalTokens ?? inputTokens + outputTokens;
477
524
  totals.costUsd += costUsd;
478
525
  }
526
+ /**
527
+ * Refuses a reading the totals cannot hold, where it enters rather than once it
528
+ * has been absorbed. A NaN folded in makes every later `maxCostUsd` comparison
529
+ * false, so the ceiling stops holding without saying so, and a value that is
530
+ * not a number at all concatenates onto the totals instead of adding to them.
531
+ * `RolloutUsage` binds TypeScript callers and nothing else.
532
+ */
533
+ function assertUsage(args) {
534
+ const { reading, source } = args;
535
+ for (const [field, value] of Object.entries(reading)) {
536
+ if (value === void 0) continue;
537
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new Error(`${source} ${field} as ${value}; usage must be a non-negative finite number`);
538
+ }
539
+ }
479
540
  function transientIndices(evaluation) {
480
541
  const { transient } = evaluation;
481
542
  if (transient === void 0) return [];
@@ -518,6 +579,114 @@ function delay(milliseconds) {
518
579
  });
519
580
  }
520
581
  //#endregion
582
+ //#region src/reporting.ts
583
+ /**
584
+ * Narrows an event off any optimizer's union to an acceptance. The tag is
585
+ * enough: every optimizer's `candidateAccepted` intersects `CandidateAccepted`,
586
+ * so carrying the payload is a compile-time obligation rather than a hope.
587
+ */
588
+ function isCandidateAccepted(event) {
589
+ return event.type === "candidateAccepted";
590
+ }
591
+ /** Narrows an event off any optimizer's union to the end of the run. */
592
+ function isRunFinished(event) {
593
+ return event.type === "finish";
594
+ }
595
+ /**
596
+ * Fans one event out to every reporter, absorbing whatever they throw. A
597
+ * reporter is an observer of the search, never a participant in it: a logging
598
+ * endpoint that is down must not decide a run's outcome.
599
+ */
600
+ function createEmitter(reporters) {
601
+ return function emit(event) {
602
+ for (const reporter of reporters) try {
603
+ reporter.onEvent?.(event);
604
+ } catch (err) {
605
+ console.warn("[textopt] reporter threw while handling an event", {
606
+ type: event.type,
607
+ err
608
+ });
609
+ }
610
+ };
611
+ }
612
+ /**
613
+ * Gives every reporter its one chance to upload what it buffered. Called from
614
+ * a `finally` rather than after the run: a reporter that buffers has the most
615
+ * to say about a run that aborted or threw, and that is exactly the run that
616
+ * never reaches its last line.
617
+ */
618
+ async function flushReporters(reporters) {
619
+ await Promise.all(reporters.map(async (reporter) => {
620
+ try {
621
+ await reporter.flush?.();
622
+ } catch (err) {
623
+ console.warn("[textopt] reporter threw while flushing", { err });
624
+ }
625
+ }));
626
+ }
627
+ /**
628
+ * A scored batch as the per-instance row a reporter should read: an instance
629
+ * an infrastructure failure left unmeasured becomes `undefined` rather than
630
+ * the zero the adapter reported for it.
631
+ *
632
+ * The same distinction `measuredMean` makes when it averages — a row of zeros
633
+ * and a row of unknowns describe very different runs.
634
+ */
635
+ function instanceRow(batch) {
636
+ return batch.scores.map((score, index) => batch.transient[index] === true ? void 0 : score);
637
+ }
638
+ //#endregion
639
+ //#region src/warnings.ts
640
+ /**
641
+ * The validation set a run will actually select against, and whatever the
642
+ * choice costs it.
643
+ *
644
+ * Defaulting to the training set is the right default for a first run and the
645
+ * wrong number to report from one. It is worse than ordinary overfitting under
646
+ * reflective search: the reflection prompt asks the model to mine domain facts
647
+ * out of the traces it is shown, so those facts come out of the very instances
648
+ * that then select the candidate carrying them. `"reuseTraining"` is the same
649
+ * behaviour with the caller's name on it, and silences the warning.
650
+ */
651
+ function resolveValidationSet(args) {
652
+ const { validationSet, trainingSet } = args;
653
+ if (validationSet === void 0) return {
654
+ validationSet: trainingSet,
655
+ warnings: [{
656
+ code: "validationSetReusesTraining",
657
+ message: "No validationSet was given, so the search selected candidates on the training instances reflection read. bestScore is fitted to them; pass a validationSet, or a testSet to measure the gap, or validationSet: \"reuseTraining\" to accept it."
658
+ }]
659
+ };
660
+ return {
661
+ validationSet: validationSet === "reuseTraining" ? trainingSet : validationSet,
662
+ warnings: []
663
+ };
664
+ }
665
+ /**
666
+ * What the seed's own validation row says about whether the run could have
667
+ * learned anything.
668
+ *
669
+ * A search ranks candidates by how they differ across instances, so a seed row
670
+ * with no spread leaves nothing to rank: at the ceiling every proposal is a tie
671
+ * the acceptance test resolves by noise, and at the floor no proposal has a
672
+ * partial improvement to build on. Both produce a run that spends its whole
673
+ * budget and reports a stop reason that looks like any other.
674
+ */
675
+ function seedScoreWarnings(args) {
676
+ const { scores, perfectScore } = args;
677
+ const measured = scores.filter((score) => score !== void 0);
678
+ if (measured.length === 0) return [];
679
+ if (measured.every((score) => score >= perfectScore)) return [{
680
+ code: "seedScoreSaturated",
681
+ message: `The seed candidate already scores ${perfectScore} on every validation instance, so no proposal has anything to improve on and the search ranks ties. Use a harder validation set or a metric that separates these instances.`
682
+ }];
683
+ if (measured.every((score) => score <= 0)) return [{
684
+ code: "seedScoreFloored",
685
+ message: "The seed candidate scores 0 on every validation instance. That is a seed with everything to gain when the feedback says what is missing, and a metric that scores nothing when it does not — the score alone cannot tell the two apart. Check the metric against a candidate you know is good before reading much into this run."
686
+ }];
687
+ return [];
688
+ }
689
+ //#endregion
521
690
  Object.defineProperty(exports, "BudgetExhausted", {
522
691
  enumerable: true,
523
692
  get: function() {
@@ -536,6 +705,12 @@ Object.defineProperty(exports, "candidateFingerprint", {
536
705
  return candidateFingerprint;
537
706
  }
538
707
  });
708
+ Object.defineProperty(exports, "candidateHash", {
709
+ enumerable: true,
710
+ get: function() {
711
+ return candidateHash;
712
+ }
713
+ });
539
714
  Object.defineProperty(exports, "componentNames", {
540
715
  enumerable: true,
541
716
  get: function() {
@@ -560,6 +735,12 @@ Object.defineProperty(exports, "createDeadline", {
560
735
  return createDeadline;
561
736
  }
562
737
  });
738
+ Object.defineProperty(exports, "createEmitter", {
739
+ enumerable: true,
740
+ get: function() {
741
+ return createEmitter;
742
+ }
743
+ });
563
744
  Object.defineProperty(exports, "createEvaluator", {
564
745
  enumerable: true,
565
746
  get: function() {
@@ -572,6 +753,36 @@ Object.defineProperty(exports, "createMemoryCache", {
572
753
  return createMemoryCache;
573
754
  }
574
755
  });
756
+ Object.defineProperty(exports, "defaultInstanceId", {
757
+ enumerable: true,
758
+ get: function() {
759
+ return defaultInstanceId;
760
+ }
761
+ });
762
+ Object.defineProperty(exports, "flushReporters", {
763
+ enumerable: true,
764
+ get: function() {
765
+ return flushReporters;
766
+ }
767
+ });
768
+ Object.defineProperty(exports, "instanceRow", {
769
+ enumerable: true,
770
+ get: function() {
771
+ return instanceRow;
772
+ }
773
+ });
774
+ Object.defineProperty(exports, "isCandidateAccepted", {
775
+ enumerable: true,
776
+ get: function() {
777
+ return isCandidateAccepted;
778
+ }
779
+ });
780
+ Object.defineProperty(exports, "isRunFinished", {
781
+ enumerable: true,
782
+ get: function() {
783
+ return isRunFinished;
784
+ }
785
+ });
575
786
  Object.defineProperty(exports, "measuredMean", {
576
787
  enumerable: true,
577
788
  get: function() {
@@ -584,12 +795,24 @@ Object.defineProperty(exports, "requireMeasuredMean", {
584
795
  return requireMeasuredMean;
585
796
  }
586
797
  });
798
+ Object.defineProperty(exports, "resolveValidationSet", {
799
+ enumerable: true,
800
+ get: function() {
801
+ return resolveValidationSet;
802
+ }
803
+ });
587
804
  Object.defineProperty(exports, "runFingerprint", {
588
805
  enumerable: true,
589
806
  get: function() {
590
807
  return runFingerprint;
591
808
  }
592
809
  });
810
+ Object.defineProperty(exports, "seedScoreWarnings", {
811
+ enumerable: true,
812
+ get: function() {
813
+ return seedScoreWarnings;
814
+ }
815
+ });
593
816
  Object.defineProperty(exports, "stableHash", {
594
817
  enumerable: true,
595
818
  get: function() {
@@ -30,6 +30,8 @@ function createBudget(args) {
30
30
  }
31
31
  //#endregion
32
32
  //#region src/cache.ts
33
+ /** What `stableHash` returns for any value that serializes to `{}`. */
34
+ const EMPTY_OBJECT_HASH = stableHash({});
33
35
  /**
34
36
  * The loop re-evaluates unchanged candidates against the same validation
35
37
  * instances constantly (every accepted child inherits most of its parent's
@@ -65,6 +67,20 @@ function stableHash(value) {
65
67
  }
66
68
  return `${hash32(serialized, 2166136261)}${hash32(serialized, 16777619)}`;
67
69
  }
70
+ /**
71
+ * Names one data instance for the evaluation cache: a content hash, so the same
72
+ * row is the same instance wherever it appears in a run.
73
+ *
74
+ * Falls back to the row's position when the datum carries nothing the hash can
75
+ * read. A Map, a Set and a class instance holding its state privately all
76
+ * serialize to `{}`, and an id two rows share serves each of them the score the
77
+ * other measured. Position is a weaker id — it is only stable while the data is
78
+ * — but it is one instance per row, which is what the cache needs to be sound.
79
+ */
80
+ function defaultInstanceId(args) {
81
+ const hash = stableHash(args.datum);
82
+ return hash === "" || hash === EMPTY_OBJECT_HASH ? String(args.index) : hash;
83
+ }
68
84
  function createMemoryCache(args = {}) {
69
85
  const { maxEntries = 1e5, entries: initial = [] } = args;
70
86
  const entries = new Map(initial);
@@ -172,25 +188,39 @@ const DEFAULT_RETRY = {
172
188
  */
173
189
  var BudgetExhausted = class extends Error {};
174
190
  function createEvaluator(args) {
175
- const { adapter, budget, cache, trackOutputs = false, onEvaluation, signal, cacheHits: initialCacheHits = 0, retry, cacheNamespace } = args;
191
+ const { adapter, budget, cache, trackOutputs = false, onEvaluation, signal, cacheHits: initialCacheHits = 0, usage: initialUsage, retry, cacheNamespace } = args;
176
192
  const { attempts: retryAttempts, delayMs: retryDelayMs } = {
177
193
  ...DEFAULT_RETRY,
178
194
  ...retry
179
195
  };
196
+ if (initialUsage !== void 0) assertUsage({
197
+ reading: initialUsage,
198
+ source: "Checkpoint carries"
199
+ });
180
200
  let cacheHits = initialCacheHits;
181
201
  let unchargedCalls = 0;
182
202
  const usage = {
203
+ inputTokens: 0,
204
+ outputTokens: 0,
205
+ totalTokens: 0,
206
+ costUsd: 0,
207
+ rollouts: 0,
208
+ ...initialUsage
209
+ };
210
+ /** What `charge: false` bought, kept out of the totals a ceiling reads. */
211
+ const unchargedUsage = {
183
212
  inputTokens: 0,
184
213
  outputTokens: 0,
185
214
  totalTokens: 0,
186
215
  costUsd: 0,
187
216
  rollouts: 0
188
217
  };
189
- /** Folds one adapter call's reported usage into the run's totals. */
218
+ /** Folds one adapter call's reported usage into the totals that bought it. */
190
219
  function recordUsage(args) {
191
- usage.rollouts += args.rollouts;
220
+ const totals = args.charge ? usage : unchargedUsage;
221
+ totals.rollouts += args.rollouts;
192
222
  for (const rollout of args.evaluation.usage ?? []) addUsage({
193
- totals: usage,
223
+ totals,
194
224
  rollout
195
225
  });
196
226
  }
@@ -252,7 +282,8 @@ function createEvaluator(args) {
252
282
  });
253
283
  recordUsage({
254
284
  evaluation,
255
- rollouts: rows.length
285
+ rollouts: rows.length,
286
+ charge
256
287
  });
257
288
  return evaluation;
258
289
  } catch (err) {
@@ -395,6 +426,18 @@ function createEvaluator(args) {
395
426
  cacheHits: () => cacheHits,
396
427
  unchargedCalls: () => unchargedCalls,
397
428
  usage: () => ({ ...usage }),
429
+ unchargedUsage: () => ({ ...unchargedUsage }),
430
+ absorbUsage: (spent) => {
431
+ assertUsage({
432
+ reading: spent,
433
+ source: "Absorbed usage carries"
434
+ });
435
+ usage.inputTokens += spent.inputTokens;
436
+ usage.outputTokens += spent.outputTokens;
437
+ usage.totalTokens += spent.totalTokens;
438
+ usage.costUsd += spent.costUsd;
439
+ usage.rollouts += spent.rollouts;
440
+ },
398
441
  entries: () => cache?.entries?.(),
399
442
  restore: (entries) => {
400
443
  for (const [key, cached] of entries) cache?.set(key, cached);
@@ -471,11 +514,29 @@ function requireMeasuredMean(args) {
471
514
  function addUsage(args) {
472
515
  const { totals, rollout } = args;
473
516
  const { inputTokens = 0, outputTokens = 0, costUsd = 0 } = rollout;
517
+ assertUsage({
518
+ reading: rollout,
519
+ source: "Adapter reported"
520
+ });
474
521
  totals.inputTokens += inputTokens;
475
522
  totals.outputTokens += outputTokens;
476
523
  totals.totalTokens += rollout.totalTokens ?? inputTokens + outputTokens;
477
524
  totals.costUsd += costUsd;
478
525
  }
526
+ /**
527
+ * Refuses a reading the totals cannot hold, where it enters rather than once it
528
+ * has been absorbed. A NaN folded in makes every later `maxCostUsd` comparison
529
+ * false, so the ceiling stops holding without saying so, and a value that is
530
+ * not a number at all concatenates onto the totals instead of adding to them.
531
+ * `RolloutUsage` binds TypeScript callers and nothing else.
532
+ */
533
+ function assertUsage(args) {
534
+ const { reading, source } = args;
535
+ for (const [field, value] of Object.entries(reading)) {
536
+ if (value === void 0) continue;
537
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new Error(`${source} ${field} as ${value}; usage must be a non-negative finite number`);
538
+ }
539
+ }
479
540
  function transientIndices(evaluation) {
480
541
  const { transient } = evaluation;
481
542
  if (transient === void 0) return [];
@@ -518,4 +579,112 @@ function delay(milliseconds) {
518
579
  });
519
580
  }
520
581
  //#endregion
521
- export { requireMeasuredMean as a, candidateFingerprint as c, createMemoryCache as d, stableHash as f, measuredMean as i, runFingerprint as l, costExhausted as n, createDeadline as o, createBudget as p, createEvaluator as r, assertResumable as s, BudgetExhausted as t, componentNames as u };
582
+ //#region src/reporting.ts
583
+ /**
584
+ * Narrows an event off any optimizer's union to an acceptance. The tag is
585
+ * enough: every optimizer's `candidateAccepted` intersects `CandidateAccepted`,
586
+ * so carrying the payload is a compile-time obligation rather than a hope.
587
+ */
588
+ function isCandidateAccepted(event) {
589
+ return event.type === "candidateAccepted";
590
+ }
591
+ /** Narrows an event off any optimizer's union to the end of the run. */
592
+ function isRunFinished(event) {
593
+ return event.type === "finish";
594
+ }
595
+ /**
596
+ * Fans one event out to every reporter, absorbing whatever they throw. A
597
+ * reporter is an observer of the search, never a participant in it: a logging
598
+ * endpoint that is down must not decide a run's outcome.
599
+ */
600
+ function createEmitter(reporters) {
601
+ return function emit(event) {
602
+ for (const reporter of reporters) try {
603
+ reporter.onEvent?.(event);
604
+ } catch (err) {
605
+ console.warn("[textopt] reporter threw while handling an event", {
606
+ type: event.type,
607
+ err
608
+ });
609
+ }
610
+ };
611
+ }
612
+ /**
613
+ * Gives every reporter its one chance to upload what it buffered. Called from
614
+ * a `finally` rather than after the run: a reporter that buffers has the most
615
+ * to say about a run that aborted or threw, and that is exactly the run that
616
+ * never reaches its last line.
617
+ */
618
+ async function flushReporters(reporters) {
619
+ await Promise.all(reporters.map(async (reporter) => {
620
+ try {
621
+ await reporter.flush?.();
622
+ } catch (err) {
623
+ console.warn("[textopt] reporter threw while flushing", { err });
624
+ }
625
+ }));
626
+ }
627
+ /**
628
+ * A scored batch as the per-instance row a reporter should read: an instance
629
+ * an infrastructure failure left unmeasured becomes `undefined` rather than
630
+ * the zero the adapter reported for it.
631
+ *
632
+ * The same distinction `measuredMean` makes when it averages — a row of zeros
633
+ * and a row of unknowns describe very different runs.
634
+ */
635
+ function instanceRow(batch) {
636
+ return batch.scores.map((score, index) => batch.transient[index] === true ? void 0 : score);
637
+ }
638
+ //#endregion
639
+ //#region src/warnings.ts
640
+ /**
641
+ * The validation set a run will actually select against, and whatever the
642
+ * choice costs it.
643
+ *
644
+ * Defaulting to the training set is the right default for a first run and the
645
+ * wrong number to report from one. It is worse than ordinary overfitting under
646
+ * reflective search: the reflection prompt asks the model to mine domain facts
647
+ * out of the traces it is shown, so those facts come out of the very instances
648
+ * that then select the candidate carrying them. `"reuseTraining"` is the same
649
+ * behaviour with the caller's name on it, and silences the warning.
650
+ */
651
+ function resolveValidationSet(args) {
652
+ const { validationSet, trainingSet } = args;
653
+ if (validationSet === void 0) return {
654
+ validationSet: trainingSet,
655
+ warnings: [{
656
+ code: "validationSetReusesTraining",
657
+ message: "No validationSet was given, so the search selected candidates on the training instances reflection read. bestScore is fitted to them; pass a validationSet, or a testSet to measure the gap, or validationSet: \"reuseTraining\" to accept it."
658
+ }]
659
+ };
660
+ return {
661
+ validationSet: validationSet === "reuseTraining" ? trainingSet : validationSet,
662
+ warnings: []
663
+ };
664
+ }
665
+ /**
666
+ * What the seed's own validation row says about whether the run could have
667
+ * learned anything.
668
+ *
669
+ * A search ranks candidates by how they differ across instances, so a seed row
670
+ * with no spread leaves nothing to rank: at the ceiling every proposal is a tie
671
+ * the acceptance test resolves by noise, and at the floor no proposal has a
672
+ * partial improvement to build on. Both produce a run that spends its whole
673
+ * budget and reports a stop reason that looks like any other.
674
+ */
675
+ function seedScoreWarnings(args) {
676
+ const { scores, perfectScore } = args;
677
+ const measured = scores.filter((score) => score !== void 0);
678
+ if (measured.length === 0) return [];
679
+ if (measured.every((score) => score >= perfectScore)) return [{
680
+ code: "seedScoreSaturated",
681
+ message: `The seed candidate already scores ${perfectScore} on every validation instance, so no proposal has anything to improve on and the search ranks ties. Use a harder validation set or a metric that separates these instances.`
682
+ }];
683
+ if (measured.every((score) => score <= 0)) return [{
684
+ code: "seedScoreFloored",
685
+ message: "The seed candidate scores 0 on every validation instance. That is a seed with everything to gain when the feedback says what is missing, and a metric that scores nothing when it does not — the score alone cannot tell the two apart. Check the metric against a candidate you know is good before reading much into this run."
686
+ }];
687
+ return [];
688
+ }
689
+ //#endregion
690
+ export { createBudget as S, componentNames as _, instanceRow as a, defaultInstanceId as b, BudgetExhausted as c, measuredMean as d, requireMeasuredMean as f, runFingerprint as g, candidateFingerprint as h, flushReporters as i, costExhausted as l, assertResumable as m, seedScoreWarnings as n, isCandidateAccepted as o, createDeadline as p, createEmitter as r, isRunFinished as s, resolveValidationSet as t, createEvaluator as u, candidateHash as v, stableHash as x, createMemoryCache as y };