@mseep/core 3.0.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 (312) hide show
  1. package/CHANGELOG.md +285 -0
  2. package/LICENSE +21 -0
  3. package/README.ja.md +14 -0
  4. package/README.ko.md +14 -0
  5. package/README.md +227 -0
  6. package/README.pt-BR.md +14 -0
  7. package/README.skills.md +50 -0
  8. package/README.uk.md +14 -0
  9. package/README.zh-CN.md +14 -0
  10. package/bin/booklib-mcp.js +458 -0
  11. package/bin/booklib.js +2394 -0
  12. package/bin/skills.cjs +1292 -0
  13. package/community/registry.json +1616 -0
  14. package/hooks/hooks.json +52 -0
  15. package/hooks/posttooluse-capture.mjs +67 -0
  16. package/hooks/posttooluse-contradict.mjs +76 -0
  17. package/hooks/posttooluse-imports.mjs +67 -0
  18. package/hooks/pretooluse-inject.mjs +82 -0
  19. package/hooks/suggest.js +153 -0
  20. package/lib/agent-detector.js +96 -0
  21. package/lib/config-loader.js +39 -0
  22. package/lib/conflict-resolver.js +148 -0
  23. package/lib/connectors/context7.js +167 -0
  24. package/lib/connectors/github.js +223 -0
  25. package/lib/connectors/local.js +120 -0
  26. package/lib/connectors/notion.js +436 -0
  27. package/lib/connectors/web.js +134 -0
  28. package/lib/context-builder.js +574 -0
  29. package/lib/discovery-engine.js +298 -0
  30. package/lib/doctor/hook-installer.js +83 -0
  31. package/lib/doctor/usage-tracker.js +87 -0
  32. package/lib/engine/auditor.js +103 -0
  33. package/lib/engine/auto-linker.js +177 -0
  34. package/lib/engine/bm25-index.js +178 -0
  35. package/lib/engine/capture.js +120 -0
  36. package/lib/engine/context-map.js +641 -0
  37. package/lib/engine/corrections.js +194 -0
  38. package/lib/engine/decision-checker.js +203 -0
  39. package/lib/engine/doctor.js +207 -0
  40. package/lib/engine/embedding-provider.js +72 -0
  41. package/lib/engine/gap-detector.js +138 -0
  42. package/lib/engine/gap-resolver.js +135 -0
  43. package/lib/engine/graph-injector.js +137 -0
  44. package/lib/engine/graph-search.js +183 -0
  45. package/lib/engine/graph.js +170 -0
  46. package/lib/engine/handoff.js +411 -0
  47. package/lib/engine/import-checker.js +249 -0
  48. package/lib/engine/import-parser.js +145 -0
  49. package/lib/engine/indexer.js +334 -0
  50. package/lib/engine/lookup-priority.js +15 -0
  51. package/lib/engine/parser.js +257 -0
  52. package/lib/engine/principle-extractor.js +116 -0
  53. package/lib/engine/project-analyzer.js +353 -0
  54. package/lib/engine/query-expander.js +42 -0
  55. package/lib/engine/reasoning-modes.js +353 -0
  56. package/lib/engine/registries.js +524 -0
  57. package/lib/engine/reranker.js +45 -0
  58. package/lib/engine/rrf.js +59 -0
  59. package/lib/engine/scanner.js +151 -0
  60. package/lib/engine/searcher.js +223 -0
  61. package/lib/engine/session-coordinator.js +291 -0
  62. package/lib/engine/session-manager.js +375 -0
  63. package/lib/engine/source-detector.js +240 -0
  64. package/lib/engine/source-manager.js +142 -0
  65. package/lib/engine/structured-response.js +47 -0
  66. package/lib/engine/synthesis-templates.js +364 -0
  67. package/lib/installer.js +70 -0
  68. package/lib/instinct-block.js +21 -0
  69. package/lib/mcp-config-writer.js +107 -0
  70. package/lib/paths.js +62 -0
  71. package/lib/project-initializer.js +856 -0
  72. package/lib/registry/skills.js +102 -0
  73. package/lib/registry-searcher.js +107 -0
  74. package/lib/rules/rules-manager.js +169 -0
  75. package/lib/skill-fetcher.js +333 -0
  76. package/lib/well-known-builder.js +74 -0
  77. package/lib/wizard/index.js +1389 -0
  78. package/lib/wizard/integration-detector.js +41 -0
  79. package/lib/wizard/project-detector.js +146 -0
  80. package/lib/wizard/prompt.js +221 -0
  81. package/lib/wizard/registry-embeddings.js +107 -0
  82. package/lib/wizard/skill-recommender.js +69 -0
  83. package/package.json +70 -0
  84. package/skills/animation-at-work/SKILL.md +270 -0
  85. package/skills/animation-at-work/assets/example_asset.txt +1 -0
  86. package/skills/animation-at-work/evals/evals.json +44 -0
  87. package/skills/animation-at-work/evals/results.json +13 -0
  88. package/skills/animation-at-work/examples/after.md +64 -0
  89. package/skills/animation-at-work/examples/before.md +35 -0
  90. package/skills/animation-at-work/references/api_reference.md +369 -0
  91. package/skills/animation-at-work/references/review-checklist.md +79 -0
  92. package/skills/animation-at-work/scripts/audit_animations.py +295 -0
  93. package/skills/animation-at-work/scripts/example.py +1 -0
  94. package/skills/booklib-mcp-guide/SKILL.md +129 -0
  95. package/skills/booklib-mcp-guide/evals/evals.json +37 -0
  96. package/skills/booklib-mcp-guide/examples/after.md +34 -0
  97. package/skills/booklib-mcp-guide/examples/before.md +27 -0
  98. package/skills/booklib-mcp-guide/references/tool-catalog.md +9 -0
  99. package/skills/clean-code-reviewer/SKILL.md +444 -0
  100. package/skills/clean-code-reviewer/audit.json +35 -0
  101. package/skills/clean-code-reviewer/evals/evals.json +185 -0
  102. package/skills/clean-code-reviewer/evals/results.json +13 -0
  103. package/skills/clean-code-reviewer/examples/after.md +48 -0
  104. package/skills/clean-code-reviewer/examples/before.md +33 -0
  105. package/skills/clean-code-reviewer/references/api_reference.md +158 -0
  106. package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
  107. package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
  108. package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
  109. package/skills/data-intensive-patterns/SKILL.md +267 -0
  110. package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
  111. package/skills/data-intensive-patterns/evals/evals.json +54 -0
  112. package/skills/data-intensive-patterns/evals/results.json +13 -0
  113. package/skills/data-intensive-patterns/examples/after.md +61 -0
  114. package/skills/data-intensive-patterns/examples/before.md +38 -0
  115. package/skills/data-intensive-patterns/references/api_reference.md +34 -0
  116. package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
  117. package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
  118. package/skills/data-intensive-patterns/scripts/adr.py +213 -0
  119. package/skills/data-intensive-patterns/scripts/example.py +1 -0
  120. package/skills/data-pipelines/SKILL.md +259 -0
  121. package/skills/data-pipelines/assets/example_asset.txt +1 -0
  122. package/skills/data-pipelines/evals/evals.json +45 -0
  123. package/skills/data-pipelines/evals/results.json +13 -0
  124. package/skills/data-pipelines/examples/after.md +97 -0
  125. package/skills/data-pipelines/examples/before.md +37 -0
  126. package/skills/data-pipelines/references/api_reference.md +301 -0
  127. package/skills/data-pipelines/references/review-checklist.md +181 -0
  128. package/skills/data-pipelines/scripts/example.py +1 -0
  129. package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
  130. package/skills/design-patterns/SKILL.md +271 -0
  131. package/skills/design-patterns/assets/example_asset.txt +1 -0
  132. package/skills/design-patterns/evals/evals.json +46 -0
  133. package/skills/design-patterns/evals/results.json +13 -0
  134. package/skills/design-patterns/examples/after.md +52 -0
  135. package/skills/design-patterns/examples/before.md +29 -0
  136. package/skills/design-patterns/references/api_reference.md +1 -0
  137. package/skills/design-patterns/references/patterns-catalog.md +726 -0
  138. package/skills/design-patterns/references/review-checklist.md +173 -0
  139. package/skills/design-patterns/scripts/example.py +1 -0
  140. package/skills/design-patterns/scripts/scaffold.py +807 -0
  141. package/skills/domain-driven-design/SKILL.md +142 -0
  142. package/skills/domain-driven-design/assets/example_asset.txt +1 -0
  143. package/skills/domain-driven-design/evals/evals.json +48 -0
  144. package/skills/domain-driven-design/evals/results.json +13 -0
  145. package/skills/domain-driven-design/examples/after.md +80 -0
  146. package/skills/domain-driven-design/examples/before.md +43 -0
  147. package/skills/domain-driven-design/references/api_reference.md +1 -0
  148. package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
  149. package/skills/domain-driven-design/references/review-checklist.md +158 -0
  150. package/skills/domain-driven-design/scripts/example.py +1 -0
  151. package/skills/domain-driven-design/scripts/scaffold.py +421 -0
  152. package/skills/effective-java/SKILL.md +227 -0
  153. package/skills/effective-java/assets/example_asset.txt +1 -0
  154. package/skills/effective-java/evals/evals.json +46 -0
  155. package/skills/effective-java/evals/results.json +13 -0
  156. package/skills/effective-java/examples/after.md +83 -0
  157. package/skills/effective-java/examples/before.md +37 -0
  158. package/skills/effective-java/references/api_reference.md +1 -0
  159. package/skills/effective-java/references/items-catalog.md +955 -0
  160. package/skills/effective-java/references/review-checklist.md +216 -0
  161. package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
  162. package/skills/effective-java/scripts/example.py +1 -0
  163. package/skills/effective-kotlin/SKILL.md +271 -0
  164. package/skills/effective-kotlin/assets/example_asset.txt +1 -0
  165. package/skills/effective-kotlin/audit.json +29 -0
  166. package/skills/effective-kotlin/evals/evals.json +45 -0
  167. package/skills/effective-kotlin/evals/results.json +13 -0
  168. package/skills/effective-kotlin/examples/after.md +36 -0
  169. package/skills/effective-kotlin/examples/before.md +38 -0
  170. package/skills/effective-kotlin/references/api_reference.md +1 -0
  171. package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
  172. package/skills/effective-kotlin/references/review-checklist.md +126 -0
  173. package/skills/effective-kotlin/scripts/example.py +1 -0
  174. package/skills/effective-python/SKILL.md +441 -0
  175. package/skills/effective-python/evals/evals.json +44 -0
  176. package/skills/effective-python/evals/results.json +13 -0
  177. package/skills/effective-python/examples/after.md +56 -0
  178. package/skills/effective-python/examples/before.md +40 -0
  179. package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
  180. package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
  181. package/skills/effective-python/ref-03-functions.md +186 -0
  182. package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
  183. package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
  184. package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
  185. package/skills/effective-python/ref-07-concurrency.md +213 -0
  186. package/skills/effective-python/ref-08-robustness-performance.md +248 -0
  187. package/skills/effective-python/ref-09-testing-debugging.md +253 -0
  188. package/skills/effective-python/ref-10-collaboration.md +175 -0
  189. package/skills/effective-python/references/api_reference.md +218 -0
  190. package/skills/effective-python/references/practices-catalog.md +483 -0
  191. package/skills/effective-python/references/review-checklist.md +190 -0
  192. package/skills/effective-python/scripts/lint.py +173 -0
  193. package/skills/effective-typescript/SKILL.md +262 -0
  194. package/skills/effective-typescript/audit.json +29 -0
  195. package/skills/effective-typescript/evals/evals.json +37 -0
  196. package/skills/effective-typescript/evals/results.json +13 -0
  197. package/skills/effective-typescript/examples/after.md +70 -0
  198. package/skills/effective-typescript/examples/before.md +47 -0
  199. package/skills/effective-typescript/references/api_reference.md +118 -0
  200. package/skills/effective-typescript/references/practices-catalog.md +371 -0
  201. package/skills/effective-typescript/scripts/review.py +169 -0
  202. package/skills/kotlin-in-action/SKILL.md +261 -0
  203. package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
  204. package/skills/kotlin-in-action/evals/evals.json +43 -0
  205. package/skills/kotlin-in-action/evals/results.json +13 -0
  206. package/skills/kotlin-in-action/examples/after.md +53 -0
  207. package/skills/kotlin-in-action/examples/before.md +39 -0
  208. package/skills/kotlin-in-action/references/api_reference.md +1 -0
  209. package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
  210. package/skills/kotlin-in-action/references/review-checklist.md +204 -0
  211. package/skills/kotlin-in-action/scripts/example.py +1 -0
  212. package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
  213. package/skills/lean-startup/SKILL.md +160 -0
  214. package/skills/lean-startup/assets/example_asset.txt +1 -0
  215. package/skills/lean-startup/evals/evals.json +43 -0
  216. package/skills/lean-startup/evals/results.json +13 -0
  217. package/skills/lean-startup/examples/after.md +80 -0
  218. package/skills/lean-startup/examples/before.md +34 -0
  219. package/skills/lean-startup/references/api_reference.md +319 -0
  220. package/skills/lean-startup/references/review-checklist.md +137 -0
  221. package/skills/lean-startup/scripts/example.py +1 -0
  222. package/skills/lean-startup/scripts/new_experiment.py +286 -0
  223. package/skills/microservices-patterns/SKILL.md +384 -0
  224. package/skills/microservices-patterns/evals/evals.json +45 -0
  225. package/skills/microservices-patterns/evals/results.json +13 -0
  226. package/skills/microservices-patterns/examples/after.md +69 -0
  227. package/skills/microservices-patterns/examples/before.md +40 -0
  228. package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
  229. package/skills/microservices-patterns/references/review-checklist.md +169 -0
  230. package/skills/microservices-patterns/scripts/new_service.py +583 -0
  231. package/skills/programming-with-rust/SKILL.md +209 -0
  232. package/skills/programming-with-rust/evals/evals.json +37 -0
  233. package/skills/programming-with-rust/evals/results.json +13 -0
  234. package/skills/programming-with-rust/examples/after.md +107 -0
  235. package/skills/programming-with-rust/examples/before.md +59 -0
  236. package/skills/programming-with-rust/references/api_reference.md +152 -0
  237. package/skills/programming-with-rust/references/practices-catalog.md +335 -0
  238. package/skills/programming-with-rust/scripts/review.py +142 -0
  239. package/skills/refactoring-ui/SKILL.md +362 -0
  240. package/skills/refactoring-ui/assets/example_asset.txt +1 -0
  241. package/skills/refactoring-ui/evals/evals.json +45 -0
  242. package/skills/refactoring-ui/evals/results.json +13 -0
  243. package/skills/refactoring-ui/examples/after.md +85 -0
  244. package/skills/refactoring-ui/examples/before.md +58 -0
  245. package/skills/refactoring-ui/references/api_reference.md +355 -0
  246. package/skills/refactoring-ui/references/review-checklist.md +114 -0
  247. package/skills/refactoring-ui/scripts/audit_css.py +250 -0
  248. package/skills/refactoring-ui/scripts/example.py +1 -0
  249. package/skills/rust-in-action/SKILL.md +350 -0
  250. package/skills/rust-in-action/evals/evals.json +38 -0
  251. package/skills/rust-in-action/evals/results.json +13 -0
  252. package/skills/rust-in-action/examples/after.md +156 -0
  253. package/skills/rust-in-action/examples/before.md +56 -0
  254. package/skills/rust-in-action/references/practices-catalog.md +346 -0
  255. package/skills/rust-in-action/scripts/review.py +147 -0
  256. package/skills/skill-router/SKILL.md +186 -0
  257. package/skills/skill-router/evals/evals.json +38 -0
  258. package/skills/skill-router/evals/results.json +13 -0
  259. package/skills/skill-router/examples/after.md +63 -0
  260. package/skills/skill-router/examples/before.md +39 -0
  261. package/skills/skill-router/references/api_reference.md +24 -0
  262. package/skills/skill-router/references/routing-heuristics.md +89 -0
  263. package/skills/skill-router/references/skill-catalog.md +174 -0
  264. package/skills/skill-router/scripts/route.py +266 -0
  265. package/skills/spring-boot-in-action/SKILL.md +340 -0
  266. package/skills/spring-boot-in-action/evals/evals.json +39 -0
  267. package/skills/spring-boot-in-action/evals/results.json +13 -0
  268. package/skills/spring-boot-in-action/examples/after.md +185 -0
  269. package/skills/spring-boot-in-action/examples/before.md +84 -0
  270. package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
  271. package/skills/spring-boot-in-action/scripts/review.py +184 -0
  272. package/skills/storytelling-with-data/SKILL.md +241 -0
  273. package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
  274. package/skills/storytelling-with-data/evals/evals.json +47 -0
  275. package/skills/storytelling-with-data/evals/results.json +13 -0
  276. package/skills/storytelling-with-data/examples/after.md +50 -0
  277. package/skills/storytelling-with-data/examples/before.md +33 -0
  278. package/skills/storytelling-with-data/references/api_reference.md +379 -0
  279. package/skills/storytelling-with-data/references/review-checklist.md +111 -0
  280. package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
  281. package/skills/storytelling-with-data/scripts/example.py +1 -0
  282. package/skills/system-design-interview/SKILL.md +233 -0
  283. package/skills/system-design-interview/assets/example_asset.txt +1 -0
  284. package/skills/system-design-interview/evals/evals.json +46 -0
  285. package/skills/system-design-interview/evals/results.json +13 -0
  286. package/skills/system-design-interview/examples/after.md +94 -0
  287. package/skills/system-design-interview/examples/before.md +27 -0
  288. package/skills/system-design-interview/references/api_reference.md +582 -0
  289. package/skills/system-design-interview/references/review-checklist.md +201 -0
  290. package/skills/system-design-interview/scripts/example.py +1 -0
  291. package/skills/system-design-interview/scripts/new_design.py +421 -0
  292. package/skills/using-asyncio-python/SKILL.md +290 -0
  293. package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
  294. package/skills/using-asyncio-python/evals/evals.json +43 -0
  295. package/skills/using-asyncio-python/evals/results.json +13 -0
  296. package/skills/using-asyncio-python/examples/after.md +68 -0
  297. package/skills/using-asyncio-python/examples/before.md +39 -0
  298. package/skills/using-asyncio-python/references/api_reference.md +267 -0
  299. package/skills/using-asyncio-python/references/review-checklist.md +149 -0
  300. package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
  301. package/skills/using-asyncio-python/scripts/example.py +1 -0
  302. package/skills/web-scraping-python/SKILL.md +280 -0
  303. package/skills/web-scraping-python/assets/example_asset.txt +1 -0
  304. package/skills/web-scraping-python/evals/evals.json +46 -0
  305. package/skills/web-scraping-python/evals/results.json +13 -0
  306. package/skills/web-scraping-python/examples/after.md +109 -0
  307. package/skills/web-scraping-python/examples/before.md +40 -0
  308. package/skills/web-scraping-python/references/api_reference.md +393 -0
  309. package/skills/web-scraping-python/references/review-checklist.md +163 -0
  310. package/skills/web-scraping-python/scripts/example.py +1 -0
  311. package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
  312. package/skills/writing-plans/audit.json +34 -0
