@kinqs/brainrouter-mcp-server 0.3.4

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 (337) hide show
  1. package/.env.example +144 -0
  2. package/README.md +56 -0
  3. package/agents/README.md +120 -0
  4. package/agents/code-reviewer.md +97 -0
  5. package/agents/security-auditor.md +101 -0
  6. package/agents/test-engineer.md +95 -0
  7. package/dist/__tests__/agent_mode.test.d.ts +1 -0
  8. package/dist/__tests__/api-routes.test.d.ts +1 -0
  9. package/dist/__tests__/api-routes.test.js +170 -0
  10. package/dist/__tests__/crypto.test.d.ts +1 -0
  11. package/dist/__tests__/crypto.test.js +28 -0
  12. package/dist/__tests__/host-integrations.test.d.ts +1 -0
  13. package/dist/__tests__/host-integrations.test.js +82 -0
  14. package/dist/__tests__/integration.test.d.ts +1 -0
  15. package/dist/__tests__/integration.test.js +50 -0
  16. package/dist/__tests__/loader.test.d.ts +1 -0
  17. package/dist/__tests__/loader.test.js +89 -0
  18. package/dist/__tests__/neural-spark.test.d.ts +1 -0
  19. package/dist/__tests__/neural-spark.test.js +112 -0
  20. package/dist/__tests__/pagination.test.d.ts +1 -0
  21. package/dist/__tests__/pagination.test.js +23 -0
  22. package/dist/__tests__/redaction.test.d.ts +1 -0
  23. package/dist/__tests__/redaction.test.js +17 -0
  24. package/dist/__tests__/registry.test.d.ts +1 -0
  25. package/dist/__tests__/registry.test.js +56 -0
  26. package/dist/__tests__/retry.test.d.ts +1 -0
  27. package/dist/__tests__/retry.test.js +30 -0
  28. package/dist/__tests__/skill-activation.test.d.ts +1 -0
  29. package/dist/__tests__/skill-activation.test.js +112 -0
  30. package/dist/__tests__/working-memory.test.d.ts +1 -0
  31. package/dist/__tests__/working-memory.test.js +200 -0
  32. package/dist/__tests__/workspace-paths.test.d.ts +1 -0
  33. package/dist/__tests__/workspace-paths.test.js +56 -0
  34. package/dist/__tests__/writer.test.d.ts +1 -0
  35. package/dist/__tests__/writer.test.js +94 -0
  36. package/dist/api/auth/crypto.d.ts +4 -0
  37. package/dist/api/auth/crypto.js +54 -0
  38. package/dist/api/middleware/auth.d.ts +12 -0
  39. package/dist/api/middleware/auth.js +90 -0
  40. package/dist/api/pagination.d.ts +18 -0
  41. package/dist/api/pagination.js +32 -0
  42. package/dist/api/routes/auth.d.ts +1 -0
  43. package/dist/api/routes/auth.js +130 -0
  44. package/dist/api/routes/chat-completions.d.ts +7 -0
  45. package/dist/api/routes/chat-completions.js +474 -0
  46. package/dist/api/routes/contradictions.d.ts +1 -0
  47. package/dist/api/routes/contradictions.js +28 -0
  48. package/dist/api/routes/evidence.d.ts +1 -0
  49. package/dist/api/routes/evidence.js +59 -0
  50. package/dist/api/routes/governance.d.ts +1 -0
  51. package/dist/api/routes/governance.js +95 -0
  52. package/dist/api/routes/graph.d.ts +1 -0
  53. package/dist/api/routes/graph.js +25 -0
  54. package/dist/api/routes/hooks.d.ts +1 -0
  55. package/dist/api/routes/hooks.js +88 -0
  56. package/dist/api/routes/memories.d.ts +1 -0
  57. package/dist/api/routes/memories.js +92 -0
  58. package/dist/api/routes/persona.d.ts +1 -0
  59. package/dist/api/routes/persona.js +9 -0
  60. package/dist/api/routes/scenes.d.ts +1 -0
  61. package/dist/api/routes/scenes.js +35 -0
  62. package/dist/api/routes/skills.d.ts +1 -0
  63. package/dist/api/routes/skills.js +14 -0
  64. package/dist/api/routes/stats.d.ts +1 -0
  65. package/dist/api/routes/stats.js +8 -0
  66. package/dist/api/routes/users.d.ts +1 -0
  67. package/dist/api/routes/users.js +82 -0
  68. package/dist/api/routes/working.d.ts +1 -0
  69. package/dist/api/routes/working.js +88 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +492 -0
  72. package/dist/integrations/claude-code.d.ts +12 -0
  73. package/dist/integrations/claude-code.js +35 -0
  74. package/dist/integrations/codex.d.ts +12 -0
  75. package/dist/integrations/codex.js +34 -0
  76. package/dist/integrations/generic-mcp.d.ts +52 -0
  77. package/dist/integrations/generic-mcp.js +118 -0
  78. package/dist/loader.d.ts +29 -0
  79. package/dist/loader.js +200 -0
  80. package/dist/memory/capture.d.ts +35 -0
  81. package/dist/memory/capture.js +230 -0
  82. package/dist/memory/config.d.ts +2 -0
  83. package/dist/memory/config.js +3 -0
  84. package/dist/memory/engine.d.ts +203 -0
  85. package/dist/memory/engine.js +626 -0
  86. package/dist/memory/llm-semaphore.d.ts +41 -0
  87. package/dist/memory/llm-semaphore.js +81 -0
  88. package/dist/memory/memory-type-config.d.ts +11 -0
  89. package/dist/memory/memory-type-config.js +65 -0
  90. package/dist/memory/pipeline/cognitive-contradiction.d.ts +7 -0
  91. package/dist/memory/pipeline/cognitive-contradiction.js +59 -0
  92. package/dist/memory/pipeline/cognitive-dedup.d.ts +23 -0
  93. package/dist/memory/pipeline/cognitive-dedup.js +38 -0
  94. package/dist/memory/pipeline/cognitive-extractor.d.ts +21 -0
  95. package/dist/memory/pipeline/cognitive-extractor.js +183 -0
  96. package/dist/memory/pipeline/contextual-focus-builder.d.ts +13 -0
  97. package/dist/memory/pipeline/contextual-focus-builder.js +135 -0
  98. package/dist/memory/pipeline/focus-direction-shift.d.ts +10 -0
  99. package/dist/memory/pipeline/focus-direction-shift.js +27 -0
  100. package/dist/memory/pipeline/graph-builder.d.ts +11 -0
  101. package/dist/memory/pipeline/graph-builder.js +88 -0
  102. package/dist/memory/pipeline/graph-recall.d.ts +13 -0
  103. package/dist/memory/pipeline/graph-recall.js +55 -0
  104. package/dist/memory/pipeline/identity-distiller.d.ts +15 -0
  105. package/dist/memory/pipeline/identity-distiller.js +40 -0
  106. package/dist/memory/pipeline/l1-contradiction.d.ts +7 -0
  107. package/dist/memory/pipeline/l1-contradiction.js +66 -0
  108. package/dist/memory/pipeline/l1-dedup.d.ts +23 -0
  109. package/dist/memory/pipeline/l1-dedup.js +39 -0
  110. package/dist/memory/pipeline/l1-extractor.d.ts +21 -0
  111. package/dist/memory/pipeline/l1-extractor.js +180 -0
  112. package/dist/memory/pipeline/l2-direction-shift.d.ts +10 -0
  113. package/dist/memory/pipeline/l2-direction-shift.js +27 -0
  114. package/dist/memory/pipeline/l2-scene.d.ts +15 -0
  115. package/dist/memory/pipeline/l2-scene.js +140 -0
  116. package/dist/memory/pipeline/l3-distiller.d.ts +15 -0
  117. package/dist/memory/pipeline/l3-distiller.js +40 -0
  118. package/dist/memory/pipeline/neural-spark.d.ts +27 -0
  119. package/dist/memory/pipeline/neural-spark.js +78 -0
  120. package/dist/memory/pipeline/skill-prewarm.d.ts +63 -0
  121. package/dist/memory/pipeline/skill-prewarm.js +127 -0
  122. package/dist/memory/pipeline/task-queue.d.ts +54 -0
  123. package/dist/memory/pipeline/task-queue.js +117 -0
  124. package/dist/memory/prompts/cognitive-contradiction.d.ts +1 -0
  125. package/dist/memory/prompts/cognitive-contradiction.js +25 -0
  126. package/dist/memory/prompts/cognitive-extraction.d.ts +10 -0
  127. package/dist/memory/prompts/cognitive-extraction.js +114 -0
  128. package/dist/memory/prompts/core-identity.d.ts +6 -0
  129. package/dist/memory/prompts/core-identity.js +60 -0
  130. package/dist/memory/prompts/focus-direction-shift.d.ts +5 -0
  131. package/dist/memory/prompts/focus-direction-shift.js +32 -0
  132. package/dist/memory/prompts/focus-scene-cluster.d.ts +2 -0
  133. package/dist/memory/prompts/focus-scene-cluster.js +33 -0
  134. package/dist/memory/prompts/focus-scene.d.ts +7 -0
  135. package/dist/memory/prompts/focus-scene.js +40 -0
  136. package/dist/memory/prompts/graph-extraction-batch.d.ts +14 -0
  137. package/dist/memory/prompts/graph-extraction-batch.js +54 -0
  138. package/dist/memory/prompts/graph-extraction.d.ts +2 -0
  139. package/dist/memory/prompts/graph-extraction.js +53 -0
  140. package/dist/memory/prompts/l1-contradiction-batch.d.ts +16 -0
  141. package/dist/memory/prompts/l1-contradiction-batch.js +47 -0
  142. package/dist/memory/prompts/l1-contradiction.d.ts +1 -0
  143. package/dist/memory/prompts/l1-contradiction.js +25 -0
  144. package/dist/memory/prompts/l1-extraction.d.ts +10 -0
  145. package/dist/memory/prompts/l1-extraction.js +114 -0
  146. package/dist/memory/prompts/l2-direction-shift.d.ts +5 -0
  147. package/dist/memory/prompts/l2-direction-shift.js +32 -0
  148. package/dist/memory/prompts/l2-scene-cluster.d.ts +2 -0
  149. package/dist/memory/prompts/l2-scene-cluster.js +33 -0
  150. package/dist/memory/prompts/l2-scene.d.ts +7 -0
  151. package/dist/memory/prompts/l2-scene.js +40 -0
  152. package/dist/memory/prompts/l3-persona.d.ts +6 -0
  153. package/dist/memory/prompts/l3-persona.js +60 -0
  154. package/dist/memory/recall.d.ts +47 -0
  155. package/dist/memory/recall.js +427 -0
  156. package/dist/memory/redaction.d.ts +1 -0
  157. package/dist/memory/redaction.js +24 -0
  158. package/dist/memory/retry.d.ts +13 -0
  159. package/dist/memory/retry.js +53 -0
  160. package/dist/memory/scheduler.d.ts +9 -0
  161. package/dist/memory/scheduler.js +16 -0
  162. package/dist/memory/skill-hints-loader.d.ts +30 -0
  163. package/dist/memory/skill-hints-loader.js +100 -0
  164. package/dist/memory/store/embedding.d.ts +16 -0
  165. package/dist/memory/store/embedding.js +68 -0
  166. package/dist/memory/store/reranker.d.ts +24 -0
  167. package/dist/memory/store/reranker.js +83 -0
  168. package/dist/memory/store/sqlite.d.ts +167 -0
  169. package/dist/memory/store/sqlite.js +1816 -0
  170. package/dist/memory/store/types.d.ts +101 -0
  171. package/dist/memory/store/types.js +1 -0
  172. package/dist/memory/types.d.ts +207 -0
  173. package/dist/memory/types.js +7 -0
  174. package/dist/memory/validation.d.ts +441 -0
  175. package/dist/memory/validation.js +129 -0
  176. package/dist/memory/working/canvas.d.ts +5 -0
  177. package/dist/memory/working/canvas.js +43 -0
  178. package/dist/memory/working/offload.d.ts +71 -0
  179. package/dist/memory/working/offload.js +211 -0
  180. package/dist/memory/working/step-log.d.ts +16 -0
  181. package/dist/memory/working/step-log.js +35 -0
  182. package/dist/registry.d.ts +34 -0
  183. package/dist/registry.js +305 -0
  184. package/dist/resolver.d.ts +17 -0
  185. package/dist/resolver.js +126 -0
  186. package/dist/scripts/validate-foreign-workspace-path.d.ts +1 -0
  187. package/dist/scripts/validate-foreign-workspace-path.js +39 -0
  188. package/dist/tools/agent_memory_tools.d.ts +485 -0
  189. package/dist/tools/agent_memory_tools.js +793 -0
  190. package/dist/tools/create_skill.d.ts +46 -0
  191. package/dist/tools/create_skill.js +46 -0
  192. package/dist/tools/get_doc.d.ts +21 -0
  193. package/dist/tools/get_doc.js +24 -0
  194. package/dist/tools/get_persona.d.ts +15 -0
  195. package/dist/tools/get_persona.js +20 -0
  196. package/dist/tools/get_reference.d.ts +15 -0
  197. package/dist/tools/get_reference.js +20 -0
  198. package/dist/tools/get_skill.d.ts +34 -0
  199. package/dist/tools/get_skill.js +65 -0
  200. package/dist/tools/get_template_doc.d.ts +21 -0
  201. package/dist/tools/get_template_doc.js +24 -0
  202. package/dist/tools/list_docs.d.ts +15 -0
  203. package/dist/tools/list_docs.js +16 -0
  204. package/dist/tools/list_skills.d.ts +18 -0
  205. package/dist/tools/list_skills.js +17 -0
  206. package/dist/tools/list_template_docs.d.ts +15 -0
  207. package/dist/tools/list_template_docs.js +16 -0
  208. package/dist/tools/memory-engineering.d.ts +225 -0
  209. package/dist/tools/memory-engineering.js +284 -0
  210. package/dist/tools/memory-explain.d.ts +34 -0
  211. package/dist/tools/memory-explain.js +109 -0
  212. package/dist/tools/memory-governance.d.ts +171 -0
  213. package/dist/tools/memory-governance.js +224 -0
  214. package/dist/tools/memory-hooks.d.ts +67 -0
  215. package/dist/tools/memory-hooks.js +102 -0
  216. package/dist/tools/memory-working.d.ts +98 -0
  217. package/dist/tools/memory-working.js +101 -0
  218. package/dist/tools/memory_capture_turn.d.ts +66 -0
  219. package/dist/tools/memory_capture_turn.js +85 -0
  220. package/dist/tools/memory_consolidate.d.ts +55 -0
  221. package/dist/tools/memory_consolidate.js +176 -0
  222. package/dist/tools/memory_contradictions.d.ts +53 -0
  223. package/dist/tools/memory_contradictions.js +52 -0
  224. package/dist/tools/memory_graph_query.d.ts +51 -0
  225. package/dist/tools/memory_graph_query.js +35 -0
  226. package/dist/tools/memory_mark_cited.d.ts +43 -0
  227. package/dist/tools/memory_mark_cited.js +63 -0
  228. package/dist/tools/memory_recall.d.ts +77 -0
  229. package/dist/tools/memory_recall.js +81 -0
  230. package/dist/tools/memory_register_skill_hints.d.ts +49 -0
  231. package/dist/tools/memory_register_skill_hints.js +55 -0
  232. package/dist/tools/memory_resolve_session.d.ts +24 -0
  233. package/dist/tools/memory_resolve_session.js +133 -0
  234. package/dist/tools/memory_search.d.ts +146 -0
  235. package/dist/tools/memory_search.js +84 -0
  236. package/dist/tools/search_skills.d.ts +18 -0
  237. package/dist/tools/search_skills.js +17 -0
  238. package/dist/tools/update_doc.d.ts +24 -0
  239. package/dist/tools/update_doc.js +35 -0
  240. package/dist/tools/update_skill.d.ts +30 -0
  241. package/dist/tools/update_skill.js +80 -0
  242. package/dist/types.d.ts +81 -0
  243. package/dist/types.js +4 -0
  244. package/dist/writer.d.ts +30 -0
  245. package/dist/writer.js +220 -0
  246. package/docs/TEMPLATE ONLY +1 -0
  247. package/docs/api/API.md +64 -0
  248. package/docs/api/security/SECURITY.md +58 -0
  249. package/docs/deployment/DockerDeployment.md +30 -0
  250. package/docs/design/Design.md +59 -0
  251. package/docs/design/themes/apple.md +101 -0
  252. package/docs/design/themes/dieter-grid.md +100 -0
  253. package/docs/design/themes/gallery-white.md +100 -0
  254. package/docs/design/themes/pinterest.md +101 -0
  255. package/docs/design/themes/realty-open-house.md +101 -0
  256. package/docs/design/themes/vodafone.md +101 -0
  257. package/docs/hooks/Hooks.md +30 -0
  258. package/docs/schema/Schema.md +35 -0
  259. package/docs/strategy/ScalingStrategy.md +19 -0
  260. package/package.json +88 -0
  261. package/references/accessibility-checklist.md +160 -0
  262. package/references/orchestration-patterns.md +370 -0
  263. package/references/performance-checklist.md +153 -0
  264. package/references/security-checklist.md +134 -0
  265. package/references/testing-patterns.md +236 -0
  266. package/skills/agent/adr-skill/SKILL.md +299 -0
  267. package/skills/agent/agentic-engineering-workflow/SKILL.md +95 -0
  268. package/skills/agent/bootstrap-skill/SKILL.md +103 -0
  269. package/skills/agent/context-engineering/SKILL.md +307 -0
  270. package/skills/agent/debugging-and-error-recovery/SKILL.md +308 -0
  271. package/skills/agent/developer-growth-analysis/SKILL.md +328 -0
  272. package/skills/agent/doubt-driven-skill/SKILL.md +249 -0
  273. package/skills/agent/handover-skill/SKILL.md +112 -0
  274. package/skills/agent/idea-refine-skill/SKILL.md +185 -0
  275. package/skills/agent/idea-refine-skill/examples.md +238 -0
  276. package/skills/agent/idea-refine-skill/frameworks.md +99 -0
  277. package/skills/agent/idea-refine-skill/refinement-criteria.md +113 -0
  278. package/skills/agent/interview-skill/SKILL.md +226 -0
  279. package/skills/agent/planning-skill/SKILL.md +270 -0
  280. package/skills/agent/skill-authoring/SKILL.md +189 -0
  281. package/skills/agent/source-driven-skill/SKILL.md +197 -0
  282. package/skills/agent/spec-driven-skill/SKILL.md +221 -0
  283. package/skills/agent/sync-skill/SKILL.md +92 -0
  284. package/skills/agent/using-agent-skills/SKILL.md +189 -0
  285. package/skills/api/a11y-skill/SKILL.md +88 -0
  286. package/skills/api/api-skill/SKILL.md +123 -0
  287. package/skills/api/auth-skill/SKILL.md +80 -0
  288. package/skills/api/debug-skill/SKILL.md +535 -0
  289. package/skills/api/performance-skill/SKILL.md +100 -0
  290. package/skills/api/testing-skill/SKILL.md +100 -0
  291. package/skills/codebase/code-review-and-quality/SKILL.md +228 -0
  292. package/skills/codebase/code-simplification/SKILL.md +352 -0
  293. package/skills/codebase/code-structure-cleanup/SKILL.md +142 -0
  294. package/skills/codebase/concerns-skill/SKILL.md +89 -0
  295. package/skills/codebase/conventions-skill/SKILL.md +95 -0
  296. package/skills/codebase/doc-management-skill/SKILL.md +47 -0
  297. package/skills/codebase/git-workflow-skill/SKILL.md +312 -0
  298. package/skills/communication/1-3-1-rule/SKILL.md +120 -0
  299. package/skills/design/brutalist-skill/SKILL.md +131 -0
  300. package/skills/design/concept-diagrams/SKILL.md +387 -0
  301. package/skills/design/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
  302. package/skills/design/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
  303. package/skills/design/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
  304. package/skills/design/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
  305. package/skills/design/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
  306. package/skills/design/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
  307. package/skills/design/concept-diagrams/examples/electricity-grid-flow.md +182 -0
  308. package/skills/design/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
  309. package/skills/design/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
  310. package/skills/design/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
  311. package/skills/design/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
  312. package/skills/design/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
  313. package/skills/design/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
  314. package/skills/design/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
  315. package/skills/design/concept-diagrams/examples/wind-turbine-structure.md +338 -0
  316. package/skills/design/concept-diagrams/references/dashboard-patterns.md +43 -0
  317. package/skills/design/concept-diagrams/references/infrastructure-patterns.md +144 -0
  318. package/skills/design/concept-diagrams/references/physical-shape-cookbook.md +42 -0
  319. package/skills/design/concept-diagrams/templates/template.html +174 -0
  320. package/skills/design/gpt-tasteskill/SKILL.md +114 -0
  321. package/skills/design/minimalist-skill/SKILL.md +116 -0
  322. package/skills/design/output-skill/SKILL.md +87 -0
  323. package/skills/design/redesign-skill/SKILL.md +213 -0
  324. package/skills/design/soft-skill/SKILL.md +132 -0
  325. package/skills/design/stitch-skill/EXAMPLE.md +121 -0
  326. package/skills/design/stitch-skill/SKILL.md +222 -0
  327. package/skills/design/taste-skill/SKILL.md +269 -0
  328. package/skills/devops/ci-cd-skill/SKILL.md +402 -0
  329. package/skills/devops/docker-skill/SKILL.md +297 -0
  330. package/skills/devops/domain-skill/SKILL.md +234 -0
  331. package/skills/lifecycle/changelog-generator/SKILL.md +135 -0
  332. package/skills/lifecycle/incremental-skill/SKILL.md +257 -0
  333. package/skills/lifecycle/migration-skill/SKILL.md +218 -0
  334. package/skills/lifecycle/shipping-skill/SKILL.md +321 -0
  335. package/skills/memory/agent-memory/SKILL.md +122 -0
  336. package/skills/qa/browser-testing-skill/SKILL.md +314 -0
  337. package/skills/ux/adversarial-ux-skill/SKILL.md +168 -0
