@soleri/core 2.1.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/dist/brain/brain.d.ts +10 -1
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +116 -13
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +36 -1
  6. package/dist/brain/intelligence.d.ts.map +1 -1
  7. package/dist/brain/intelligence.js +119 -14
  8. package/dist/brain/intelligence.js.map +1 -1
  9. package/dist/brain/types.d.ts +34 -2
  10. package/dist/brain/types.d.ts.map +1 -1
  11. package/dist/cognee/client.d.ts +3 -0
  12. package/dist/cognee/client.d.ts.map +1 -1
  13. package/dist/cognee/client.js +17 -0
  14. package/dist/cognee/client.js.map +1 -1
  15. package/dist/cognee/sync-manager.d.ts +94 -0
  16. package/dist/cognee/sync-manager.d.ts.map +1 -0
  17. package/dist/cognee/sync-manager.js +293 -0
  18. package/dist/cognee/sync-manager.js.map +1 -0
  19. package/dist/control/identity-manager.d.ts +22 -0
  20. package/dist/control/identity-manager.d.ts.map +1 -0
  21. package/dist/control/identity-manager.js +233 -0
  22. package/dist/control/identity-manager.js.map +1 -0
  23. package/dist/control/intent-router.d.ts +32 -0
  24. package/dist/control/intent-router.d.ts.map +1 -0
  25. package/dist/control/intent-router.js +242 -0
  26. package/dist/control/intent-router.js.map +1 -0
  27. package/dist/control/types.d.ts +68 -0
  28. package/dist/control/types.d.ts.map +1 -0
  29. package/dist/control/types.js +9 -0
  30. package/dist/control/types.js.map +1 -0
  31. package/dist/curator/curator.d.ts +37 -1
  32. package/dist/curator/curator.d.ts.map +1 -1
  33. package/dist/curator/curator.js +199 -1
  34. package/dist/curator/curator.js.map +1 -1
  35. package/dist/errors/classify.d.ts +13 -0
  36. package/dist/errors/classify.d.ts.map +1 -0
  37. package/dist/errors/classify.js +97 -0
  38. package/dist/errors/classify.js.map +1 -0
  39. package/dist/errors/index.d.ts +6 -0
  40. package/dist/errors/index.d.ts.map +1 -0
  41. package/dist/errors/index.js +4 -0
  42. package/dist/errors/index.js.map +1 -0
  43. package/dist/errors/retry.d.ts +40 -0
  44. package/dist/errors/retry.d.ts.map +1 -0
  45. package/dist/errors/retry.js +97 -0
  46. package/dist/errors/retry.js.map +1 -0
  47. package/dist/errors/types.d.ts +48 -0
  48. package/dist/errors/types.d.ts.map +1 -0
  49. package/dist/errors/types.js +59 -0
  50. package/dist/errors/types.js.map +1 -0
  51. package/dist/facades/types.d.ts +1 -1
  52. package/dist/governance/governance.d.ts +42 -0
  53. package/dist/governance/governance.d.ts.map +1 -0
  54. package/dist/governance/governance.js +488 -0
  55. package/dist/governance/governance.js.map +1 -0
  56. package/dist/governance/index.d.ts +3 -0
  57. package/dist/governance/index.d.ts.map +1 -0
  58. package/dist/governance/index.js +2 -0
  59. package/dist/governance/index.js.map +1 -0
  60. package/dist/governance/types.d.ts +102 -0
  61. package/dist/governance/types.d.ts.map +1 -0
  62. package/dist/governance/types.js +3 -0
  63. package/dist/governance/types.js.map +1 -0
  64. package/dist/index.d.ts +52 -3
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +47 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/intake/content-classifier.d.ts +14 -0
  69. package/dist/intake/content-classifier.d.ts.map +1 -0
  70. package/dist/intake/content-classifier.js +125 -0
  71. package/dist/intake/content-classifier.js.map +1 -0
  72. package/dist/intake/dedup-gate.d.ts +17 -0
  73. package/dist/intake/dedup-gate.d.ts.map +1 -0
  74. package/dist/intake/dedup-gate.js +66 -0
  75. package/dist/intake/dedup-gate.js.map +1 -0
  76. package/dist/intake/intake-pipeline.d.ts +63 -0
  77. package/dist/intake/intake-pipeline.d.ts.map +1 -0
  78. package/dist/intake/intake-pipeline.js +373 -0
  79. package/dist/intake/intake-pipeline.js.map +1 -0
  80. package/dist/intake/types.d.ts +65 -0
  81. package/dist/intake/types.d.ts.map +1 -0
  82. package/dist/intake/types.js +3 -0
  83. package/dist/intake/types.js.map +1 -0
  84. package/dist/intelligence/loader.js +1 -1
  85. package/dist/intelligence/loader.js.map +1 -1
  86. package/dist/intelligence/types.d.ts +3 -1
  87. package/dist/intelligence/types.d.ts.map +1 -1
  88. package/dist/logging/logger.d.ts +37 -0
  89. package/dist/logging/logger.d.ts.map +1 -0
  90. package/dist/logging/logger.js +145 -0
  91. package/dist/logging/logger.js.map +1 -0
  92. package/dist/logging/types.d.ts +19 -0
  93. package/dist/logging/types.d.ts.map +1 -0
  94. package/dist/logging/types.js +2 -0
  95. package/dist/logging/types.js.map +1 -0
  96. package/dist/loop/loop-manager.d.ts +100 -0
  97. package/dist/loop/loop-manager.d.ts.map +1 -0
  98. package/dist/loop/loop-manager.js +379 -0
  99. package/dist/loop/loop-manager.js.map +1 -0
  100. package/dist/loop/types.d.ts +103 -0
  101. package/dist/loop/types.d.ts.map +1 -0
  102. package/dist/loop/types.js +11 -0
  103. package/dist/loop/types.js.map +1 -0
  104. package/dist/persistence/index.d.ts +3 -0
  105. package/dist/persistence/index.d.ts.map +1 -0
  106. package/dist/persistence/index.js +2 -0
  107. package/dist/persistence/index.js.map +1 -0
  108. package/dist/persistence/sqlite-provider.d.ts +25 -0
  109. package/dist/persistence/sqlite-provider.d.ts.map +1 -0
  110. package/dist/persistence/sqlite-provider.js +59 -0
  111. package/dist/persistence/sqlite-provider.js.map +1 -0
  112. package/dist/persistence/types.d.ts +36 -0
  113. package/dist/persistence/types.d.ts.map +1 -0
  114. package/dist/persistence/types.js +8 -0
  115. package/dist/persistence/types.js.map +1 -0
  116. package/dist/planning/gap-analysis.d.ts +72 -0
  117. package/dist/planning/gap-analysis.d.ts.map +1 -0
  118. package/dist/planning/gap-analysis.js +442 -0
  119. package/dist/planning/gap-analysis.js.map +1 -0
  120. package/dist/planning/gap-types.d.ts +29 -0
  121. package/dist/planning/gap-types.d.ts.map +1 -0
  122. package/dist/planning/gap-types.js +28 -0
  123. package/dist/planning/gap-types.js.map +1 -0
  124. package/dist/planning/planner.d.ts +421 -4
  125. package/dist/planning/planner.d.ts.map +1 -1
  126. package/dist/planning/planner.js +949 -21
  127. package/dist/planning/planner.js.map +1 -1
  128. package/dist/playbooks/generic/brainstorming.d.ts +9 -0
  129. package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
  130. package/dist/playbooks/generic/brainstorming.js +105 -0
  131. package/dist/playbooks/generic/brainstorming.js.map +1 -0
  132. package/dist/playbooks/generic/code-review.d.ts +11 -0
  133. package/dist/playbooks/generic/code-review.d.ts.map +1 -0
  134. package/dist/playbooks/generic/code-review.js +176 -0
  135. package/dist/playbooks/generic/code-review.js.map +1 -0
  136. package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
  137. package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
  138. package/dist/playbooks/generic/subagent-execution.js +68 -0
  139. package/dist/playbooks/generic/subagent-execution.js.map +1 -0
  140. package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
  141. package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
  142. package/dist/playbooks/generic/systematic-debugging.js +87 -0
  143. package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
  144. package/dist/playbooks/generic/tdd.d.ts +9 -0
  145. package/dist/playbooks/generic/tdd.d.ts.map +1 -0
  146. package/dist/playbooks/generic/tdd.js +70 -0
  147. package/dist/playbooks/generic/tdd.js.map +1 -0
  148. package/dist/playbooks/generic/verification.d.ts +9 -0
  149. package/dist/playbooks/generic/verification.d.ts.map +1 -0
  150. package/dist/playbooks/generic/verification.js +74 -0
  151. package/dist/playbooks/generic/verification.js.map +1 -0
  152. package/dist/playbooks/index.d.ts +4 -0
  153. package/dist/playbooks/index.d.ts.map +1 -0
  154. package/dist/playbooks/index.js +5 -0
  155. package/dist/playbooks/index.js.map +1 -0
  156. package/dist/playbooks/playbook-registry.d.ts +42 -0
  157. package/dist/playbooks/playbook-registry.d.ts.map +1 -0
  158. package/dist/playbooks/playbook-registry.js +227 -0
  159. package/dist/playbooks/playbook-registry.js.map +1 -0
  160. package/dist/playbooks/playbook-seeder.d.ts +47 -0
  161. package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
  162. package/dist/playbooks/playbook-seeder.js +104 -0
  163. package/dist/playbooks/playbook-seeder.js.map +1 -0
  164. package/dist/playbooks/playbook-types.d.ts +132 -0
  165. package/dist/playbooks/playbook-types.d.ts.map +1 -0
  166. package/dist/playbooks/playbook-types.js +12 -0
  167. package/dist/playbooks/playbook-types.js.map +1 -0
  168. package/dist/project/project-registry.d.ts +79 -0
  169. package/dist/project/project-registry.d.ts.map +1 -0
  170. package/dist/project/project-registry.js +274 -0
  171. package/dist/project/project-registry.js.map +1 -0
  172. package/dist/project/types.d.ts +28 -0
  173. package/dist/project/types.d.ts.map +1 -0
  174. package/dist/project/types.js +5 -0
  175. package/dist/project/types.js.map +1 -0
  176. package/dist/prompts/index.d.ts +4 -0
  177. package/dist/prompts/index.d.ts.map +1 -0
  178. package/dist/prompts/index.js +3 -0
  179. package/dist/prompts/index.js.map +1 -0
  180. package/dist/prompts/parser.d.ts +17 -0
  181. package/dist/prompts/parser.d.ts.map +1 -0
  182. package/dist/prompts/parser.js +47 -0
  183. package/dist/prompts/parser.js.map +1 -0
  184. package/dist/prompts/template-manager.d.ts +25 -0
  185. package/dist/prompts/template-manager.d.ts.map +1 -0
  186. package/dist/prompts/template-manager.js +71 -0
  187. package/dist/prompts/template-manager.js.map +1 -0
  188. package/dist/prompts/types.d.ts +26 -0
  189. package/dist/prompts/types.d.ts.map +1 -0
  190. package/dist/prompts/types.js +5 -0
  191. package/dist/prompts/types.js.map +1 -0
  192. package/dist/runtime/admin-extra-ops.d.ts +15 -0
  193. package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
  194. package/dist/runtime/admin-extra-ops.js +595 -0
  195. package/dist/runtime/admin-extra-ops.js.map +1 -0
  196. package/dist/runtime/admin-ops.d.ts +15 -0
  197. package/dist/runtime/admin-ops.d.ts.map +1 -0
  198. package/dist/runtime/admin-ops.js +329 -0
  199. package/dist/runtime/admin-ops.js.map +1 -0
  200. package/dist/runtime/capture-ops.d.ts +15 -0
  201. package/dist/runtime/capture-ops.d.ts.map +1 -0
  202. package/dist/runtime/capture-ops.js +363 -0
  203. package/dist/runtime/capture-ops.js.map +1 -0
  204. package/dist/runtime/cognee-sync-ops.d.ts +12 -0
  205. package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
  206. package/dist/runtime/cognee-sync-ops.js +55 -0
  207. package/dist/runtime/cognee-sync-ops.js.map +1 -0
  208. package/dist/runtime/core-ops.d.ts +9 -3
  209. package/dist/runtime/core-ops.d.ts.map +1 -1
  210. package/dist/runtime/core-ops.js +693 -10
  211. package/dist/runtime/core-ops.js.map +1 -1
  212. package/dist/runtime/curator-extra-ops.d.ts +9 -0
  213. package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
  214. package/dist/runtime/curator-extra-ops.js +71 -0
  215. package/dist/runtime/curator-extra-ops.js.map +1 -0
  216. package/dist/runtime/domain-ops.d.ts.map +1 -1
  217. package/dist/runtime/domain-ops.js +61 -15
  218. package/dist/runtime/domain-ops.js.map +1 -1
  219. package/dist/runtime/grading-ops.d.ts +14 -0
  220. package/dist/runtime/grading-ops.d.ts.map +1 -0
  221. package/dist/runtime/grading-ops.js +105 -0
  222. package/dist/runtime/grading-ops.js.map +1 -0
  223. package/dist/runtime/intake-ops.d.ts +14 -0
  224. package/dist/runtime/intake-ops.d.ts.map +1 -0
  225. package/dist/runtime/intake-ops.js +110 -0
  226. package/dist/runtime/intake-ops.js.map +1 -0
  227. package/dist/runtime/loop-ops.d.ts +14 -0
  228. package/dist/runtime/loop-ops.d.ts.map +1 -0
  229. package/dist/runtime/loop-ops.js +251 -0
  230. package/dist/runtime/loop-ops.js.map +1 -0
  231. package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
  232. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
  233. package/dist/runtime/memory-cross-project-ops.js +165 -0
  234. package/dist/runtime/memory-cross-project-ops.js.map +1 -0
  235. package/dist/runtime/memory-extra-ops.d.ts +13 -0
  236. package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
  237. package/dist/runtime/memory-extra-ops.js +173 -0
  238. package/dist/runtime/memory-extra-ops.js.map +1 -0
  239. package/dist/runtime/orchestrate-ops.d.ts +17 -0
  240. package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
  241. package/dist/runtime/orchestrate-ops.js +246 -0
  242. package/dist/runtime/orchestrate-ops.js.map +1 -0
  243. package/dist/runtime/planning-extra-ops.d.ts +25 -0
  244. package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
  245. package/dist/runtime/planning-extra-ops.js +663 -0
  246. package/dist/runtime/planning-extra-ops.js.map +1 -0
  247. package/dist/runtime/playbook-ops.d.ts +14 -0
  248. package/dist/runtime/playbook-ops.d.ts.map +1 -0
  249. package/dist/runtime/playbook-ops.js +141 -0
  250. package/dist/runtime/playbook-ops.js.map +1 -0
  251. package/dist/runtime/project-ops.d.ts +15 -0
  252. package/dist/runtime/project-ops.d.ts.map +1 -0
  253. package/dist/runtime/project-ops.js +186 -0
  254. package/dist/runtime/project-ops.js.map +1 -0
  255. package/dist/runtime/runtime.d.ts.map +1 -1
  256. package/dist/runtime/runtime.js +65 -3
  257. package/dist/runtime/runtime.js.map +1 -1
  258. package/dist/runtime/types.d.ts +29 -0
  259. package/dist/runtime/types.d.ts.map +1 -1
  260. package/dist/runtime/vault-extra-ops.d.ts +10 -0
  261. package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
  262. package/dist/runtime/vault-extra-ops.js +536 -0
  263. package/dist/runtime/vault-extra-ops.js.map +1 -0
  264. package/dist/telemetry/telemetry.d.ts +48 -0
  265. package/dist/telemetry/telemetry.d.ts.map +1 -0
  266. package/dist/telemetry/telemetry.js +87 -0
  267. package/dist/telemetry/telemetry.js.map +1 -0
  268. package/dist/vault/playbook.d.ts +34 -0
  269. package/dist/vault/playbook.d.ts.map +1 -0
  270. package/dist/vault/playbook.js +60 -0
  271. package/dist/vault/playbook.js.map +1 -0
  272. package/dist/vault/vault.d.ts +97 -4
  273. package/dist/vault/vault.d.ts.map +1 -1
  274. package/dist/vault/vault.js +424 -65
  275. package/dist/vault/vault.js.map +1 -1
  276. package/package.json +7 -3
  277. package/src/__tests__/admin-extra-ops.test.ts +467 -0
  278. package/src/__tests__/admin-ops.test.ts +271 -0
  279. package/src/__tests__/brain-intelligence.test.ts +205 -0
  280. package/src/__tests__/brain.test.ts +134 -3
  281. package/src/__tests__/capture-ops.test.ts +509 -0
  282. package/src/__tests__/cognee-integration.test.ts +80 -0
  283. package/src/__tests__/cognee-sync-manager.test.ts +103 -0
  284. package/src/__tests__/core-ops.test.ts +292 -2
  285. package/src/__tests__/curator-extra-ops.test.ts +381 -0
  286. package/src/__tests__/domain-ops.test.ts +66 -0
  287. package/src/__tests__/errors.test.ts +388 -0
  288. package/src/__tests__/governance.test.ts +522 -0
  289. package/src/__tests__/grading-ops.test.ts +361 -0
  290. package/src/__tests__/identity-manager.test.ts +243 -0
  291. package/src/__tests__/intake-pipeline.test.ts +162 -0
  292. package/src/__tests__/intent-router.test.ts +222 -0
  293. package/src/__tests__/logger.test.ts +200 -0
  294. package/src/__tests__/loop-ops.test.ts +469 -0
  295. package/src/__tests__/memory-cross-project-ops.test.ts +248 -0
  296. package/src/__tests__/memory-extra-ops.test.ts +352 -0
  297. package/src/__tests__/orchestrate-ops.test.ts +289 -0
  298. package/src/__tests__/persistence.test.ts +225 -0
  299. package/src/__tests__/planner.test.ts +416 -7
  300. package/src/__tests__/planning-extra-ops.test.ts +706 -0
  301. package/src/__tests__/playbook-registry.test.ts +326 -0
  302. package/src/__tests__/playbook-seeder.test.ts +163 -0
  303. package/src/__tests__/playbook.test.ts +389 -0
  304. package/src/__tests__/project-ops.test.ts +381 -0
  305. package/src/__tests__/template-manager.test.ts +222 -0
  306. package/src/__tests__/vault-extra-ops.test.ts +482 -0
  307. package/src/brain/brain.ts +185 -16
  308. package/src/brain/intelligence.ts +179 -10
  309. package/src/brain/types.ts +40 -2
  310. package/src/cognee/client.ts +18 -0
  311. package/src/cognee/sync-manager.ts +389 -0
  312. package/src/control/identity-manager.ts +354 -0
  313. package/src/control/intent-router.ts +326 -0
  314. package/src/control/types.ts +102 -0
  315. package/src/curator/curator.ts +295 -1
  316. package/src/errors/classify.ts +102 -0
  317. package/src/errors/index.ts +5 -0
  318. package/src/errors/retry.ts +132 -0
  319. package/src/errors/types.ts +81 -0
  320. package/src/governance/governance.ts +698 -0
  321. package/src/governance/index.ts +18 -0
  322. package/src/governance/types.ts +111 -0
  323. package/src/index.ts +213 -2
  324. package/src/intake/content-classifier.ts +146 -0
  325. package/src/intake/dedup-gate.ts +92 -0
  326. package/src/intake/intake-pipeline.ts +503 -0
  327. package/src/intake/types.ts +69 -0
  328. package/src/intelligence/loader.ts +1 -1
  329. package/src/intelligence/types.ts +3 -1
  330. package/src/logging/logger.ts +154 -0
  331. package/src/logging/types.ts +21 -0
  332. package/src/loop/loop-manager.ts +448 -0
  333. package/src/loop/types.ts +115 -0
  334. package/src/persistence/index.ts +7 -0
  335. package/src/persistence/sqlite-provider.ts +62 -0
  336. package/src/persistence/types.ts +44 -0
  337. package/src/planning/gap-analysis.ts +775 -0
  338. package/src/planning/gap-types.ts +61 -0
  339. package/src/planning/planner.ts +1273 -24
  340. package/src/playbooks/generic/brainstorming.ts +110 -0
  341. package/src/playbooks/generic/code-review.ts +181 -0
  342. package/src/playbooks/generic/subagent-execution.ts +74 -0
  343. package/src/playbooks/generic/systematic-debugging.ts +92 -0
  344. package/src/playbooks/generic/tdd.ts +75 -0
  345. package/src/playbooks/generic/verification.ts +79 -0
  346. package/src/playbooks/index.ts +27 -0
  347. package/src/playbooks/playbook-registry.ts +284 -0
  348. package/src/playbooks/playbook-seeder.ts +119 -0
  349. package/src/playbooks/playbook-types.ts +162 -0
  350. package/src/project/project-registry.ts +370 -0
  351. package/src/project/types.ts +31 -0
  352. package/src/prompts/index.ts +3 -0
  353. package/src/prompts/parser.ts +59 -0
  354. package/src/prompts/template-manager.ts +77 -0
  355. package/src/prompts/types.ts +28 -0
  356. package/src/runtime/admin-extra-ops.ts +652 -0
  357. package/src/runtime/admin-ops.ts +340 -0
  358. package/src/runtime/capture-ops.ts +404 -0
  359. package/src/runtime/cognee-sync-ops.ts +63 -0
  360. package/src/runtime/core-ops.ts +787 -9
  361. package/src/runtime/curator-extra-ops.ts +85 -0
  362. package/src/runtime/domain-ops.ts +67 -15
  363. package/src/runtime/grading-ops.ts +130 -0
  364. package/src/runtime/intake-ops.ts +126 -0
  365. package/src/runtime/loop-ops.ts +277 -0
  366. package/src/runtime/memory-cross-project-ops.ts +191 -0
  367. package/src/runtime/memory-extra-ops.ts +186 -0
  368. package/src/runtime/orchestrate-ops.ts +278 -0
  369. package/src/runtime/planning-extra-ops.ts +718 -0
  370. package/src/runtime/playbook-ops.ts +169 -0
  371. package/src/runtime/project-ops.ts +202 -0
  372. package/src/runtime/runtime.ts +77 -3
  373. package/src/runtime/types.ts +29 -0
  374. package/src/runtime/vault-extra-ops.ts +606 -0
  375. package/src/telemetry/telemetry.ts +118 -0
  376. package/src/vault/playbook.ts +87 -0
  377. package/src/vault/vault.ts +575 -98
