@evomap/evolver-core 2.0.0-beta.2 → 2.0.0-beta.22

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 (240) hide show
  1. package/assets/gep/genes.jsonl +5 -5
  2. package/dist/algo/bans.d.ts +10 -1
  3. package/dist/algo/bans.js +56 -6
  4. package/dist/algo/candidateAssembly.d.ts +25 -2
  5. package/dist/algo/candidateAssembly.js +171 -26
  6. package/dist/algo/capabilityCandidates.js +10 -0
  7. package/dist/algo/conversationSniffer.js +25 -8
  8. package/dist/algo/cycleEngine.d.ts +63 -5
  9. package/dist/algo/cycleEngine.js +320 -42
  10. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  11. package/dist/algo/evolutionEvent.d.ts +26 -0
  12. package/dist/algo/evolutionEvent.js +32 -0
  13. package/dist/algo/exploration.d.ts +7 -0
  14. package/dist/algo/exploration.js +16 -3
  15. package/dist/algo/geneHealth.d.ts +36 -3
  16. package/dist/algo/geneHealth.js +47 -4
  17. package/dist/algo/geneIntake.d.ts +46 -1
  18. package/dist/algo/geneIntake.js +121 -10
  19. package/dist/algo/geneSelection.d.ts +126 -6
  20. package/dist/algo/geneSelection.js +479 -36
  21. package/dist/algo/index.d.ts +6 -1
  22. package/dist/algo/index.js +6 -1
  23. package/dist/algo/kautoProjection.d.ts +41 -0
  24. package/dist/algo/kautoProjection.js +95 -0
  25. package/dist/algo/kautoValidator.d.ts +68 -0
  26. package/dist/algo/kautoValidator.js +256 -0
  27. package/dist/algo/memoryGraph.d.ts +62 -0
  28. package/dist/algo/memoryGraph.js +86 -0
  29. package/dist/algo/orchestrator.d.ts +17 -1
  30. package/dist/algo/orchestrator.js +30 -4
  31. package/dist/algo/publishEligibility.d.ts +34 -0
  32. package/dist/algo/publishEligibility.js +52 -0
  33. package/dist/algo/solidify.d.ts +11 -2
  34. package/dist/algo/solidify.js +37 -7
  35. package/dist/algo/ucb1.d.ts +53 -0
  36. package/dist/algo/ucb1.js +156 -0
  37. package/dist/assetrepair/hubRejection.d.ts +12 -0
  38. package/dist/assetrepair/hubRejection.js +109 -0
  39. package/dist/assetrepair/index.d.ts +2 -0
  40. package/dist/assetrepair/index.js +2 -0
  41. package/dist/assetrepair/repair.d.ts +33 -0
  42. package/dist/assetrepair/repair.js +155 -0
  43. package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
  44. package/dist/assetstore/assetSidecarRecords.js +384 -0
  45. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  46. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  47. package/dist/assetstore/assetStoreHealth.d.ts +81 -0
  48. package/dist/assetstore/assetStoreHealth.js +319 -0
  49. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  50. package/dist/assetstore/assetStoreLayout.js +6 -0
  51. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  52. package/dist/assetstore/assetStoreStorage.js +336 -0
  53. package/dist/assetstore/assetSyncLedger.d.ts +91 -1
  54. package/dist/assetstore/assetSyncLedger.js +718 -59
  55. package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
  56. package/dist/assetstore/foreignJsonlSource.js +150 -0
  57. package/dist/assetstore/index.d.ts +5 -0
  58. package/dist/assetstore/index.js +5 -0
  59. package/dist/assetstore/learningHistory.js +3 -3
  60. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  61. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  62. package/dist/assetstore/localJsonl.d.ts +12 -1
  63. package/dist/assetstore/localJsonl.js +251 -42
  64. package/dist/assetstore/provenance.d.ts +93 -4
  65. package/dist/assetstore/provenance.js +368 -84
  66. package/dist/assetstore/provider.d.ts +63 -0
  67. package/dist/assetstore/provider.js +97 -6
  68. package/dist/assetstore/reviewFilter.d.ts +19 -1
  69. package/dist/assetstore/reviewFilter.js +39 -1
  70. package/dist/assetstore/reviewLedger.d.ts +8 -2
  71. package/dist/assetstore/reviewLedger.js +71 -45
  72. package/dist/assetstore/unionReadStore.d.ts +25 -0
  73. package/dist/assetstore/unionReadStore.js +119 -0
  74. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  75. package/dist/benchmark/antiGeneBenchmark.js +4 -3
  76. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  77. package/dist/benchmark/antiGeneRollout.js +4 -3
  78. package/dist/benchmark/index.d.ts +3 -1
  79. package/dist/benchmark/index.js +3 -1
  80. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  81. package/dist/benchmark/selectionFlatAbstention.js +481 -0
  82. package/dist/benchmark/triggerShift.d.ts +62 -0
  83. package/dist/benchmark/triggerShift.js +106 -0
  84. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  85. package/dist/bootstrap/envFingerprint.js +5 -0
  86. package/dist/bootstrap/index.d.ts +3 -1
  87. package/dist/bootstrap/index.js +3 -1
  88. package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
  89. package/dist/bootstrap/lifecycleBootstrap.js +433 -0
  90. package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
  91. package/dist/bootstrap/v1EnvCompat.js +303 -0
  92. package/dist/events/eventArchive.d.ts +2 -0
  93. package/dist/events/eventArchive.js +13 -3
  94. package/dist/events/eventSchema.d.ts +7 -7
  95. package/dist/events/eventStore.d.ts +2 -0
  96. package/dist/events/eventStore.js +5 -1
  97. package/dist/events/ingest.d.ts +2 -1
  98. package/dist/events/ingest.js +15 -0
  99. package/dist/events/paths.d.ts +12 -10
  100. package/dist/events/paths.js +24 -20
  101. package/dist/events/public.d.ts +2 -2
  102. package/dist/events/public.js +2 -2
  103. package/dist/events/reports.d.ts +2 -0
  104. package/dist/events/reports.js +4 -0
  105. package/dist/exec/autoExec.d.ts +68 -4
  106. package/dist/exec/autoExec.js +392 -33
  107. package/dist/exec/autonomousCycle.d.ts +31 -4
  108. package/dist/exec/autonomousCycle.js +71 -13
  109. package/dist/exec/claudeBridge.d.ts +80 -15
  110. package/dist/exec/claudeBridge.js +845 -76
  111. package/dist/exec/executionBinding.d.ts +414 -0
  112. package/dist/exec/executionBinding.js +588 -0
  113. package/dist/exec/index.d.ts +1 -0
  114. package/dist/exec/index.js +1 -0
  115. package/dist/exec/openPrRegistry.d.ts +8 -2
  116. package/dist/exec/openPrRegistry.js +32 -22
  117. package/dist/exec/prompt.js +14 -1
  118. package/dist/exec/proofOfWork.d.ts +1 -1
  119. package/dist/exec/proofOfWork.js +2 -2
  120. package/dist/exec/runnerRegistry.d.ts +153 -36
  121. package/dist/exec/runnerRegistry.js +848 -65
  122. package/dist/exec/selfPr.js +1 -7
  123. package/dist/feedback/envelope.d.ts +61 -0
  124. package/dist/feedback/envelope.js +168 -0
  125. package/dist/feedback/index.d.ts +1 -0
  126. package/dist/feedback/index.js +1 -0
  127. package/dist/hooks/hooks.js +1 -0
  128. package/dist/hub/assetCallLog.d.ts +35 -1
  129. package/dist/hub/assetCallLog.js +124 -1
  130. package/dist/hub/bindings.d.ts +8 -1
  131. package/dist/hub/bindings.js +29 -8
  132. package/dist/hub/capability.d.ts +130 -4
  133. package/dist/hub/conversationDistiller.d.ts +19 -0
  134. package/dist/hub/conversationDistiller.js +115 -37
  135. package/dist/hub/fake.d.ts +3 -2
  136. package/dist/hub/fake.js +2 -1
  137. package/dist/hub/index.d.ts +1 -0
  138. package/dist/hub/index.js +1 -0
  139. package/dist/hub/questionGenerator.d.ts +5 -1
  140. package/dist/hub/questionGenerator.js +8 -6
  141. package/dist/hub/recipeCompose.d.ts +27 -0
  142. package/dist/hub/recipeCompose.js +90 -0
  143. package/dist/hub/sanitize.js +122 -7
  144. package/dist/index.d.ts +6 -1
  145. package/dist/index.js +7 -1
  146. package/dist/issueReporter/index.d.ts +156 -0
  147. package/dist/issueReporter/index.js +1688 -0
  148. package/dist/mailbox/dispatch.d.ts +1 -1
  149. package/dist/mailbox/dispatch.js +22 -6
  150. package/dist/mailbox/envelope.d.ts +7 -1
  151. package/dist/mailbox/envelope.js +9 -2
  152. package/dist/mailbox/ipcServer.d.ts +12 -2
  153. package/dist/mailbox/ipcServer.js +183 -13
  154. package/dist/mailbox/store.d.ts +89 -3
  155. package/dist/mailbox/store.js +895 -41
  156. package/dist/modelCompatibility.d.ts +164 -0
  157. package/dist/modelCompatibility.js +309 -0
  158. package/dist/observers/valueDigestObserver.d.ts +9 -0
  159. package/dist/observers/valueDigestObserver.js +35 -2
  160. package/dist/ops/cleanup.js +1 -1
  161. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  162. package/dist/ops/evolutionGraphProjection.js +315 -0
  163. package/dist/ops/index.d.ts +2 -1
  164. package/dist/ops/index.js +2 -1
  165. package/dist/ops/savingsCore.js +1 -2
  166. package/dist/ops/selfUpdate.d.ts +18 -1
  167. package/dist/ops/selfUpdate.js +88 -23
  168. package/dist/ops/valueOutreach.d.ts +3 -1
  169. package/dist/ops/valueOutreach.js +5 -1
  170. package/dist/personality/schema.d.ts +24 -24
  171. package/dist/schema/evolutionGraph.d.ts +784 -0
  172. package/dist/schema/evolutionGraph.js +187 -0
  173. package/dist/schema/index.d.ts +1 -0
  174. package/dist/schema/index.js +1 -0
  175. package/dist/schema/proofOfWork.d.ts +125 -6
  176. package/dist/schema/proofOfWork.js +102 -4
  177. package/dist/schema/signal.d.ts +3 -3
  178. package/dist/schema/signal.js +1 -1
  179. package/dist/shadow/shadowHub.js +1 -0
  180. package/dist/signals/curriculum.d.ts +55 -0
  181. package/dist/signals/curriculum.js +202 -0
  182. package/dist/signals/cycleHistoryFromEvents.js +17 -8
  183. package/dist/signals/expand.d.ts +15 -1
  184. package/dist/signals/expand.js +169 -1
  185. package/dist/signals/extractor.d.ts +2 -2
  186. package/dist/signals/extractor.js +31 -6
  187. package/dist/signals/index.d.ts +4 -1
  188. package/dist/signals/index.js +4 -1
  189. package/dist/signals/metaSignals.d.ts +4 -0
  190. package/dist/signals/metaSignals.js +42 -0
  191. package/dist/signals/scopeVocabulary.d.ts +75 -0
  192. package/dist/signals/scopeVocabulary.js +91 -0
  193. package/dist/signals/signalGate.js +1 -1
  194. package/dist/signals/taskDomain.d.ts +22 -0
  195. package/dist/signals/taskDomain.js +43 -0
  196. package/dist/strategy/constraintAblation.d.ts +64 -0
  197. package/dist/strategy/constraintAblation.js +2820 -0
  198. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  199. package/dist/strategy/constraintAblationPredicates.js +339 -0
  200. package/dist/strategy/index.d.ts +2 -1
  201. package/dist/strategy/index.js +2 -1
  202. package/dist/trace/index.d.ts +3 -1
  203. package/dist/trace/index.js +3 -1
  204. package/dist/trace/learningTrace.d.ts +216 -0
  205. package/dist/trace/learningTrace.js +298 -0
  206. package/dist/trace/proxyTurns.d.ts +31 -0
  207. package/dist/trace/proxyTurns.js +137 -0
  208. package/dist/trace/trajectory.d.ts +8 -0
  209. package/dist/trace/trajectory.js +14 -2
  210. package/dist/util/fetchPort.d.ts +1 -0
  211. package/dist/util/fetchPort.js +11 -0
  212. package/dist/util/fileLock.d.ts +113 -7
  213. package/dist/util/fileLock.js +1035 -80
  214. package/dist/util/index.d.ts +3 -1
  215. package/dist/util/index.js +2 -1
  216. package/dist/verify/index.d.ts +2 -1
  217. package/dist/verify/index.js +1 -1
  218. package/dist/verify/sandboxRunner.d.ts +30 -0
  219. package/dist/verify/sandboxRunner.js +370 -27
  220. package/dist/verify/sandboxedValidation.d.ts +16 -2
  221. package/dist/verify/sandboxedValidation.js +200 -22
  222. package/dist/verify/validation.d.ts +35 -4
  223. package/dist/verify/validation.js +164 -16
  224. package/dist/wire/geneHints.d.ts +88 -16
  225. package/dist/wire/geneHints.js +124 -15
  226. package/dist/wire/index.d.ts +8 -3
  227. package/dist/wire/index.js +2 -2
  228. package/dist/wire/schemaGate.d.ts +21 -0
  229. package/dist/wire/schemaGate.js +119 -10
  230. package/dist/workflow/dsl.d.ts +24 -3
  231. package/dist/workflow/dsl.js +4 -0
  232. package/dist/workflow/engine.d.ts +5 -1
  233. package/dist/workflow/engine.js +3 -0
  234. package/dist/workflow/index.d.ts +3 -1
  235. package/dist/workflow/index.js +3 -1
  236. package/dist/workflow/runtime.d.ts +110 -0
  237. package/dist/workflow/runtime.js +1298 -0
  238. package/dist/workflow/stateStore.d.ts +172 -0
  239. package/dist/workflow/stateStore.js +1044 -0
  240. package/package.json +12 -4