@@ -0,0 +1,353 @@
1
+ // A/B Test Findings (April 2026):
2
+ //
3
+ // Three processing modes tested against codegen quality:
4
+ // fast (regex extraction): No model curation. Returns raw chunks.
5
+ // local (Qwen 2.5 14B): Fixed by atomic chunking (was broken with blob chunks).
6
+ // api (Haiku): Best for retrieval quality (+60%). For codegen, the
7
+ // "synthesize a skill" pattern outperforms raw filtering.
8
+ //
9
+ // Key finding: the prompt matters more than the mode.
10
+ // "Be strict" prompt → over-filters, loses specific details (SameSite, @Pattern)
11
+ // "Synthesize a skill" prompt → produces structured Principles/Anti-Patterns/Code
12
+ // that the codegen model follows more consistently.
13
+ //
14
+ // Best codegen results: search → top 15 small chunks → synthesize task-specific skill
15
+ // See: webshop-test/codegen-ab.js, webshop-test/codegen-ui-test.js
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { buildStructuredResponse } from './structured-response.js';
20
+ import { getSynthesisPrompt } from './synthesis-templates.js';
21
+
22
+ /**
23
+ * Groups search results by source and formats them as clean bullet points.
24
+ * Strips frontmatter and avoids mid-sentence cuts.
25
+ *
26
+ * @param {Array} results - raw search results with text and metadata
27
+ * @returns {string} formatted text grouped by source
28
+ */
29
+ export function formatResultsForModel(results) {
30
+ if (!results || results.length === 0) return '(no results)';
31
+
32
+ // Group by source + parentTitle. Option to join siblings controlled by joinSiblings param.
33
+ const groups = new Map();
34
+ for (const r of results) {
35
+ const name = r.metadata?.name ?? r.metadata?.title ?? r.metadata?.id ?? 'unknown';
36
+ const parentTitle = r.metadata?.parentTitle ?? '';
37
+ const key = parentTitle ? `${name}|||${parentTitle}` : name;
38
+
39
+ if (!groups.has(key)) {
40
+ groups.set(key, { name, parentTitle, bullets: [] });
41
+ }
42
+
43
+ const text = (r.text ?? '').trim();
44
+ if (text) {
45
+ const cleaned = text.replace(/^---[\s\S]*?---\s*/m, '').trim();
46
+ if (cleaned) groups.get(key).bullets.push(cleaned);
47
+ }
48
+ }
49
+
50
+ let index = 0;
51
+ const sections = [];
52
+ for (const { name, parentTitle, bullets } of groups.values()) {
53
+ if (bullets.length === 0) continue;
54
+ index++;
55
+ const title = parentTitle
56
+ ? `[${index}] Source: ${name} — "${parentTitle}"`
57
+ : `[${index}] Source: ${name}`;
58
+ const body = bullets.map(b => ` • ${b}`).join('\n');
59
+ sections.push(`${title}\n${body}`);
60
+ }
61
+
62
+ return sections.length > 0 ? sections.join('\n\n') : '(no results)';
63
+ }
64
+
65
+ /**
66
+ * Loads API key from .env file if not already in process.env.
67
+ * Checks project-local .env first, then home directory .env.
68
+ */
69
+ function loadEnvKey() {
70
+ if (process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY) return;
71
+
72
+ const candidates = [
73
+ path.join(process.cwd(), '.env'),
74
+ path.join(process.env.HOME ?? '', '.env'),
75
+ ];
76
+
77
+ for (const envPath of candidates) {
78
+ if (!fs.existsSync(envPath)) continue;
79
+ try {
80
+ const content = fs.readFileSync(envPath, 'utf8');
81
+ for (const line of content.split('\n')) {
82
+ const match = line.match(/^(ANTHROPIC_API_KEY|OPENAI_API_KEY)=(.+)$/);
83
+ if (match) {
84
+ process.env[match[1]] = match[2].trim();
85
+ }
86
+ }
87
+ } catch { /* best-effort */ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Process search results through the configured reasoning mode.
93
+ *
94
+ * @param {string} query - the search query
95
+ * @param {Array} results - raw search results
96
+ * @param {string} mode - 'fast' | 'local' | 'api'
97
+ * @param {object} opts
98
+ * @param {number} [opts.maxPrinciples=3]
99
+ * @param {string} [opts.file]
100
+ * @param {string} [opts.apiKey] - for API mode
101
+ * @param {string} [opts.apiModel] - for API mode
102
+ * @param {string} [opts.apiProvider] - 'anthropic' | 'openai'
103
+ * @param {string} [opts.ollamaModel] - for local mode
104
+ * @returns {Promise<object>} structured response
105
+ */
106
+ export async function processResults(query, results, mode = 'fast', opts = {}) {
107
+ // Auto-load .env keys if not in environment
108
+ if (mode === 'api' && !opts.apiKey) {
109
+ loadEnvKey();
110
+ if (!opts.apiKey) {
111
+ opts.apiKey = process.env.ANTHROPIC_API_KEY ?? process.env.OPENAI_API_KEY;
112
+ opts.apiProvider = process.env.ANTHROPIC_API_KEY ? 'anthropic' : 'openai';
113
+ }
114
+ }
115
+
116
+ switch (mode) {
117
+ case 'local':
118
+ return processLocal(query, results, opts);
119
+ case 'api':
120
+ return processAPI(query, results, opts);
121
+ case 'fast':
122
+ default:
123
+ return buildStructuredResponse(query, results, opts);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Local model processing via Ollama.
129
+ * Sends search results to a local Ollama model for relevance reasoning.
130
+ * Falls back to fast mode if Ollama is not running.
131
+ */
132
+ async function processLocal(query, results, opts = {}) {
133
+ const { maxPrinciples = 3, file, ollamaModel = 'phi3', sourceType } = opts;
134
+
135
+ if (!results || results.length === 0) {
136
+ return { query, file: file ?? null, results: [], mode: 'local', note: 'No relevant knowledge found.' };
137
+ }
138
+
139
+ // Check if Ollama is running
140
+ const ollamaUrl = process.env.OLLAMA_HOST ?? 'http://localhost:11434';
141
+
142
+ try {
143
+ const healthCheck = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(2000) });
144
+ if (!healthCheck.ok) throw new Error('Ollama not responding');
145
+ } catch {
146
+ // Ollama not running — fall back to fast mode
147
+ const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
148
+ fallback.mode = 'local-fallback';
149
+ fallback.note = 'Local AI configured but Ollama not running. Using fast mode instead.\n' +
150
+ 'To fix: start Ollama with: ollama serve\n' +
151
+ 'Or switch to API mode: set "reasoning": "api" in booklib.config.json.';
152
+ return fallback;
153
+ }
154
+
155
+ try {
156
+ const resultsText = formatResultsForModel(results);
157
+
158
+ const prompt = sourceType
159
+ ? getSynthesisPrompt(sourceType, { query, file, results: resultsText })
160
+ : `You are a knowledge curator. Given a query and search results, synthesize actionable guidance. Keep specific details — code examples, annotations, configuration values. Drop only completely unrelated results.
161
+
162
+ Query: "${query}"
163
+ ${file ? `File: ${file}` : ''}
164
+
165
+ Results:
166
+ ${resultsText}
167
+
168
+ Return JSON array: [{"principle": "specific actionable guidance", "context": "why and how to apply", "source": "..."}]
169
+ Include all results that could help. Only drop completely unrelated ones.
170
+ Maximum ${maxPrinciples} entries.`;
171
+
172
+ const res = await fetch(`${ollamaUrl}/api/generate`, {
173
+ method: 'POST',
174
+ headers: { 'Content-Type': 'application/json' },
175
+ body: JSON.stringify({
176
+ model: ollamaModel,
177
+ prompt,
178
+ stream: false,
179
+ options: { temperature: 0.1, num_predict: 1024 },
180
+ }),
181
+ signal: AbortSignal.timeout(30000),
182
+ });
183
+
184
+ if (!res.ok) {
185
+ throw new Error(`Ollama error: ${res.status}`);
186
+ }
187
+
188
+ const data = await res.json();
189
+ const response = data.response ?? '';
190
+
191
+ // DEBUG: log raw Ollama response to stderr
192
+ console.error(`[booklib-debug] Ollama raw response (${ollamaModel}, ${response.length} chars): ${response.slice(0, 500)}`);
193
+
194
+ // Try JSON array first (well-behaved models)
195
+ const jsonMatch = response.match(/\[[\s\S]*\]/);
196
+ if (jsonMatch) {
197
+ try {
198
+ const principles = JSON.parse(jsonMatch[0]).slice(0, maxPrinciples);
199
+ return {
200
+ query, file: file ?? null, results: principles,
201
+ mode: 'local', model: ollamaModel,
202
+ note: `${principles.length} result(s) after local AI reasoning (${ollamaModel}).`,
203
+ };
204
+ } catch { /* JSON parse failed — try markdown fallback */ }
205
+ }
206
+
207
+ // Fallback: parse markdown response (small models often return markdown not JSON)
208
+ if (response.length > 20) {
209
+ const lines = response.split('\n').filter(l => l.trim() && !l.startsWith('#'));
210
+ const principles = lines
211
+ .filter(l => l.startsWith('- ') || l.startsWith('* ') || l.match(/^\d+\./))
212
+ .map(l => l.replace(/^[-*]\s+|^\d+\.\s+/, '').trim())
213
+ .filter(l => l.length > 10)
214
+ .slice(0, maxPrinciples)
215
+ .map(p => ({ principle: p, context: '', source: ollamaModel }));
216
+
217
+ if (principles.length > 0) {
218
+ return {
219
+ query, file: file ?? null, results: principles,
220
+ mode: 'local', model: ollamaModel,
221
+ note: `${principles.length} result(s) synthesized by ${ollamaModel}.`,
222
+ };
223
+ }
224
+ }
225
+
226
+ // Last resort — fast mode
227
+ const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
228
+ fallback.mode = 'local-fallback';
229
+ fallback.note = `Local model response too short. Using fast mode.`;
230
+ return fallback;
231
+
232
+ } catch (err) {
233
+ const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
234
+ fallback.mode = 'local-error';
235
+ fallback.note = `Local reasoning failed: ${err.message}. Using fast mode.`;
236
+ return fallback;
237
+ }
238
+ }
239
+
240
+ /**
241
+ * API processing — sends results to an external LLM for reasoning.
242
+ * Requires an API key (Anthropic or OpenAI).
243
+ *
244
+ * The LLM receives the query + raw results and returns synthesized,
245
+ * relevant principles. This produces the highest quality output.
246
+ */
247
+ async function processAPI(query, results, opts = {}) {
248
+ const { maxPrinciples = 3, file, apiKey, apiModel, apiProvider = 'anthropic', sourceType } = opts;
249
+
250
+ if (!apiKey) {
251
+ // Fall back to fast mode if no API key
252
+ const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
253
+ fallback.mode = 'api-fallback';
254
+ fallback.note = 'API mode configured but no API key found. Using fast mode instead.\n' +
255
+ 'To fix: add your key to .env (echo "ANTHROPIC_API_KEY=sk-ant-..." >> .env)\n' +
256
+ 'Or try local AI: set "reasoning": "local" in booklib.config.json and run Ollama.';
257
+ return fallback;
258
+ }
259
+
260
+ if (!results || results.length === 0) {
261
+ return { query, file: file ?? null, results: [], mode: 'api', note: 'No relevant knowledge found.' };
262
+ }
263
+
264
+ try {
265
+ // Build the prompt for the reasoning model
266
+ const resultsText = formatResultsForModel(results);
267
+
268
+ const prompt = sourceType
269
+ ? getSynthesisPrompt(sourceType, { query, file, results: resultsText })
270
+ : `You are a knowledge curator for a coding assistant. Given a query and search results from a knowledge base, synthesize actionable guidance. Keep specific details — code examples, exact annotations, configuration values, security headers. Drop only results that are completely unrelated to the query.
271
+
272
+ Query: "${query}"
273
+ ${file ? `File being edited: ${file}` : ''}
274
+
275
+ Search results:
276
+ ${resultsText}
277
+
278
+ Return a JSON array. Each entry: {"principle": "the specific actionable guidance", "context": "why this matters and how to apply it", "source": "where this came from"}
279
+ Include ALL results that could help with this task — keep specific details like annotation names, config values, and code patterns. Only drop results that are about a completely different topic.
280
+ Maximum ${maxPrinciples} entries.`;
281
+
282
+ let response;
283
+
284
+ if (apiProvider === 'anthropic') {
285
+ response = await callAnthropic(prompt, apiKey, apiModel ?? 'claude-haiku-4-5-20251001');
286
+ } else {
287
+ response = await callOpenAI(prompt, apiKey, apiModel ?? 'gpt-4o-mini');
288
+ }
289
+
290
+ // Parse LLM response
291
+ const jsonMatch = response.match(/\[[\s\S]*\]/);
292
+ if (jsonMatch) {
293
+ const principles = JSON.parse(jsonMatch[0]).slice(0, maxPrinciples);
294
+ return {
295
+ query,
296
+ file: file ?? null,
297
+ results: principles,
298
+ mode: 'api',
299
+ note: `${principles.length} result(s) after API reasoning (${apiProvider}).`,
300
+ };
301
+ }
302
+
303
+ // Couldn't parse — fall back to fast
304
+ const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
305
+ fallback.mode = 'api-parse-error';
306
+ fallback.note = 'API reasoning returned unparseable response. Using fast mode.';
307
+ return fallback;
308
+
309
+ } catch (err) {
310
+ // API call failed — fall back to fast
311
+ const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
312
+ fallback.mode = 'api-error';
313
+ fallback.note = `API reasoning failed: ${err.message}. Using fast mode.`;
314
+ return fallback;
315
+ }
316
+ }
317
+
318
+ async function callAnthropic(prompt, apiKey, model) {
319
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
320
+ method: 'POST',
321
+ headers: {
322
+ 'Content-Type': 'application/json',
323
+ 'x-api-key': apiKey,
324
+ 'anthropic-version': '2023-06-01',
325
+ },
326
+ body: JSON.stringify({
327
+ model,
328
+ max_tokens: 1024,
329
+ messages: [{ role: 'user', content: prompt }],
330
+ }),
331
+ });
332
+ if (!res.ok) throw new Error(`Anthropic API error: ${res.status}`);
333
+ const data = await res.json();
334
+ return data.content?.[0]?.text ?? '';
335
+ }
336
+
337
+ async function callOpenAI(prompt, apiKey, model) {
338
+ const res = await fetch('https://api.openai.com/v1/chat/completions', {
339
+ method: 'POST',
340
+ headers: {
341
+ 'Content-Type': 'application/json',
342
+ 'Authorization': `Bearer ${apiKey}`,
343
+ },
344
+ body: JSON.stringify({
345
+ model,
346
+ max_tokens: 1024,
347
+ messages: [{ role: 'user', content: prompt }],
348
+ }),
349
+ });
350
+ if (!res.ok) throw new Error(`OpenAI API error: ${res.status}`);
351
+ const data = await res.json();
352
+ return data.choices?.[0]?.message?.content ?? '';
353
+ }