@@ -0,0 +1,271 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { createAgentRuntime } from '../runtime/runtime.js';
3
+ import { createAdminOps } from '../runtime/admin-ops.js';
4
+ import type { AgentRuntime } from '../runtime/types.js';
5
+ import type { OpDefinition } from '../facades/types.js';
6
+
7
+ describe('createAdminOps', () => {
8
+ let runtime: AgentRuntime;
9
+ let ops: OpDefinition[];
10
+
11
+ function findOp(name: string): OpDefinition {
12
+ const op = ops.find((o) => o.name === name);
13
+ if (!op) throw new Error(`Op "${name}" not found`);
14
+ return op;
15
+ }
16
+
17
+ afterEach(() => {
18
+ runtime?.close();
19
+ });
20
+
21
+ function setup() {
22
+ runtime = createAgentRuntime({
23
+ agentId: 'test-admin',
24
+ vaultPath: ':memory:',
25
+ });
26
+ ops = createAdminOps(runtime);
27
+ }
28
+
29
+ it('should return 8 ops', () => {
30
+ setup();
31
+ expect(ops).toHaveLength(8);
32
+ const names = ops.map((o) => o.name);
33
+ expect(names).toEqual([
34
+ 'admin_health',
35
+ 'admin_tool_list',
36
+ 'admin_config',
37
+ 'admin_vault_size',
38
+ 'admin_uptime',
39
+ 'admin_version',
40
+ 'admin_reset_cache',
41
+ 'admin_diagnostic',
42
+ ]);
43
+ });
44
+
45
+ // ─── admin_health ──────────────────────────────────────────────
46
+
47
+ describe('admin_health', () => {
48
+ it('should return comprehensive health status', async () => {
49
+ setup();
50
+ const result = (await findOp('admin_health').handler({})) as Record<string, unknown>;
51
+
52
+ expect(result.status).toBe('ok');
53
+ expect(result.vault).toEqual({ entries: 0, domains: [] });
54
+ expect(result.cognee).toEqual({ available: false });
55
+ expect(result.llm).toHaveProperty('openai');
56
+ expect(result.llm).toHaveProperty('anthropic');
57
+ expect(result.brain).toHaveProperty('vocabularySize');
58
+ expect(result.brain).toHaveProperty('feedbackCount');
59
+ expect(result.curator).toEqual({ initialized: true });
60
+ });
61
+
62
+ it('should reflect vault entries after seed', async () => {
63
+ setup();
64
+ runtime.vault.seed([
65
+ {
66
+ id: 'ah-1',
67
+ type: 'pattern',
68
+ domain: 'testing',
69
+ title: 'Health Test',
70
+ severity: 'warning',
71
+ description: 'Test entry.',
72
+ tags: ['test'],
73
+ },
74
+ ]);
75
+ const result = (await findOp('admin_health').handler({})) as Record<string, unknown>;
76
+ const vault = result.vault as { entries: number; domains: string[] };
77
+ expect(vault.entries).toBe(1);
78
+ expect(vault.domains).toContain('testing');
79
+ });
80
+ });
81
+
82
+ // ─── admin_tool_list ───────────────────────────────────────────
83
+
84
+ describe('admin_tool_list', () => {
85
+ it('should return fallback list without _allOps', async () => {
86
+ setup();
87
+ const result = (await findOp('admin_tool_list').handler({})) as {
88
+ count: number;
89
+ ops: Array<{ name: string }>;
90
+ };
91
+
92
+ expect(result.count).toBe(8);
93
+ expect(result.ops.map((o) => o.name)).toContain('admin_health');
94
+ expect(result.ops.map((o) => o.name)).toContain('admin_diagnostic');
95
+ });
96
+
97
+ it('should return injected ops when _allOps is provided', async () => {
98
+ setup();
99
+ const allOps = [
100
+ { name: 'search', description: 'Search vault', auth: 'read' },
101
+ { name: 'admin_health', description: 'Health check', auth: 'read' },
102
+ ];
103
+ const result = (await findOp('admin_tool_list').handler({ _allOps: allOps })) as {
104
+ count: number;
105
+ ops: Array<{ name: string; description: string; auth: string }>;
106
+ };
107
+
108
+ expect(result.count).toBe(2);
109
+ expect(result.ops[0].name).toBe('search');
110
+ expect(result.ops[1].name).toBe('admin_health');
111
+ });
112
+ });
113
+
114
+ // ─── admin_config ──────────────────────────────────────────────
115
+
116
+ describe('admin_config', () => {
117
+ it('should return runtime config', async () => {
118
+ setup();
119
+ const result = (await findOp('admin_config').handler({})) as Record<string, unknown>;
120
+
121
+ expect(result.agentId).toBe('test-admin');
122
+ expect(result.vaultPath).toBe(':memory:');
123
+ expect(result.logLevel).toBe('info');
124
+ });
125
+ });
126
+
127
+ // ─── admin_vault_size ──────────────────────────────────────────
128
+
129
+ describe('admin_vault_size', () => {
130
+ it('should return null size for in-memory vault', async () => {
131
+ setup();
132
+ const result = (await findOp('admin_vault_size').handler({})) as Record<string, unknown>;
133
+
134
+ expect(result.path).toBe(':memory:');
135
+ expect(result.sizeBytes).toBeNull();
136
+ expect(result.sizeHuman).toBe('in-memory');
137
+ });
138
+ });
139
+
140
+ // ─── admin_uptime ──────────────────────────────────────────────
141
+
142
+ describe('admin_uptime', () => {
143
+ it('should return uptime since creation', async () => {
144
+ setup();
145
+ const result = (await findOp('admin_uptime').handler({})) as Record<string, unknown>;
146
+
147
+ expect(result.createdAt).toBeDefined();
148
+ expect(typeof result.uptimeMs).toBe('number');
149
+ expect(typeof result.uptimeSec).toBe('number');
150
+ expect(typeof result.uptimeHuman).toBe('string');
151
+ expect(result.uptimeMs as number).toBeGreaterThanOrEqual(0);
152
+ expect(result.uptimeMs as number).toBeLessThan(5000); // should be nearly instant
153
+ });
154
+
155
+ it('should have valid ISO date in createdAt', async () => {
156
+ setup();
157
+ const result = (await findOp('admin_uptime').handler({})) as { createdAt: string };
158
+ const parsed = new Date(result.createdAt);
159
+ expect(parsed.getTime()).toBeGreaterThan(0);
160
+ });
161
+ });
162
+
163
+ // ─── admin_version ─────────────────────────────────────────────
164
+
165
+ describe('admin_version', () => {
166
+ it('should return version information', async () => {
167
+ setup();
168
+ const result = (await findOp('admin_version').handler({})) as Record<string, unknown>;
169
+
170
+ expect(typeof result.core).toBe('string');
171
+ expect(result.node).toMatch(/^v\d+/);
172
+ expect(typeof result.platform).toBe('string');
173
+ expect(typeof result.arch).toBe('string');
174
+ });
175
+ });
176
+
177
+ // ─── admin_reset_cache ─────────────────────────────────────────
178
+
179
+ describe('admin_reset_cache', () => {
180
+ it('should clear caches and return status', async () => {
181
+ setup();
182
+ const result = (await findOp('admin_reset_cache').handler({})) as Record<string, unknown>;
183
+
184
+ expect(result.cleared).toEqual(['brain_vocabulary', 'cognee_health_cache']);
185
+ expect(typeof (result as { brainVocabularySize: number }).brainVocabularySize).toBe('number');
186
+ expect(typeof (result as { cogneeAvailable: boolean }).cogneeAvailable).toBe('boolean');
187
+ });
188
+
189
+ it('should have write auth', () => {
190
+ setup();
191
+ const op = findOp('admin_reset_cache');
192
+ expect(op.auth).toBe('write');
193
+ });
194
+ });
195
+
196
+ // ─── admin_diagnostic ──────────────────────────────────────────
197
+
198
+ describe('admin_diagnostic', () => {
199
+ it('should return diagnostic report with all checks', async () => {
200
+ setup();
201
+ const result = (await findOp('admin_diagnostic').handler({})) as {
202
+ overall: string;
203
+ checks: Array<{ name: string; status: string; detail: string }>;
204
+ summary: string;
205
+ };
206
+
207
+ expect(['healthy', 'degraded', 'unhealthy']).toContain(result.overall);
208
+ expect(result.checks.length).toBeGreaterThanOrEqual(7);
209
+
210
+ const checkNames = result.checks.map((c) => c.name);
211
+ expect(checkNames).toContain('vault');
212
+ expect(checkNames).toContain('brain_vocabulary');
213
+ expect(checkNames).toContain('brain_intelligence');
214
+ expect(checkNames).toContain('cognee');
215
+ expect(checkNames).toContain('llm_openai');
216
+ expect(checkNames).toContain('llm_anthropic');
217
+ expect(checkNames).toContain('curator');
218
+ });
219
+
220
+ it('should report vault as ok for empty vault', async () => {
221
+ setup();
222
+ const result = (await findOp('admin_diagnostic').handler({})) as {
223
+ checks: Array<{ name: string; status: string }>;
224
+ };
225
+
226
+ const vaultCheck = result.checks.find((c) => c.name === 'vault');
227
+ expect(vaultCheck?.status).toBe('ok');
228
+ });
229
+
230
+ it('should report curator as ok', async () => {
231
+ setup();
232
+ const result = (await findOp('admin_diagnostic').handler({})) as {
233
+ checks: Array<{ name: string; status: string }>;
234
+ };
235
+
236
+ const curatorCheck = result.checks.find((c) => c.name === 'curator');
237
+ expect(curatorCheck?.status).toBe('ok');
238
+ });
239
+
240
+ it('should include summary string', async () => {
241
+ setup();
242
+ const result = (await findOp('admin_diagnostic').handler({})) as { summary: string };
243
+ expect(result.summary).toMatch(/\d+ checks:/);
244
+ });
245
+ });
246
+
247
+ // ─── Auth levels ───────────────────────────────────────────────
248
+
249
+ describe('auth levels', () => {
250
+ it('should use read auth for status ops', () => {
251
+ setup();
252
+ const readOps = [
253
+ 'admin_health',
254
+ 'admin_tool_list',
255
+ 'admin_config',
256
+ 'admin_vault_size',
257
+ 'admin_uptime',
258
+ 'admin_version',
259
+ 'admin_diagnostic',
260
+ ];
261
+ for (const name of readOps) {
262
+ expect(findOp(name).auth).toBe('read');
263
+ }
264
+ });
265
+
266
+ it('should use write auth for mutation ops', () => {
267
+ setup();
268
+ expect(findOp('admin_reset_cache').auth).toBe('write');
269
+ });
270
+ });
271
+ });
@@ -431,6 +431,41 @@ describe('BrainIntelligence', () => {
431
431
  expect(result.failed).toContain('non-existent');
432
432
  });