@@ -1,19 +1,33 @@
1
1
  import { StrategyPoint } from '../strategy/strategyPoint.js';
2
2
  import { geneHealthScore, HEALTH_WEIGHTS_VERSION } from './geneHealth.js';
3
- import { tagOverlapScore, bagCosine } from '../signals/expand.js';
4
- import { driftSelect } from './exploration.js';
3
+ import { bagCosine, buildSemanticIdfProfile, geneTags, idfBagCosine, idfTagOverlapScore, tagOverlapScore, } from '../signals/expand.js';
4
+ import { driftSelect, explorationWindowSize } from './exploration.js';
5
5
  import { isDistilledGeneId } from './geneIntake.js';
6
+ import { chooseUcb1Arm, ucb1StatsForCandidate, UCB1_REWARD_POLICY_VERSION, UCB1_SELECTION_POLICY_VERSION, } from './ucb1.js';
7
+ import { resolveTaskDomainSignals, withoutTaskDomainSignals, } from '../signals/taskDomain.js';
8
+ export const SELECTION_GUARD_VERSION = 'relevance-guard-v1';
6
9
  function decisionWithWarnings(input, decision) {
10
+ const selected = decision.selectedGeneId
11
+ ? decision.candidates.find((candidate) => (candidate.geneId === decision.selectedGeneId
12
+ || (decision.selectedAssetId !== undefined && candidate.assetId === decision.selectedAssetId)))
13
+ : undefined;
14
+ const enriched = {
15
+ ...decision,
16
+ ...(selected ? { selectedReason: selected.reasons.join('; ') } : {}),
17
+ ...(input.memoryEvidence && input.memoryEvidence.length > 0 ? { memoryEvidence: [...input.memoryEvidence] } : {}),
18
+ };
7
19
  return input.antiWarnings && input.antiWarnings.length > 0
8
- ? { ...decision, antiWarnings: [...input.antiWarnings] }
9
- : decision;
20
+ ? { ...enriched, antiWarnings: [...input.antiWarnings] }
21
+ : enriched;
10
22
  }
