@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,5 +1,5 @@
1
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_publish_feishu_doc","category":"innovate","signals_match":["publish_markdown_to_feishu","create_feishu_doc","export_report_to_feishu","publish_results_to_lark_doc","feishu_doc","lark_doc"],"preconditions":["lark-cli is installed and available on PATH.","lark-cli auth status reports a ready user or bot identity."],"strategy":["Run lark-cli doctor and require an ok result before publishing.","Use the Docs v2 API and avoid deprecated Docs v1 flags.","Write long Markdown content to a temporary file and pass it as an @file input.","Create or update the document with the intended user or bot identity, then return the document URL.","Never overwrite local lark-cli credential files while preparing the document."],"constraints":{"max_files":2,"forbidden_paths":[".git","node_modules","~/.lark-cli/config.json"]},"validation":["node --version"],"summary":"Publish Markdown content as a Feishu/Lark document through the official CLI, using file-backed content and Docs v2.","asset_id":"sha256:4cdcf2b2a30bcb29a3310360c3cb16c7fc7d427d23a43c836ffa5076cfe3c041"}
2
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_conventional_git_commit","category":"optimize","signals_match":["git_commit","create_commit","commit_changes","conventional_commit","stage_and_commit","write_commit_message"],"preconditions":["A git repository has staged or unstaged changes that should become one logical commit."],"strategy":["Inspect git status plus the staged or unstaged diff before choosing a commit shape.","Pick a Conventional Commits type and optional scope from the actual changed files.","Stage only the intended logical files and never stage credentials or unrelated changes.","Write a present-tense imperative subject under 72 characters, with body or footer when needed.","Do not amend, force-push, hard-reset, or bypass hooks without explicit operator approval."],"constraints":{"max_files":50,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Create a focused Conventional Commits-style git commit from the real diff while avoiding secrets and unrelated changes.","asset_id":"sha256:8f0acd8af3d0a94d6f50e7fe20d96ff39dc982022b851b204eddce47e177f467"}
3
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_poll_bugbot_review","category":"optimize","signals_match":["poll_bugbot","bugbot_review","wait_for_ci_review","pr_review_gate","cursor_bugbot","pr_opened"],"preconditions":["An open GitHub PR exists and Cursor Bugbot is expected to review it."],"strategy":["Poll the Cursor Bugbot check by name until it reaches a terminal conclusion or the operator timeout is reached.","Treat SUCCESS as mergeable only when required checks are green and there are no unresolved Bugbot comments.","Treat NEUTRAL as not passed; fetch and summarize Cursor Bugbot inline comments for the operator.","On FAILURE or ACTION_REQUIRED, surface the findings and do not merge automatically.","Only squash-merge when the operator explicitly asked for merge and the review gate is clean."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Wait for Cursor Bugbot on a GitHub PR and gate merge decisions on the actual check conclusion and comments.","asset_id":"sha256:87737bf9d2702667c31e336fc79096f4da7f12a84baae25e827ad62b4a08d332"}
4
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_gateway_timeout_recovery","category":"repair","signals_match":["gateway_timeout","upstream_timeout","http_524","http_504","request_timed_out","retry_on_timeout"],"preconditions":["A tool call, fetch, subagent request, or long command returned a gateway-class timeout."],"strategy":["Treat gateway timeout as transient or size-driven until one bounded recovery attempt proves otherwise.","Retry the exact same operation once, then stop retrying the monolithic call.","If the retry also times out, split the work by file, directory, endpoint, record, section, or time window.","Run independent slices in parallel when safe and merge the results.","Apply the same decomposition recursively only to the slice that still times out."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Recover from gateway or upstream timeouts by retrying once, then decomposing the work instead of looping the same large call.","asset_id":"sha256:fe6820477e47a8eb848ecfaa33cdb4242d9af77b78fac21fa1d716244984615c"}
5
- {"type":"Gene","schema_version":"1.12.1","id":"gene_seed_github_webhook_listener","category":"innovate","signals_match":["github_webhook_listener","bugbot_webhook","passive_pr_notifications","notify_when_bugbot_finishes","webhook_tunnel","github_pr_inbox"],"preconditions":["A developer machine can run a local listener and an outbound tunnel for GitHub webhook delivery."],"strategy":["Use an HMAC-validated local listener for GitHub webhook payloads and deduplicate delivery ids.","Expose the listener through an outbound-only tunnel rather than an inbound public port.","Write vetted notifications into a local inbox with restrictive permissions; never execute webhook payloads.","On session start, re-fetch PR state through GitHub before surfacing any inbox item.","Rotate webhook secrets periodically and immediately after suspected exposure."],"constraints":{"max_files":20,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Deploy a local HMAC-validated GitHub webhook listener that turns PR and Bugbot events into safe operator notifications.","asset_id":"sha256:a64e8d0a91cebd54290451527987230572d8a5bfe90b6ca73cd3368fd3d1f689"}
1
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_publish_feishu_doc","category":"innovate","signals_match":["publish_markdown_to_feishu","create_feishu_doc","export_report_to_feishu","publish_results_to_lark_doc","feishu_doc","lark_doc"],"preconditions":["lark-cli is installed and available on PATH.","lark-cli auth status reports a ready user or bot identity."],"strategy":["Run lark-cli doctor and require an ok result before publishing.","Use the Docs v2 API and avoid deprecated Docs v1 flags.","Write long Markdown content to a temporary file and pass it as an @file input.","Create or update the document with the intended user or bot identity, then return the document URL.","Never overwrite local lark-cli credential files while preparing the document."],"constraints":{"max_files":2,"forbidden_paths":[".git","node_modules","~/.lark-cli/config.json"]},"validation":["node --version"],"summary":"Publish Markdown content as a Feishu/Lark document through the official CLI, using file-backed content and Docs v2.","asset_id":"sha256:7a8f6c4217ab2594b1412e4db1eccd4c3ac30d3a689054b956c0da6477c154bd"}
2
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_conventional_git_commit","category":"optimize","signals_match":["git_commit","create_commit","commit_changes","conventional_commit","stage_and_commit","write_commit_message"],"preconditions":["A git repository has staged or unstaged changes that should become one logical commit."],"strategy":["Inspect git status plus the staged or unstaged diff before choosing a commit shape.","Pick a Conventional Commits type and optional scope from the actual changed files.","Stage only the intended logical files and never stage credentials or unrelated changes.","Write a present-tense imperative subject under 72 characters, with body or footer when needed.","Do not amend, force-push, hard-reset, or bypass hooks without explicit operator approval."],"constraints":{"max_files":50,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Create a focused Conventional Commits-style git commit from the real diff while avoiding secrets and unrelated changes.","asset_id":"sha256:46b34569c273b60d84501764ae20e15c6b7eb52a743db1a393e0d52272212574"}
3
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_poll_bugbot_review","category":"optimize","signals_match":["poll_bugbot","bugbot_review","wait_for_ci_review","pr_review_gate","cursor_bugbot","pr_opened"],"preconditions":["An open GitHub PR exists and Cursor Bugbot is expected to review it."],"strategy":["Poll the Cursor Bugbot check by name until it reaches a terminal conclusion or the operator timeout is reached.","Treat SUCCESS as mergeable only when required checks are green and there are no unresolved Bugbot comments.","Treat NEUTRAL as not passed; fetch and summarize Cursor Bugbot inline comments for the operator.","On FAILURE or ACTION_REQUIRED, surface the findings and do not merge automatically.","Only squash-merge when the operator explicitly asked for merge and the review gate is clean."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Wait for Cursor Bugbot on a GitHub PR and gate merge decisions on the actual check conclusion and comments.","asset_id":"sha256:e137a220e84e08c784f438186c100d785c45bcc3d327082fc5661f5a76cb8de1"}
4
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_gateway_timeout_recovery","category":"repair","signals_match":["gateway_timeout","upstream_timeout","http_524","http_504","request_timed_out","retry_on_timeout"],"preconditions":["A tool call, fetch, subagent request, or long command returned a gateway-class timeout."],"strategy":["Treat gateway timeout as transient or size-driven until one bounded recovery attempt proves otherwise.","Retry the exact same operation once, then stop retrying the monolithic call.","If the retry also times out, split the work by file, directory, endpoint, record, section, or time window.","Run independent slices in parallel when safe and merge the results.","Apply the same decomposition recursively only to the slice that still times out."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Recover from gateway or upstream timeouts by retrying once, then decomposing the work instead of looping the same large call.","asset_id":"sha256:72177d7186fe623de4d9fcc440e1e6d36055467e7f86b248a97318db04be26e5"}
5
+ {"type":"Gene","schema_version":"1.13.0","id":"gene_seed_github_webhook_listener","category":"innovate","signals_match":["github_webhook_listener","bugbot_webhook","passive_pr_notifications","notify_when_bugbot_finishes","webhook_tunnel","github_pr_inbox"],"preconditions":["A developer machine can run a local listener and an outbound tunnel for GitHub webhook delivery."],"strategy":["Use an HMAC-validated local listener for GitHub webhook payloads and deduplicate delivery ids.","Expose the listener through an outbound-only tunnel rather than an inbound public port.","Write vetted notifications into a local inbox with restrictive permissions; never execute webhook payloads.","On session start, re-fetch PR state through GitHub before surfacing any inbox item.","Rotate webhook secrets periodically and immediately after suspected exposure."],"constraints":{"max_files":20,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Deploy a local HMAC-validated GitHub webhook listener that turns PR and Bugbot events into safe operator notifications.","asset_id":"sha256:dc6f0573c360e8e31b41142e3e4d782dcfb848a2256a4187079c3cea7edba584"}
@@ -1,6 +1,15 @@
1
1
  export interface FailedCapsuleRef {
2
2
  gene?: string;
3
3
  trigger?: readonly string[];
4
+ /** Stable logical FailureRecord id; duplicate deliveries of the same record count once. */
5
+ failureId?: string;
6
+ /** Root attempt shared by automatic retries; sharing this id makes retry fan-out count once. */
7
+ rootAttemptId?: string;
8
+ /** Execution/run id; duplicate deliveries of one execution count once even if other metadata diverges. */
9
+ executionId?: string;
10
+ /** Optional verifier+artifact digest pair refines legacy evidence when no direct failure/root/execution id exists. */
11
+ verifierDigest?: string;
12
+ artifactDigest?: string;
4
13
  }
5
- /** Gene ids with >=2 failed capsules whose trigger covers >=60% of the current signals. */
14
+ /** Gene ids with >=2 independent failed capsules whose trigger covers >=60% of the current signals. */
6
15
  export declare function bannedGenesFromFailures(failures: readonly FailedCapsuleRef[], signals: readonly string[]): Set<string>;
package/dist/algo/bans.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Ban genes that have repeatedly failed on the SAME signals (ported from v1 selector.js
2
2
  // banGenesFromFailedCapsules). Complements epigenetic suppression (which is per-environment): this is a
3
- // hard, signal-overlap-based exclusion — a gene with >=2 failed capsules whose trigger covers the current
4
- // signals is dropped from the candidate set, so a strategy that keeps failing here is not resurrected.
3
+ // hard, signal-overlap-based exclusion — but only when the failures carry counted identity quality. Legacy raw
4
+ // or partial-digest rows remain audit/advisory evidence and cannot satisfy the automatic tau=2 quarantine.
5
5
  const BAN_THRESHOLD = 2;
6
6
  const OVERLAP_MIN = 0.6;
7
7
  /** Fraction of the current signals covered by a failure's trigger (how much it is "the same problem"). */
@@ -15,7 +15,52 @@ function signalCoverage(signals, trigger) {
15
15
  hit += 1;
16
16
  return hit / signals.length;
17
17
  }
18
- /** Gene ids with >=2 failed capsules whose trigger covers >=60% of the current signals. */
18
+ function nonEmpty(s) {
19
+ return typeof s === 'string' && s.trim().length > 0 ? s.trim() : undefined;
20
+ }
21
+ function failureEvidenceIdentity(fc) {
22
+ const failure = nonEmpty(fc.failureId);
23
+ const root = nonEmpty(fc.rootAttemptId);
24
+ const execution = nonEmpty(fc.executionId);
25
+ const direct = [
26
+ failure ? `failure:${failure}` : undefined,
27
+ root ? `root:${root}` : undefined,
28
+ execution ? `execution:${execution}` : undefined,
29
+ ].filter((key) => key !== undefined);
30
+ if (direct.length > 0)
31
+ return { quality: 'strong_id', keys: direct };
32
+ const verifier = nonEmpty(fc.verifierDigest);
33
+ const artifact = nonEmpty(fc.artifactDigest);
34
+ if (verifier && artifact)
35
+ return { quality: 'complete_verifier_artifact_digest', keys: [`evidence:${verifier}:${artifact}`] };
36
+ if (verifier || artifact)
37
+ return { quality: 'legacy_partial', keys: [] };
38
+ return { quality: 'legacy_raw', keys: [] };
39
+ }
40
+ function isCountedIdentity(identity) {
41
+ return identity.quality === 'strong_id' || identity.quality === 'complete_verifier_artifact_digest';
42
+ }
43
+ function addFailureIdentity(clusters, keys) {
44
+ const matches = [];
45
+ for (let i = 0; i < clusters.length; i++) {
46
+ if (keys.some((key) => clusters[i].has(key)))
47
+ matches.push(i);
48
+ }
49
+ if (matches.length === 0) {
50
+ clusters.push(new Set(keys));
51
+ return;
52
+ }
53
+ const target = clusters[matches[0]];
54
+ for (const key of keys)
55
+ target.add(key);
56
+ for (let i = matches.length - 1; i > 0; i--) {
57
+ const index = matches[i];
58
+ for (const key of clusters[index])
59
+ target.add(key);
60
+ clusters.splice(index, 1);
61
+ }
62
+ }
63
+ /** Gene ids with >=2 independent failed capsules whose trigger covers >=60% of the current signals. */
19
64
  export function bannedGenesFromFailures(failures, signals) {
20
65
  const counts = new Map();
21
66
  for (const fc of failures) {
@@ -23,11 +68,16 @@ export function bannedGenesFromFailures(failures, signals) {
23
68
  continue;
24
69
  if (signalCoverage(signals, fc.trigger ?? []) < OVERLAP_MIN)
25
70
  continue;
26
- counts.set(fc.gene, (counts.get(fc.gene) ?? 0) + 1);
71
+ const identity = failureEvidenceIdentity(fc);
72
+ if (!isCountedIdentity(identity))
73
+ continue;
74
+ const clusters = counts.get(fc.gene) ?? [];
75
+ addFailureIdentity(clusters, identity.keys);
76
+ counts.set(fc.gene, clusters);
27
77
  }
28
78
  const banned = new Set();
29
- for (const [gene, c] of counts)
30
- if (c >= BAN_THRESHOLD)
79
+ for (const [gene, clusters] of counts)
80
+ if (clusters.length >= BAN_THRESHOLD)
31
81
  banned.add(gene);
32
82
  return banned;
33
83
  }
@@ -3,8 +3,12 @@ import { type ReuseCounts } from '../ops/reuseOutcomes.js';
3
3
  import type { ProvenanceStore } from '../assetstore/provenance.js';
4
4
  import type { ReviewLedger } from '../assetstore/reviewLedger.js';
5
5
  import type { AntiWarning, GeneCandidateInput } from './geneSelection.js';
6
+ import { type RuntimeRegistry } from './kautoValidator.js';
7
+ import { type CompatibilityEvidenceIndex } from '../modelCompatibility.js';
6
8
  export interface AssembleOptions {
7
9
  limit?: number;
10
+ /** Build the bounded trusted pre-admission corpus used by semantic IDF. Default true. */
11
+ includeSemanticCorpus?: boolean;
8
12
  provenance?: ProvenanceStore;
9
13
  includeUntrusted?: boolean;
10
14
  review?: ReviewLedger;
@@ -31,6 +35,22 @@ export interface AssembleOptions {
31
35
  * (default-off). Never resurrects a hard-gated gene (the trust/review/ban filters above run first).
32
36
  */
33
37
  reuseCounts?: ReadonlyMap<string, ReuseCounts>;
38
+ /** Exact compatibility evidence; only quarantine decisions remove candidates. */
39
+ compatibility?: CompatibilityEvidenceIndex;
40
+ /**
41
+ * K_auto EvidenceProjection revocation guard (T1, default-OFF). When provided, a candidate whose five
42
+ * coordinates are all machine-decidable AND whose current projection touches a revoked (coordinate, value)
43
+ * pair is treated as NOT eligible — the fail-safe "a revoked projection is not eligible" rule, applied
44
+ * coordinate-locally (a sibling that differs on the revoked coordinate stays eligible). `revoked` is the set
45
+ * produced by `projectRevocations(rootEvents)` (algo/kautoProjection). Absent (default) = deployed T2 behavior
46
+ * unchanged: no projection guard runs, so signal-scoped `bannedGenesFromFailures` + the soft λ preference are
47
+ * the only K_auto-related effects. `runtimeRegistry` is forwarded to `decideKauto` so the runtime coordinate
48
+ * resolves against the same registry the caller uses elsewhere.
49
+ */
50
+ kautoProjection?: {
51
+ revoked: ReadonlySet<string>;
52
+ runtimeRegistry?: RuntimeRegistry;
53
+ };
34
54
  }
35
55
  /** The selection pool for one cycle: the normal scored candidates + the last-resort distilled fallback (#97). */
36
56
  export interface SelectionPool {
@@ -39,10 +59,13 @@ export interface SelectionPool {
39
59
  /**
40
60
  * Distilled genes that passed the trust/review/ban gates but do NOT match the live signals. Surfaced
41
61
  * separately (NOT in `candidates`, so they never compete in normal scoring) for the v1 #97 fallback: when no
42
- * candidate clears the floor, selection reuses one of these broadly-applicable distilled strategies instead of
43
- * falling through to a blind innovate. Kept lightweight (no learning-history aggregation) — picked first-match.
62
+ * normal selection has no reusable positive choice, selection reuses one of these broadly-applicable distilled
63
+ * strategies instead of falling through to a blind innovate. Kept lightweight (no learning-history aggregation)
64
+ * — picked first-match.
44
65
  */
45
66
  distilledFallback: GeneCandidateInput[];
67
+ /** Trust-filtered library documents captured before live-signal relevance admission. */
68
+ semanticCorpus: GeneCandidateInput[];
46
69
  /**
47
70
  * Matched AntiGene guardrails for the live signals. This list is advisory-only and is kept out of candidates and
48
71
  * distilledFallback so negative memory cannot be selected as a strategy.
@@ -1,14 +1,50 @@
1
1
  import { aggregateLearningHistory } from '../assetstore/learningHistory.js';
2
2
  import { reuseSentiment } from '../ops/reuseOutcomes.js';
3
- import { tagOverlapScore } from '../signals/expand.js';
3
+ import { decideKauto } from './kautoValidator.js';
4
+ import { isProjectionRevoked } from './kautoProjection.js';
5
+ import { expandSignals, geneTags, tagOverlapScore, } from '../signals/expand.js';
6
+ import { resolveTaskDomainSignals, withoutTaskDomainSignals } from '../signals/taskDomain.js';
4
7
  import { bannedGenesFromFailures } from './bans.js';
5
8
  import { geneGenerationSource } from './geneIntake.js';
9
+ import { isCompatibilityBlocked } from '../modelCompatibility.js';
10
+ const SEMANTIC_CORPUS_LIMIT = 1_000;
6
11
  function asStrings(v) {
7
12
  return Array.isArray(v) ? v.filter((x) => typeof x === 'string') : [];
8
13
  }
9
14
  function stringField(v) {
10
15
  return typeof v === 'string' && v.trim().length > 0 ? v.trim() : undefined;
11
16
  }
17
+ function firstStringField(record, keys) {
18
+ for (const key of keys) {
19
+ const value = stringField(record[key]);
20
+ if (value)
21
+ return value;
22
+ }
23
+ return undefined;
24
+ }
25
+ function recordList(v) {
26
+ return Array.isArray(v) ? v.filter((x) => x !== null && typeof x === 'object' && !Array.isArray(x)) : [];
27
+ }
28
+ function failureIdentitySource(record) {
29
+ for (const trace of recordList(record['execution_trace'])) {
30
+ const stage = stringField(trace['stage']);
31
+ if (stage === 'validate' && firstStringField(trace, ['root_attempt_id', 'rootAttemptId', 'execution_id', 'executionId', 'failure_id', 'failureId', 'verifier_digest', 'verifierDigest', 'artifact_digest', 'artifactDigest']))
32
+ return trace;
33
+ }
34
+ return record;
35
+ }
36
+ function failureRefFromCapsule(record) {
37
+ const identity = failureIdentitySource(record);
38
+ return {
39
+ gene: stringField(record['gene']),
40
+ trigger: asStrings(record['trigger']),
41
+ failureId: firstStringField(identity, ['failure_id', 'failureId']),
42
+ rootAttemptId: firstStringField(identity, ['root_attempt_id', 'rootAttemptId', 'attempt_root_id', 'attemptRootId']),
43
+ executionId: firstStringField(identity, ['execution_id', 'executionId', 'run_id', 'runId', 'cycle_id', 'cycleId']),
44
+ verifierDigest: firstStringField(identity, ['verifier_digest', 'verifierDigest']),
45
+ artifactDigest: firstStringField(identity, ['artifact_digest', 'artifactDigest']),
46
+ };
47
+ }
12
48
  function severityOf(v) {
13
49
  return v === 'low' || v === 'medium' || v === 'high' ? v : undefined;
14
50
  }
@@ -18,14 +54,34 @@ function candidateIds(geneId, assetId) {
18
54
  function isBannedCandidate(banned, geneId, assetId) {
19
55
  return candidateIds(geneId, assetId).some((id) => banned.has(id));
20
56
  }
21
- function passesInjectedCandidateGates(candidate, opts, provenance) {
57
+ function requiredSignals(signalsMatch) {
58
+ return signalsMatch.flatMap((signal) => signal.startsWith('required:') ? [signal.slice('required:'.length).trim()] : []).filter(Boolean);
59
+ }
60
+ function matchSignals(signalsMatch) {
61
+ return signalsMatch.map((signal) => signal.startsWith('required:') ? signal.slice('required:'.length).trim() : signal).filter(Boolean);
62
+ }
63
+ function passesRequiredSignals(signalsMatch, liveSignals) {
64
+ const required = requiredSignals(signalsMatch);
65
+ return required.length === 0 || required.every((signal) => liveSignals.has(signal));
66
+ }
67
+ function passesFallbackTaskDomain(liveSignals, candidateSignals) {
68
+ const candidateDomain = resolveTaskDomainSignals(candidateSignals);
69
+ if (candidateDomain.status === 'absent')
70
+ return true;
71
+ if (candidateDomain.status !== 'resolved')
72
+ return false;
73
+ const liveDomain = resolveTaskDomainSignals(liveSignals);
74
+ return liveDomain.status === 'resolved' && liveDomain.slug === candidateDomain.slug;
75
+ }
76
+ function passesInjectedCandidateGates(candidate, opts, provenance, review) {
22
77
  const assetId = candidate.assetId;
23
78
  if (opts.provenance && !opts.includeUntrusted) {
24
79
  if (!assetId || provenance?.get(assetId)?.trusted !== true)
25
80
  return false;
26
81
  }
27
82
  if (opts.review) {
28
- if (!assetId || !opts.review.isApproved(assetId))
83
+ const reviewRecord = assetId ? review?.get(assetId) : undefined;
84
+ if (!assetId || (reviewRecord !== undefined && reviewRecord.state !== 'approved'))
29
85
  return false;
30
86
  }
31
87
  return true;
@@ -58,15 +114,32 @@ function combinedReuseSentiment(map, ...ids) {
58
114
  }
59
115
  /** Genes banned by repeated signal-matched failures (>=2 failed capsules covering the signals). */
60
116
  async function computeBans(store, signals, limit) {
117
+ if (signals.length === 0)
118
+ return new Set();
61
119
  const caps = await store.search({ kind: 'Capsule', signalsAny: [...signals], limit });
62
120
  const failures = caps
63
121
  .filter((c) => c['outcome']?.status === 'failed')
64
- .map((c) => ({
65
- gene: typeof c['gene'] === 'string' ? String(c['gene']) : undefined,
66
- trigger: asStrings(c['trigger']),
67
- }));
122
+ .map((c) => failureRefFromCapsule(c));
68
123
  return bannedGenesFromFailures(failures, signals);
69
124
  }
125
+ const GENERIC_NAMESPACE_TAGS = new Set(['action', 'area', 'problem', 'risk', 'signal']);
126
+ // Generic namespace tags and inferred action/signal subtypes are too broad to admit a candidate by themselves.
127
+ function hasSelectionAdmissionEvidence(liveSignals, gene) {
128
+ const tags = new Set(geneTags(gene));
129
+ const triggerTags = new Set(expandSignals(gene.signalsMatch ?? []));
130
+ const categoryActionTag = gene.category ? `action:${gene.category.toLowerCase()}` : undefined;
131
+ if (liveSignals.some((signal) => ((triggerTags.has(signal) || signal === categoryActionTag)
132
+ && !GENERIC_NAMESPACE_TAGS.has(signal)))) {
133
+ return true;
134
+ }
135
+ const liveTags = expandSignals(liveSignals);
136
+ if (liveTags.some((tag) => (tags.has(tag)
137
+ && (tag.startsWith('problem:')
138
+ || tag.startsWith('area:')
139
+ || tag.startsWith('risk:')))))
140
+ return true;
141
+ return liveTags.some((tag) => tag.startsWith('signal:') && triggerTags.has(tag));
142
+ }
70
143
  /**
71
144
  * Assemble the full selection pool from the store for the given signals: the relevant scored candidates AND the
72
145
  * distilled-gene fallback pool, in a single pass over the gene list (one store.list + one ban computation). A gene
@@ -76,34 +149,81 @@ async function computeBans(store, signals, limit) {
76
149
  */
77
150
  export async function assembleSelectionPool(store, signals, opts = {}) {
78
151
  const limit = opts.limit ?? 500;
79
- const genes = await store.list('Gene', limit);
80
- const banned = await computeBans(store, signals, limit);
81
- const sigSet = new Set(signals);
152
+ const includeSemanticCorpus = opts.includeSemanticCorpus !== false;
153
+ const scanLimit = includeSemanticCorpus ? Math.max(limit, SEMANTIC_CORPUS_LIMIT) : limit;
154
+ const genes = await store.list('Gene', scanLimit);
155
+ const matchingSignals = withoutTaskDomainSignals(signals);
156
+ const banned = await computeBans(store, matchingSignals, limit);
157
+ const liveSignalSet = new Set(signals);
158
+ const sigSet = new Set(matchingSignals);
82
159
  const provenance = opts.provenance?.snapshot();
160
+ const review = opts.review?.snapshot();
83
161
  const out = [];
84
162
  const distilledFallback = [];
163
+ const semanticCorpus = [];
164
+ const semanticIdentities = new Set();
165
+ const addSemanticCandidate = (candidate) => {
166
+ if (!includeSemanticCorpus || semanticCorpus.length >= SEMANTIC_CORPUS_LIMIT)
167
+ return;
168
+ const identity = candidate.assetId ?? candidate.geneId;
169
+ if (semanticIdentities.has(identity))
170
+ return;
171
+ semanticIdentities.add(identity);
172
+ semanticCorpus.push(candidate);
173
+ };
85
174
  const antiWarnings = [];
86
- for (const g of genes) {
175
+ for (const [geneIndex, g] of genes.entries()) {
176
+ const provenanceRecord = provenance?.get(String(g.asset_id));
177
+ if (isCompatibilityBlocked({ assetType: 'Gene', assetId: String(g.asset_id), revision: stringField(g['revision']) ?? '' }, opts.compatibility))
178
+ continue;
87
179
  // Trust-first (#30): untrusted (e.g. hub-ingested) genes are excluded from the candidate pool by default;
88
180
  // they enter only with includeUntrusted or after an explicit promotion. Provenance is keyed by asset_id.
89
- if (opts.provenance && !opts.includeUntrusted && provenance?.get(String(g.asset_id))?.trusted === false)
181
+ if (opts.provenance && !opts.includeUntrusted && provenanceRecord?.trusted === false)
90
182
  continue;
91
183
  // Review-first (#89/#91): auto-distilled drafts land quarantined until a human approves; rejected drafts stay
92
184
  // out too. No record → eligible (cycle/migrate genes). Symmetric to the provenance trust-first filter above.
93
185
  // Probation (#306): includeProbation lets a quarantined draft be TRIED so it can earn evidence; a rejected
94
186
  // draft is never tried. Off (default) keeps quarantined drafts out until approval.
95
- if (opts.review && !opts.review.isApproved(String(g.asset_id))
96
- && (!opts.includeProbation || opts.review.get(String(g.asset_id))?.state === 'rejected'))
187
+ const reviewRecord = review?.get(String(g.asset_id));
188
+ if (opts.review && reviewRecord !== undefined && reviewRecord.state !== 'approved'
189
+ && (!opts.includeProbation || reviewRecord.state === 'rejected'))
97
190
  continue;
98
191
  const geneId = typeof g['id'] === 'string' ? String(g['id']) : String(g.asset_id);
99
192
  const assetId = String(g.asset_id);
100
193
  if (isBannedCandidate(banned, geneId, assetId))
101
194
  continue; // repeated signal-matched failures → not a candidate (nor a fallback)
195
+ // K_auto projection revocation (T1, opt-in). A decidable record whose current five-coordinate projection
196
+ // touches a revoked (coordinate, value) pair is not eligible — coordinate-locally, so a sibling differing on
197
+ // the revoked coordinate is unaffected. Runs AFTER trust/review/ban (those are the deployed hard gates) and
198
+ // before signal matching, so a revoked projection is dropped from both the candidate and #97 fallback pools.
199
+ // Default-off: without opts.kautoProjection the deployed T2 path is unchanged.
200
+ if (opts.kautoProjection
201
+ && isProjectionRevoked(decideKauto(g, opts.kautoProjection.runtimeRegistry ? { runtimeRegistry: opts.kautoProjection.runtimeRegistry } : undefined), opts.kautoProjection.revoked))
202
+ continue;
102
203
  const signalsMatch = asStrings(g['signals_match']);
204
+ const effectiveSignalsMatch = matchSignals(signalsMatch);
103
205
  const category = typeof g['category'] === 'string' ? String(g['category']) : undefined;
104
206
  const summary = typeof g['summary'] === 'string' ? String(g['summary']) : undefined;
105
- const literal = signalsMatch.some((m) => sigSet.has(m));
106
- const relevant = literal || tagOverlapScore(signals, { signalsMatch, geneId, category, summary }) > 0;
207
+ // Experimental probation and explicitly untrusted records may be selectable by opt-in, but never shape global
208
+ // document frequency. Only trusted, approved/legacy-local records enter the pre-admission semantic corpus.
209
+ if (provenanceRecord?.trusted !== false && (reviewRecord === undefined || reviewRecord.state === 'approved')) {
210
+ addSemanticCandidate({
211
+ geneId,
212
+ assetId,
213
+ signalsMatch: effectiveSignalsMatch,
214
+ view: emptyLearningView(geneId),
215
+ ...(category ? { category } : {}),
216
+ ...(summary ? { summary } : {}),
217
+ });
218
+ }
219
+ if (geneIndex >= limit)
220
+ continue;
221
+ if (!passesRequiredSignals(signalsMatch, liveSignalSet))
222
+ continue;
223
+ const matchingSignalsForGene = withoutTaskDomainSignals(effectiveSignalsMatch);
224
+ const literal = matchingSignalsForGene.some((m) => sigSet.has(m));
225
+ const tagInput = { signalsMatch: matchingSignalsForGene, geneId, category, summary };
226
+ const relevant = literal || hasSelectionAdmissionEvidence(matchingSignals, tagInput);
107
227
  if (!relevant) {
108
228
  // #97: a trusted, approved, non-banned distilled (or evolved) gene that doesn't match the live signals is not
109
229
  // a normal candidate, but it IS eligible as a last-resort fallback (selection uses it only when nothing clears
@@ -111,11 +231,12 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
111
231
  // generation_meta (V1 #302); a legacy gene without it falls back to the `gene_distilled_` id namespace.
112
232
  // Lightweight: no learning-history aggregation — it is picked first-match, not ranked by health.
113
233
  const gsrc = geneGenerationSource(g, geneId);
114
- if (gsrc === 'distilled' || gsrc === 'evolved') {
234
+ if ((gsrc === 'distilled' || gsrc === 'evolved')
235
+ && passesFallbackTaskDomain(signals, effectiveSignalsMatch)) {
115
236
  distilledFallback.push({
116
237
  geneId,
117
238
  assetId,
118
- signalsMatch,
239
+ signalsMatch: effectiveSignalsMatch,
119
240
  view: emptyLearningView(geneId),
120
241
  reuseCount: 0,
121
242
  generationSource: gsrc,
@@ -126,14 +247,19 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
126
247
  continue;
127
248
  }
128
249
  const view = await aggregateLearningHistory(store, geneId);
250
+ const explorationEligible = provenanceRecord?.trusted !== false
251
+ && (reviewRecord === undefined || reviewRecord.state === 'approved');
129
252
  // #268 Bugbot: reuse events key by assetId, which may be the content asset_id (sha256:…) OR the logical id —
130
253
  // candidates key by the logical geneId. Look the sentiment up by BOTH so the soft re-order actually matches
131
254
  // regardless of which form the reuse-result carried (recall's resolveGene accepts both for the same reason).
132
255
  const reuseAdjustVal = combinedReuseSentiment(opts.reuseCounts, geneId, String(g.asset_id));
256
+ // Soft K_auto preference: stamp only when the source record clears the strict five-coordinate bar.
257
+ // Absent/false leaves score unchanged so the historical non-member catalogue is not zeroed.
258
+ const kautoMember = decideKauto(g).inKauto;
133
259
  out.push({
134
260
  geneId,
135
261
  assetId: String(g.asset_id),
136
- signalsMatch,
262
+ signalsMatch: effectiveSignalsMatch,
137
263
  view,
138
264
  // #195: inert (zero-work) cycles are not productive reuse — exclude them so a do-nothing gene can't earn
139
265
  // the reuse bonus. success + failed === total when there are no inert cycles (back-compat).
@@ -141,32 +267,47 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
141
267
  ...(category ? { category } : {}),
142
268
  ...(summary ? { summary } : {}),
143
269
  ...(reuseAdjustVal !== undefined ? { reuseAdjust: reuseAdjustVal } : {}),
270
+ ...(kautoMember ? { kautoMember: true } : {}),
271
+ explorationEligible,
144
272
  });
145
273
  }
146
274
  // #110: merge hub reuse candidates into the same pool, trust-first — local (trusted) genes win on a
147
275
  // geneId collision, and a hub candidate banned by repeated signal-matched failures stays out too.
148
276
  if (opts.hubCandidates && opts.hubCandidates.length > 0) {
149
277
  const localIds = new Set(out.map((c) => c.geneId));
278
+ const localAssetIds = new Set(out.map((c) => c.assetId).filter((id) => id !== undefined));
150
279
  for (const h of opts.hubCandidates) {
151
- if (!passesInjectedCandidateGates(h, opts, provenance))
280
+ if (isCompatibilityBlocked({ assetType: 'Gene', assetId: h.assetId ?? h.geneId, revision: String(h.hubAsset?.['revision'] ?? '') }, opts.compatibility))
281
+ continue;
282
+ if (!passesInjectedCandidateGates(h, opts, provenance, review))
283
+ continue;
284
+ if (!passesRequiredSignals(h.signalsMatch, sigSet))
152
285
  continue;
153
286
  if (localIds.has(h.geneId))
154
287
  continue; // a trusted local gene already covers this id
288
+ if (h.assetId && localAssetIds.has(h.assetId))
289
+ continue; // the same content identity is already local/trusted
155
290
  if (isBannedCandidate(banned, h.geneId, h.assetId))
156
291
  continue;
157
292
  // Stamp the reuse sentiment onto a hub candidate too (only when it didn't carry one), matched by its geneId
158
293
  // OR its asset_id — symmetric with local genes, so a map keyed like reuse events (often sha256:…) still hits
159
294
  // (#268 Bugbot). A hub candidate that exposes neither matching id is simply left unstamped.
295
+ const hubSignalsMatch = matchSignals(h.signalsMatch);
160
296
  const hubAdj = h.reuseAdjust === undefined ? combinedReuseSentiment(opts.reuseCounts, h.geneId, h.assetId) : undefined;
161
- out.push(hubAdj !== undefined ? { ...h, reuseAdjust: hubAdj } : h);
297
+ // A transient Hub candidate must never self-assert active-bandit cold-start eligibility. It still competes
298
+ // under the existing base score and legacy drift; UCB1 fails safe for a mixed window.
299
+ const guarded = { ...h, signalsMatch: hubSignalsMatch, explorationEligible: false };
300
+ out.push(hubAdj !== undefined ? { ...guarded, reuseAdjust: hubAdj } : guarded);
162
301
  localIds.add(h.geneId);
302
+ if (h.assetId)
303
+ localAssetIds.add(h.assetId);
163
304
  }
164
305
  }
165
306
  for (const a of await store.list('AntiGene', limit)) {
166
307
  // AntiGene is negative memory that changes an autonomous prompt. Unlike legacy/cycle-authored Genes, it must
167
308
  // never inherit ReviewLedger's backward-compatible "no record = approved" default: no ledger, no record,
168
309
  // quarantined, and rejected all fail closed. Only an explicit human approval can enable warning injection.
169
- if (!opts.review?.isExplicitlyApproved(String(a.asset_id)))
310
+ if (review?.get(String(a.asset_id))?.state !== 'approved')
170
311
  continue;
171
312
  const trigger = asStrings(a['trigger']);
172
313
  const avoid = asStrings(a['avoid']);
@@ -174,8 +315,10 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
174
315
  continue;
175
316
  const antiGeneId = stringField(a['id']) ?? String(a.asset_id);
176
317
  const summary = stringField(a['summary']);
177
- const relevant = trigger.some((m) => sigSet.has(m))
178
- || tagOverlapScore(signals, { signalsMatch: trigger, geneId: antiGeneId, summary }) > 0;
318
+ const matchingTrigger = withoutTaskDomainSignals(trigger);
319
+ // This is a safety inclusion gate: ubiquitous evidence must still emit warnings.
320
+ const relevant = matchingTrigger.some((m) => sigSet.has(m))
321
+ || tagOverlapScore(matchingSignals, { signalsMatch: matchingTrigger, geneId: antiGeneId, summary }) > 0;
179
322
  if (!relevant)
180
323
  continue;
181
324
  const severity = severityOf(a['severity']);
@@ -190,7 +333,7 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
190
333
  ...(rationale ? { rationale } : {}),
191
334
  });
192
335
  }
193
- return { candidates: out, distilledFallback, antiWarnings };
336
+ return { candidates: out, distilledFallback, semanticCorpus, antiWarnings };
194
337
  }
195
338
  /**
196
339
  * Assemble selection candidates from the store for the given signals (the relevant scored set only).
@@ -198,5 +341,7 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
198
341
  * distilled-gene fallback pool.
199
342
  */
200
343
  export async function assembleCandidates(store, signals, opts = {}) {
201
- return (await assembleSelectionPool(store, signals, opts)).candidates;
344
+ // This compatibility wrapper discards semanticCorpus, so preserve its legacy I/O bound instead of scanning
345
+ // 1,000 records for data the caller cannot consume.
346
+ return (await assembleSelectionPool(store, signals, { ...opts, includeSemanticCorpus: false })).candidates;
202
347
  }
@@ -20,6 +20,16 @@ const SIGNAL_CANDIDATES = [
20
20
  { signal: 'capability_gap', title: 'Fill capability gap' },
21
21
  { signal: 'stable_success_plateau', title: 'Explore new strategies during stability plateau' },
22
22
  { signal: 'external_opportunity', title: 'Evaluate external A2A asset for local adoption' },
23
+ // Harness context-budget family (v1 context-compression gene family port). v2 ships no seed-gene catalog, so
24
+ // the reusable strategies arrive as ADVISORY candidates the runtime may distill into genes — one per bloat
25
+ // source, because a single "shorten the prompt" proposal loses the distinction that decides what is safe to
26
+ // drop (an always-on tool schema is lazy-loadable; an approval gate is not).
27
+ { signal: 'prompt_budget_measurement', title: 'Measure the prompt budget per source before compressing context' },
28
+ { signal: 'context_explosion', title: 'Attribute a context-window overflow to its largest prompt source' },
29
+ { signal: 'tool_schema_bloat', title: 'Load exact tool/MCP schemas from the runtime loader instead of always-on prompt text' },
30
+ { signal: 'skill_list_bloat', title: 'Compress skill and agent-type descriptions into selectable route cards' },
31
+ { signal: 'transcript_context_bloat', title: 'Compress a pasted transcript into a session handoff packet' },
32
+ { signal: 'memory_index_budget', title: 'Keep the memory index lean and rely on relevance recall for detail' },
23
33
  ];
24
34
  /** Highest → lowest priority problem class used to pick a failure cluster's grouping key (ported v1). */
25
35
  const PROBLEM_PRIORITY = ['problem:performance', 'problem:protocol', 'problem:reliability', 'problem:stagnation', 'problem:capability'];
@@ -1,8 +1,16 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { redactString } from '../hub/sanitize.js';
3
- const REUSABLE_RE = /\b(reusable|repeatable|workflow|playbook|runbook|capability|procedure|pattern|recipe|documented|future runs?|next time|can reuse|reuse this)\b|可复用|复用|能力|流程|工作流/i;
4
- const PROOF_RE = /\b(validated|verified|passed|green|success(?:ful|fully)?|succeeded|works?|completed|published|uploaded|recorded:true|exit code:?\s*0|all tests passed)\b|验证|通过|成功|已发布|可用/i;
3
+ // The Chinese branches require compound/intentful phrases on purpose: everyday words (能力/流程/成功/通过)
4
+ // occur in nearly every Chinese dev session, and matching them bare made this fallback the DOMINANT distill
5
+ // path — 105 of 118 drafts in one bulk ingest (#562). English gets specificity from \b word boundaries;
6
+ // Chinese has no \b, so specificity must come from the phrase itself.
7
+ const REUSABLE_RE = /\b(reusable|repeatable|workflow|playbook|runbook|capability|procedure|pattern|recipe|documented|future runs?|next time|can reuse|reuse this)\b|复用|可重用|工作流(?!程)|方法论|沉淀/i;
8
+ const PROOF_RE = /\b(validated|verified|passed|green|success(?:ful|fully)?|succeeded|works?|completed|published|uploaded|recorded:true|exit code:?\s*0|all tests passed)\b|(?:验证|校验|测试|检查|构建|编译|运行|执行|部署|发布)(?:都|均|全部)?(?:通过|成功)|全部通过|跑通|已(?:验证|发布|上线)/i;
5
9
  const FAILURE_RE = /\b(failed|failure|error|exception|traceback|exit code:?\s*[1-9]|not working|unable to)\b|失败|错误|报错/i;
10
+ const CHINESE_NEGATED_PROOF_PREFIX_SOURCE = '(?:尚未|并未|还没有|尚没有|并没有|没有|未能|无法|不能|不曾|并非|未|没)';
11
+ const CHINESE_NEGATED_PROOF_SOURCE = `(?:${CHINESE_NEGATED_PROOF_PREFIX_SOURCE}\\s*(?:验证|校验|测试|检查|构建|编译|运行|执行|部署|发布)(?:都|均|全部)?\\s*(?:通过|成功)|(?:验证|校验|测试|检查|构建|编译|运行|执行|部署|发布)(?:仍|还|尚)?(?:没有|未能|未)(?:都|均|全部)?\\s*(?:通过|成功))`;
12
+ const CHINESE_NEGATED_PROOF_RE = new RegExp(CHINESE_NEGATED_PROOF_SOURCE, 'i');
13
+ const CHINESE_NEGATED_PROOF_GLOBAL_RE = new RegExp(CHINESE_NEGATED_PROOF_SOURCE, 'gi');
6
14
  const EXIT_ZERO_RE = /\bexit code:?\s*0\b/i;
7
15
  const EXIT_NON_ZERO_RE = /\bexit code:?\s*[1-9]\d*\b/i;
8
16
  const DOMAIN_TOKENS = [
@@ -54,15 +62,24 @@ function domainSignals(text, toolCalls) {
54
62
  function isToolTurn(turn) {
55
63
  return turn.role === 'tool' || Boolean(turn.toolName);
56
64
  }
65
+ function hasPositiveProof(text) {
66
+ return PROOF_RE.test(text.replace(CHINESE_NEGATED_PROOF_GLOBAL_RE, ''));
67
+ }
68
+ function hasFailure(text) {
69
+ return FAILURE_RE.test(text) || CHINESE_NEGATED_PROOF_RE.test(text);
70
+ }
57
71
  function classifyToolOutcome(turn) {
58
72
  const text = turnText(turn);
59
73
  if (EXIT_NON_ZERO_RE.test(text))
60
74
  return 'failure';
75
+ // A semantic "not yet successful" result is not proof even when the command process itself exited zero.
76
+ if (CHINESE_NEGATED_PROOF_RE.test(text))
77
+ return 'failure';
61
78
  if (EXIT_ZERO_RE.test(text))
62
79
  return 'success';
63
- if (PROOF_RE.test(text) && !FAILURE_RE.test(text))
80
+ if (hasPositiveProof(text) && !FAILURE_RE.test(text))
64
81
  return 'success';
65
- if (FAILURE_RE.test(text))
82
+ if (hasFailure(text))
66
83
  return 'failure';
67
84
  return 'unknown';
68
85
  }
@@ -81,7 +98,7 @@ function pickEvidence(turns, matcher, max = 3) {
81
98
  const evidence = [];
82
99
  for (const turn of turns) {
83
100
  const text = turnText(turn);
84
- if (!text || !matcher.test(text))
101
+ if (!text || !(typeof matcher === 'function' ? matcher(text) : matcher.test(text)))
85
102
  continue;
86
103
  evidence.push(cleanText(text, 180));
87
104
  if (evidence.length >= max)
@@ -93,7 +110,7 @@ function pickSummary(turns, reusableEvidence, proofEvidence) {
93
110
  const assistant = turns
94
111
  .filter((turn) => turn.role === 'assistant' && !turn.isMeta)
95
112
  .map(turnText)
96
- .find((text) => text.length >= 40 && (REUSABLE_RE.test(text) || PROOF_RE.test(text)));
113
+ .find((text) => text.length >= 40 && (REUSABLE_RE.test(text) || hasPositiveProof(text)));
97
114
  return cleanText(assistant ?? reusableEvidence[0] ?? proofEvidence[0] ?? 'Verified reusable conversation capability.', 180);
98
115
  }
99
116
  export function sniffConversationCapabilities(turns, opts = {}) {
@@ -104,10 +121,10 @@ export function sniffConversationCapabilities(turns, opts = {}) {
104
121
  if (!allText.trim())
105
122
  return [];
106
123
  const reusableEvidence = pickEvidence(usableTurns, REUSABLE_RE);
107
- const proofEvidence = pickEvidence(usableTurns, PROOF_RE);
124
+ const proofEvidence = pickEvidence(usableTurns, hasPositiveProof);
108
125
  if (reusableEvidence.length === 0 || proofEvidence.length === 0)
109
126
  return [];
110
- const failureOnly = FAILURE_RE.test(allText) && !PROOF_RE.test(allText);
127
+ const failureOnly = hasFailure(allText) && !hasPositiveProof(allText);
111
128
  if (failureOnly)
112
129
  return [];
113
130
  if (!hasTerminalSuccessfulToolRun(usableTurns))