433
433
 
434
+ it('should gate promote through governance when strict preset', () => {
435
+ // Create a brain session and extract proposals
436
+ runtime.brainIntelligence.lifecycle({
437
+ action: 'start',
438
+ sessionId: 'gov-promo-1',
439
+ toolsUsed: ['w', 'w', 'w'],
440
+ });
441
+ runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'gov-promo-1' });
442
+ runtime.brainIntelligence.extractKnowledge('gov-promo-1');
443
+
444
+ const proposals = runtime.brainIntelligence.getProposals({ sessionId: 'gov-promo-1' });
445
+ expect(proposals.length).toBeGreaterThan(0);
446
+
447
+ // Apply strict preset — requireReview: true
448
+ runtime.governance.applyPreset('.', 'strict', 'test');
449
+
450
+ const result = runtime.brainIntelligence.promoteProposals(
451
+ [proposals[0].id],
452
+ runtime.governance,
453
+ '.',
454
+ );
455
+
456
+ // Should be gated, not promoted
457
+ expect(result.promoted).toBe(0);
458
+ expect(result.gated.length).toBe(1);
459
+ expect(result.gated[0].action).toBe('propose');
460
+
461
+ // Entry should NOT be in vault
462
+ expect(runtime.vault.get(`proposal-${proposals[0].id}`)).toBeNull();
463
+
464
+ // But should be in governance proposals
465
+ const pending = runtime.governance.listPendingProposals('.');
466
+ expect(pending.some((p) => p.source === 'brain-promote')).toBe(true);
467
+ });
468
+
434
469
  // ─── Build Intelligence ────────────────────────────────────────