11
23
  function signalMatchScore(signals, match) {
12
- if (signals.length === 0 || match.length === 0)
24
+ const genericSignals = withoutTaskDomainSignals(signals);
25
+ const genericMatch = withoutTaskDomainSignals(match);
26
+ if (genericSignals.length === 0 || genericMatch.length === 0)
13
27
  return 0;
14
- const set = new Set(signals);
15
- const hit = match.filter((m) => set.has(m)).length;
16
- return hit / match.length; // 覆盖率
28
+ const set = new Set(genericSignals);
29
+ const hit = genericMatch.filter((m) => set.has(m)).length;
30
+ return hit / genericMatch.length; // 覆盖率
17
31
  }
18
32
  // Weight of the bag-of-words cosine path when it fills the recall gap left by literal + tag overlap.
19
33
  const SEMANTIC_WEIGHT = 0.5;
@@ -29,12 +43,90 @@ export const CONFIDENCE_WEIGHT = 0.15;
29
43
  * ±REUSE_WEIGHT and can never dominate health/signal-match.
30
44
  */
31
45
  export const REUSE_WEIGHT = 0.1;
46
+ /** Weight of scoped local MemoryGraph outcome evidence. */
47
+ export const MEMORY_GRAPH_WEIGHT = 0.12;
48
+ /** Bounded weight for a canonical task-domain signal match (#628). */
49
+ export const TASK_DOMAIN_WEIGHT = 0.08;
50
+ /** signals_match is weak domain evidence; its maximum score contribution is 0.08 * 0.5 = 0.04. */
51
+ export const TASK_DOMAIN_SIGNAL_EVIDENCE = 0.5;
52
+ /**
53
+ * Soft boost for strict K_auto members. Smaller than CONFIDENCE_WEIGHT: membership is a writer-side property,
54
+ * not verified cycle history, so it only breaks near-ties between already-eligible candidates.
55
+ */
56
+ export const KAUTO_WEIGHT = 0.05;
57
+ /** λ is intentionally NOT inlined into SELECTION_WEIGHTS_VERSION text:
58
+ * decision.gene_selected root events already sit within ~2B of the 4096B line budget
59
+ * under UCB1/plateau fixtures; a `,kauto=0.05` suffix drops selectionPolicy. */
60
+ const SELECTION_GUARD_MATCH_EPS = 1e-6;
32
61
  /**
33
62
  * Version of the full engine-health weight vector (health 0.6 + signal-match 0.4 − epigenetic penalty
34
- * + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment). Bumped whenever a factor is added so golden
35
- * weight snapshots track the change. Composed from the health-weights version so a change to either layer shows.
63
+ * + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment + KAUTO_WEIGHT × kauto-member).
64
+ * Bumped whenever a factor is added so golden weight snapshots track the change. Composed from the
65
+ * health-weights version so a change to either layer shows.
36
66
  */