@@ -0,0 +1,427 @@
1
+ import { expandRecallWithGraph } from "./pipeline/graph-recall.js";
2
+ import { detectPrewarmSkills, buildPrewarmBlock } from "./pipeline/skill-prewarm.js";
3
+ import { detectTaskIntent, extractFilePathHints, getMemoryTypeConfig } from "./memory-type-config.js";
4
+ import { randomUUID } from "node:crypto";
5
+ import { NeuralSparkEngine } from "./pipeline/neural-spark.js";
6
+ function effectivePriority(memory) {
7
+ const halfLife = getMemoryTypeConfig(memory.type).halfLifeDays;
8
+ const ageMs = Date.now() - new Date(memory.created_time).getTime();
9
+ const ageDays = ageMs / 86_400_000;
10
+ let base = memory.priority;
11
+ if (halfLife) {
12
+ const decayFactor = Math.pow(0.5, ageDays / halfLife);
13
+ base = memory.priority * decayFactor;
14
+ }
15
+ const citationBoost = Math.min((memory.citation_count ?? 0) * 0.05, 0.30);
16
+ // Freshness boost: anything captured in the last 24h gets a small lift so
17
+ // brand-new facts surface even before they've been cited. Linear ramp from
18
+ // 1.15× at age 0 to 1.0× at age 1d.
19
+ const freshness = ageDays <= 1 ? 1 + 0.15 * (1 - ageDays) : 1;
20
+ return base * (1 + citationBoost) * freshness;
21
+ }
22
+ function applyFilters(records, filters) {
23
+ if (!filters)
24
+ return records;
25
+ const afterMs = filters.capturedAfter ? new Date(filters.capturedAfter).getTime() : undefined;
26
+ const beforeMs = filters.capturedBefore ? new Date(filters.capturedBefore).getTime() : undefined;
27
+ const types = filters.types && filters.types.length > 0 ? new Set(filters.types) : undefined;
28
+ const scenes = filters.scenes && filters.scenes.length > 0 ? new Set(filters.scenes) : undefined;
29
+ return records.filter((r) => {
30
+ if (types && !types.has(r.type))
31
+ return false;
32
+ if (scenes && (!r.scene_name || !scenes.has(r.scene_name)))
33
+ return false;
34
+ if (filters.skillTag && r.skill_tag !== filters.skillTag)
35
+ return false;
36
+ if (filters.minPriority !== undefined && r.priority < filters.minPriority)
37
+ return false;
38
+ if (afterMs !== undefined || beforeMs !== undefined) {
39
+ const created = r.created_time ? new Date(r.created_time).getTime() : NaN;
40
+ if (Number.isNaN(created))
41
+ return false;
42
+ if (afterMs !== undefined && created < afterMs)
43
+ return false;
44
+ if (beforeMs !== undefined && created > beforeMs)
45
+ return false;
46
+ }
47
+ return true;
48
+ });
49
+ }
50
+ export class MemoryRecallPipeline {
51
+ store;
52
+ embeddingService;
53
+ rerankerService;
54
+ constructor(store, embeddingService, rerankerService) {
55
+ this.store = store;
56
+ this.embeddingService = embeddingService;
57
+ this.rerankerService = rerankerService;
58
+ }
59
+ async recall(params) {
60
+ const startTime = Date.now();
61
+ const { userId, sessionKey, query, activeSkill, filters } = params;
62
+ const intent = detectTaskIntent(query);
63
+ // 1. FTS5 BM25 search (Top 15)
64
+ const ftsResultsRaw = this.store.searchCognitiveFts(userId, query, 15);
65
+ const filePathResultsRaw = this.expandWithFilePathMatches(userId, query);
66
+ // 2. Vector search (Top 15, if enabled)
67
+ let vecResultsRaw = [];
68
+ if (this.embeddingService.isReady()) {
69
+ try {
70
+ const queryVec = await this.embeddingService.embed(query);
71
+ vecResultsRaw = this.store.searchCognitiveVec(userId, queryVec, 15);
72
+ }
73
+ catch (e) {
74
+ console.error("[BrainRouter] Vector search skipped during recall:", e.message);
75
+ }
76
+ }
77
+ // Filter the three candidate streams BEFORE RRF so the rank is computed
78
+ // on the actually-relevant pool, not a filtered subset of an unfiltered
79
+ // rank (which would bias scores toward records that happen to be in the
80
+ // top-15 globally even if irrelevant to the filter).
81
+ const ftsResults = applyFilters(ftsResultsRaw, filters);
82
+ const vecResults = applyFilters(vecResultsRaw, filters);
83
+ const filePathResults = applyFilters(filePathResultsRaw, filters);
84
+ if (ftsResults.length === 0 && vecResults.length === 0 && filePathResults.length === 0) {
85
+ const emptyStrategy = this.embeddingService.isReady() ? "hybrid-empty" : "keyword-empty";
86
+ const durationMs = Date.now() - startTime;
87
+ const recallExplanation = {
88
+ ftsHits: 0,
89
+ vecHits: 0,
90
+ filePathHits: 0,
91
+ rrfTopScore: 0,
92
+ intentDetected: intent,
93
+ typeBoosts: {},
94
+ skillBoostApplied: false,
95
+ rerankerUsed: false,
96
+ graphExpansion: false,
97
+ citationBoosts: {},
98
+ durationMs,
99
+ rerankerCandidates: 0,
100
+ scoredRecords: [],
101
+ };
102
+ if (!params.explain) {
103
+ this.writeRecallOp(userId, sessionKey, query, emptyStrategy, 0, durationMs, recallExplanation);
104
+ }
105
+ return { recallStrategy: emptyStrategy, recallExplanation };
106
+ }
107
+ // 3. RRF Merge (Reciprocal Rank Fusion)
108
+ const rrfMap = new Map();
109
+ ftsResults.forEach((r, idx) => {
110
+ const rank = idx + 1;
111
+ rrfMap.set(r.record_id, { record: r, rrfScore: 1 / (60 + rank) });
112
+ });
113
+ vecResults.forEach((r, idx) => {
114
+ const rank = idx + 1;
115
+ const existing = rrfMap.get(r.record_id);
116
+ if (existing) {
117
+ existing.rrfScore += 1 / (60 + rank);
118
+ }
119
+ else {
120
+ rrfMap.set(r.record_id, { record: r, rrfScore: 1 / (60 + rank) });
121
+ }
122
+ });
123
+ filePathResults.forEach((r, idx) => {
124
+ const existing = rrfMap.get(r.record_id);
125
+ const filePathScore = 1 / (45 + idx + 1);
126
+ if (existing) {
127
+ existing.rrfScore += filePathScore;
128
+ }
129
+ else {
130
+ rrfMap.set(r.record_id, { record: r, rrfScore: filePathScore });
131
+ }
132
+ });
133
+ const rrfValues = Array.from(rrfMap.values()).map(v => v.rrfScore);
134
+ const rrfTopScore = rrfValues.length > 0 ? Math.max(...rrfValues) : 0;
135
+ // 4. Combine RRF with Decay + Skill boost
136
+ const typeBoosts = {};
137
+ const citationBoosts = {};
138
+ let skillBoostApplied = false;
139
+ const scoredResults = Array.from(rrfMap.values()).map(({ record, rrfScore }) => {
140
+ const baseScore = rrfScore * 30;
141
+ const priorityScore = (effectivePriority(record) / 100);
142
+ let finalScore = (baseScore * 0.7) + (priorityScore * 0.3);
143
+ if (activeSkill && record.skill_tag === activeSkill) {
144
+ finalScore *= 1.2;
145
+ skillBoostApplied = true;
146
+ }
147
+ const intentMultiplier = getMemoryTypeConfig(record.type).intentAffinity[intent] ?? 1;
148
+ if (intentMultiplier !== 1) {
149
+ typeBoosts[record.type] = intentMultiplier;
150
+ }
151
+ finalScore *= intentMultiplier;
152
+ const citationCount = record.citation_count ?? 0;
153
+ const citBoost = Math.min(citationCount * 0.05, 0.30);
154
+ if (citBoost > 0) {
155
+ citationBoosts[record.record_id] = citBoost;
156
+ }
157
+ return { record, score: finalScore };
158
+ });
159
+ // --- Neural Sparks & Spreading Activation ---
160
+ const maxScore = scoredResults.length > 0 ? Math.max(...scoredResults.map(r => r.score)) : 1.0;
161
+ const initialNodes = scoredResults.map(r => ({
162
+ id: r.record.record_id,
163
+ potential: maxScore > 0 ? r.score / maxScore : 0.0,
164
+ fired: false
165
+ }));
166
+ const sparkEngine = new NeuralSparkEngine(this.store);
167
+ const propagatedNodes = sparkEngine.propagateSparks(userId, initialNodes);
168
+ const propagatedMap = new Map(propagatedNodes.map(n => [n.id, n]));
169
+ const existingIds = new Set(scoredResults.map(r => r.record.record_id));
170
+ // Carry the full {id, potential, fired, type, preview, sceneName} so the
171
+ // UI can render a human-friendly label instead of the opaque record id.
172
+ // Track seen ids so we don't double-list a node that appears as both a
173
+ // seed and a propagation target.
174
+ const sparkedNodes = [];
175
+ const sparkedSeen = new Set();
176
+ const previewFromContent = (content) => {
177
+ const text = (content ?? "").toString().trim();
178
+ if (!text)
179
+ return undefined;
180
+ const oneLine = text.replace(/\s+/g, " ");
181
+ // Keep the preview short — the UI renders a compact pill, anything
182
+ // longer than ~70 chars wraps awkwardly even with ellipsis fallback.
183
+ return oneLine.length > 70 ? `${oneLine.slice(0, 67)}…` : oneLine;
184
+ };
185
+ const pushNode = (node, meta) => {
186
+ if (!node.id || sparkedSeen.has(node.id))
187
+ return;
188
+ sparkedSeen.add(node.id);
189
+ sparkedNodes.push({
190
+ id: node.id,
191
+ potential: Math.max(0, Math.min(1, Number(node.potential) || 0)),
192
+ fired: Boolean(node.fired),
193
+ type: meta?.type,
194
+ preview: meta?.preview,
195
+ sceneName: meta?.sceneName,
196
+ });
197
+ };
198
+ const sparkScoredResults = [];
199
+ for (const scored of scoredResults) {
200
+ const propNode = propagatedMap.get(scored.record.record_id);
201
+ if (propNode) {
202
+ const newScore = Math.max(scored.score, propNode.potential * maxScore);
203
+ // Every initial-seed node belongs in the trace, fired or not — the
204
+ // sub-threshold pills carry useful "we considered this but it didn't
205
+ // spread" signal.
206
+ pushNode(propNode, {
207
+ type: scored.record.type,
208
+ preview: previewFromContent(scored.record.content),
209
+ sceneName: scored.record.scene_name,
210
+ });
211
+ sparkScoredResults.push({
212
+ record: scored.record,
213
+ score: propNode.fired ? newScore * 1.5 : newScore,
214
+ fired: propNode.fired
215
+ });
216
+ }
217
+ else {
218
+ sparkScoredResults.push(scored);
219
+ }
220
+ }
221
+ // Pull in connected memories that were excited above the firing threshold
222
+ for (const propNode of propagatedNodes) {
223
+ if (propNode.fired && !existingIds.has(propNode.id)) {
224
+ const record = this.store.getMemoryById(userId, propNode.id);
225
+ if (record) {
226
+ pushNode(propNode, {
227
+ type: record.type,
228
+ preview: previewFromContent(record.content),
229
+ sceneName: record.sceneName,
230
+ });
231
+ const formattedRecord = {
232
+ record_id: record.id,
233
+ user_id: record.userId,
234
+ content: record.content,
235
+ type: record.type,
236
+ priority: record.priority,
237
+ scene_name: record.sceneName,
238
+ skill_tag: record.skillTag,
239
+ session_key: record.sessionKey,
240
+ timestamp_str: record.timestampStr,
241
+ created_time: record.createdTime,
242
+ citation_count: record.citationCount
243
+ };
244
+ const baseScore = propNode.potential * maxScore;
245
+ sparkScoredResults.push({
246
+ record: formattedRecord,
247
+ score: baseScore * 1.5,
248
+ fired: true
249
+ });
250
+ }
251
+ }
252
+ }
253
+ sparkScoredResults.sort((a, b) => b.score - a.score);
254
+ let topResults = sparkScoredResults.slice(0, 5);
255
+ // Stage 3 — Reranker (Top 20 from RRF + Sparks)
256
+ const rerankCandidates = sparkScoredResults.slice(0, 20);
257
+ let usedReranker = false;
258
+ if (this.rerankerService.isReady()) {
259
+ try {
260
+ const documents = rerankCandidates.map(r => r.record.content);
261
+ const ranked = await this.rerankerService.rerank({
262
+ query,
263
+ documents,
264
+ topN: this.rerankerService.getTopN()
265
+ });
266
+ topResults = ranked.map(r => rerankCandidates[r.index]);
267
+ usedReranker = true;
268
+ }
269
+ catch (e) {
270
+ console.error("[BrainRouter] Reranker failed during recall, falling back to RRF:", e.message);
271
+ }
272
+ }
273
+ // 5. Format for context
274
+ const memoryLines = topResults.map(({ record }) => {
275
+ const tag = record.scene_name ? `${record.type}|${record.scene_name}` : record.type;
276
+ let line = `- [${tag}] ${record.content}`;
277
+ if (record.skill_tag) {
278
+ line += ` (skill: ${record.skill_tag})`;
279
+ }
280
+ return line;
281
+ });
282
+ const prependContext = `<relevant-memories>\n The following memories are relevant to this query. Reference only if helpful:\n\n ${memoryLines.join("\n ")}\n</relevant-memories>`;
283
+ // Build appendSystemContext with Contextual Focus Navigation + tools guide
284
+ const topScenes = this.store.getTopContextualFocus(userId, 3);
285
+ let appendSystemContext = "";
286
+ if (topScenes.length > 0) {
287
+ const sceneNav = topScenes
288
+ .map(s => ` - ${s.sceneName} (heat: ${s.heatScore.toFixed(0)})`)
289
+ .join("\n");
290
+ appendSystemContext += `<scene-navigation>\n Recent focus scenes:\n${sceneNav}\n</scene-navigation>\n\n`;
291
+ }
292
+ appendSystemContext += `<memory-tools-guide>
293
+ Use memory_search to retrieve more specific memories.
294
+ Use memory_contradictions to review unresolved conflicts.
295
+ Max 3 memory tool calls per turn.
296
+ </memory-tools-guide>`;
297
+ // Graph context expansion (2-hop BFS from matched entities)
298
+ const graphContext = expandRecallWithGraph({
299
+ topCognitiveResults: topResults.map(r => r.record),
300
+ query,
301
+ userId,
302
+ activeSkill,
303
+ store: this.store
304
+ });
305
+ const hasGraphExpansion = !!graphContext;
306
+ if (graphContext) {
307
+ appendSystemContext += `\n${graphContext}`;
308
+ }
309
+ if (process.env.BRAINROUTER_PREWARM_ENABLED === "true") {
310
+ try {
311
+ const prewarmResults = detectPrewarmSkills({
312
+ userId,
313
+ store: this.store,
314
+ excludeSkill: activeSkill,
315
+ });
316
+ const prewarmBlock = buildPrewarmBlock(prewarmResults);
317
+ if (prewarmBlock) {
318
+ appendSystemContext += `\n${prewarmBlock}`;
319
+ }
320
+ }
321
+ catch (e) {
322
+ console.error("[BrainRouter] Skill pre-warming skipped:", e.message);
323
+ }
324
+ }
325
+ const recalledCognitiveMemories = topResults.map(r => ({
326
+ content: r.record.content,
327
+ score: r.score,
328
+ type: r.record.type,
329
+ recordId: r.record.record_id,
330
+ skillTag: r.record.skill_tag
331
+ }));
332
+ const recallStrategy = vecResults.length > 0
333
+ ? (usedReranker ? "hybrid+rerank" : "hybrid")
334
+ : (usedReranker ? "keyword+rerank" : (filePathResults.length > 0 ? "keyword+file" : "keyword"));
335
+ const durationMs = Date.now() - startTime;
336
+ const recallExplanation = {
337
+ ftsHits: ftsResults.length,
338
+ vecHits: vecResults.length,
339
+ filePathHits: filePathResults.length,
340
+ rrfTopScore,
341
+ intentDetected: intent,
342
+ typeBoosts,
343
+ skillBoostApplied,
344
+ rerankerUsed: usedReranker,
345
+ graphExpansion: hasGraphExpansion,
346
+ citationBoosts,
347
+ durationMs,
348
+ rerankerCandidates: rerankCandidates.length,
349
+ scoredRecords: topResults.map(r => ({
350
+ recordId: r.record.record_id,
351
+ finalScore: r.score,
352
+ type: r.record.type,
353
+ })),
354
+ sparkedNodes,
355
+ };
356
+ if (!params.explain) {
357
+ this.writeRecallOp(userId, sessionKey, query, recallStrategy, topResults.length, durationMs, recallExplanation);
358
+ }
359
+ return {
360
+ prependContext,
361
+ appendSystemContext,
362
+ recalledCognitiveMemories,
363
+ recallStrategy,
364
+ activeFocusName: topScenes[0]?.sceneName,
365
+ recallExplanation,
366
+ };
367
+ }
368
+ async explainRecall(params) {
369
+ return this.recall({ ...params, explain: true });
370
+ }
371
+ writeRecallOp(userId, sessionKey, query, strategy, hitCount, durationMs, explanation) {
372
+ try {
373
+ this.store.insertOperation({
374
+ id: randomUUID(),
375
+ userId,
376
+ recordId: null,
377
+ operation: "recall",
378
+ actor: "agent",
379
+ sessionKey,
380
+ reason: "",
381
+ createdAt: new Date().toISOString(),
382
+ metadata: {
383
+ query: query.slice(0, 500),
384
+ strategy,
385
+ hitCount,
386
+ durationMs,
387
+ ftsHits: explanation?.ftsHits ?? 0,
388
+ vecHits: explanation?.vecHits ?? 0,
389
+ intentDetected: explanation?.intentDetected ?? "none",
390
+ rerankerUsed: explanation?.rerankerUsed ?? false,
391
+ },
392
+ });
393
+ }
394
+ catch {
395
+ // Audit writes are best-effort
396
+ }
397
+ }
398
+ expandWithFilePathMatches(userId, query) {
399
+ const filePaths = extractFilePathHints(query);
400
+ if (filePaths.length === 0)
401
+ return [];
402
+ const records = new Map();
403
+ for (const filePath of filePaths) {
404
+ for (const record of this.store.getMemoriesByFilePath(userId, filePath, 10)) {
405
+ records.set(record.id, record);
406
+ }
407
+ }
408
+ return Array.from(records.values()).map((record) => ({
409
+ record_id: record.id,
410
+ user_id: record.userId,
411
+ content: record.content,
412
+ type: record.type,
413
+ priority: record.priority,
414
+ scene_name: record.sceneName,
415
+ skill_tag: record.skillTag,
416
+ score: 1,
417
+ timestamp_str: record.timestampStr,
418
+ timestamp_start: record.timestampStart,
419
+ timestamp_end: record.timestampEnd,
420
+ session_key: record.sessionKey,
421
+ session_id: record.sessionId,
422
+ metadata_json: JSON.stringify(record.metadata),
423
+ created_time: record.createdTime,
424
+ citation_count: record.citationCount,
425
+ }));
426
+ }
427
+ }
@@ -0,0 +1 @@
1
+ export declare function redactSensitiveMemoryText(text: string): string;
@@ -0,0 +1,24 @@
1
+ // Each pattern targets a specific secret format.
2
+ // All patterns use the /g flag so String.replace replaces all occurrences per call.
3
+ // The array is module-level (not re-created per call) — safe because .replace() does
4
+ // not mutate lastIndex on string arguments.
5
+ const REDACTION_PATTERNS = [
6
+ // HTTP Authorization: Bearer <token>
7
+ [/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "[REDACTED]"],
8
+ // OpenAI-style secret keys (sk-...)
9
+ [/\bsk-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]"],
10
+ // GitHub personal access tokens (ghp_...)
11
+ [/\bghp_[A-Za-z0-9_]{8,}\b/g, "[REDACTED]"],
12
+ // PEM private key blocks
13
+ [/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, "[REDACTED]"],
14
+ // Database connection strings (Postgres, MongoDB, MySQL, Redis, SQLite).
15
+ [/\b(?:postgres|postgresql|mongodb|mysql|mongodb\+srv|redis|sqlite):\/\/[^:\s]+:[^@\s]+@[^\s]+\b/gi, "[REDACTED_CONN_STR]"],
16
+ // IPv4 addresses can expose infrastructure details.
17
+ [/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, "[REDACTED_IP]"],
18
+ // .env-style assignments: API_KEY=... SECRET=... — require ≥6 chars in value to
19
+ // avoid over-redacting innocuous env vars like RETRY_COUNT=3 or LOG_LEVEL=info.
20
+ [/^[ \t]*[A-Z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*[ \t]*=[ \t]*\S{6,}.*$/gim, "[REDACTED]"],
21
+ ];
22
+ export function redactSensitiveMemoryText(text) {
23
+ return REDACTION_PATTERNS.reduce((value, [pattern, replacement]) => value.replace(pattern, replacement), text);
24
+ }
@@ -0,0 +1,13 @@
1
+ export interface ExternalApiRetryOptions {
2
+ label: string;
3
+ maxRetries?: number;
4
+ baseDelayMs?: number;
5
+ sleep?: (ms: number) => Promise<void>;
6
+ }
7
+ export declare class ExternalApiError extends Error {
8
+ readonly status?: number | undefined;
9
+ constructor(message: string, status?: number | undefined);
10
+ }
11
+ export declare function isRetryableExternalError(error: unknown): boolean;
12
+ export declare function retryExternalCall<T>(operation: () => Promise<T>, options: ExternalApiRetryOptions): Promise<T>;
13
+ export declare function fetchWithExternalRetry(input: string | URL | Request, init: RequestInit, options: ExternalApiRetryOptions): Promise<Response>;
@@ -0,0 +1,53 @@
1
+ export class ExternalApiError extends Error {
2
+ status;
3
+ constructor(message, status) {
4
+ super(message);
5
+ this.status = status;
6
+ this.name = "ExternalApiError";
7
+ }
8
+ }
9
+ const DEFAULT_MAX_RETRIES = 3;
10
+ const DEFAULT_BASE_DELAY_MS = 2_000;
11
+ const RETRYABLE_HTTP_STATUSES = new Set([429, 503]);
12
+ function defaultSleep(ms) {
13
+ return new Promise((resolve) => setTimeout(resolve, ms));
14
+ }
15
+ export function isRetryableExternalError(error) {
16
+ if (error instanceof ExternalApiError) {
17
+ return error.status !== undefined && RETRYABLE_HTTP_STATUSES.has(error.status);
18
+ }
19
+ if (error instanceof TypeError) {
20
+ return true;
21
+ }
22
+ return false;
23
+ }
24
+ export async function retryExternalCall(operation, options) {
25
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
26
+ const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
27
+ const sleep = options.sleep ?? defaultSleep;
28
+ let attempt = 0;
29
+ while (true) {
30
+ try {
31
+ return await operation();
32
+ }
33
+ catch (error) {
34
+ if (attempt >= maxRetries || !isRetryableExternalError(error)) {
35
+ throw error;
36
+ }
37
+ const delayMs = baseDelayMs * (2 ** attempt);
38
+ attempt += 1;
39
+ console.error(`[BrainRouter] ${options.label} failed with a retryable error. Retrying in ${delayMs}ms (attempt ${attempt}/${maxRetries}).`);
40
+ await sleep(delayMs);
41
+ }
42
+ }
43
+ }
44
+ export async function fetchWithExternalRetry(input, init, options) {
45
+ return retryExternalCall(async () => {
46
+ const response = await fetch(input, init);
47
+ if (RETRYABLE_HTTP_STATUSES.has(response.status)) {
48
+ response.body?.cancel().catch(() => undefined);
49
+ throw new ExternalApiError(`${options.label} failed with HTTP ${response.status} ${response.statusText}`, response.status);
50
+ }
51
+ return response;
52
+ }, options);
53
+ }
@@ -0,0 +1,9 @@
1
+ /** Focus distillation fires every N new Cognitive extractions per user (default: 10). */
2
+ export declare const FOCUS_TRIGGER_EVERY_N: number;
3
+ /** Identity distillation fires every N new Cognitive extractions per user (default: 50). */
4
+ export declare const IDENTITY_TRIGGER_EVERY_N: number;
5
+ /** Focus auto-merge max scenes threshold (default: 20). */
6
+ export declare const MAX_FOCUS_SCENES: number;
7
+ import type { SchedulerState } from "@kinqs/brainrouter-types";
8
+ export declare function shouldRunFocusDistill(state: SchedulerState): boolean;
9
+ export declare function shouldRunIdentityDistill(state: SchedulerState): boolean;
@@ -0,0 +1,16 @@
1
+ // ============================
2
+ // Scheduler — N-turn trigger evaluator
3
+ // ============================
4
+ // Stateless logic only. State is persisted in the DB via SqliteMemoryStore.
5
+ /** Focus distillation fires every N new Cognitive extractions per user (default: 10). */
6
+ export const FOCUS_TRIGGER_EVERY_N = parseInt(process.env.BRAINROUTER_FOCUS_TRIGGER_N ?? "10", 10);
7
+ /** Identity distillation fires every N new Cognitive extractions per user (default: 50). */
8
+ export const IDENTITY_TRIGGER_EVERY_N = parseInt(process.env.BRAINROUTER_IDENTITY_TRIGGER_N ?? "50", 10);
9
+ /** Focus auto-merge max scenes threshold (default: 20). */
10
+ export const MAX_FOCUS_SCENES = parseInt(process.env.BRAINROUTER_MAX_FOCUS_SCENES ?? "20", 10);
11
+ export function shouldRunFocusDistill(state) {
12
+ return state.cognitiveCountSinceLastFocus >= FOCUS_TRIGGER_EVERY_N;
13
+ }
14
+ export function shouldRunIdentityDistill(state) {
15
+ return state.cognitiveCountSinceLastIdentity >= IDENTITY_TRIGGER_EVERY_N;
16
+ }
@@ -0,0 +1,30 @@
1
+ export interface SkillFrontmatter {
2
+ name?: string;
3
+ description?: string;
4
+ memory_hints?: string;
5
+ [key: string]: unknown;
6
+ }
7
+ /**
8
+ * Parse YAML frontmatter from a SKILL.md file.
9
+ * Uses a simple, zero-dependency regex parser that handles the common
10
+ * key: value and key: | (block scalar) patterns used in BrainRouter skills.
11
+ */
12
+ export declare function parseSkillFrontmatter(content: string): SkillFrontmatter;
13
+ /**
14
+ * Load memory_hints from a SKILL.md file.
15
+ * Returns null if the file doesn't exist or has no memory_hints field.
16
+ */
17
+ export declare function loadSkillHints(skillMdPath: string): {
18
+ name: string;
19
+ hints: string;
20
+ } | null;
21
+ /**
22
+ * Scan a directory tree for SKILL.md files that contain memory_hints.
23
+ * Returns an array of { skillDir, name, hints } for each skill found.
24
+ */
25
+ export declare function scanSkillsForHints(rootDir: string): Array<{
26
+ skillDir: string;
27
+ name: string;
28
+ hints: string;
29
+ filePath: string;
30
+ }>;
@@ -0,0 +1,100 @@
1
+ import { readFileSync, existsSync, readdirSync } from "node:fs";
2
+ /**
3
+ * Parse YAML frontmatter from a SKILL.md file.
4
+ * Uses a simple, zero-dependency regex parser that handles the common
5
+ * key: value and key: | (block scalar) patterns used in BrainRouter skills.
6
+ */
7
+ export function parseSkillFrontmatter(content) {
8
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9
+ if (!fmMatch)
10
+ return {};
11
+ const yaml = fmMatch[1];
12
+ const result = {};
13
+ // Parse block scalars (key: |\n line1\n line2) and simple key: value lines
14
+ const lines = yaml.split(/\r?\n/);
15
+ let i = 0;
16
+ while (i < lines.length) {
17
+ const line = lines[i];
18
+ // Detect block scalar: "key: |"
19
+ const blockMatch = line.match(/^(\w[\w_-]*):\s*\|(.*)$/);
20
+ if (blockMatch) {
21
+ const key = blockMatch[0].split(":")[0].trim();
22
+ const blockLines = [];
23
+ i++;
24
+ // Collect indented lines
25
+ while (i < lines.length && (lines[i].startsWith(" ") || lines[i].startsWith("\t") || lines[i] === "")) {
26
+ blockLines.push(lines[i].replace(/^ /, "").replace(/^\t/, ""));
27
+ i++;
28
+ }
29
+ result[key] = blockLines.join("\n").trimEnd();
30
+ continue;
31
+ }
32
+ // Simple key: value
33
+ const kvMatch = line.match(/^(\w[\w_-]*):\s*(.*)$/);
34
+ if (kvMatch) {
35
+ const key = kvMatch[1];
36
+ const val = kvMatch[2].trim().replace(/^["']|["']$/g, ""); // strip optional quotes
37
+ result[key] = val;
38
+ }
39
+ i++;
40
+ }
41
+ return result;
42
+ }
43
+ /**
44
+ * Load memory_hints from a SKILL.md file.
45
+ * Returns null if the file doesn't exist or has no memory_hints field.
46
+ */
47
+ export function loadSkillHints(skillMdPath) {
48
+ if (!existsSync(skillMdPath))
49
+ return null;
50
+ let content;
51
+ try {
52
+ content = readFileSync(skillMdPath, "utf-8");
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ const fm = parseSkillFrontmatter(content);
58
+ if (!fm.memory_hints || typeof fm.memory_hints !== "string" || !fm.memory_hints.trim()) {
59
+ return null;
60
+ }
61
+ const skillName = typeof fm.name === "string" ? fm.name : "";
62
+ return {
63
+ name: skillName,
64
+ hints: fm.memory_hints.trim()
65
+ };
66
+ }
67
+ /**
68
+ * Scan a directory tree for SKILL.md files that contain memory_hints.
69
+ * Returns an array of { skillDir, name, hints } for each skill found.
70
+ */
71
+ export function scanSkillsForHints(rootDir) {
72
+ const results = [];
73
+ function walk(dir) {
74
+ try {
75
+ const entries = readdirSync(dir, { withFileTypes: true });
76
+ for (const entry of entries) {
77
+ if (entry.isDirectory()) {
78
+ walk(`${dir}/${entry.name}`);
79
+ }
80
+ else if (entry.name === "SKILL.md") {
81
+ const filePath = `${dir}/${entry.name}`;
82
+ const loaded = loadSkillHints(filePath);
83
+ if (loaded) {
84
+ results.push({
85
+ skillDir: dir,
86
+ name: loaded.name,
87
+ hints: loaded.hints,
88
+ filePath
89
+ });
90
+ }
91
+ }
92
+ }
93
+ }
94
+ catch {
95
+ return;
96
+ }
97
+ }
98
+ walk(rootDir);
99
+ return results;
100
+ }