435
470
 
436
471
  it('should build intelligence pipeline', () => {
@@ -557,6 +592,7 @@ describe('BrainIntelligence', () => {
557
592
  filesModified: [],
558
593
  planId: null,
559
594
  planOutcome: null,
595
+ extractedAt: null,
560
596
  },
561
597
  ],
562
598
  proposals: [],
@@ -609,6 +645,7 @@ describe('BrainIntelligence', () => {
609
645
  filesModified: [],
610
646
  planId: null,
611
647
  planOutcome: null,
648
+ extractedAt: null,
612
649
  },
613
650
  ],
614
651
  proposals: [],
@@ -620,4 +657,172 @@ describe('BrainIntelligence', () => {
620
657
  const result = runtime.brainIntelligence.importData(data);
621
658
  expect(result.imported.sessions).toBe(0); // Duplicate ignored
622
659
  });
660
+
661
+ // ─── Auto-extraction on session end ─────────────────────────
662
+
663
+ it('should auto-extract when session ends with tools used', () => {
664
+ runtime.brainIntelligence.lifecycle({
665
+ action: 'start',
666
+ sessionId: 'auto-1',
667
+ domain: 'testing',
668
+ toolsUsed: ['search', 'search', 'search'],
669
+ });
670
+ const session = runtime.brainIntelligence.lifecycle({
671
+ action: 'end',
672
+ sessionId: 'auto-1',
673
+ });
674
+ // extractedAt should be set automatically
675
+ expect(session.extractedAt).not.toBeNull();
676
+ });
677
+
678
+ it('should not auto-extract when session has no signal', () => {
679
+ runtime.brainIntelligence.lifecycle({
680
+ action: 'start',
681
+ sessionId: 'auto-2',
682
+ });
683
+ const session = runtime.brainIntelligence.lifecycle({
684
+ action: 'end',
685
+ sessionId: 'auto-2',
686
+ });
687
+ // No tools, no files, no plan — should not extract
688
+ expect(session.extractedAt).toBeNull();
689
+ });
690
+
691
+ it('should auto-extract when session has a plan', () => {
692
+ runtime.brainIntelligence.lifecycle({
693
+ action: 'start',
694
+ sessionId: 'auto-3',
695
+ planId: 'plan-123',
696
+ });
697
+ const session = runtime.brainIntelligence.lifecycle({
698
+ action: 'end',
699
+ sessionId: 'auto-3',
700
+ planOutcome: 'completed',
701
+ });
702
+ expect(session.extractedAt).not.toBeNull();
703
+ });
704
+
705
+ // ─── extractedAt Tracking ──────────────────────────────────
706
+
707
+ it('should set extractedAt when extractKnowledge is called manually', () => {
708
+ // Use a session with no tools/files/plan so auto-extraction doesn't fire
709
+ runtime.brainIntelligence.lifecycle({
710
+ action: 'start',
711
+ sessionId: 'ext-1',
712
+ domain: 'testing',
713
+ });
714
+ runtime.brainIntelligence.lifecycle({
715
+ action: 'end',
716
+ sessionId: 'ext-1',
717
+ });
718
+
719
+ // Before manual extraction, extractedAt should be null (no auto-extract — no signal)
720
+ const ctx = runtime.brainIntelligence.getSessionContext(10);
721
+ const before = ctx.recentSessions.find((s) => s.id === 'ext-1');
722
+ expect(before?.extractedAt).toBeNull();
723
+
724
+ // Manual extract
725
+ runtime.brainIntelligence.extractKnowledge('ext-1');
726
+
727
+ // After extraction, extractedAt should be set
728
+ const ctx2 = runtime.brainIntelligence.getSessionContext(10);
729
+ const after = ctx2.recentSessions.find((s) => s.id === 'ext-1');
730
+ expect(after?.extractedAt).not.toBeNull();
731
+ });
732
+
733
+ // ─── resetExtracted ────────────────────────────────────────
734
+
735
+ it('should reset extractedAt for a specific session', () => {
736
+ runtime.brainIntelligence.lifecycle({
737
+ action: 'start',
738
+ sessionId: 'reset-1',
739
+ toolsUsed: ['a', 'a', 'a'],
740
+ });
741
+ runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'reset-1' });
742
+ runtime.brainIntelligence.extractKnowledge('reset-1');
743
+
744
+ const result = runtime.brainIntelligence.resetExtracted({ sessionId: 'reset-1' });
745
+ expect(result.reset).toBe(1);
746
+
747
+ const ctx = runtime.brainIntelligence.getSessionContext(10);
748
+ const session = ctx.recentSessions.find((s) => s.id === 'reset-1');
749
+ expect(session?.extractedAt).toBeNull();
750
+ });
751
+
752
+ it('should reset all extracted sessions', () => {
753
+ runtime.brainIntelligence.lifecycle({
754
+ action: 'start',
755
+ sessionId: 'reset-a',
756
+ toolsUsed: ['a', 'a', 'a'],
757
+ });
758
+ runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'reset-a' });
759
+ runtime.brainIntelligence.extractKnowledge('reset-a');
760
+
761
+ runtime.brainIntelligence.lifecycle({
762
+ action: 'start',
763
+ sessionId: 'reset-b',
764
+ toolsUsed: ['b', 'b', 'b'],
765
+ });
766
+ runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'reset-b' });
767
+ runtime.brainIntelligence.extractKnowledge('reset-b');
768
+
769
+ const result = runtime.brainIntelligence.resetExtracted({ all: true });
770
+ expect(result.reset).toBe(2);
771
+ });
772
+
773
+ it('should return 0 when nothing to reset', () => {
774
+ const result = runtime.brainIntelligence.resetExtracted({ all: true });
775
+ expect(result.reset).toBe(0);
776
+ });
777
+
778
+ it('should return 0 when no filter is provided', () => {
779
+ const result = runtime.brainIntelligence.resetExtracted();
780
+ expect(result.reset).toBe(0);
781
+ });
782
+
783
+ it('should allow re-extraction after reset', () => {
784
+ runtime.brainIntelligence.lifecycle({
785
+ action: 'start',
786
+ sessionId: 'reext-1',
787
+ toolsUsed: ['search', 'search', 'search'],
788
+ });
789
+ runtime.brainIntelligence.lifecycle({ action: 'end', sessionId: 'reext-1' });
790
+
791
+ const first = runtime.brainIntelligence.extractKnowledge('reext-1');
792
+ expect(first.proposals.length).toBeGreaterThan(0);
793
+
794
+ runtime.brainIntelligence.resetExtracted({ sessionId: 'reext-1' });
795
+
796
+ const second = runtime.brainIntelligence.extractKnowledge('reext-1');
797
+ expect(second.proposals.length).toBeGreaterThan(0);
798
+ });
799
+
800
+ // ─── computeStrengths with modified feedback ───────────────
801
+
802
+ it('should weight modified feedback at 0.5 in computeStrengths', () => {
803
+ // Seed an entry for feedback to reference
804
+ runtime.vault.seed([
805
+ {
806
+ id: 'str-1',
807
+ type: 'pattern',
808
+ domain: 'testing',
809
+ title: 'Strength test pattern',
810
+ severity: 'warning',
811
+ description: 'Testing strength computation with modified feedback.',
812
+ tags: ['test'],
813
+ },
814
+ ]);
815
+
816
+ // Record feedback: 1 accepted, 1 modified, 1 failed
817
+ runtime.brain.recordFeedback({ query: 'q1', entryId: 'str-1', action: 'accepted' });
818
+ runtime.brain.recordFeedback({ query: 'q2', entryId: 'str-1', action: 'modified' });
819
+ runtime.brain.recordFeedback({ query: 'q3', entryId: 'str-1', action: 'failed' });
820
+
821
+ const strengths = runtime.brainIntelligence.computeStrengths();
822
+ const s = strengths.find((p) => p.pattern === 'Strength test pattern');
823
+ expect(s).toBeDefined();
824
+
825
+ // successRate = (1 + 0.5) / (3 - 1) = 1.5/2 = 0.75
826
+ expect(s!.successRate).toBeCloseTo(0.75, 2);
827
+ });
623
828
  });