37
- export const SELECTION_WEIGHTS_VERSION = `sel-3(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},reuse=${REUSE_WEIGHT})`;
67
+ export const LEGACY_SELECTION_WEIGHTS_VERSION = `sel-6-domain(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},memory=${MEMORY_GRAPH_WEIGHT},reuse=${REUSE_WEIGHT},domain=${TASK_DOMAIN_WEIGHT})`;
68
+ export const SELECTION_WEIGHTS_VERSION = `sel-7-idf-domain(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},memory=${MEMORY_GRAPH_WEIGHT},reuse=${REUSE_WEIGHT},domain=${TASK_DOMAIN_WEIGHT})`;
69
+ const SEMANTIC_CORPUS_LIMIT = 1_000;
70
+ const SEMANTIC_SIGNAL_LIMIT = 16;
71
+ const SEMANTIC_SIGNAL_CHARS = 96;
72
+ const SEMANTIC_GENE_ID_CHARS = 128;
73
+ const SEMANTIC_CATEGORY_CHARS = 96;
74
+ const SEMANTIC_SUMMARY_CHARS = 1_024;
75
+ const SEMANTIC_IDENTITY_CHARS = 256;
76
+ function legacyCandidateSemanticText(candidate) {
77
+ return [
78
+ ...withoutTaskDomainSignals(candidate.signalsMatch),
79
+ candidate.category ?? '',
80
+ candidate.summary ?? '',
81
+ ].join(' ');
82
+ }
83
+ function boundedSignals(signals) {
84
+ const out = [];
85
+ const genericSignals = withoutTaskDomainSignals(signals);
86
+ const limit = Math.min(genericSignals.length, SEMANTIC_SIGNAL_LIMIT);
87
+ for (let index = 0; index < limit; index += 1) {
88
+ const signal = genericSignals[index];
89
+ if (typeof signal === 'string')
90
+ out.push(signal.slice(0, SEMANTIC_SIGNAL_CHARS));
91
+ }
92
+ return out;
93
+ }
94
+ function semanticCandidateProjection(candidate) {
95
+ const signalsMatch = boundedSignals(candidate.signalsMatch);
96
+ const geneId = candidate.geneId.slice(0, SEMANTIC_GENE_ID_CHARS);
97
+ const category = candidate.category?.slice(0, SEMANTIC_CATEGORY_CHARS);
98
+ const summary = candidate.summary?.slice(0, SEMANTIC_SUMMARY_CHARS);
99
+ return {
100
+ tagInput: {
101
+ signalsMatch,
102
+ geneId,
103
+ ...(category ? { category } : {}),
104
+ ...(summary ? { summary } : {}),
105
+ },
106
+ text: [...signalsMatch, category ?? '', summary ?? ''].join(' '),
107
+ hasContent: signalsMatch.some((signal) => signal.trim().length > 0)
108
+ || Boolean(category?.trim())
109
+ || Boolean(summary?.trim()),
110
+ };
111
+ }
112
+ function buildSelectionSemanticProfile(corpus) {
113
+ const documents = [];
114
+ const identities = new Set();
115
+ for (const candidate of corpus) {
116
+ if (documents.length >= SEMANTIC_CORPUS_LIMIT)
117
+ break;
118
+ const identityValue = candidate.assetId ?? candidate.geneId;
119
+ const identity = `${candidate.assetId ? 'asset' : 'gene'}:${identityValue.slice(0, SEMANTIC_IDENTITY_CHARS)}`;
120
+ if (identities.has(identity))
121
+ continue;
122
+ identities.add(identity);
123
+ const projection = semanticCandidateProjection(candidate);
124
+ if (!projection.hasContent)
125
+ continue;
126
+ documents.push({ tags: geneTags(projection.tagInput), text: projection.text });
127
+ }
128
+ return buildSemanticIdfProfile(documents);
129
+ }
38
130
  /**
39
131
  * Match score with semantic signal expansion (ported from v1): literal coverage first, then fill the
40
132
  * recall gap with semantic tag overlap, then with bag-of-words cosine similarity. A perfect literal match
@@ -42,22 +134,43 @@ export const SELECTION_WEIGHTS_VERSION = `sel-3(${HEALTH_WEIGHTS_VERSION},conf=$
42
134
  * for 'repair'), and cosine catches lexical similarity the curated tags miss (e.g. a 'timeout_slow' signal
43
135
  * vs a gene summarized "fix the slow timeout"). Each layer only fills the gap the previous one left.
44
136
  */
