nexus-agents 8.85.3 → 8.86.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 (135) hide show
  1. package/dist/{ast-rule-runner-6XKCMMSK.js → ast-rule-runner-USLLC3XL.js} +4 -4
  2. package/dist/{child-mcp-config-DMTIHAH5.js → child-mcp-config-K563PP6S.js} +3 -3
  3. package/dist/{chunk-KVDXUGMQ.js → chunk-2SQO4FET.js} +2 -2
  4. package/dist/{chunk-O6TFBXE5.js → chunk-4Q753QZM.js} +3 -3
  5. package/dist/{chunk-OHM625YL.js → chunk-5U4XTYAB.js} +2 -2
  6. package/dist/{chunk-OMIR6ISN.js → chunk-64IJKT4I.js} +469 -348
  7. package/dist/chunk-64IJKT4I.js.map +1 -0
  8. package/dist/{chunk-5JQL2KOJ.js → chunk-C4YTAB6K.js} +43 -22
  9. package/dist/chunk-C4YTAB6K.js.map +1 -0
  10. package/dist/{chunk-PFZSS67U.js → chunk-CBZAMHBA.js} +2 -2
  11. package/dist/{chunk-6ZCKENQU.js → chunk-DSVMQVJZ.js} +4 -4
  12. package/dist/{chunk-LKNT7XND.js → chunk-EVYZZL7J.js} +2 -2
  13. package/dist/{chunk-DSIJ5L3Y.js → chunk-FNL4J77P.js} +2 -2
  14. package/dist/{chunk-LJNUMK67.js → chunk-GKLTEUFU.js} +2 -2
  15. package/dist/{chunk-HXSR24QG.js → chunk-IXKFIO6O.js} +2 -2
  16. package/dist/{chunk-ABIS6ZE7.js → chunk-J6GIJFV4.js} +2 -2
  17. package/dist/{chunk-UWAJUJKU.js → chunk-KX7A23JM.js} +2 -2
  18. package/dist/{chunk-LBIFDT6N.js → chunk-KXFPJOPL.js} +2 -2
  19. package/dist/{chunk-UXUOJOXL.js → chunk-NNYVXSQF.js} +2 -2
  20. package/dist/{chunk-DMUVBBOD.js → chunk-NQA77WXP.js} +3 -3
  21. package/dist/{chunk-CPW3LHST.js → chunk-O24ENDHN.js} +3 -3
  22. package/dist/{chunk-QFL3BFIV.js → chunk-OOIZ3IQD.js} +2 -2
  23. package/dist/{chunk-3HU4VVD7.js → chunk-P6D7S7CY.js} +2 -2
  24. package/dist/{chunk-RIB56PWI.js → chunk-Q75P5CVJ.js} +2 -2
  25. package/dist/{chunk-T2ZQUZQW.js → chunk-QCDYJYGB.js} +7 -7
  26. package/dist/{chunk-GNTWB3O2.js → chunk-QW6J2UV4.js} +43 -9
  27. package/dist/chunk-QW6J2UV4.js.map +1 -0
  28. package/dist/{chunk-4UHDDCRB.js → chunk-RQLIPUQK.js} +5 -1
  29. package/dist/{chunk-4UHDDCRB.js.map → chunk-RQLIPUQK.js.map} +1 -1
  30. package/dist/{chunk-KUZV4AW5.js → chunk-S6MNVNQB.js} +2 -2
  31. package/dist/{chunk-TECOLJ4Z.js → chunk-SQ4M3JHW.js} +41 -39
  32. package/dist/chunk-SQ4M3JHW.js.map +1 -0
  33. package/dist/{chunk-FQ6UCX2J.js → chunk-V66MMBO4.js} +5 -5
  34. package/dist/{chunk-AHJUIF44.js → chunk-XARB7ITD.js} +8 -8
  35. package/dist/{chunk-HQQ3ES3J.js → chunk-XNNNDQLK.js} +2 -2
  36. package/dist/{chunk-XGAMDSC5.js → chunk-XWPZZEBX.js} +4 -4
  37. package/dist/{chunk-VEMKYAW4.js → chunk-YG73EH43.js} +2 -2
  38. package/dist/{chunk-ALJPZZ57.js → chunk-YPJRVA2H.js} +4 -4
  39. package/dist/{chunk-QW343BTQ.js → chunk-ZE73MT66.js} +4 -4
  40. package/dist/{chunk-AOSBH5H7.js → chunk-ZEOHUE2N.js} +10 -10
  41. package/dist/{chunk-CJ2LQLZV.js → chunk-ZSFRB2S4.js} +3 -3
  42. package/dist/{cli-circuit-breaker-VRTWILPB.js → cli-circuit-breaker-4ASNTBDP.js} +4 -4
  43. package/dist/cli.js +43 -43
  44. package/dist/{composite-router-LAMVSGZ3.js → composite-router-V45AK6WD.js} +3 -3
  45. package/dist/{consensus-vote-ZB72SHTV.js → consensus-vote-N2Z7FX3D.js} +17 -17
  46. package/dist/{context-retriever-VRAIKZTE.js → context-retriever-NZOSGG6S.js} +9 -9
  47. package/dist/{doctor-deep-ZZWIF64Z.js → doctor-deep-JRY3D7BP.js} +4 -4
  48. package/dist/{doctor-live-KKIWEO3K.js → doctor-live-NSRHMHMG.js} +11 -11
  49. package/dist/{expert-bridge-TMHBUOUK.js → expert-bridge-Q4SEPVTE.js} +5 -5
  50. package/dist/factory-JBK45P7G.js +15 -0
  51. package/dist/{factory-KAMTEOXC.js → factory-ZV4MR3VM.js} +10 -10
  52. package/dist/{improvement-review-BI3B3PQ2.js → improvement-review-KHTIDV4R.js} +6 -6
  53. package/dist/index.d.ts +52 -2
  54. package/dist/index.js +31 -31
  55. package/dist/{init-opencode-EY7TUIML.js → init-opencode-6WGN5J6J.js} +6 -6
  56. package/dist/issue-triage-XVDXOZRW.js +16 -0
  57. package/dist/{learning-persistence-7BEPQEGN.js → learning-persistence-ER7E5Q6T.js} +6 -4
  58. package/dist/{pr-reviewer-helpers-3A6ZFECZ.js → pr-reviewer-helpers-UMNMOUAO.js} +5 -5
  59. package/dist/{registry-command-PWWFOIT3.js → registry-command-QRBAKWRK.js} +3 -3
  60. package/dist/{repo-security-plan-Y6PXBY44.js → repo-security-plan-UM6WX5PN.js} +4 -4
  61. package/dist/research-helpers-synthesize-DUOVXI5V.js +14 -0
  62. package/dist/{routing-memory-XFJJT5AW.js → routing-memory-OKYSDPXT.js} +3 -3
  63. package/dist/{session-memory-SRH5ZJRX.js → session-memory-ONUSRMYV.js} +4 -4
  64. package/dist/setup-config-BAZRBFN3.js +11 -0
  65. package/dist/{setup-custom-api-RQW6VSLO.js → setup-custom-api-LSMLWGKW.js} +5 -5
  66. package/dist/setup-data-dir-7PDX3R3Y.js +32 -0
  67. package/dist/{tool-memory-72T6Z6H4.js → tool-memory-Q5UWNVN3.js} +6 -6
  68. package/dist/{unified-registry-J2PCYXUU.js → unified-registry-Y327PN6R.js} +11 -11
  69. package/dist/{weather-report-GPYYWU5C.js → weather-report-C2VIAC2Q.js} +3 -3
  70. package/package.json +1 -1
  71. package/dist/chunk-5JQL2KOJ.js.map +0 -1
  72. package/dist/chunk-GNTWB3O2.js.map +0 -1
  73. package/dist/chunk-OMIR6ISN.js.map +0 -1
  74. package/dist/chunk-TECOLJ4Z.js.map +0 -1
  75. package/dist/factory-WAI626V6.js +0 -15
  76. package/dist/issue-triage-VAWDVGET.js +0 -16
  77. package/dist/research-helpers-synthesize-L22FP57N.js +0 -14
  78. package/dist/setup-config-T64H4UOO.js +0 -11
  79. package/dist/setup-data-dir-ZD4N4CEP.js +0 -32
  80. /package/dist/{ast-rule-runner-6XKCMMSK.js.map → ast-rule-runner-USLLC3XL.js.map} +0 -0
  81. /package/dist/{child-mcp-config-DMTIHAH5.js.map → child-mcp-config-K563PP6S.js.map} +0 -0
  82. /package/dist/{chunk-KVDXUGMQ.js.map → chunk-2SQO4FET.js.map} +0 -0
  83. /package/dist/{chunk-O6TFBXE5.js.map → chunk-4Q753QZM.js.map} +0 -0
  84. /package/dist/{chunk-OHM625YL.js.map → chunk-5U4XTYAB.js.map} +0 -0
  85. /package/dist/{chunk-PFZSS67U.js.map → chunk-CBZAMHBA.js.map} +0 -0
  86. /package/dist/{chunk-6ZCKENQU.js.map → chunk-DSVMQVJZ.js.map} +0 -0
  87. /package/dist/{chunk-LKNT7XND.js.map → chunk-EVYZZL7J.js.map} +0 -0
  88. /package/dist/{chunk-DSIJ5L3Y.js.map → chunk-FNL4J77P.js.map} +0 -0
  89. /package/dist/{chunk-LJNUMK67.js.map → chunk-GKLTEUFU.js.map} +0 -0
  90. /package/dist/{chunk-HXSR24QG.js.map → chunk-IXKFIO6O.js.map} +0 -0
  91. /package/dist/{chunk-ABIS6ZE7.js.map → chunk-J6GIJFV4.js.map} +0 -0
  92. /package/dist/{chunk-UWAJUJKU.js.map → chunk-KX7A23JM.js.map} +0 -0
  93. /package/dist/{chunk-LBIFDT6N.js.map → chunk-KXFPJOPL.js.map} +0 -0
  94. /package/dist/{chunk-UXUOJOXL.js.map → chunk-NNYVXSQF.js.map} +0 -0
  95. /package/dist/{chunk-DMUVBBOD.js.map → chunk-NQA77WXP.js.map} +0 -0
  96. /package/dist/{chunk-CPW3LHST.js.map → chunk-O24ENDHN.js.map} +0 -0
  97. /package/dist/{chunk-QFL3BFIV.js.map → chunk-OOIZ3IQD.js.map} +0 -0
  98. /package/dist/{chunk-3HU4VVD7.js.map → chunk-P6D7S7CY.js.map} +0 -0
  99. /package/dist/{chunk-RIB56PWI.js.map → chunk-Q75P5CVJ.js.map} +0 -0
  100. /package/dist/{chunk-T2ZQUZQW.js.map → chunk-QCDYJYGB.js.map} +0 -0
  101. /package/dist/{chunk-KUZV4AW5.js.map → chunk-S6MNVNQB.js.map} +0 -0
  102. /package/dist/{chunk-FQ6UCX2J.js.map → chunk-V66MMBO4.js.map} +0 -0
  103. /package/dist/{chunk-AHJUIF44.js.map → chunk-XARB7ITD.js.map} +0 -0
  104. /package/dist/{chunk-HQQ3ES3J.js.map → chunk-XNNNDQLK.js.map} +0 -0
  105. /package/dist/{chunk-XGAMDSC5.js.map → chunk-XWPZZEBX.js.map} +0 -0
  106. /package/dist/{chunk-VEMKYAW4.js.map → chunk-YG73EH43.js.map} +0 -0
  107. /package/dist/{chunk-ALJPZZ57.js.map → chunk-YPJRVA2H.js.map} +0 -0
  108. /package/dist/{chunk-QW343BTQ.js.map → chunk-ZE73MT66.js.map} +0 -0
  109. /package/dist/{chunk-AOSBH5H7.js.map → chunk-ZEOHUE2N.js.map} +0 -0
  110. /package/dist/{chunk-CJ2LQLZV.js.map → chunk-ZSFRB2S4.js.map} +0 -0
  111. /package/dist/{cli-circuit-breaker-VRTWILPB.js.map → cli-circuit-breaker-4ASNTBDP.js.map} +0 -0
  112. /package/dist/{composite-router-LAMVSGZ3.js.map → composite-router-V45AK6WD.js.map} +0 -0
  113. /package/dist/{consensus-vote-ZB72SHTV.js.map → consensus-vote-N2Z7FX3D.js.map} +0 -0
  114. /package/dist/{context-retriever-VRAIKZTE.js.map → context-retriever-NZOSGG6S.js.map} +0 -0
  115. /package/dist/{doctor-deep-ZZWIF64Z.js.map → doctor-deep-JRY3D7BP.js.map} +0 -0
  116. /package/dist/{doctor-live-KKIWEO3K.js.map → doctor-live-NSRHMHMG.js.map} +0 -0
  117. /package/dist/{expert-bridge-TMHBUOUK.js.map → expert-bridge-Q4SEPVTE.js.map} +0 -0
  118. /package/dist/{factory-KAMTEOXC.js.map → factory-JBK45P7G.js.map} +0 -0
  119. /package/dist/{factory-WAI626V6.js.map → factory-ZV4MR3VM.js.map} +0 -0
  120. /package/dist/{improvement-review-BI3B3PQ2.js.map → improvement-review-KHTIDV4R.js.map} +0 -0
  121. /package/dist/{init-opencode-EY7TUIML.js.map → init-opencode-6WGN5J6J.js.map} +0 -0
  122. /package/dist/{issue-triage-VAWDVGET.js.map → issue-triage-XVDXOZRW.js.map} +0 -0
  123. /package/dist/{learning-persistence-7BEPQEGN.js.map → learning-persistence-ER7E5Q6T.js.map} +0 -0
  124. /package/dist/{pr-reviewer-helpers-3A6ZFECZ.js.map → pr-reviewer-helpers-UMNMOUAO.js.map} +0 -0
  125. /package/dist/{registry-command-PWWFOIT3.js.map → registry-command-QRBAKWRK.js.map} +0 -0
  126. /package/dist/{repo-security-plan-Y6PXBY44.js.map → repo-security-plan-UM6WX5PN.js.map} +0 -0
  127. /package/dist/{research-helpers-synthesize-L22FP57N.js.map → research-helpers-synthesize-DUOVXI5V.js.map} +0 -0
  128. /package/dist/{routing-memory-XFJJT5AW.js.map → routing-memory-OKYSDPXT.js.map} +0 -0
  129. /package/dist/{session-memory-SRH5ZJRX.js.map → session-memory-ONUSRMYV.js.map} +0 -0
  130. /package/dist/{setup-config-T64H4UOO.js.map → setup-config-BAZRBFN3.js.map} +0 -0
  131. /package/dist/{setup-custom-api-RQW6VSLO.js.map → setup-custom-api-LSMLWGKW.js.map} +0 -0
  132. /package/dist/{setup-data-dir-ZD4N4CEP.js.map → setup-data-dir-7PDX3R3Y.js.map} +0 -0
  133. /package/dist/{tool-memory-72T6Z6H4.js.map → tool-memory-Q5UWNVN3.js.map} +0 -0
  134. /package/dist/{unified-registry-J2PCYXUU.js.map → unified-registry-Y327PN6R.js.map} +0 -0
  135. /package/dist/{weather-report-GPYYWU5C.js.map → weather-report-C2VIAC2Q.js.map} +0 -0