@@ -140,7 +140,7 @@ describe('Brain', () => {
140
140
  expect(breakdown).toHaveProperty('semantic');
141
141
  expect(breakdown).toHaveProperty('vector');
142
142
  expect(breakdown).toHaveProperty('severity');
143
- expect(breakdown).toHaveProperty('recency');
143
+ expect(breakdown).toHaveProperty('temporalDecay');
144
144
  expect(breakdown).toHaveProperty('tagOverlap');
145
145
  expect(breakdown).toHaveProperty('domainMatch');
146
146
  expect(breakdown).toHaveProperty('total');
@@ -584,7 +584,7 @@ describe('Brain', () => {
584
584
  stats.weights.semantic +
585
585
  stats.weights.vector +
586
586
  stats.weights.severity +
587
- stats.weights.recency +
587
+ stats.weights.temporalDecay +
588
588
  stats.weights.tagOverlap +
589
589
  stats.weights.domainMatch;
590
590
  expect(sum).toBeCloseTo(1.0, 5);
@@ -741,6 +741,137 @@ describe('Brain', () => {
741
741
  });
742
742
  });
743
743
 
744
+ // ─── Enhanced Feedback ─────────────────────────────────────
745
+
746
+ describe('enhanced feedback', () => {
747
+ beforeEach(() => {
748
+ vault.seed([makeEntry({ id: 'fb-1', title: 'Feedback test pattern', tags: ['feedback'] })]);
749
+ brain = new Brain(vault);
750
+ });
751
+
752
+ it('should accept legacy 3-arg form (backward compat)', () => {
753
+ brain.recordFeedback('test query', 'fb-1', 'accepted');
754
+ const stats = brain.getFeedbackStats();
755
+ expect(stats.total).toBe(1);
756
+ expect(stats.byAction['accepted']).toBe(1);
757
+ });
758
+
759
+ it('should accept FeedbackInput and return FeedbackEntry', () => {
760
+ const entry = brain.recordFeedback({
761
+ query: 'test query',
762
+ entryId: 'fb-1',
763
+ action: 'modified',
764
+ source: 'recommendation',
765
+ confidence: 0.85,
766
+ duration: 1200,
767
+ reason: 'adjusted wording',
768
+ });
769
+ expect(entry).toBeDefined();
770
+ expect(entry.action).toBe('modified');
771
+ expect(entry.source).toBe('recommendation');
772
+ expect(entry.confidence).toBe(0.85);
773
+ expect(entry.duration).toBe(1200);
774
+ expect(entry.reason).toBe('adjusted wording');
775
+ expect(entry.id).toBeGreaterThan(0);
776
+ });
777
+
778
+ it('should accept modified and failed action types', () => {
779
+ brain.recordFeedback({ query: 'q1', entryId: 'fb-1', action: 'modified' });
780
+ brain.recordFeedback({ query: 'q2', entryId: 'fb-1', action: 'failed' });
781
+ const stats = brain.getFeedbackStats();
782
+ expect(stats.total).toBe(2);
783
+ expect(stats.byAction['modified']).toBe(1);
784
+ expect(stats.byAction['failed']).toBe(1);
785
+ });
786
+
787
+ it('should use default source and confidence when not provided', () => {
788
+ const entry = brain.recordFeedback({ query: 'q1', entryId: 'fb-1', action: 'accepted' });
789
+ expect(entry.source).toBe('search');
790
+ expect(entry.confidence).toBe(0.6);
791
+ });
792
+ });
793
+
794
+ // ─── Feedback Stats ───────────────────────────────────────
795
+
796
+ describe('getFeedbackStats', () => {
797
+ beforeEach(() => {
798
+ vault.seed([makeEntry({ id: 'fs-1', tags: ['stats'] })]);
799
+ brain = new Brain(vault);
800
+ });
801
+
802
+ it('should return zero stats on empty feedback', () => {
803
+ const stats = brain.getFeedbackStats();
804
+ expect(stats.total).toBe(0);
805
+ expect(stats.acceptanceRate).toBe(0);
806
+ expect(stats.averageConfidence).toBe(0);
807
+ });
808
+
809
+ it('should compute acceptance rate correctly', () => {
810
+ brain.recordFeedback('q1', 'fs-1', 'accepted');
811
+ brain.recordFeedback('q2', 'fs-1', 'dismissed');
812
+ brain.recordFeedback('q3', 'fs-1', 'accepted');
813
+ const stats = brain.getFeedbackStats();
814
+ expect(stats.total).toBe(3);
815
+ expect(stats.acceptanceRate).toBeCloseTo(2 / 3, 2);
816
+ });
817
+
818
+ it('should group by action and source', () => {
819
+ brain.recordFeedback({ query: 'q1', entryId: 'fs-1', action: 'accepted', source: 'search' });
820
+ brain.recordFeedback({
821
+ query: 'q2',
822
+ entryId: 'fs-1',
823
+ action: 'modified',
824
+ source: 'recommendation',
825
+ });
826
+ brain.recordFeedback({
827
+ query: 'q3',
828
+ entryId: 'fs-1',
829
+ action: 'failed',
830
+ source: 'tool-execution',
831
+ });
832
+ const stats = brain.getFeedbackStats();
833
+ expect(stats.byAction['accepted']).toBe(1);
834
+ expect(stats.byAction['modified']).toBe(1);
835
+ expect(stats.byAction['failed']).toBe(1);
836
+ expect(stats.bySource['search']).toBe(1);
837
+ expect(stats.bySource['recommendation']).toBe(1);
838
+ expect(stats.bySource['tool-execution']).toBe(1);
839
+ });
840
+
841
+ it('should compute average confidence', () => {
842
+ brain.recordFeedback({ query: 'q1', entryId: 'fs-1', action: 'accepted', confidence: 0.9 });
843
+ brain.recordFeedback({ query: 'q2', entryId: 'fs-1', action: 'dismissed', confidence: 0.3 });
844
+ const stats = brain.getFeedbackStats();
845
+ expect(stats.averageConfidence).toBeCloseTo(0.6, 2);
846
+ });
847
+ });
848
+
849
+ // ─── Recompute Weights with Modified/Failed ──────────────
850
+
851
+ describe('recomputeWeights with modified/failed', () => {
852
+ beforeEach(() => {
853
+ vault.seed([makeEntry({ id: 'rw-1', tags: ['weights'] })]);
854
+ brain = new Brain(vault);
855
+ });
856
+
857
+ it('should exclude failed from weight computation', () => {
858
+ // Add enough feedback to exceed threshold (30)
859
+ for (let i = 0; i < 20; i++) {
860
+ brain.recordFeedback('q', 'rw-1', 'accepted');
861
+ }
862
+ for (let i = 0; i < 10; i++) {
863
+ brain.recordFeedback({ query: 'q', entryId: 'rw-1', action: 'failed' });
864
+ }
865
+ // Failed entries should not count toward total for weight adaptation
866
+ // 20 accepted out of 20 relevant = 100% accept rate
867
+ const stats = brain.getStats();
868
+ // Weights should have adapted since we have 30+ total but only 20 non-failed
869
+ // (threshold is 30, total is 30, but only 20 are non-failed so threshold not met)
870
+ // The recomputeWeights() counts non-failed, which is 20 < 30, so weights stay default
871
+ expect(stats.weights.semantic).toBeCloseTo(0.4, 2);
872
+ });
873
+ });
874
+
744
875
  // ─── Graceful Degradation ───────────────────────────────────
745
876
 
746
877
  describe('graceful degradation', () => {
@@ -750,7 +881,7 @@ describe('Brain', () => {
750
881
  expect(results).toEqual([]);
751
882
  });
752
883
 
753
- it('should fall back to severity + recency scoring when vocabulary is empty', async () => {
884
+ it('should fall back to severity + temporalDecay scoring when vocabulary is empty', async () => {
754
885
  vault.seed([
755
886
  makeEntry({
756
887
  id: 'gd-1',