45
- function expandedMatchScore(signals, c) {
137
+ function expandedMatchScore(signals, c, semanticProfile) {
46
138
  const literal = signalMatchScore(signals, c.signalsMatch);
47
139
  if (literal >= 1)
48
140
  return { score: 1, literal, tag: 0, cos: 0 };
49
- const tag = tagOverlapScore(signals, { signalsMatch: c.signalsMatch, geneId: c.geneId, category: c.category, summary: c.summary });
141
+ const projection = semanticProfile ? semanticCandidateProjection(c) : undefined;
142
+ const semanticSignals = semanticProfile ? boundedSignals(signals) : withoutTaskDomainSignals(signals);
143
+ const tag = semanticProfile
144
+ ? idfTagOverlapScore(semanticSignals, projection.tagInput, semanticProfile)
145
+ : tagOverlapScore(semanticSignals, {
146
+ signalsMatch: withoutTaskDomainSignals(c.signalsMatch),
147
+ geneId: c.geneId,
148
+ category: c.category,
149
+ summary: c.summary,
150
+ });
50
151
  const base = Math.min(1, literal + (1 - literal) * tag);
51
152
  if (base >= 1)
52
153
  return { score: 1, literal, tag, cos: 0 };
53
- const geneText = [...c.signalsMatch, c.category ?? '', c.summary ?? ''].join(' ');
54
- const cos = bagCosine(signals.join(' '), geneText);
154
+ const cos = semanticProfile
155
+ ? idfBagCosine(semanticSignals.join(' '), projection.text, semanticProfile)
156
+ : bagCosine(semanticSignals.join(' '), legacyCandidateSemanticText(c));
55
157
  return { score: Math.min(1, base + (1 - base) * cos * SEMANTIC_WEIGHT), literal, tag, cos };
56
158
  }
57
- /** Score one candidate under the engine-health weight vector (health 0.6 + 信号匹配 0.4 − epigenetic + confidence). */
58
- function scoreCandidate(signals, c) {
159
+ function taskDomainEvidence(queryDomain, candidateSignals) {
160
+ if (queryDomain.status !== 'resolved')
161
+ return 0;
162
+ const candidateDomain = resolveTaskDomainSignals(candidateSignals);
163
+ return candidateDomain.status === 'resolved' && candidateDomain.slug === queryDomain.slug
164
+ ? TASK_DOMAIN_SIGNAL_EVIDENCE
165
+ : 0;
166
+ }
167
+ /**
168
+ * Score one candidate under the engine-health weight vector, including bounded task-domain evidence.
169
+ * `kautoWeight` defaults to production KAUTO_WEIGHT; ablation passes λ ∈ {0,0.01,0.05,0.1}.
170
+ */
171
+ function scoreCandidate(signals, c, semanticProfile, queryDomain, kautoWeight = KAUTO_WEIGHT) {
59
172
  const health = geneHealthScore(c.view, { reuseCount: c.reuseCount, antiPatternCount: c.antiPatternCount });
60
- const m = expandedMatchScore(signals, c);
173
+ const m = expandedMatchScore(signals, c, semanticProfile);
61
174
  const epi = c.epigeneticPenalty ?? 0;
62
175
  // Confidence is the fourth (positive) factor: a [0,1] preferred-gene edge for this signal fingerprint.
63
176
  // Clamped defensively so an upstream bug cannot turn the soft nudge into a dominant term.
@@ -65,16 +178,139 @@ function scoreCandidate(signals, c) {
65
178
  // Cross-runtime reuse sentiment (#268 phase 1): clamped to [-1,1] so an upstream bug cannot turn the soft nudge
66
179
  // into a dominant (or unbounded) term. Absent → 0 → no effect (default-off until a caller injects it).
67
180
  const reuse = Math.max(-1, Math.min(1, c.reuseAdjust ?? 0));
68
- const score = 0.6 * health.score + 0.4 * m.score - epi + CONFIDENCE_WEIGHT * conf + REUSE_WEIGHT * reuse;
181
+ const memory = Math.max(-1, Math.min(1, c.memoryBoost ?? 0));
182
+ const domainEvidence = taskDomainEvidence(queryDomain, c.signalsMatch);
183
+ const domainContribution = TASK_DOMAIN_WEIGHT * domainEvidence;
184
+ // Soft K_auto membership preference: binary, already-admitted candidates only.
185
+ const kauto = c.kautoMember === true ? 1 : 0;
186
+ const kautoContribution = kautoWeight * kauto;
187
+ // score_base is everything except the K_auto soft term — used by λ ablation.
188
+ const scoreBase = 0.6 * health.score + 0.4 * m.score - epi + CONFIDENCE_WEIGHT * conf
189
+ + MEMORY_GRAPH_WEIGHT * memory + REUSE_WEIGHT * reuse + domainContribution;
190
+ const score = scoreBase + kautoContribution;
69
191
  const breakdown = m.tag > 0 || m.cos > 0 ? `(literal=${m.literal.toFixed(2)}+tag=${m.tag.toFixed(2)}+cos=${m.cos.toFixed(2)})` : '';
70
192
  const reasons = [`health=${health.score.toFixed(3)}(succ=${health.successRate.toFixed(2)},reuse=${health.reuseCount})`, `信号匹配=${m.score.toFixed(2)}${breakdown}`];
71
193
  if (epi > 0)
72
194
  reasons.push(`epigenetic 环境抑制 -${epi.toFixed(2)}`);
73
195
  if (conf > 0)
74
196
  reasons.push(`preferred-gene confidence +${(CONFIDENCE_WEIGHT * conf).toFixed(3)} (conf=${conf.toFixed(2)})`);
197
+ if (memory !== 0)
198
+ reasons.push(`scoped memory-graph outcome ${memory >= 0 ? '+' : ''}${(MEMORY_GRAPH_WEIGHT * memory).toFixed(3)} (boost=${memory.toFixed(2)})`);
75
199
  if (reuse !== 0)
76
200
  reasons.push(`cross-runtime reuse ${reuse >= 0 ? '+' : ''}${(REUSE_WEIGHT * reuse).toFixed(3)} (sentiment=${reuse.toFixed(2)})`);
77
- return { geneId: c.geneId, ...(c.assetId ? { assetId: c.assetId } : {}), score, health, reasons };
201
+ if (domainContribution > 0)
202
+ reasons.push(`task_domain match +${domainContribution.toFixed(3)} (evidence=${domainEvidence.toFixed(2)})`);
203
+ if (kautoContribution > 0)
204
+ reasons.push(`K_auto member +${kautoContribution.toFixed(3)}`);
205
+ // Keep scoreBase/kautoContribution off ScoredCandidate so decision.gene_selected
206
+ // root-event candidate payloads retain the pre-K_auto 4096B line budget.
207
+ return {
208
+ geneId: c.geneId,
209
+ ...(c.assetId ? { assetId: c.assetId } : {}),
210
+ score,
211
+ matchScore: m.score,
212
+ health,
213
+ reasons,
214
+ };
215
+ }
216
+ function candidateIdentity(c) {
217
+ return c.assetId ? `asset:${c.assetId}` : `gene:${c.geneId}`;
218
+ }
219
+ /**
220
+ * Offline λ ablation over an already-admitted candidate pool.
221
+ * Does not run hard gates / force / distilled fallback — those stay outside the soft preference.
222
+ * Pure ranking sensitivity for score_T2 = score_base + λ · 1[k_a ∈ K_auto].
223
+ */
224
+ export function ablateKautoLambda(input, lambdas = [0, 0.01, 0.05, 0.1], opts = {}) {
225
+ const topK = opts.topK ?? 3;
226
+ const floor = opts.floor ?? 0;
227
+ const semanticProfile = input.disableSemanticIdf || input.semanticCorpus === undefined
228
+ ? undefined
229
+ : buildSelectionSemanticProfile(input.semanticCorpus);
230
+ const queryDomain = resolveTaskDomainSignals(input.signals);
231
+ const pools = lambdas.map((lambda) => {
232
+ const scored = input.candidates
233
+ .map((c) => {
234
+ const s = scoreCandidate(input.signals, c, semanticProfile, queryDomain, lambda);
235
+ const kautoContribution = (c.kautoMember === true ? 1 : 0) * lambda;
236
+ return {
237
+ geneId: s.geneId,
238
+ ...(s.assetId ? { assetId: s.assetId } : {}),
239
+ score: s.score,
240
+ scoreBase: s.score - kautoContribution,
241
+ kautoContribution,
242
+ kautoMember: c.kautoMember === true,
243
+ };
244
+ })
245
+ .sort((a, b) => b.score - a.score || (a.assetId ?? a.geneId).localeCompare(b.assetId ?? b.geneId));
246
+ const eligible = scored.filter((s) => s.score > floor);
247
+ const chosen = eligible[0];
248
+ const bases = scored.map((s) => s.scoreBase);
249
+ const scoreBaseMin = bases.length ? Math.min(...bases) : 0;
250
+ const scoreBaseMax = bases.length ? Math.max(...bases) : 0;
251
+ const scoreBaseMean = bases.length ? bases.reduce((a, b) => a + b, 0) / bases.length : 0;
252
+ return {
253
+ lambda,
254
+ ranking: scored.map((s, index) => ({
255
+ rank: index + 1,
256
+ geneId: s.geneId,
257
+ ...(s.assetId ? { assetId: s.assetId } : {}),
258
+ score: s.score,
259
+ scoreBase: s.scoreBase,
260
+ kautoContribution: s.kautoContribution,
261
+ kautoMember: s.kautoMember,
262
+ })),
263
+ selectedGeneId: chosen?.geneId ?? null,
264
+ ...(chosen?.assetId ? { selectedAssetId: chosen.assetId } : {}),
265
+ scoreBaseMin,
266
+ scoreBaseMax,
267
+ scoreBaseMean,
268
+ };
269
+ });
270
+ const baseline = pools.find((p) => p.lambda === 0) ?? pools[0];
271
+ const baselineRank = new Map(baseline?.ranking.map((r) => [candidateIdentity(r), r.rank]) ?? []);
272
+ const baselineTopK = new Set((baseline?.ranking.slice(0, topK) ?? []).map((r) => candidateIdentity(r)));
273
+ const baselineSelected = baseline
274
+ ? candidateIdentity({
275
+ geneId: baseline.selectedGeneId ?? '',
276
+ ...(baseline.selectedAssetId ? { assetId: baseline.selectedAssetId } : {}),
277
+ })
278
+ : '';
279
+ const rankChangesByLambda = {};
280
+ const selectedChangesByLambda = {};
281
+ const topKChangesByLambda = {};
282
+ for (const pool of pools) {
283
+ const key = String(pool.lambda);
284
+ let rankChanges = 0;
285
+ for (const row of pool.ranking) {
286
+ const id = candidateIdentity(row);
287
+ if ((baselineRank.get(id) ?? -1) !== row.rank)
288
+ rankChanges += 1;
289
+ }
290
+ rankChangesByLambda[key] = rankChanges;
291
+ const selectedId = candidateIdentity({
292
+ geneId: pool.selectedGeneId ?? '',
293
+ ...(pool.selectedAssetId ? { assetId: pool.selectedAssetId } : {}),
294
+ });
295
+ selectedChangesByLambda[key] = selectedId !== baselineSelected;
296
+ const topSet = new Set(pool.ranking.slice(0, topK).map((r) => candidateIdentity(r)));
297
+ let topDiff = 0;
298
+ for (const id of topSet)
299
+ if (!baselineTopK.has(id))
300
+ topDiff += 1;
301
+ for (const id of baselineTopK)
302
+ if (!topSet.has(id))
303
+ topDiff += 1;
304
+ topKChangesByLambda[key] = topDiff;
305
+ }
306
+ return {
307
+ lambdas,
308
+ pools,
309
+ rankChangesByLambda,
310
+ selectedChangesByLambda,
311
+ topKChangesByLambda,
312
+ topK,
313
+ };
78
314
  }
79
315
  function matchesForcedGeneId(c, forcedGeneId) {
80
316
  return c.geneId === forcedGeneId || c.assetId === forcedGeneId;
@@ -88,6 +324,23 @@ function matchesScoredIdentity(a, b) {
88
324
  function withoutCandidateIdentity(scored, c) {
89
325
  return scored.filter((s) => !matchesCandidateIdentity(s, c));
90
326
  }
327
+ function candidateForScore(input, scored) {
328
+ // Eligibility is attached to one assembled candidate, not to every logical alias of the same content.
329
+ // Match the exact scored row so an untrusted alias cannot borrow a trusted candidate's UCB1 admission bit.
330
+ return input.candidates.find((candidate) => (candidate.geneId === scored.geneId && candidate.assetId === scored.assetId));
331
+ }
332
+ function compactMetric(value) {
333
+ return Math.round(value * 1_000_000) / 1_000_000;
334
+ }
335
+ function compactChoice(choice) {
336
+ return {
337
+ ...choice,
338
+ armId: choice.armId.slice(0, 160),
339
+ meanReward: compactMetric(choice.meanReward),
340
+ ...(choice.bonus !== null ? { bonus: compactMetric(choice.bonus) } : { bonus: null }),
341
+ ...(choice.index !== null ? { index: compactMetric(choice.index) } : { index: null }),
342
+ };
343
+ }
91
344
  function isReusableGenerationSource(source) {
92
345
  return source === 'distilled' || source === 'evolved';
93
346
  }
@@ -133,20 +386,150 @@ function forcedSelection(input, scored, scoreForcedFallback, floor) {
133
386
  forcedFallback.reasons.push(`forced gene selected despite floor ${floor}`);
134
387
  return { selectedGeneId: fallback.geneId, ...(fallback.assetId ? { selectedAssetId: fallback.assetId } : {}), scoredForChoice: scoredCandidatesOnly };
135
388
  }
389
+ /** Refs #626: identify selections whose relevance is absent or cannot discriminate during a plateau. */
390
+ export function assessSelectionGuard(scored, plateauActive) {
391
+ if (scored.length === 0) {
392
+ return { wouldAbstain: true, reason: 'no_match', maxMatch: 0, matchSpread: 0 };
393
+ }
394
+ const matches = scored.map((candidate) => candidate.matchScore ?? 0);
395
+ const maxMatch = Math.max(...matches);
396
+ const minMatch = Math.min(...matches);
397
+ const matchSpread = maxMatch - minMatch;
398
+ const metrics = {
399
+ maxMatch: compactMetric(maxMatch),
400
+ matchSpread: compactMetric(matchSpread),
401
+ };
402
+ if (maxMatch <= SELECTION_GUARD_MATCH_EPS) {
403
+ return { wouldAbstain: true, reason: 'no_match', ...metrics };
404
+ }
405
+ if (plateauActive && scored.length > 1 && matchSpread <= SELECTION_GUARD_MATCH_EPS) {
406
+ return { wouldAbstain: true, reason: 'plateau_flat_match', ...metrics };
407
+ }
408
+ return { wouldAbstain: false, ...metrics };
409
+ }
136
410
  /** 实现1: engine 健康分主导(health 0.6 + 信号匹配 0.4). */
137
411
  export const engineHealthSelection = {
138
412
  name: 'engine-health',
139
413
  version: '1',
140
414
  run(input, ctx) {
141
- const scored = input.candidates.map((c) => scoreCandidate(input.signals, c)).sort((a, b) => b.score - a.score);
415
+ const semanticProfile = input.disableSemanticIdf || input.semanticCorpus === undefined
416
+ ? undefined
417
+ : buildSelectionSemanticProfile(input.semanticCorpus);
418
+ const weightsVersion = semanticProfile ? SELECTION_WEIGHTS_VERSION : LEGACY_SELECTION_WEIGHTS_VERSION;
419
+ const semanticMetadata = semanticProfile
420
+ ? {
421
+ semanticProfileVersion: semanticProfile.version,
422
+ semanticDocumentCount: semanticProfile.documentCount,
423
+ }
424
+ : {};
425
+ const queryDomain = resolveTaskDomainSignals(input.signals);
426
+ const scoreForSelection = (candidate) => (scoreCandidate(input.signals, candidate, semanticProfile, queryDomain));
427
+ const scored = input.candidates.map(scoreForSelection).sort((a, b) => b.score - a.score);
142
428
  const floor = input.floor ?? 0;
143
- const forced = forcedSelection(input, scored, (c) => scoreCandidate(input.signals, c), floor);
429
+ const forced = forcedSelection(input, scored, scoreForSelection, floor);
144
430
  const eligible = forced.scoredForChoice.filter((s) => s.score > floor);
145
- // Deterministic top by default; exploration drift picks from the top-N to escape local optima.
146
- const drift = driftSelect(eligible.length, input.exploration, ctx?.rng ?? Math.random);
147
- const chosen = eligible[drift.index] ?? eligible[0];
431
+ const guardMode = input.selectionGuard ?? 'legacy';
432
+ const plateauActive = input.exploration?.plateau?.active === true;
433
+ let guardAssessment = assessSelectionGuard(eligible, plateauActive);
434
+ if (eligible.length === 0) {
435
+ const fullAssessment = assessSelectionGuard(forced.scoredForChoice, plateauActive);
436
+ if (fullAssessment.reason !== 'no_match') {
437
+ guardAssessment = {
438
+ wouldAbstain: false,
439
+ ...(fullAssessment.maxMatch !== undefined ? { maxMatch: fullAssessment.maxMatch } : {}),
440
+ ...(fullAssessment.matchSpread !== undefined ? { matchSpread: fullAssessment.matchSpread } : {}),
441
+ };
442
+ }
443
+ }
444
+ const policyEligible = guardMode === 'enforce' && guardAssessment.reason === 'no_match'
445
+ ? []
446
+ : eligible;
447
+ // Deterministic top by default. Plateau exploration either preserves legacy random drift or, when explicitly
448
+ // requested, evaluates UCB1 over the same adaptive top-N window. Forced selection always wins below.
449
+ const requestedPolicy = input.exploration?.policy ?? 'engine-health';
450
+ const ucb1Active = input.exploration?.plateau?.active === true;
451
+ let drift = { driftMode: 'deterministic', index: 0, intensity: 0 };
452
+ let chosen;
453
+ let selectionPolicy;
454
+ if (forced.selectedGeneId === undefined && ucb1Active && requestedPolicy !== 'engine-health') {
455
+ // Canonicalize ties before slicing the UCB window. The legacy/default ranked list intentionally keeps its
456
+ // stable input-order tie behavior; UCB1 must produce the same arm for any permutation of the same pool.
457
+ const ucbRanked = [...policyEligible].sort((left, right) => {
458
+ const leftScore = Number.isFinite(left.score) ? left.score : Number.NEGATIVE_INFINITY;
459
+ const rightScore = Number.isFinite(right.score) ? right.score : Number.NEGATIVE_INFINITY;
460
+ return rightScore !== leftScore
461
+ ? rightScore - leftScore
462
+ : (left.assetId ?? left.geneId).localeCompare(right.assetId ?? right.geneId);
463
+ });
464
+ const window = ucbRanked.slice(0, explorationWindowSize(ucbRanked.length, input.exploration));
465
+ const logicalIdCounts = new Map();
466
+ for (const scored of window) {
467
+ logicalIdCounts.set(scored.geneId, (logicalIdCounts.get(scored.geneId) ?? 0) + 1);
468
+ }
469
+ const history = input.exploration?.ucb1History;
470
+ const result = history
471
+ ? chooseUcb1Arm(window.map((scored) => {
472
+ const candidate = candidateForScore(input, scored);
473
+ const armId = scored.assetId ?? scored.geneId;
474
+ return {
475
+ armId,
476
+ baseScore: scored.score,
477
+ explorationEligible: candidate?.explorationEligible === true,
478
+ stats: candidate
479
+ ? ucb1StatsForCandidate(history, candidate.geneId, candidate.assetId, (logicalIdCounts.get(candidate.geneId) ?? 0) === 1)
480
+ : { armId, pulls: 0, completedPulls: 0, rewardSum: 0, meanReward: 0 },
481
+ };
482
+ }), history.totalPulls)
483
+ : { choice: null, fallbackReason: 'missing_history' };
484
+ const ucbChosen = result.choice
485
+ ? window.find((candidate) => (candidate.assetId ?? candidate.geneId) === result.choice.armId)
486
+ : undefined;
487
+ if (requestedPolicy === 'ucb1' && ucbChosen) {
488
+ chosen = ucbChosen;
489
+ const choice = compactChoice(result.choice);
490
+ chosen.reasons.push('ucb1(index=' + (choice.index ?? 'cold') + ',mean=' + choice.meanReward.toFixed(3) + ',pulls=' + choice.pulls + ')');
491
+ selectionPolicy = {
492
+ requested: requestedPolicy,
493
+ effective: 'ucb1',
494
+ selectionPolicyVersion: UCB1_SELECTION_POLICY_VERSION,
495
+ rewardPolicyVersion: UCB1_REWARD_POLICY_VERSION,
496
+ arm: choice,
497
+ };
498
+ }
499
+ else {
500
+ drift = driftSelect(eligible.length, input.exploration, ctx?.rng ?? Math.random);
501
+ chosen = eligible[drift.index] ?? eligible[0];
502
+ selectionPolicy = {
503
+ requested: requestedPolicy,
504
+ effective: 'engine-health',
505
+ selectionPolicyVersion: UCB1_SELECTION_POLICY_VERSION,
506
+ rewardPolicyVersion: UCB1_REWARD_POLICY_VERSION,
507
+ ...(result.choice ? {
508
+ arm: compactChoice(result.choice),
509
+ shadowArmId: result.choice.armId.slice(0, 160),
510
+ shadowDisagrees: Boolean(chosen && (chosen.assetId ?? chosen.geneId) !== result.choice.armId),
511
+ } : {}),
512
+ ...(result.fallbackReason ? { fallbackReason: result.fallbackReason } : {}),
513
+ };
514
+ }
515
+ }
516
+ else {
517
+ drift = driftSelect(eligible.length, input.exploration, ctx?.rng ?? Math.random);
518
+ chosen = eligible[drift.index] ?? eligible[0];
519
+ }
520
+ const forcedSelected = forced.selectedGeneId !== undefined;
521
+ const ucb1ResolvedPlateau = guardAssessment.reason === 'plateau_flat_match'
522
+ && selectionPolicy?.effective === 'ucb1';
523
+ const guardApplied = guardMode === 'enforce'
524
+ && !forcedSelected
525
+ && guardAssessment.wouldAbstain
526
+ && !ucb1ResolvedPlateau;
527
+ if (guardApplied) {
528
+ chosen = undefined;
529
+ }
148
530
  let selectedGeneId = forced.selectedGeneId ?? (chosen ? chosen.geneId : null);
149
531
  let selectedAssetId = forced.selectedAssetId ?? chosen?.assetId;
532
+ let fallbackSelected = false;
150
533
  if (!selectedGeneId) {
151
534
  // Issue #97 (ported from v1 selector.js): no candidate matched any live signal (every gene scored <= 0). Rather
152
535
  // than fall straight through to a blind innovate, reuse a *distilled* (or evolved) gene if one is available.
@@ -163,25 +546,64 @@ export const engineHealthSelection = {
163
546
  // scored above zero, NOT merely when nothing cleared a positive `floor`. Otherwise a real but weak signal match
164
547
  // (0 < score <= floor) would be preempted by an unrelated zero-evidence distilled gene; V1 (which has no floor)
165
548
  // keeps such a match. When candidates scored > 0 but below floor, defer to V2's floor policy (innovate).
166
- const noSignalMatch = !forced.forceRejected && forced.scoredForChoice.every((s) => s.score <= 0);
167
- const fb = noSignalMatch
168
- ? (input.distilledFallback ?? []).find((c) => isReusableFallbackCandidate(c) && (c.epigeneticPenalty ?? 0) === 0)
549
+ const normalPoolHasMatch = forced.scoredForChoice.some((candidate) => (candidate.matchScore ?? 0) > SELECTION_GUARD_MATCH_EPS);
550
+ const noSignalMatch = !forced.forceRejected
551
+ && ((guardApplied && guardAssessment.reason === 'no_match' && !normalPoolHasMatch)
552
+ || forced.scoredForChoice.every((candidate) => candidate.score <= 0));
553
+ const fallback = noSignalMatch
554
+ ? (input.distilledFallback ?? [])
555
+ .filter((candidate) => isReusableFallbackCandidate(candidate) && (candidate.epigeneticPenalty ?? 0) === 0)
556
+ .map((candidate) => ({ candidate, scored: scoreForSelection(candidate) }))
557
+ .sort((left, right) => right.scored.score - left.scored.score || left.candidate.geneId.localeCompare(right.candidate.geneId))[0]
169
558
  : undefined;
170
- if (fb) {
171
- const fbScored = scoreCandidate(input.signals, fb);
559
+ if (fallback) {
560
+ const { candidate: fb, scored: fbScored } = fallback;
172
561
  fbScored.reasons.push('distilled_fallback(#97): 无信号匹配, 低置信度复用蒸馏 gene');
173
562
  scored.push(fbScored);
174
563
  selectedGeneId = fb.geneId;
175
564
  selectedAssetId = fb.assetId;
565
+ fallbackSelected = true;
176
566
  }
177
- else {
178
- forced.scoredForChoice.forEach((s) => s.reasons.push(`< floor ${floor} → innovate`));
567
+ else if (!guardApplied) {
568
+ forced.scoredForChoice.forEach((candidate) => candidate.reasons.push(`< floor ${floor} → innovate`));
179
569
  }
180
570
  }
181
- else if (forced.selectedGeneId === undefined && drift.driftMode !== 'deterministic') {
571
+ else if (forced.selectedGeneId === undefined && selectionPolicy?.effective !== 'ucb1' && drift.driftMode !== 'deterministic') {
182
572
  scored.find((s) => s.geneId === selectedGeneId)?.reasons.push(`drift(${drift.driftMode},intensity=${drift.intensity.toFixed(2)})`);
183
573
  }
184
- return decisionWithWarnings(input, { selectedGeneId, ...(selectedAssetId ? { selectedAssetId } : {}), candidates: scored, weightsVersion: SELECTION_WEIGHTS_VERSION, strategyName: 'engine-health' });
574
+ if (selectionPolicy?.requested === 'ucb1-shadow' && selectionPolicy.shadowArmId) {
575
+ selectionPolicy.shadowDisagrees = (selectedAssetId ?? selectedGeneId) !== selectionPolicy.shadowArmId;
576
+ }
577
+ const selectionGuard = guardMode === 'legacy'
578
+ ? undefined
579
+ : {
580
+ mode: guardMode,
581
+ version: SELECTION_GUARD_VERSION,
582
+ status: forcedSelected
583
+ ? 'forced'
584
+ : ucb1ResolvedPlateau
585
+ ? 'ucb1'
586
+ : fallbackSelected
587
+ ? 'fallback'
588
+ : guardApplied
589
+ ? 'innovate'
590
+ : guardAssessment.wouldAbstain
591
+ ? 'shadow'
592
+ : 'allowed',
593
+ ...(guardAssessment.wouldAbstain && guardAssessment.reason ? { reason: guardAssessment.reason } : {}),
594
+ ...(guardAssessment.maxMatch !== undefined ? { maxMatch: guardAssessment.maxMatch } : {}),
595
+ ...(guardAssessment.matchSpread !== undefined ? { matchSpread: guardAssessment.matchSpread } : {}),
596
+ };
597
+ return decisionWithWarnings(input, {
598
+ selectedGeneId,
599
+ ...(selectedAssetId ? { selectedAssetId } : {}),
600
+ candidates: scored,
601
+ weightsVersion,
602
+ ...semanticMetadata,
603
+ strategyName: 'engine-health',
604
+ ...(selectionPolicy ? { selectionPolicy } : {}),
605
+ ...(selectionGuard ? { selectionGuard } : {}),
606
+ });
185
607
  },
186
608
  };
187
609
  /** 实现2: 纯信号匹配采样(忽略 health, 对照基线 — 经验主义要可对比). */
@@ -213,14 +635,35 @@ export function agentLedSelection(pick) {
213
635
  run(input, ctx) {
214
636
  const baseDecision = engineHealthSelection.run(input, ctx);
215
637
  const base = baseDecision.candidates;
638
+ const decisionMetadata = {
639
+ weightsVersion: baseDecision.weightsVersion,
640
+ ...(baseDecision.semanticProfileVersion
641
+ ? { semanticProfileVersion: baseDecision.semanticProfileVersion }
642
+ : {}),
643
+ ...(baseDecision.semanticDocumentCount !== undefined
644
+ ? { semanticDocumentCount: baseDecision.semanticDocumentCount }
645
+ : {}),
646
+ };
216
647
  const forced = input.forcedGeneId ? base.find((s) => s.reasons.some((r) => r.includes('forced gene'))) : undefined;
217
648
  if (forced?.reasons.some((r) => r.includes('forced gene selected'))) {
218
- return decisionWithWarnings(input, { selectedGeneId: forced.geneId, ...(forced.assetId ? { selectedAssetId: forced.assetId } : {}), candidates: base, weightsVersion: SELECTION_WEIGHTS_VERSION, strategyName: 'agent-led' });
649
+ return decisionWithWarnings(input, {
650
+ selectedGeneId: forced.geneId,
651
+ ...(forced.assetId ? { selectedAssetId: forced.assetId } : {}),
652
+ candidates: base,
653
+ ...decisionMetadata,
654
+ strategyName: 'agent-led',
655
+ });
219
656
  }
220
657
  const candidatesForPick = forced?.reasons.some((r) => r.includes('forced gene rejected')) ? base.filter((s) => !matchesScoredIdentity(s, forced)) : base;
221
658
  const choice = pick(candidatesForPick, input);
222
659
  const selected = candidatesForPick.find((s) => s.geneId === choice) ?? base.find((s) => s.geneId === choice);
223
- return decisionWithWarnings(input, { selectedGeneId: choice, ...(selected?.assetId ? { selectedAssetId: selected.assetId } : {}), candidates: base, weightsVersion: SELECTION_WEIGHTS_VERSION, strategyName: 'agent-led' });
660
+ return decisionWithWarnings(input, {
661
+ selectedGeneId: choice,
662
+ ...(selected?.assetId ? { selectedAssetId: selected.assetId } : {}),
663
+ candidates: base,
664
+ ...decisionMetadata,
665
+ strategyName: 'agent-led',
666
+ });
224
667
  },
225
668
  };
226
669
  }
@@ -1,12 +1,15 @@
1
1
  export * from './geneHealth.js';
2
2
  export * from './exploration.js';
3
+ export * from './ucb1.js';
3
4
  export * from './epigenetics.js';
4
5
  export * from './confidence.js';
6
+ export * from './memoryGraph.js';
5
7
  export * from './capabilityCandidates.js';
6
8
  export * from './conversationSniffer.js';
7
9
  export * from './geneIntake.js';
8
10
  export * from './candidateAssembly.js';
9
11
  export * from './genePromotion.js';
12
+ export * from './publishEligibility.js';
10
13
  export * from './bans.js';
11
14
  export * from './antiDistill.js';
12
15
  export * from './orchestrator.js';
@@ -16,4 +19,6 @@ export * from './solidify.js';
16
19
  export * from './evolutionEvent.js';
17
20
  export * from './cycleEngine.js';
18
21
  export * from './strategyPresets.js';
19
- export * from './cycleFailureClassifier.js';
22
+ export * from './cycleFailureClassifier.js';
23
+ export * from './kautoValidator.js';
24
+ export * from './kautoProjection.js';