@@ -2,8 +2,9 @@ import {
2
2
  ensureLearningDir,
3
3
  getDecisionCostFile,
4
4
  getModelSelectionShadowFile,
5
- isPersistenceEnabled
6
- } from "./chunk-4UHDDCRB.js";
5
+ isPersistenceEnabled,
6
+ isStrategyDistillationEnabled
7
+ } from "./chunk-RQLIPUQK.js";
7
8
  import {
8
9
  getNexusDataDir,
9
10
  nexusDataPath,
@@ -2642,7 +2643,7 @@ function setDefaultRegistry(registry) {
2642
2643
  }
2643
2644
  async function reloadDefaultRegistry() {
2644
2645
  globalRegistry = buildDefaultRegistry();
2645
- const { resetGlobalRegistry } = await import("./unified-registry-J2PCYXUU.js");
2646
+ const { resetGlobalRegistry } = await import("./unified-registry-Y327PN6R.js");
2646
2647
  resetGlobalRegistry();
2647
2648
  return globalRegistry;
2648
2649
  }
@@ -9655,20 +9656,40 @@ var DistilledRuleStage = class {
9655
9656
  config;
9656
9657
  logger;
9657
9658
  rulesAppliedCount = 0;
9659
+ firstRunChecked = false;
9658
9660
  constructor(distiller, config, logger15) {
9659
9661
  this.distiller = distiller;
9660
9662
  this.config = { ...DEFAULT_CONFIG5, ...config };
9661
9663
  this.logger = logger15 ?? createLogger({ component: "DistilledRuleStage" });
9662
9664
  }
9663
9665
  canHandle(ctx) {
9666
+ this.checkPersistedTriggerOnce();
9664
9667
  const candidates = getRemainingCandidates(ctx);
9665
9668
  if (candidates.length <= 1) return false;
9666
9669
  const activeRules = this.distiller.getRules("active");
9667
9670
  return activeRules.length > 0;
9668
9671
  }
9669
9672
  route(ctx) {
9673
+ this.checkPersistedTriggerOnce();
9670
9674
  return Promise.resolve(this.routeSync(ctx));
9671
9675
  }
9676
+ /**
9677
+ * The first time this stage runs, let the distiller distill from the
9678
+ * persisted outcome store (#6512). Without it, rules only ever came from 50
9679
+ * outcomes seen inside one process, which short-lived CLI processes never
9680
+ * reach. A failure here is logged and routing continues without rules.
9681
+ */
9682
+ checkPersistedTriggerOnce() {
9683
+ if (this.firstRunChecked) return;
9684
+ this.firstRunChecked = true;
9685
+ try {
9686
+ this.distiller.checkPersistedTrigger();
9687
+ } catch (error) {
9688
+ this.logger.warn("Persisted-store distill trigger failed; routing without new rules", {
9689
+ error: error instanceof Error ? error.message : String(error)
9690
+ });
9691
+ }
9692
+ }
9672
9693
  recordOutcome(outcome) {
9673
9694
  this.distiller.onOutcome();
9674
9695
  this.logger.debug("Forwarded outcome to distiller", {
@@ -10196,317 +10217,8 @@ var DEFAULT_DISTILLER_CONFIG = {
10196
10217
  ruleExpiryMs: 24 * 60 * 60 * 1e3
10197
10218
  };
10198
10219
 
10199
- // src/learning/strategy-distiller.ts
10200
- function sigmoidConfidence(observations, center = 30) {
10201
- const raw = 1 / (1 + Math.exp(-(observations - center) / 5));
10202
- return Math.max(0, Math.min(1, raw));
10203
- }
10204
- function clampUnit(value) {
10205
- if (!Number.isFinite(value)) return 0;
10206
- return Math.max(0, Math.min(1, value));
10207
- }
10208
- function effectFor(patternType, metric, thresholds) {
10209
- switch (patternType) {
10210
- case "failure-rate":
10211
- return clampUnit(
10212
- (metric - thresholds.failureRateThreshold) / (1 - thresholds.failureRateThreshold)
10213
- );
10214
- case "success-rate":
10215
- return clampUnit(
10216
- (metric - thresholds.successRateThreshold) / (1 - thresholds.successRateThreshold)
10217
- );
10218
- case "latency-spike":
10219
- return clampUnit(
10220
- (metric - thresholds.latencyRatioThreshold) / thresholds.latencyRatioThreshold
10221
- );
10222
- }
10223
- }
10224
- function ruleFingerprint(patternType, cli, category) {
10225
- return `${patternType}:${cli}:${category}`;
10226
- }
10227
- function groupOutcomes(outcomes) {
10228
- const map2 = /* @__PURE__ */ new Map();
10229
- for (const o of outcomes) {
10230
- const key = `${o.cli}:${o.category}`;
10231
- const list = map2.get(key);
10232
- if (list !== void 0) {
10233
- list.push(o);
10234
- } else {
10235
- map2.set(key, [o]);
10236
- }
10237
- }
10238
- const groups = [];
10239
- for (const [key, list] of map2) {
10240
- const [cli, category] = key.split(":");
10241
- groups.push({ cli, category, outcomes: list });
10242
- }
10243
- return groups;
10244
- }
10245
- function percentile(sorted, p) {
10246
- if (sorted.length === 0) return 0;
10247
- const idx = Math.ceil(p / 100 * sorted.length) - 1;
10248
- return sorted[Math.max(0, idx)] ?? 0;
10249
- }
10250
- function detectFailurePatterns(groups, threshold) {
10251
- const patterns = [];
10252
- for (const g of groups) {
10253
- const failCount = g.outcomes.filter((o) => !o.success).length;
10254
- const failRate = failCount / g.outcomes.length;
10255
- if (failRate >= threshold) {
10256
- patterns.push({
10257
- cli: g.cli,
10258
- category: g.category,
10259
- patternType: "failure-rate",
10260
- action: failRate >= 0.8 ? "avoid" : "penalize",
10261
- metric: failRate,
10262
- observationCount: g.outcomes.length
10263
- });
10264
- }
10265
- }
10266
- return patterns;
10267
- }
10268
- function detectSuccessPatterns(groups, threshold) {
10269
- const patterns = [];
10270
- for (const g of groups) {
10271
- const successCount = g.outcomes.filter((o) => o.success).length;
10272
- const successRate3 = successCount / g.outcomes.length;
10273
- if (successRate3 >= threshold) {
10274
- patterns.push({
10275
- cli: g.cli,
10276
- category: g.category,
10277
- patternType: "success-rate",
10278
- action: "boost",
10279
- metric: successRate3,
10280
- observationCount: g.outcomes.length
10281
- });
10282
- }
10283
- }
10284
- return patterns;
10285
- }
10286
- function detectLatencyPatterns(groups, threshold) {
10287
- const patterns = [];
10288
- for (const g of groups) {
10289
- const durations = g.outcomes.map((o) => o.durationMs).sort((a, b) => a - b);
10290
- if (durations.length < 3) continue;
10291
- const median = percentile(durations, 50);
10292
- const p90 = percentile(durations, 90);
10293
- if (median <= 0) continue;
10294
- const ratio = p90 / median;
10295
- if (ratio >= threshold) {
10296
- patterns.push({
10297
- cli: g.cli,
10298
- category: g.category,
10299
- patternType: "latency-spike",
10300
- action: "penalize",
10301
- metric: ratio,
10302
- observationCount: g.outcomes.length
10303
- });
10304
- }
10305
- }
10306
- return patterns;
10307
- }
10308
- var StrategyDistiller = class {
10309
- /** Protected so `PersistentStrategyDistiller` hydrates legacy rules under the live thresholds. */
10310
- config;
10311
- outcomeStore;
10312
- logger;
10313
- rules = /* @__PURE__ */ new Map();
10314
- outcomeCounter = 0;
10315
- lastDistillAt;
10316
- constructor(outcomeStore, logger15, config) {
10317
- this.outcomeStore = outcomeStore;
10318
- this.config = { ...DEFAULT_DISTILLER_CONFIG, ...config };
10319
- this.logger = logger15 ?? createLogger({ component: "StrategyDistiller" });
10320
- }
10321
- /** Called for each processed outcome. Triggers distillation at threshold. */
10322
- onOutcome() {
10323
- this.outcomeCounter++;
10324
- if (this.outcomeCounter >= this.config.triggerThreshold) {
10325
- this.distill();
10326
- this.outcomeCounter = 0;
10327
- }
10328
- }
10329
- /** Run distillation on current OutcomeStore data. */
10330
- distill() {
10331
- const now = getTimeProvider().now();
10332
- const outcomes = this.outcomeStore.query();
10333
- const groups = groupOutcomes(outcomes);
10334
- this.expireRules(now);
10335
- const failurePatterns = detectFailurePatterns(groups, this.config.failureRateThreshold);
10336
- const successPatterns = detectSuccessPatterns(groups, this.config.successRateThreshold);
10337
- const latencyPatterns = detectLatencyPatterns(groups, this.config.latencyRatioThreshold);
10338
- const allPatterns = [...failurePatterns, ...successPatterns, ...latencyPatterns];
10339
- for (const pattern of allPatterns) {
10340
- this.upsertRule(pattern, now);
10341
- }
10342
- this.enforceMaxRules();
10343
- this.lastDistillAt = now;
10344
- this.logger.debug("Distillation complete", {
10345
- rulesTotal: this.rules.size,
10346
- patternsFound: allPatterns.length,
10347
- outcomesAnalyzed: outcomes.length
10348
- });
10349
- }
10350
- /** Get rules filtered by status. */
10351
- getRules(status) {
10352
- const all = [...this.rules.values()];
10353
- if (status === void 0) return all;
10354
- return all.filter((r) => r.status === status);
10355
- }
10356
- /** Get distiller statistics. */
10357
- getStats() {
10358
- const countByStatus = {
10359
- draft: 0,
10360
- active: 0,
10361
- promoted: 0,
10362
- expired: 0
10363
- };
10364
- for (const rule of this.rules.values()) {
10365
- countByStatus[rule.status]++;
10366
- }
10367
- return {
10368
- ruleCountByStatus: countByStatus,
10369
- totalRules: this.rules.size,
10370
- lastDistillAt: this.lastDistillAt,
10371
- outcomesSinceLastDistill: this.outcomeCounter
10372
- };
10373
- }
10374
- /**
10375
- * Promote high-confidence rules to RoutingMemory.
10376
- * Rules must be active, with sufficient observations and confidence.
10377
- *
10378
- * @deprecated No production caller (#5004 finding 4). `DistilledRuleStage`
10379
- * is the single channel by which distilled rules reach routing; this
10380
- * second channel into `RoutingMemory` is kept only so the deprecation is
10381
- * non-breaking. Removal is tracked in #5467. Note the gate now compares
10382
- * `confidence = support × effect` against `promotionConfidence`, so a rule
10383
- * that would have promoted on sample size alone may no longer clear it.
10384
- */
10385
- promote(routingMemory) {
10386
- let promoted = 0;
10387
- for (const [id, rule] of this.rules) {
10388
- if (rule.status !== "active") continue;
10389
- if (rule.observationCount < this.config.minObservationsForActive) continue;
10390
- if (rule.confidence < this.config.promotionConfidence) continue;
10391
- const performance2 = this.ruleToPerformance(rule);
10392
- routingMemory.storePreference(rule.cli, rule.category, performance2);
10393
- this.rules.set(id, { ...rule, status: "promoted", updatedAt: getTimeProvider().now() });
10394
- promoted++;
10395
- this.logger.info("Promoted distilled rule to RoutingMemory", {
10396
- ruleId: id,
10397
- cli: rule.cli,
10398
- category: rule.category,
10399
- confidence: rule.confidence
10400
- });
10401
- }
10402
- return promoted;
10403
- }
10404
- // ==========================================================================
10405
- // Protected — for subclass hydration (Issue #1009)
10406
- // ==========================================================================
10407
- /** Load pre-existing rules (e.g., from disk). Used by PersistentStrategyDistiller. */
10408
- loadRules(rules) {
10409
- for (const rule of rules) {
10410
- this.rules.set(rule.id, rule);
10411
- }
10412
- }
10413
- // ==========================================================================
10414
- // Private
10415
- // ==========================================================================
10416
- upsertRule(pattern, now) {
10417
- const id = ruleFingerprint(pattern.patternType, pattern.cli, pattern.category);
10418
- const existing = this.rules.get(id);
10419
- if (existing === void 0 && pattern.observationCount < this.config.minObservationsForDraft) {
10420
- return;
10421
- }
10422
- const support = sigmoidConfidence(pattern.observationCount);
10423
- const effect = effectFor(pattern.patternType, pattern.metric, this.config);
10424
- const confidence = support * effect;
10425
- const status = this.computeStatus(pattern.observationCount, existing?.status);
10426
- if (existing !== void 0) {
10427
- this.rules.set(id, {
10428
- ...existing,
10429
- action: pattern.action,
10430
- confidence,
10431
- support,
10432
- effect,
10433
- observationCount: pattern.observationCount,
10434
- metric: pattern.metric,
10435
- status,
10436
- updatedAt: now
10437
- });
10438
- } else {
10439
- this.rules.set(id, {
10440
- id,
10441
- patternType: pattern.patternType,
10442
- cli: pattern.cli,
10443
- category: pattern.category,
10444
- action: pattern.action,
10445
- confidence,
10446
- support,
10447
- effect,
10448
- observationCount: pattern.observationCount,
10449
- metric: pattern.metric,
10450
- status,
10451
- createdAt: now,
10452
- updatedAt: now,
10453
- tainted: false
10454
- });
10455
- }
10456
- }
10457
- computeStatus(observations, existing) {
10458
- if (existing === "promoted") return "promoted";
10459
- if (observations >= this.config.minObservationsForActive) return "active";
10460
- return "draft";
10461
- }
10462
- expireRules(now) {
10463
- for (const [id, rule] of this.rules) {
10464
- if (rule.status === "promoted") continue;
10465
- if (now - rule.updatedAt > this.config.ruleExpiryMs) {
10466
- this.rules.set(id, { ...rule, status: "expired", updatedAt: now });
10467
- }
10468
- }
10469
- }
10470
- enforceMaxRules() {
10471
- if (this.rules.size <= this.config.maxRules) return;
10472
- const sorted = [...this.rules.entries()].sort((a, b) => {
10473
- if (a[1].status === "expired" && b[1].status !== "expired") return -1;
10474
- if (b[1].status === "expired" && a[1].status !== "expired") return 1;
10475
- return a[1].confidence - b[1].confidence;
10476
- });
10477
- const excess = this.rules.size - this.config.maxRules;
10478
- for (let i = 0; i < excess; i++) {
10479
- const entry = sorted[i];
10480
- if (entry !== void 0) {
10481
- this.rules.delete(entry[0]);
10482
- }
10483
- }
10484
- }
10485
- /** Convert rule metrics into ModelPerformance for RoutingMemory. */
10486
- ruleToPerformance(rule) {
10487
- const successRate3 = rule.patternType === "success-rate" ? rule.metric : 1 - rule.metric;
10488
- return {
10489
- avgQuality: rule.confidence,
10490
- successRate: Math.max(0, Math.min(1, successRate3)),
10491
- avgLatencyMs: 0,
10492
- avgTokens: 0,
10493
- observations: rule.observationCount
10494
- };
10495
- }
10496
- };
10497
- function createStrategyDistiller(outcomeStore, logger15, config) {
10498
- return new StrategyDistiller(outcomeStore, logger15, config);
10499
- }
10500
- var persistentDistillerFactory;
10501
- function registerPersistentDistillerFactory(factory) {
10502
- persistentDistillerFactory = factory;
10503
- }
10504
- function createPersistentDistillerOrFallback(outcomeStore, logger15) {
10505
- if (persistentDistillerFactory !== void 0) {
10506
- return persistentDistillerFactory(outcomeStore, logger15);
10507
- }
10508
- return new StrategyDistiller(outcomeStore, logger15);
10509
- }
10220
+ // src/learning/distiller-eligibility.ts
10221
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
10510
10222
 
10511
10223
  // src/orchestration/outcomes/outcome-types.ts
10512
10224
  import { z as z14 } from "zod";
@@ -10747,37 +10459,442 @@ function classifyText(text) {
10747
10459
  if (matchesAny(text, PARSE_PATTERNS)) return "parse";
10748
10460
  return classifyExecutionOrGeneric(text);
10749
10461
  }
10750
- var MAX_ERROR_MESSAGE_LENGTH = 500;
10751
- function extractNonErrorMessage(error) {
10752
- if (error === null || error === void 0) return void 0;
10753
- if (typeof error === "string") {
10754
- return error.slice(0, MAX_ERROR_MESSAGE_LENGTH);
10462
+ var MAX_ERROR_MESSAGE_LENGTH = 500;
10463
+ function extractNonErrorMessage(error) {
10464
+ if (error === null || error === void 0) return void 0;
10465
+ if (typeof error === "string") {
10466
+ return error.slice(0, MAX_ERROR_MESSAGE_LENGTH);
10467
+ }
10468
+ if (typeof error === "object") {
10469
+ const record = error;
10470
+ if (typeof record["message"] === "string") {
10471
+ return record["message"].slice(0, MAX_ERROR_MESSAGE_LENGTH);
10472
+ }
10473
+ try {
10474
+ const json = JSON.stringify(error);
10475
+ return json.slice(0, MAX_ERROR_MESSAGE_LENGTH);
10476
+ } catch {
10477
+ return void 0;
10478
+ }
10479
+ }
10480
+ return void 0;
10481
+ }
10482
+ function categorizeOutcomeError(error) {
10483
+ if (error instanceof Error) {
10484
+ const text = `${error.message.toLowerCase()} ${error.name.toLowerCase()}`;
10485
+ return classifyText(text);
10486
+ }
10487
+ const extracted = extractNonErrorMessage(error);
10488
+ if (extracted === void 0) return "execution";
10489
+ return classifyText(extracted.toLowerCase());
10490
+ }
10491
+ function categorizeOutcomeErrorMessage(msg) {
10492
+ return classifyText(msg.toLowerCase());
10493
+ }
10494
+
10495
+ // src/learning/distiller-eligibility.ts
10496
+ var ROUTABLE_CLIS = new Set(CLI_NAMES);
10497
+ function isDistillerEligible(outcome) {
10498
+ if (outcome.source !== "delegate") return false;
10499
+ if (outcome.cliSource !== "executed") return false;
10500
+ if (!(outcome.durationMs > 0)) return false;
10501
+ return ROUTABLE_CLIS.has(outcome.cli);
10502
+ }
10503
+ function countEligibleSince(outcomes, sinceMs) {
10504
+ let count = 0;
10505
+ for (const outcome of outcomes) {
10506
+ if (!isDistillerEligible(outcome)) continue;
10507
+ if (sinceMs !== void 0 && !(Date.parse(outcome.timestamp) > sinceMs)) continue;
10508
+ count++;
10509
+ }
10510
+ return count;
10511
+ }
10512
+ var eligibilityFieldsSchema;
10513
+ function getEligibilityFieldsSchema() {
10514
+ eligibilityFieldsSchema ??= TaskOutcomeSchema.pick({
10515
+ source: true,
10516
+ cli: true,
10517
+ cliSource: true,
10518
+ durationMs: true
10519
+ });
10520
+ return eligibilityFieldsSchema;
10521
+ }
10522
+ function parseLine(line) {
10523
+ try {
10524
+ const parsed = getEligibilityFieldsSchema().safeParse(JSON.parse(line));
10525
+ return parsed.success ? parsed.data : void 0;
10526
+ } catch {
10527
+ return void 0;
10528
+ }
10529
+ }
10530
+ function countEligibleOutcomesInFile(filePath) {
10531
+ if (!existsSync6(filePath)) return 0;
10532
+ let count = 0;
10533
+ for (const line of readFileSync6(filePath, "utf-8").split("\n")) {
10534
+ if (line.trim().length === 0) continue;
10535
+ const fields = parseLine(line);
10536
+ if (fields !== void 0 && isDistillerEligible(fields)) count++;
10537
+ }
10538
+ return count;
10539
+ }
10540
+
10541
+ // src/learning/strategy-distiller.ts
10542
+ function sigmoidConfidence(observations, center = 30) {
10543
+ const raw = 1 / (1 + Math.exp(-(observations - center) / 5));
10544
+ return Math.max(0, Math.min(1, raw));
10545
+ }
10546
+ function clampUnit(value) {
10547
+ if (!Number.isFinite(value)) return 0;
10548
+ return Math.max(0, Math.min(1, value));
10549
+ }
10550
+ function effectFor(patternType, metric, thresholds) {
10551
+ switch (patternType) {
10552
+ case "failure-rate":
10553
+ return clampUnit(
10554
+ (metric - thresholds.failureRateThreshold) / (1 - thresholds.failureRateThreshold)
10555
+ );
10556
+ case "success-rate":
10557
+ return clampUnit(
10558
+ (metric - thresholds.successRateThreshold) / (1 - thresholds.successRateThreshold)
10559
+ );
10560
+ case "latency-spike":
10561
+ return clampUnit(
10562
+ (metric - thresholds.latencyRatioThreshold) / thresholds.latencyRatioThreshold
10563
+ );
10564
+ }
10565
+ }
10566
+ function ruleFingerprint(patternType, cli, category) {
10567
+ return `${patternType}:${cli}:${category}`;
10568
+ }
10569
+ function groupOutcomes(outcomes) {
10570
+ const map2 = /* @__PURE__ */ new Map();
10571
+ for (const o of outcomes) {
10572
+ const key = `${o.cli}:${o.category}`;
10573
+ const list = map2.get(key);
10574
+ if (list !== void 0) {
10575
+ list.push(o);
10576
+ } else {
10577
+ map2.set(key, [o]);
10578
+ }
10579
+ }
10580
+ const groups = [];
10581
+ for (const [key, list] of map2) {
10582
+ const [cli, category] = key.split(":");
10583
+ groups.push({ cli, category, outcomes: list });
10584
+ }
10585
+ return groups;
10586
+ }
10587
+ function percentile(sorted, p) {
10588
+ if (sorted.length === 0) return 0;
10589
+ const idx = Math.ceil(p / 100 * sorted.length) - 1;
10590
+ return sorted[Math.max(0, idx)] ?? 0;
10591
+ }
10592
+ function detectFailurePatterns(groups, threshold) {
10593
+ const patterns = [];
10594
+ for (const g of groups) {
10595
+ const failCount = g.outcomes.filter((o) => !o.success).length;
10596
+ const failRate = failCount / g.outcomes.length;
10597
+ if (failRate >= threshold) {
10598
+ patterns.push({
10599
+ cli: g.cli,
10600
+ category: g.category,
10601
+ patternType: "failure-rate",
10602
+ action: failRate >= 0.8 ? "avoid" : "penalize",
10603
+ metric: failRate,
10604
+ observationCount: g.outcomes.length
10605
+ });
10606
+ }
10607
+ }
10608
+ return patterns;
10609
+ }
10610
+ function detectSuccessPatterns(groups, threshold) {
10611
+ const patterns = [];
10612
+ for (const g of groups) {
10613
+ const successCount = g.outcomes.filter((o) => o.success).length;
10614
+ const successRate3 = successCount / g.outcomes.length;
10615
+ if (successRate3 >= threshold) {
10616
+ patterns.push({
10617
+ cli: g.cli,
10618
+ category: g.category,
10619
+ patternType: "success-rate",
10620
+ action: "boost",
10621
+ metric: successRate3,
10622
+ observationCount: g.outcomes.length
10623
+ });
10624
+ }
10625
+ }
10626
+ return patterns;
10627
+ }
10628
+ function detectLatencyPatterns(groups, threshold) {
10629
+ const patterns = [];
10630
+ for (const g of groups) {
10631
+ const durations = g.outcomes.map((o) => o.durationMs).sort((a, b) => a - b);
10632
+ if (durations.length < 3) continue;
10633
+ const median = percentile(durations, 50);
10634
+ const p90 = percentile(durations, 90);
10635
+ if (median <= 0) continue;
10636
+ const ratio = p90 / median;
10637
+ if (ratio >= threshold) {
10638
+ patterns.push({
10639
+ cli: g.cli,
10640
+ category: g.category,
10641
+ patternType: "latency-spike",
10642
+ action: "penalize",
10643
+ metric: ratio,
10644
+ observationCount: g.outcomes.length
10645
+ });
10646
+ }
10755
10647
  }
10756
- if (typeof error === "object") {
10757
- const record = error;
10758
- if (typeof record["message"] === "string") {
10759
- return record["message"].slice(0, MAX_ERROR_MESSAGE_LENGTH);
10648
+ return patterns;
10649
+ }
10650
+ var StrategyDistiller = class {
10651
+ /** Protected so `PersistentStrategyDistiller` hydrates legacy rules under the live thresholds. */
10652
+ config;
10653
+ outcomeStore;
10654
+ logger;
10655
+ rules = /* @__PURE__ */ new Map();
10656
+ outcomeCounter = 0;
10657
+ lastDistillAt;
10658
+ eligibleAtLastDistill;
10659
+ persistedTriggerChecked = false;
10660
+ constructor(outcomeStore, logger15, config) {
10661
+ this.outcomeStore = outcomeStore;
10662
+ this.config = { ...DEFAULT_DISTILLER_CONFIG, ...config };
10663
+ this.logger = logger15 ?? createLogger({ component: "StrategyDistiller" });
10664
+ }
10665
+ /**
10666
+ * Called for each processed outcome. Triggers distillation at threshold.
10667
+ * This counter is per-process; `checkPersistedTrigger` covers the
10668
+ * cross-process case (#6512).
10669
+ */
10670
+ onOutcome() {
10671
+ this.outcomeCounter++;
10672
+ if (this.outcomeCounter >= this.config.triggerThreshold) {
10673
+ this.distill();
10674
+ this.outcomeCounter = 0;
10760
10675
  }
10761
- try {
10762
- const json = JSON.stringify(error);
10763
- return json.slice(0, MAX_ERROR_MESSAGE_LENGTH);
10764
- } catch {
10765
- return void 0;
10676
+ }
10677
+ /**
10678
+ * First-route trigger against the persisted store (#6512).
10679
+ *
10680
+ * The in-process counter above only fires after `triggerThreshold` outcomes
10681
+ * in ONE process, which short-lived CLI processes never reach. This checks
10682
+ * the store instead: distill when it holds at least `triggerThreshold`
10683
+ * eligible outcomes newer than the last distill (a hydrated snapshot's
10684
+ * `savedAt`; no snapshot means all of them count). With no snapshot and zero
10685
+ * eligible outcomes it distills anyway, so the empty state is recorded as
10686
+ * "0 eligible, 0 rules, at <time>" rather than left as "never".
10687
+ *
10688
+ * Before deciding, rules past `ruleExpiryMs` are expired, so a hydrated rule
10689
+ * that is out of date is never applied.
10690
+ *
10691
+ * Runs at most once per instance; `DistilledRuleStage` calls it when it first
10692
+ * runs, so constructing a distiller stays cheap. Returns whether it distilled.
10693
+ */
10694
+ checkPersistedTrigger() {
10695
+ if (this.persistedTriggerChecked) return false;
10696
+ this.persistedTriggerChecked = true;
10697
+ this.expireRules(getTimeProvider().now());
10698
+ const newer = countEligibleSince(this.outcomeStore.query(), this.lastDistillAt);
10699
+ const recordEmpty = this.lastDistillAt === void 0 && newer === 0;
10700
+ if (newer < this.config.triggerThreshold && !recordEmpty) return false;
10701
+ this.logger.debug("Persisted-store distill trigger fired", {
10702
+ eligibleSinceLastDistill: newer,
10703
+ triggerThreshold: this.config.triggerThreshold
10704
+ });
10705
+ this.distill();
10706
+ return true;
10707
+ }
10708
+ /** Run distillation on the eligible outcomes in the OutcomeStore (#6512). */
10709
+ distill() {
10710
+ const now = getTimeProvider().now();
10711
+ const outcomes = this.outcomeStore.query().filter(isDistillerEligible);
10712
+ const groups = groupOutcomes(outcomes);
10713
+ this.expireRules(now);
10714
+ const failurePatterns = detectFailurePatterns(groups, this.config.failureRateThreshold);
10715
+ const successPatterns = detectSuccessPatterns(groups, this.config.successRateThreshold);
10716
+ const latencyPatterns = detectLatencyPatterns(groups, this.config.latencyRatioThreshold);
10717
+ const allPatterns = [...failurePatterns, ...successPatterns, ...latencyPatterns];
10718
+ for (const pattern of allPatterns) {
10719
+ this.upsertRule(pattern, now);
10766
10720
  }
10721
+ this.enforceMaxRules();
10722
+ this.lastDistillAt = now;
10723
+ this.eligibleAtLastDistill = outcomes.length;
10724
+ this.logger.debug("Distillation complete", {
10725
+ rulesTotal: this.rules.size,
10726
+ patternsFound: allPatterns.length,
10727
+ outcomesAnalyzed: outcomes.length
10728
+ });
10767
10729
  }
10768
- return void 0;
10769
- }
10770
- function categorizeOutcomeError(error) {
10771
- if (error instanceof Error) {
10772
- const text = `${error.message.toLowerCase()} ${error.name.toLowerCase()}`;
10773
- return classifyText(text);
10730
+ /** Get rules filtered by status. */
10731
+ getRules(status) {
10732
+ const all = [...this.rules.values()];
10733
+ if (status === void 0) return all;
10734
+ return all.filter((r) => r.status === status);
10774
10735
  }
10775
- const extracted = extractNonErrorMessage(error);
10776
- if (extracted === void 0) return "execution";
10777
- return classifyText(extracted.toLowerCase());
10736
+ /** Get distiller statistics. */
10737
+ getStats() {
10738
+ const countByStatus = {
10739
+ draft: 0,
10740
+ active: 0,
10741
+ promoted: 0,
10742
+ expired: 0
10743
+ };
10744
+ for (const rule of this.rules.values()) {
10745
+ countByStatus[rule.status]++;
10746
+ }
10747
+ return {
10748
+ ruleCountByStatus: countByStatus,
10749
+ totalRules: this.rules.size,
10750
+ lastDistillAt: this.lastDistillAt,
10751
+ outcomesSinceLastDistill: this.outcomeCounter,
10752
+ eligibleOutcomesAtLastDistill: this.eligibleAtLastDistill
10753
+ };
10754
+ }
10755
+ /**
10756
+ * Promote high-confidence rules to RoutingMemory.
10757
+ * Rules must be active, with sufficient observations and confidence.
10758
+ *
10759
+ * @deprecated No production caller (#5004 finding 4). `DistilledRuleStage`
10760
+ * is the single channel by which distilled rules reach routing; this
10761
+ * second channel into `RoutingMemory` is kept only so the deprecation is
10762
+ * non-breaking. Removal is tracked in #5467. Note the gate now compares
10763
+ * `confidence = support × effect` against `promotionConfidence`, so a rule
10764
+ * that would have promoted on sample size alone may no longer clear it.
10765
+ */
10766
+ promote(routingMemory) {
10767
+ let promoted = 0;
10768
+ for (const [id, rule] of this.rules) {
10769
+ if (rule.status !== "active") continue;
10770
+ if (rule.observationCount < this.config.minObservationsForActive) continue;
10771
+ if (rule.confidence < this.config.promotionConfidence) continue;
10772
+ const performance2 = this.ruleToPerformance(rule);
10773
+ routingMemory.storePreference(rule.cli, rule.category, performance2);
10774
+ this.rules.set(id, { ...rule, status: "promoted", updatedAt: getTimeProvider().now() });
10775
+ promoted++;
10776
+ this.logger.info("Promoted distilled rule to RoutingMemory", {
10777
+ ruleId: id,
10778
+ cli: rule.cli,
10779
+ category: rule.category,
10780
+ confidence: rule.confidence
10781
+ });
10782
+ }
10783
+ return promoted;
10784
+ }
10785
+ // ==========================================================================
10786
+ // Protected — for subclass hydration (Issue #1009)
10787
+ // ==========================================================================
10788
+ /** Load pre-existing rules (e.g., from disk). Used by PersistentStrategyDistiller. */
10789
+ loadRules(rules) {
10790
+ for (const rule of rules) {
10791
+ this.rules.set(rule.id, rule);
10792
+ }
10793
+ }
10794
+ /**
10795
+ * Restore last-distill state from a persisted snapshot (#6512), so the
10796
+ * first-route trigger counts only outcomes newer than that snapshot.
10797
+ */
10798
+ restoreDistillState(lastDistillAt, eligibleOutcomes) {
10799
+ this.lastDistillAt = lastDistillAt;
10800
+ this.eligibleAtLastDistill = eligibleOutcomes;
10801
+ }
10802
+ // ==========================================================================
10803
+ // Private
10804
+ // ==========================================================================
10805
+ upsertRule(pattern, now) {
10806
+ const id = ruleFingerprint(pattern.patternType, pattern.cli, pattern.category);
10807
+ const existing = this.rules.get(id);
10808
+ if (existing === void 0 && pattern.observationCount < this.config.minObservationsForDraft) {
10809
+ return;
10810
+ }
10811
+ const support = sigmoidConfidence(pattern.observationCount);
10812
+ const effect = effectFor(pattern.patternType, pattern.metric, this.config);
10813
+ const confidence = support * effect;
10814
+ const status = this.computeStatus(pattern.observationCount, existing?.status);
10815
+ if (existing !== void 0) {
10816
+ this.rules.set(id, {
10817
+ ...existing,
10818
+ action: pattern.action,
10819
+ confidence,
10820
+ support,
10821
+ effect,
10822
+ observationCount: pattern.observationCount,
10823
+ metric: pattern.metric,
10824
+ status,
10825
+ updatedAt: now
10826
+ });
10827
+ } else {
10828
+ this.rules.set(id, {
10829
+ id,
10830
+ patternType: pattern.patternType,
10831
+ cli: pattern.cli,
10832
+ category: pattern.category,
10833
+ action: pattern.action,
10834
+ confidence,
10835
+ support,
10836
+ effect,
10837
+ observationCount: pattern.observationCount,
10838
+ metric: pattern.metric,
10839
+ status,
10840
+ createdAt: now,
10841
+ updatedAt: now,
10842
+ tainted: false
10843
+ });
10844
+ }
10845
+ }
10846
+ computeStatus(observations, existing) {
10847
+ if (existing === "promoted") return "promoted";
10848
+ if (observations >= this.config.minObservationsForActive) return "active";
10849
+ return "draft";
10850
+ }
10851
+ expireRules(now) {
10852
+ for (const [id, rule] of this.rules) {
10853
+ if (rule.status === "promoted") continue;
10854
+ if (now - rule.updatedAt > this.config.ruleExpiryMs) {
10855
+ this.rules.set(id, { ...rule, status: "expired", updatedAt: now });
10856
+ }
10857
+ }
10858
+ }
10859
+ enforceMaxRules() {
10860
+ if (this.rules.size <= this.config.maxRules) return;
10861
+ const sorted = [...this.rules.entries()].sort((a, b) => {
10862
+ if (a[1].status === "expired" && b[1].status !== "expired") return -1;
10863
+ if (b[1].status === "expired" && a[1].status !== "expired") return 1;
10864
+ return a[1].confidence - b[1].confidence;
10865
+ });
10866
+ const excess = this.rules.size - this.config.maxRules;
10867
+ for (let i = 0; i < excess; i++) {
10868
+ const entry = sorted[i];
10869
+ if (entry !== void 0) {
10870
+ this.rules.delete(entry[0]);
10871
+ }
10872
+ }
10873
+ }
10874
+ /** Convert rule metrics into ModelPerformance for RoutingMemory. */
10875
+ ruleToPerformance(rule) {
10876
+ const successRate3 = rule.patternType === "success-rate" ? rule.metric : 1 - rule.metric;
10877
+ return {
10878
+ avgQuality: rule.confidence,
10879
+ successRate: Math.max(0, Math.min(1, successRate3)),
10880
+ avgLatencyMs: 0,
10881
+ avgTokens: 0,
10882
+ observations: rule.observationCount
10883
+ };
10884
+ }
10885
+ };
10886
+ function createStrategyDistiller(outcomeStore, logger15, config) {
10887
+ return new StrategyDistiller(outcomeStore, logger15, config);
10778
10888
  }
10779
- function categorizeOutcomeErrorMessage(msg) {
10780
- return classifyText(msg.toLowerCase());
10889
+ var persistentDistillerFactory;
10890
+ function registerPersistentDistillerFactory(factory) {
10891
+ persistentDistillerFactory = factory;
10892
+ }
10893
+ function createPersistentDistillerOrFallback(outcomeStore, logger15) {
10894
+ if (persistentDistillerFactory !== void 0) {
10895
+ return persistentDistillerFactory(outcomeStore, logger15);
10896
+ }
10897
+ return new StrategyDistiller(outcomeStore, logger15);
10781
10898
  }
10782
10899
 
10783
10900
  // src/orchestration/outcomes/outcome-store.ts
@@ -12874,7 +12991,7 @@ function aggregateDecisionCosts(records, windowMs) {
12874
12991
  import { z as z18 } from "zod";
12875
12992
 
12876
12993
  // src/config/jsonl-store.ts
12877
- import { appendFileSync as appendFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync } from "node:fs";
12994
+ import { appendFileSync as appendFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync } from "node:fs";
12878
12995
  import { dirname as dirname5 } from "node:path";
12879
12996
  var DIR_MODE = 448;
12880
12997
  var JsonlStore = class {
@@ -12937,14 +13054,14 @@ var JsonlStore = class {
12937
13054
  }
12938
13055
  }
12939
13056
  hydrate() {
12940
- if (!existsSync6(this.filePath)) {
13057
+ if (!existsSync7(this.filePath)) {
12941
13058
  this.logger.debug("No JSONL file found, starting fresh", { path: this.filePath });
12942
13059
  return;
12943
13060
  }
12944
13061
  let loaded = 0;
12945
13062
  let skipped = 0;
12946
13063
  try {
12947
- const content = readFileSync6(this.filePath, "utf-8");
13064
+ const content = readFileSync7(this.filePath, "utf-8");
12948
13065
  const lines = content.split("\n").filter((line) => line.trim().length > 0);
12949
13066
  for (const line of lines) {
12950
13067
  try {
@@ -15216,7 +15333,7 @@ function resolveModelForTier(cliName, tier, opts = {}) {
15216
15333
  }
15217
15334
 
15218
15335
  // src/cli-adapters/model-selection-shadow.ts
15219
- import { appendFileSync as appendFileSync4, existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
15336
+ import { appendFileSync as appendFileSync4, existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
15220
15337
  import { z as z22 } from "zod";
15221
15338
  function isRouteModelShadowEnabled() {
15222
15339
  return parseBoolEnv("NEXUS_ROUTE_MODEL_SHADOW", false) && isPersistenceEnabled();
@@ -15267,11 +15384,11 @@ function persistModelSelectionShadowRecord(record) {
15267
15384
  }
15268
15385
  function readModelSelectionShadowRecords() {
15269
15386
  const file = getModelSelectionShadowFile();
15270
- if (!existsSync7(file)) return [];
15387
+ if (!existsSync8(file)) return [];
15271
15388
  const records = [];
15272
15389
  try {
15273
15390
  const cutoff = Date.now() - READ_LOOKBACK_MS;
15274
- const lines = readFileSync7(file, "utf-8").split("\n").filter((l) => l.trim().length > 0);
15391
+ const lines = readFileSync8(file, "utf-8").split("\n").filter((l) => l.trim().length > 0);
15275
15392
  for (const line of lines) {
15276
15393
  let parsed;
15277
15394
  try {
@@ -15626,6 +15743,9 @@ function generateTraceId2() {
15626
15743
  // src/cli-adapters/composite-router.ts
15627
15744
  var FAILURE_QUALITY_SCORE = 0.3;
15628
15745
  var DEFAULT_TOKEN_ESTIMATE = 1e3;
15746
+ function applyDistillationSwitch(config) {
15747
+ return isStrategyDistillationEnabled() ? config : { ...config, enableStrategyDistillation: false };
15748
+ }
15629
15749
  var CompositeRouter = class _CompositeRouter {
15630
15750
  config;
15631
15751
  logger;
@@ -15708,7 +15828,7 @@ var CompositeRouter = class _CompositeRouter {
15708
15828
  availableModelsCache,
15709
15829
  ...baseConfig
15710
15830
  } = config ?? {};
15711
- this.config = CompositeRouterConfigSchema.parse(baseConfig);
15831
+ this.config = applyDistillationSwitch(CompositeRouterConfigSchema.parse(baseConfig));
15712
15832
  this.logger = logger15 ?? createLogger({ component: "CompositeRouter" });
15713
15833
  this.pendingRoutingOutcomes = new PendingRoutingOutcomes(this.logger);
15714
15834
  this.adapters = adapters;
@@ -15894,7 +16014,7 @@ var CompositeRouter = class _CompositeRouter {
15894
16014
  */
15895
16015
  async consultUnifiedContext(task) {
15896
16016
  try {
15897
- const { getContextForTask, inferTaskCategory } = await import("./context-retriever-VRAIKZTE.js");
16017
+ const { getContextForTask, inferTaskCategory } = await import("./context-retriever-NZOSGG6S.js");
15898
16018
  const ctx = await getContextForTask({
15899
16019
  task: task.content,
15900
16020
  category: inferTaskCategory(task.content),
@@ -17477,6 +17597,7 @@ export {
17477
17597
  RoutingMemory,
17478
17598
  createRoutingMemory,
17479
17599
  DEFAULT_DISTILLER_CONFIG,
17600
+ countEligibleOutcomesInFile,
17480
17601
  sigmoidConfidence,
17481
17602
  effectFor,
17482
17603
  detectFailurePatterns,
@@ -17529,4 +17650,4 @@ export {
17529
17650
  AgentCapability,
17530
17651
  OrchestratorError
17531
17652
  };
17532
- //# sourceMappingURL=chunk-OMIR6ISN.js.map
17653
+ //# sourceMappingURL=chunk-64IJKT4I.js.map