@booklib/core 2.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 (374) hide show
  1. package/.cursor/rules/booklib-standards.mdc +40 -0
  2. package/.gemini/context.md +372 -0
  3. package/AGENTS.md +166 -0
  4. package/CHANGELOG.md +226 -0
  5. package/CLAUDE.md +81 -0
  6. package/CODE_OF_CONDUCT.md +31 -0
  7. package/CONTRIBUTING.md +304 -0
  8. package/LICENSE +21 -0
  9. package/PLAN.md +28 -0
  10. package/README.ja.md +198 -0
  11. package/README.ko.md +198 -0
  12. package/README.md +503 -0
  13. package/README.pt-BR.md +198 -0
  14. package/README.uk.md +241 -0
  15. package/README.zh-CN.md +198 -0
  16. package/SECURITY.md +9 -0
  17. package/agents/architecture-reviewer.md +136 -0
  18. package/agents/booklib-reviewer.md +90 -0
  19. package/agents/data-reviewer.md +107 -0
  20. package/agents/jvm-reviewer.md +146 -0
  21. package/agents/python-reviewer.md +128 -0
  22. package/agents/rust-reviewer.md +115 -0
  23. package/agents/ts-reviewer.md +110 -0
  24. package/agents/ui-reviewer.md +117 -0
  25. package/assets/logo.svg +36 -0
  26. package/bin/booklib-mcp.js +304 -0
  27. package/bin/booklib.js +1705 -0
  28. package/bin/skills.cjs +1292 -0
  29. package/booklib-router.mdc +36 -0
  30. package/booklib.config.json +19 -0
  31. package/commands/animation-at-work.md +10 -0
  32. package/commands/clean-code-reviewer.md +10 -0
  33. package/commands/data-intensive-patterns.md +10 -0
  34. package/commands/data-pipelines.md +10 -0
  35. package/commands/design-patterns.md +10 -0
  36. package/commands/domain-driven-design.md +10 -0
  37. package/commands/effective-java.md +10 -0
  38. package/commands/effective-kotlin.md +10 -0
  39. package/commands/effective-python.md +10 -0
  40. package/commands/effective-typescript.md +10 -0
  41. package/commands/kotlin-in-action.md +10 -0
  42. package/commands/lean-startup.md +10 -0
  43. package/commands/microservices-patterns.md +10 -0
  44. package/commands/programming-with-rust.md +10 -0
  45. package/commands/refactoring-ui.md +10 -0
  46. package/commands/rust-in-action.md +10 -0
  47. package/commands/skill-router.md +10 -0
  48. package/commands/spring-boot-in-action.md +10 -0
  49. package/commands/storytelling-with-data.md +10 -0
  50. package/commands/system-design-interview.md +10 -0
  51. package/commands/using-asyncio-python.md +10 -0
  52. package/commands/web-scraping-python.md +10 -0
  53. package/community/registry.json +1616 -0
  54. package/hooks/hooks.json +23 -0
  55. package/hooks/posttooluse-capture.mjs +67 -0
  56. package/hooks/suggest.js +153 -0
  57. package/lib/agent-behaviors.js +40 -0
  58. package/lib/agent-detector.js +96 -0
  59. package/lib/config-loader.js +39 -0
  60. package/lib/conflict-resolver.js +148 -0
  61. package/lib/context-builder.js +574 -0
  62. package/lib/discovery-engine.js +298 -0
  63. package/lib/doctor/hook-installer.js +83 -0
  64. package/lib/doctor/usage-tracker.js +87 -0
  65. package/lib/engine/ai-features.js +253 -0
  66. package/lib/engine/auditor.js +103 -0
  67. package/lib/engine/bm25-index.js +178 -0
  68. package/lib/engine/capture.js +120 -0
  69. package/lib/engine/corrections.js +198 -0
  70. package/lib/engine/doctor.js +195 -0
  71. package/lib/engine/graph-injector.js +137 -0
  72. package/lib/engine/graph.js +161 -0
  73. package/lib/engine/handoff.js +405 -0
  74. package/lib/engine/indexer.js +242 -0
  75. package/lib/engine/parser.js +53 -0
  76. package/lib/engine/query-expander.js +42 -0
  77. package/lib/engine/reranker.js +40 -0
  78. package/lib/engine/rrf.js +59 -0
  79. package/lib/engine/scanner.js +151 -0
  80. package/lib/engine/searcher.js +139 -0
  81. package/lib/engine/session-coordinator.js +306 -0
  82. package/lib/engine/session-manager.js +429 -0
  83. package/lib/engine/synthesizer.js +70 -0
  84. package/lib/installer.js +70 -0
  85. package/lib/instinct-block.js +33 -0
  86. package/lib/mcp-config-writer.js +88 -0
  87. package/lib/paths.js +57 -0
  88. package/lib/profiles/design.md +19 -0
  89. package/lib/profiles/general.md +16 -0
  90. package/lib/profiles/research-analysis.md +22 -0
  91. package/lib/profiles/software-development.md +23 -0
  92. package/lib/profiles/writing-content.md +19 -0
  93. package/lib/project-initializer.js +916 -0
  94. package/lib/registry/skills.js +102 -0
  95. package/lib/registry-searcher.js +99 -0
  96. package/lib/rules/rules-manager.js +169 -0
  97. package/lib/skill-fetcher.js +333 -0
  98. package/lib/well-known-builder.js +70 -0
  99. package/lib/wizard/index.js +404 -0
  100. package/lib/wizard/integration-detector.js +41 -0
  101. package/lib/wizard/project-detector.js +100 -0
  102. package/lib/wizard/prompt.js +156 -0
  103. package/lib/wizard/registry-embeddings.js +107 -0
  104. package/lib/wizard/skill-recommender.js +69 -0
  105. package/llms-full.txt +254 -0
  106. package/llms.txt +70 -0
  107. package/package.json +45 -0
  108. package/research-reports/2026-04-01-current-architecture.md +160 -0
  109. package/research-reports/IDEAS.md +93 -0
  110. package/rules/common/clean-code.md +42 -0
  111. package/rules/java/effective-java.md +42 -0
  112. package/rules/kotlin/effective-kotlin.md +37 -0
  113. package/rules/python/effective-python.md +38 -0
  114. package/rules/rust/rust.md +37 -0
  115. package/rules/typescript/effective-typescript.md +42 -0
  116. package/scripts/gen-llms-full.mjs +36 -0
  117. package/scripts/gen-og.mjs +142 -0
  118. package/scripts/validate-frontmatter.js +25 -0
  119. package/skills/animation-at-work/SKILL.md +270 -0
  120. package/skills/animation-at-work/assets/example_asset.txt +1 -0
  121. package/skills/animation-at-work/evals/evals.json +44 -0
  122. package/skills/animation-at-work/evals/results.json +13 -0
  123. package/skills/animation-at-work/examples/after.md +64 -0
  124. package/skills/animation-at-work/examples/before.md +35 -0
  125. package/skills/animation-at-work/references/api_reference.md +369 -0
  126. package/skills/animation-at-work/references/review-checklist.md +79 -0
  127. package/skills/animation-at-work/scripts/audit_animations.py +295 -0
  128. package/skills/animation-at-work/scripts/example.py +1 -0
  129. package/skills/clean-code-reviewer/SKILL.md +444 -0
  130. package/skills/clean-code-reviewer/audit.json +35 -0
  131. package/skills/clean-code-reviewer/evals/evals.json +185 -0
  132. package/skills/clean-code-reviewer/evals/results.json +13 -0
  133. package/skills/clean-code-reviewer/examples/after.md +48 -0
  134. package/skills/clean-code-reviewer/examples/before.md +33 -0
  135. package/skills/clean-code-reviewer/references/api_reference.md +158 -0
  136. package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
  137. package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
  138. package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
  139. package/skills/data-intensive-patterns/SKILL.md +267 -0
  140. package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
  141. package/skills/data-intensive-patterns/evals/evals.json +54 -0
  142. package/skills/data-intensive-patterns/evals/results.json +13 -0
  143. package/skills/data-intensive-patterns/examples/after.md +61 -0
  144. package/skills/data-intensive-patterns/examples/before.md +38 -0
  145. package/skills/data-intensive-patterns/references/api_reference.md +34 -0
  146. package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
  147. package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
  148. package/skills/data-intensive-patterns/scripts/adr.py +213 -0
  149. package/skills/data-intensive-patterns/scripts/example.py +1 -0
  150. package/skills/data-pipelines/SKILL.md +259 -0
  151. package/skills/data-pipelines/assets/example_asset.txt +1 -0
  152. package/skills/data-pipelines/evals/evals.json +45 -0
  153. package/skills/data-pipelines/evals/results.json +13 -0
  154. package/skills/data-pipelines/examples/after.md +97 -0
  155. package/skills/data-pipelines/examples/before.md +37 -0
  156. package/skills/data-pipelines/references/api_reference.md +301 -0
  157. package/skills/data-pipelines/references/review-checklist.md +181 -0
  158. package/skills/data-pipelines/scripts/example.py +1 -0
  159. package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
  160. package/skills/design-patterns/SKILL.md +271 -0
  161. package/skills/design-patterns/assets/example_asset.txt +1 -0
  162. package/skills/design-patterns/evals/evals.json +46 -0
  163. package/skills/design-patterns/evals/results.json +13 -0
  164. package/skills/design-patterns/examples/after.md +52 -0
  165. package/skills/design-patterns/examples/before.md +29 -0
  166. package/skills/design-patterns/references/api_reference.md +1 -0
  167. package/skills/design-patterns/references/patterns-catalog.md +726 -0
  168. package/skills/design-patterns/references/review-checklist.md +173 -0
  169. package/skills/design-patterns/scripts/example.py +1 -0
  170. package/skills/design-patterns/scripts/scaffold.py +807 -0
  171. package/skills/domain-driven-design/SKILL.md +142 -0
  172. package/skills/domain-driven-design/assets/example_asset.txt +1 -0
  173. package/skills/domain-driven-design/evals/evals.json +48 -0
  174. package/skills/domain-driven-design/evals/results.json +13 -0
  175. package/skills/domain-driven-design/examples/after.md +80 -0
  176. package/skills/domain-driven-design/examples/before.md +43 -0
  177. package/skills/domain-driven-design/references/api_reference.md +1 -0
  178. package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
  179. package/skills/domain-driven-design/references/review-checklist.md +158 -0
  180. package/skills/domain-driven-design/scripts/example.py +1 -0
  181. package/skills/domain-driven-design/scripts/scaffold.py +421 -0
  182. package/skills/effective-java/SKILL.md +227 -0
  183. package/skills/effective-java/assets/example_asset.txt +1 -0
  184. package/skills/effective-java/evals/evals.json +46 -0
  185. package/skills/effective-java/evals/results.json +13 -0
  186. package/skills/effective-java/examples/after.md +83 -0
  187. package/skills/effective-java/examples/before.md +37 -0
  188. package/skills/effective-java/references/api_reference.md +1 -0
  189. package/skills/effective-java/references/items-catalog.md +955 -0
  190. package/skills/effective-java/references/review-checklist.md +216 -0
  191. package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
  192. package/skills/effective-java/scripts/example.py +1 -0
  193. package/skills/effective-kotlin/SKILL.md +271 -0
  194. package/skills/effective-kotlin/assets/example_asset.txt +1 -0
  195. package/skills/effective-kotlin/audit.json +29 -0
  196. package/skills/effective-kotlin/evals/evals.json +45 -0
  197. package/skills/effective-kotlin/evals/results.json +13 -0
  198. package/skills/effective-kotlin/examples/after.md +36 -0
  199. package/skills/effective-kotlin/examples/before.md +38 -0
  200. package/skills/effective-kotlin/references/api_reference.md +1 -0
  201. package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
  202. package/skills/effective-kotlin/references/review-checklist.md +126 -0
  203. package/skills/effective-kotlin/scripts/example.py +1 -0
  204. package/skills/effective-python/SKILL.md +441 -0
  205. package/skills/effective-python/evals/evals.json +44 -0
  206. package/skills/effective-python/evals/results.json +13 -0
  207. package/skills/effective-python/examples/after.md +56 -0
  208. package/skills/effective-python/examples/before.md +40 -0
  209. package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
  210. package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
  211. package/skills/effective-python/ref-03-functions.md +186 -0
  212. package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
  213. package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
  214. package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
  215. package/skills/effective-python/ref-07-concurrency.md +213 -0
  216. package/skills/effective-python/ref-08-robustness-performance.md +248 -0
  217. package/skills/effective-python/ref-09-testing-debugging.md +253 -0
  218. package/skills/effective-python/ref-10-collaboration.md +175 -0
  219. package/skills/effective-python/references/api_reference.md +218 -0
  220. package/skills/effective-python/references/practices-catalog.md +483 -0
  221. package/skills/effective-python/references/review-checklist.md +190 -0
  222. package/skills/effective-python/scripts/lint.py +173 -0
  223. package/skills/effective-typescript/SKILL.md +262 -0
  224. package/skills/effective-typescript/audit.json +29 -0
  225. package/skills/effective-typescript/evals/evals.json +37 -0
  226. package/skills/effective-typescript/evals/results.json +13 -0
  227. package/skills/effective-typescript/examples/after.md +70 -0
  228. package/skills/effective-typescript/examples/before.md +47 -0
  229. package/skills/effective-typescript/references/api_reference.md +118 -0
  230. package/skills/effective-typescript/references/practices-catalog.md +371 -0
  231. package/skills/effective-typescript/scripts/review.py +169 -0
  232. package/skills/kotlin-in-action/SKILL.md +261 -0
  233. package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
  234. package/skills/kotlin-in-action/evals/evals.json +43 -0
  235. package/skills/kotlin-in-action/evals/results.json +13 -0
  236. package/skills/kotlin-in-action/examples/after.md +53 -0
  237. package/skills/kotlin-in-action/examples/before.md +39 -0
  238. package/skills/kotlin-in-action/references/api_reference.md +1 -0
  239. package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
  240. package/skills/kotlin-in-action/references/review-checklist.md +204 -0
  241. package/skills/kotlin-in-action/scripts/example.py +1 -0
  242. package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
  243. package/skills/lean-startup/SKILL.md +160 -0
  244. package/skills/lean-startup/assets/example_asset.txt +1 -0
  245. package/skills/lean-startup/evals/evals.json +43 -0
  246. package/skills/lean-startup/evals/results.json +13 -0
  247. package/skills/lean-startup/examples/after.md +80 -0
  248. package/skills/lean-startup/examples/before.md +34 -0
  249. package/skills/lean-startup/references/api_reference.md +319 -0
  250. package/skills/lean-startup/references/review-checklist.md +137 -0
  251. package/skills/lean-startup/scripts/example.py +1 -0
  252. package/skills/lean-startup/scripts/new_experiment.py +286 -0
  253. package/skills/microservices-patterns/SKILL.md +384 -0
  254. package/skills/microservices-patterns/evals/evals.json +45 -0
  255. package/skills/microservices-patterns/evals/results.json +13 -0
  256. package/skills/microservices-patterns/examples/after.md +69 -0
  257. package/skills/microservices-patterns/examples/before.md +40 -0
  258. package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
  259. package/skills/microservices-patterns/references/review-checklist.md +169 -0
  260. package/skills/microservices-patterns/scripts/new_service.py +583 -0
  261. package/skills/programming-with-rust/SKILL.md +209 -0
  262. package/skills/programming-with-rust/evals/evals.json +37 -0
  263. package/skills/programming-with-rust/evals/results.json +13 -0
  264. package/skills/programming-with-rust/examples/after.md +107 -0
  265. package/skills/programming-with-rust/examples/before.md +59 -0
  266. package/skills/programming-with-rust/references/api_reference.md +152 -0
  267. package/skills/programming-with-rust/references/practices-catalog.md +335 -0
  268. package/skills/programming-with-rust/scripts/review.py +142 -0
  269. package/skills/refactoring-ui/SKILL.md +362 -0
  270. package/skills/refactoring-ui/assets/example_asset.txt +1 -0
  271. package/skills/refactoring-ui/evals/evals.json +45 -0
  272. package/skills/refactoring-ui/evals/results.json +13 -0
  273. package/skills/refactoring-ui/examples/after.md +85 -0
  274. package/skills/refactoring-ui/examples/before.md +58 -0
  275. package/skills/refactoring-ui/references/api_reference.md +355 -0
  276. package/skills/refactoring-ui/references/review-checklist.md +114 -0
  277. package/skills/refactoring-ui/scripts/audit_css.py +250 -0
  278. package/skills/refactoring-ui/scripts/example.py +1 -0
  279. package/skills/rust-in-action/SKILL.md +350 -0
  280. package/skills/rust-in-action/evals/evals.json +38 -0
  281. package/skills/rust-in-action/evals/results.json +13 -0
  282. package/skills/rust-in-action/examples/after.md +156 -0
  283. package/skills/rust-in-action/examples/before.md +56 -0
  284. package/skills/rust-in-action/references/practices-catalog.md +346 -0
  285. package/skills/rust-in-action/scripts/review.py +147 -0
  286. package/skills/skill-router/SKILL.md +186 -0
  287. package/skills/skill-router/evals/evals.json +38 -0
  288. package/skills/skill-router/evals/results.json +13 -0
  289. package/skills/skill-router/examples/after.md +63 -0
  290. package/skills/skill-router/examples/before.md +39 -0
  291. package/skills/skill-router/references/api_reference.md +24 -0
  292. package/skills/skill-router/references/routing-heuristics.md +89 -0
  293. package/skills/skill-router/references/skill-catalog.md +174 -0
  294. package/skills/skill-router/scripts/route.py +266 -0
  295. package/skills/spring-boot-in-action/SKILL.md +340 -0
  296. package/skills/spring-boot-in-action/evals/evals.json +39 -0
  297. package/skills/spring-boot-in-action/evals/results.json +13 -0
  298. package/skills/spring-boot-in-action/examples/after.md +185 -0
  299. package/skills/spring-boot-in-action/examples/before.md +84 -0
  300. package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
  301. package/skills/spring-boot-in-action/scripts/review.py +184 -0
  302. package/skills/storytelling-with-data/SKILL.md +241 -0
  303. package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
  304. package/skills/storytelling-with-data/evals/evals.json +47 -0
  305. package/skills/storytelling-with-data/evals/results.json +13 -0
  306. package/skills/storytelling-with-data/examples/after.md +50 -0
  307. package/skills/storytelling-with-data/examples/before.md +33 -0
  308. package/skills/storytelling-with-data/references/api_reference.md +379 -0
  309. package/skills/storytelling-with-data/references/review-checklist.md +111 -0
  310. package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
  311. package/skills/storytelling-with-data/scripts/example.py +1 -0
  312. package/skills/system-design-interview/SKILL.md +233 -0
  313. package/skills/system-design-interview/assets/example_asset.txt +1 -0
  314. package/skills/system-design-interview/evals/evals.json +46 -0
  315. package/skills/system-design-interview/evals/results.json +13 -0
  316. package/skills/system-design-interview/examples/after.md +94 -0
  317. package/skills/system-design-interview/examples/before.md +27 -0
  318. package/skills/system-design-interview/references/api_reference.md +582 -0
  319. package/skills/system-design-interview/references/review-checklist.md +201 -0
  320. package/skills/system-design-interview/scripts/example.py +1 -0
  321. package/skills/system-design-interview/scripts/new_design.py +421 -0
  322. package/skills/using-asyncio-python/SKILL.md +290 -0
  323. package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
  324. package/skills/using-asyncio-python/evals/evals.json +43 -0
  325. package/skills/using-asyncio-python/evals/results.json +13 -0
  326. package/skills/using-asyncio-python/examples/after.md +68 -0
  327. package/skills/using-asyncio-python/examples/before.md +39 -0
  328. package/skills/using-asyncio-python/references/api_reference.md +267 -0
  329. package/skills/using-asyncio-python/references/review-checklist.md +149 -0
  330. package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
  331. package/skills/using-asyncio-python/scripts/example.py +1 -0
  332. package/skills/web-scraping-python/SKILL.md +280 -0
  333. package/skills/web-scraping-python/assets/example_asset.txt +1 -0
  334. package/skills/web-scraping-python/evals/evals.json +46 -0
  335. package/skills/web-scraping-python/evals/results.json +13 -0
  336. package/skills/web-scraping-python/examples/after.md +109 -0
  337. package/skills/web-scraping-python/examples/before.md +40 -0
  338. package/skills/web-scraping-python/references/api_reference.md +393 -0
  339. package/skills/web-scraping-python/references/review-checklist.md +163 -0
  340. package/skills/web-scraping-python/scripts/example.py +1 -0
  341. package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
  342. package/skills/writing-plans/audit.json +34 -0
  343. package/tests/agent-detector.test.js +83 -0
  344. package/tests/corrections.test.js +245 -0
  345. package/tests/doctor/hook-installer.test.js +72 -0
  346. package/tests/doctor/usage-tracker.test.js +140 -0
  347. package/tests/engine/benchmark-eval.test.js +31 -0
  348. package/tests/engine/bm25-index.test.js +85 -0
  349. package/tests/engine/capture-command.test.js +35 -0
  350. package/tests/engine/capture.test.js +17 -0
  351. package/tests/engine/graph-augmented-search.test.js +107 -0
  352. package/tests/engine/graph-injector.test.js +44 -0
  353. package/tests/engine/graph.test.js +216 -0
  354. package/tests/engine/hybrid-searcher.test.js +74 -0
  355. package/tests/engine/indexer-bm25.test.js +37 -0
  356. package/tests/engine/mcp-tools.test.js +73 -0
  357. package/tests/engine/project-initializer-mcp.test.js +99 -0
  358. package/tests/engine/query-expander.test.js +36 -0
  359. package/tests/engine/reranker.test.js +51 -0
  360. package/tests/engine/rrf.test.js +49 -0
  361. package/tests/engine/srag-prefix.test.js +47 -0
  362. package/tests/instinct-block.test.js +23 -0
  363. package/tests/mcp-config-writer.test.js +60 -0
  364. package/tests/project-initializer-new-agents.test.js +48 -0
  365. package/tests/rules/rules-manager.test.js +230 -0
  366. package/tests/well-known-builder.test.js +40 -0
  367. package/tests/wizard/integration-detector.test.js +31 -0
  368. package/tests/wizard/project-detector.test.js +51 -0
  369. package/tests/wizard/prompt-session.test.js +61 -0
  370. package/tests/wizard/prompt.test.js +16 -0
  371. package/tests/wizard/registry-embeddings.test.js +35 -0
  372. package/tests/wizard/skill-recommender.test.js +34 -0
  373. package/tests/wizard/slot-count.test.js +25 -0
  374. package/vercel.json +21 -0
@@ -0,0 +1,429 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import os from 'os';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ export class BookLibSessionManager {
9
+ constructor(projectRoot = process.cwd()) {
10
+ this.projectRoot = projectRoot;
11
+ this.sessionsDir = path.join(projectRoot, '.booklib/sessions');
12
+ this.globalSessionsDir = path.join(os.homedir(), '.booklib/sessions');
13
+ this.archiveDir = path.join(this.sessionsDir, '_archive');
14
+ this.templatesDir = path.join(this.sessionsDir, '_templates');
15
+ this.tagsFile = path.join(this.sessionsDir, '_tags.json');
16
+ this.versionsDir = path.join(this.sessionsDir, '_versions');
17
+ this.configFile = path.join(projectRoot, '.booklib/config.json');
18
+ this.hooksDir = path.join(projectRoot, '.git/hooks');
19
+ }
20
+
21
+ findSession(sessionName, options = {}) {
22
+ const { searchGlobal = false } = options;
23
+ const projectSession = path.join(this.sessionsDir, `${sessionName}.md`);
24
+ if (fs.existsSync(projectSession)) {
25
+ return { path: projectSession, scope: 'project' };
26
+ }
27
+ if (searchGlobal || this._getConfig().indexFallback === 'global') {
28
+ if (fs.existsSync(this.globalSessionsDir)) {
29
+ const globalSession = path.join(this.globalSessionsDir, `${sessionName}.md`);
30
+ if (fs.existsSync(globalSession)) {
31
+ return { path: globalSession, scope: 'global' };
32
+ }
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+
38
+ cleanupSessions(options = {}) {
39
+ const { beforeDays = 90, archive = true, dryRun = false } = options;
40
+ if (!fs.existsSync(this.sessionsDir)) return { archived: 0, deleted: 0 };
41
+
42
+ const cutoffTime = Date.now() - (beforeDays * 24 * 60 * 60 * 1000);
43
+ const files = fs.readdirSync(this.sessionsDir)
44
+ .filter(f => f.endsWith('.md') && !f.startsWith('_'));
45
+
46
+ let archived = 0, deleted = 0;
47
+ const result = [];
48
+
49
+ for (const file of files) {
50
+ const filePath = path.join(this.sessionsDir, file);
51
+ const stat = fs.statSync(filePath);
52
+
53
+ if (stat.mtime.getTime() < cutoffTime) {
54
+ const action = archive ? 'archive' : 'delete';
55
+ result.push({ file, action, mtime: stat.mtime });
56
+
57
+ if (!dryRun) {
58
+ if (archive) {
59
+ this._archiveFile(filePath);
60
+ archived++;
61
+ } else {
62
+ fs.unlinkSync(filePath);
63
+ deleted++;
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ return { archived, deleted, preview: result };
70
+ }
71
+
72
+ diffSessions(session1Name, session2Name) {
73
+ const s1 = this.findSession(session1Name);
74
+ const s2 = this.findSession(session2Name);
75
+
76
+ if (!s1 || !s2) {
77
+ return { error: 'Session not found' };
78
+ }
79
+
80
+ const data1 = this._parseSession(fs.readFileSync(s1.path, 'utf8'));
81
+ const data2 = this._parseSession(fs.readFileSync(s2.path, 'utf8'));
82
+
83
+ return {
84
+ session1: session1Name,
85
+ session2: session2Name,
86
+ goal: {
87
+ s1: data1.goal,
88
+ s2: data2.goal,
89
+ changed: data1.goal !== data2.goal
90
+ },
91
+ progress: {
92
+ s1: data1.progress,
93
+ s2: data2.progress,
94
+ changed: data1.progress !== data2.progress
95
+ },
96
+ tasks: {
97
+ s1: data1.pending_tasks,
98
+ s2: data2.pending_tasks,
99
+ conflicts: this._detectConflicts(data1.pending_tasks, data2.pending_tasks)
100
+ },
101
+ skills: {
102
+ s1: data1.skills,
103
+ s2: data2.skills,
104
+ added: data2.skills.filter(s => !data1.skills.includes(s)),
105
+ removed: data1.skills.filter(s => !data2.skills.includes(s))
106
+ }
107
+ };
108
+ }
109
+
110
+ installGitHooks() {
111
+ if (!fs.existsSync(this.hooksDir)) {
112
+ fs.mkdirSync(this.hooksDir, { recursive: true });
113
+ }
114
+
115
+ const postCommitPath = path.join(this.hooksDir, 'post-commit');
116
+ const postCommitContent = `#!/bin/bash
117
+ if [ -f ".booklib/handoff.md" ]; then
118
+ node bin/booklib.js sessions auto-save
119
+ fi
120
+ `;
121
+ if (!fs.existsSync(postCommitPath)) {
122
+ fs.writeFileSync(postCommitPath, postCommitContent);
123
+ fs.chmodSync(postCommitPath, 0o755);
124
+ }
125
+
126
+ const postCheckoutPath = path.join(this.hooksDir, 'post-checkout');
127
+ const postCheckoutContent = `#!/bin/bash
128
+ BRANCH=$(git rev-parse --abbrev-ref HEAD)
129
+ if [ -f ".booklib/sessions/\${BRANCH}.md" ]; then
130
+ echo "📝 Restored session for branch: \${BRANCH}"
131
+ fi
132
+ `;
133
+ if (!fs.existsSync(postCheckoutPath)) {
134
+ fs.writeFileSync(postCheckoutPath, postCheckoutContent);
135
+ fs.chmodSync(postCheckoutPath, 0o755);
136
+ }
137
+
138
+ return { installed: ['post-commit', 'post-checkout'] };
139
+ }
140
+
141
+ createFromTemplate(templateName, sessionName, overrides = {}) {
142
+ const templatePath = path.join(this.templatesDir, `${templateName}.md`);
143
+
144
+ if (!fs.existsSync(templatePath)) {
145
+ return { error: `Template not found: ${templateName}` };
146
+ }
147
+
148
+ let content = fs.readFileSync(templatePath, 'utf8');
149
+ content = content.replace(/\{\{session_name\}\}/g, sessionName);
150
+ content = content.replace(/\{\{timestamp\}\}/g, new Date().toISOString());
151
+
152
+ if (overrides.goal) {
153
+ content = content.replace(/goal>.+?<\/goal/, `goal>${overrides.goal}</goal`);
154
+ }
155
+
156
+ const sessionPath = path.join(this.sessionsDir, `${sessionName}.md`);
157
+ fs.writeFileSync(sessionPath, content);
158
+
159
+ return { created: sessionPath, template: templateName };
160
+ }
161
+
162
+ searchSessions(query, options = {}) {
163
+ if (!fs.existsSync(this.sessionsDir)) return [];
164
+
165
+ const files = fs.readdirSync(this.sessionsDir)
166
+ .filter(f => f.endsWith('.md') && !f.startsWith('_'));
167
+
168
+ const results = [];
169
+ const lowerQuery = query.toLowerCase();
170
+
171
+ for (const file of files) {
172
+ const filePath = path.join(this.sessionsDir, file);
173
+ const content = fs.readFileSync(filePath, 'utf8');
174
+ const data = this._parseSession(content);
175
+
176
+ const matches =
177
+ data.goal.toLowerCase().includes(lowerQuery) ||
178
+ data.progress.toLowerCase().includes(lowerQuery) ||
179
+ (data.tags && data.tags.some(t => t.toLowerCase().includes(lowerQuery)));
180
+
181
+ if (matches) {
182
+ results.push({
183
+ name: file.replace('.md', ''),
184
+ goal: data.goal.substring(0, 50),
185
+ tags: data.tags || [],
186
+ timestamp: data.timestamp
187
+ });
188
+ }
189
+ }
190
+
191
+ return results.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
192
+ }
193
+
194
+ tagSession(sessionName, tags, action = 'add') {
195
+ let allTags = this._loadTags();
196
+
197
+ if (!allTags[sessionName]) {
198
+ allTags[sessionName] = [];
199
+ }
200
+
201
+ if (action === 'add') {
202
+ allTags[sessionName] = [...new Set([...allTags[sessionName], ...tags])];
203
+ } else if (action === 'remove') {
204
+ allTags[sessionName] = allTags[sessionName].filter(t => !tags.includes(t));
205
+ }
206
+
207
+ this._saveTags(allTags);
208
+ return { session: sessionName, tags: allTags[sessionName] };
209
+ }
210
+
211
+ generateReport(options = {}) {
212
+ const { since = null, groupBy = null } = options;
213
+
214
+ if (!fs.existsSync(this.sessionsDir)) {
215
+ return { sessions: 0, report: {} };
216
+ }
217
+
218
+ const files = fs.readdirSync(this.sessionsDir)
219
+ .filter(f => f.endsWith('.md') && !f.startsWith('_'));
220
+
221
+ const sessions = [];
222
+ const sinceTime = since ? new Date(since).getTime() : 0;
223
+
224
+ for (const file of files) {
225
+ const filePath = path.join(this.sessionsDir, file);
226
+ const content = fs.readFileSync(filePath, 'utf8');
227
+ const data = this._parseSession(content);
228
+
229
+ if (new Date(data.timestamp).getTime() >= sinceTime) {
230
+ sessions.push(data);
231
+ }
232
+ }
233
+
234
+ const stats = {
235
+ total_sessions: sessions.length,
236
+ total_tasks: sessions.reduce((sum, s) => sum + (s.pending_tasks ? s.pending_tasks.split('\n').length : 0), 0),
237
+ total_skills: [...new Set(sessions.flatMap(s => s.skills || []))].length,
238
+ unique_skills: [...new Set(sessions.flatMap(s => s.skills || []))],
239
+ by_branch: this._groupBy(sessions, 'branch'),
240
+ recent_activity: sessions.slice(0, 5).map(s => ({
241
+ name: s.session_id,
242
+ timestamp: s.timestamp,
243
+ goal: s.goal
244
+ }))
245
+ };
246
+
247
+ return stats;
248
+ }
249
+
250
+ validateSession(sessionName) {
251
+ const session = this.findSession(sessionName);
252
+ if (!session) return { valid: false, errors: ['Session not found'] };
253
+
254
+ const content = fs.readFileSync(session.path, 'utf8');
255
+ const data = this._parseSession(content);
256
+ const errors = [];
257
+ const warnings = [];
258
+
259
+ if (!data.goal || data.goal.length < 10) {
260
+ errors.push('Goal too vague (< 10 characters)');
261
+ }
262
+
263
+ if (!data.progress || data.progress.length < 10) {
264
+ warnings.push('Progress not detailed enough');
265
+ }
266
+
267
+ if (!data.pending_tasks || data.pending_tasks.length === 0) {
268
+ warnings.push('No pending tasks defined');
269
+ }
270
+
271
+ if (!data.skills || data.skills.length === 0) {
272
+ warnings.push('No skills specified');
273
+ }
274
+
275
+ return {
276
+ valid: errors.length === 0,
277
+ errors,
278
+ warnings,
279
+ score: this._calculateSessionScore(data)
280
+ };
281
+ }
282
+
283
+ saveVersion(sessionName, reason = 'auto-save') {
284
+ const session = this.findSession(sessionName);
285
+ if (!session) return { error: 'Session not found' };
286
+
287
+ if (!fs.existsSync(this.versionsDir)) {
288
+ fs.mkdirSync(this.versionsDir, { recursive: true });
289
+ }
290
+
291
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
292
+ const versionDir = path.join(this.versionsDir, sessionName);
293
+ if (!fs.existsSync(versionDir)) {
294
+ fs.mkdirSync(versionDir, { recursive: true });
295
+ }
296
+
297
+ const versionPath = path.join(versionDir, `${timestamp}.md`);
298
+ const content = fs.readFileSync(session.path, 'utf8');
299
+ fs.writeFileSync(versionPath, content);
300
+
301
+ return { saved: versionPath, reason, timestamp };
302
+ }
303
+
304
+ getVersionHistory(sessionName) {
305
+ const versionDir = path.join(this.versionsDir, sessionName);
306
+ if (!fs.existsSync(versionDir)) return [];
307
+
308
+ return fs.readdirSync(versionDir)
309
+ .map(f => path.join(versionDir, f))
310
+ .sort((a, b) => fs.statSync(b).mtime - fs.statSync(a).mtime)
311
+ .map((p, i) => ({
312
+ version: i + 1,
313
+ timestamp: path.basename(p, '.md'),
314
+ path: p
315
+ }));
316
+ }
317
+
318
+ encryptSession(sessionName) {
319
+ const session = this.findSession(sessionName);
320
+ if (!session) return { error: 'Session not found' };
321
+
322
+ const content = fs.readFileSync(session.path, 'utf8');
323
+ const encrypted = Buffer.from(content).toString('base64');
324
+
325
+ const encryptedPath = session.path.replace('.md', '.encrypted');
326
+ fs.writeFileSync(encryptedPath, encrypted);
327
+
328
+ return { encrypted: encryptedPath };
329
+ }
330
+
331
+ decryptSession(sessionName) {
332
+ const encryptedPath = path.join(this.sessionsDir, `${sessionName}.encrypted`);
333
+ if (!fs.existsSync(encryptedPath)) return { error: 'Encrypted session not found' };
334
+
335
+ const content = fs.readFileSync(encryptedPath, 'utf8');
336
+ const decrypted = Buffer.from(content, 'base64').toString('utf8');
337
+
338
+ return { decrypted, preview: decrypted.substring(0, 200) };
339
+ }
340
+
341
+ // PRIVATE METHODS
342
+
343
+ _parseSession(content) {
344
+ const extract = (tag) => {
345
+ const regex = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`);
346
+ const match = content.match(regex);
347
+ return match ? match[1].trim() : '';
348
+ };
349
+
350
+ const skillRegex = /<skill id="([^"]+)"/g;
351
+ const skills = [];
352
+ let match;
353
+ while ((match = skillRegex.exec(content)) !== null) {
354
+ skills.push(match[1]);
355
+ }
356
+
357
+ return {
358
+ timestamp: extract('timestamp'),
359
+ session_id: extract('session_id'),
360
+ branch: extract('branch'),
361
+ goal: extract('goal'),
362
+ progress: extract('progress'),
363
+ pending_tasks: extract('pending_tasks'),
364
+ skills,
365
+ tags: this._extractTags(extract('session_id'))
366
+ };
367
+ }
368
+
369
+ _extractTags(sessionId) {
370
+ const tagsFile = this._loadTags();
371
+ return tagsFile[sessionId] || [];
372
+ }
373
+
374
+ _loadTags() {
375
+ if (fs.existsSync(this.tagsFile)) {
376
+ return JSON.parse(fs.readFileSync(this.tagsFile, 'utf8'));
377
+ }
378
+ return {};
379
+ }
380
+
381
+ _saveTags(tags) {
382
+ if (!fs.existsSync(this.sessionsDir)) {
383
+ fs.mkdirSync(this.sessionsDir, { recursive: true });
384
+ }
385
+ fs.writeFileSync(this.tagsFile, JSON.stringify(tags, null, 2));
386
+ }
387
+
388
+ _archiveFile(filePath) {
389
+ if (!fs.existsSync(this.archiveDir)) {
390
+ fs.mkdirSync(this.archiveDir, { recursive: true });
391
+ }
392
+ const fileName = path.basename(filePath);
393
+ const destPath = path.join(this.archiveDir, fileName);
394
+ fs.copyFileSync(filePath, destPath);
395
+ fs.unlinkSync(filePath);
396
+ }
397
+
398
+ _detectConflicts(tasks1, tasks2) {
399
+ if (!tasks1 || !tasks2) return [];
400
+ const t1Array = tasks1.split('\n');
401
+ const t2Array = tasks2.split('\n');
402
+ return t1Array.filter(t => t2Array.some(t2 => t.toLowerCase() === t2.toLowerCase()));
403
+ }
404
+
405
+ _calculateSessionScore(data) {
406
+ let score = 0;
407
+ if (data.goal && data.goal.length > 10) score += 25;
408
+ if (data.progress && data.progress.length > 20) score += 25;
409
+ if (data.pending_tasks && data.pending_tasks.length > 0) score += 25;
410
+ if (data.skills && data.skills.length > 0) score += 25;
411
+ return score;
412
+ }
413
+
414
+ _groupBy(items, key) {
415
+ return items.reduce((acc, item) => {
416
+ const groupKey = item[key] || 'unknown';
417
+ if (!acc[groupKey]) acc[groupKey] = [];
418
+ acc[groupKey].push(item);
419
+ return acc;
420
+ }, {});
421
+ }
422
+
423
+ _getConfig() {
424
+ if (fs.existsSync(this.configFile)) {
425
+ return JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
426
+ }
427
+ return {};
428
+ }
429
+ }
@@ -0,0 +1,70 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { parseSkillFile } from './parser.js';
4
+ import { resolveBookLibPaths } from '../paths.js';
5
+
6
+ /**
7
+ * Handles the creation of 'Hybrid Skills' by combining wisdom from multiple sources.
8
+ */
9
+ export class BookLibSynthesizer {
10
+ constructor() {}
11
+
12
+ /**
13
+ * Synthesizes multiple skills into a single 'Master Standard' template.
14
+ *
15
+ * @param {Array<string>} skillNames - List of skill folders to combine.
16
+ * @param {string} projectType - Brief description of the project context.
17
+ * @returns {string} - A unified synthesis prompt for the agent.
18
+ */
19
+ async synthesize(skillNames, projectType = 'Universal Project') {
20
+ const skills = [];
21
+
22
+ for (const name of skillNames) {
23
+ const skillPath = path.join(resolveBookLibPaths().skillsPath, name, 'SKILL.md');
24
+ if (fs.existsSync(skillPath)) {
25
+ const content = fs.readFileSync(skillPath, 'utf8');
26
+ const chunks = parseSkillFile(content, skillPath);
27
+ skills.push({ name, chunks });
28
+ }
29
+ }
30
+
31
+ if (skills.length === 0) {
32
+ throw new Error(`No valid skills found for synthesis: ${skillNames.join(', ')}`);
33
+ }
34
+
35
+ let synthesisOutput = `
36
+ # 🧪 BookLib Dynamic Synthesis: ${projectType}
37
+ **Combined Wisdom from**: ${skillNames.join(', ')}
38
+
39
+ ## 🛠 Instructions for the Agent
40
+ You are acting as a Senior Consultant. Your task is to resolve conflicts between these experts and create a unified **Standard Operating Procedure (SOP)** for this project.
41
+
42
+ ### Step 1: Combine Frameworks
43
+ Merge the following principles into a single cohesive workflow. If two authors disagree, prioritize the one most relevant to "${projectType}".
44
+
45
+ ${skills.map(s => {
46
+ const framework = s.chunks.find(c => c.metadata.type === 'framework' || c.metadata.type === 'core_principles')?.text || 'See metadata';
47
+ return `#### From ${s.name}:\n${framework}`;
48
+ }).join('\n\n')}
49
+
50
+ ### Step 2: Unify Pitfalls
51
+ Combine the anti-patterns and pitfalls from all sources into a "Stop List".
52
+
53
+ ${skills.map(s => {
54
+ const pitfalls = s.chunks.find(c => c.metadata.type === 'pitfalls' || c.metadata.type === 'anti_patterns')?.text || 'See metadata';
55
+ return `#### From ${s.name}:\n${pitfalls}`;
56
+ }).join('\n\n')}
57
+
58
+ ### Step 3: Generate the Final SOP
59
+ Write a new \`.cursorrules\` or \`CLAUDE.md\` file that embodies this synthesized wisdom. Ensure it is:
60
+ 1. **Surgical**: Only includes rules relevant to "${projectType}".
61
+ 2. **Actionable**: Provides concrete examples.
62
+ 3. **Cited**: Mentions which author contributed each rule.
63
+
64
+ ---
65
+ > **Synthesized by the BookLib Universal Engine.**
66
+ `;
67
+
68
+ return synthesisOutput;
69
+ }
70
+ }
@@ -0,0 +1,70 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import https from 'https';
4
+ import { SKILL_REGISTRY } from './registry/skills.js';
5
+ import { BookLibIndexer } from './engine/indexer.js';
6
+ import { resolveBookLibPaths } from './paths.js';
7
+
8
+ /**
9
+ * Handles adding new skills from the registry or external URLs.
10
+ */
11
+ export class BookLibInstaller {
12
+ constructor() {
13
+ this.indexer = new BookLibIndexer();
14
+ }
15
+
16
+ async add(skillId) {
17
+ const registryEntry = SKILL_REGISTRY.find(s => s.id === skillId);
18
+ let url = registryEntry ? registryEntry.url : skillId;
19
+
20
+ if (!url.startsWith('http')) {
21
+ throw new Error(`Invalid skill ID or URL: ${skillId}`);
22
+ }
23
+
24
+ console.log(`Fetching skill from ${url}...`);
25
+ let content = await this.fetchUrl(url);
26
+
27
+ // Universal Adapter: Ensure the content is wrapped in BookLib tags for retrieval
28
+ if (!content.includes('<framework>') && !content.includes('<core_principles>')) {
29
+ console.log('External skill detected. Applying Universal Wrap...');
30
+ content = `
31
+ ---
32
+ name: ${registryEntry ? registryEntry.id : 'external-skill'}
33
+ source: ${url}
34
+ ---
35
+
36
+ # ${registryEntry ? registryEntry.name : 'External Skill'}
37
+
38
+ <framework>
39
+ ${content}
40
+ </framework>
41
+
42
+ > **Note**: This skill was automatically optimized by the BookLib Universal Engine.
43
+ `;
44
+ }
45
+
46
+ const { cachePath } = resolveBookLibPaths();
47
+ const targetDir = path.join(cachePath, 'skills', registryEntry ? registryEntry.name : 'external');
48
+ if (!fs.existsSync(targetDir)) {
49
+ fs.mkdirSync(targetDir, { recursive: true });
50
+ }
51
+
52
+ const targetFile = path.join(targetDir, 'SKILL.md');
53
+ fs.writeFileSync(targetFile, content);
54
+ console.log(`Skill saved to ${targetFile}`);
55
+
56
+ console.log('Re-indexing to include new skill...');
57
+ await this.indexer.indexDirectory(targetDir);
58
+ console.log('Success.');
59
+ }
60
+
61
+ fetchUrl(url) {
62
+ return new Promise((resolve, reject) => {
63
+ https.get(url, (res) => {
64
+ let data = '';
65
+ res.on('data', (chunk) => data += chunk);
66
+ res.on('end', () => resolve(data));
67
+ }).on('error', reject);
68
+ });
69
+ }
70
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Generates the instinct block -- 5-10 lines of behavioral triggers
3
+ * that tell the AI agent WHEN to use BookLib tools.
4
+ * Differentiates between MCP-capable tools (use MCP tool names) and
5
+ * non-MCP tools (use CLI commands).
6
+ *
7
+ * @param {string} target - Tool target (claude, cursor, copilot, etc.)
8
+ * @returns {string} Markdown block
9
+ */
10
+
11
+ const MCP_TOOLS = new Set([
12
+ 'claude', 'cursor', 'copilot', 'gemini', 'codex',
13
+ 'windsurf', 'roo-code', 'goose', 'zed', 'continue',
14
+ ]);
15
+
16
+ export function renderInstinctBlock(target) {
17
+ if (MCP_TOOLS.has(target)) {
18
+ return `## BookLib
19
+ BookLib MCP tools are available for code quality guidance.
20
+ - Before answering best-practices questions \u2192 search_skills
21
+ - When reviewing or writing code \u2192 search_skills for relevant principles
22
+ - When user says "remember/note/capture this" \u2192 create_note
23
+ - For deep file review against a skill \u2192 audit_content
24
+ - When code quality issues accumulate \u2192 suggest running \`booklib doctor\``;
25
+ }
26
+
27
+ return `## BookLib
28
+ Code quality skills are active for this project.
29
+ Run \`booklib search "query"\` to find relevant principles before reviewing code.
30
+ Run \`booklib capture --title "..." --type insight\` to save knowledge.
31
+ Run \`booklib audit <skill> <file>\` for deep file review.
32
+ Run \`booklib doctor\` when skills feel irrelevant.`;
33
+ }
@@ -0,0 +1,88 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+
5
+ const BOOKLIB_MCP_ENTRY = { command: 'booklib-mcp', args: [] };
6
+
7
+ export function writeMCPConfig(tool, cwd = process.cwd()) {
8
+ switch (tool) {
9
+ case 'claude':
10
+ return writeJSON(path.join(cwd, '.claude', 'settings.json'), 'mcpServers');
11
+ case 'cursor':
12
+ return writeJSON(path.join(cwd, '.cursor', 'mcp.json'), 'mcpServers');
13
+ case 'copilot':
14
+ return writeJSON(path.join(cwd, '.vscode', 'mcp.json'), 'servers');
15
+ case 'gemini':
16
+ return writeJSON(path.join(cwd, '.gemini', 'settings.json'), 'mcpServers');
17
+ case 'codex':
18
+ return writeTOML(path.join(cwd, '.codex', 'config.toml'));
19
+ case 'roo-code':
20
+ return writeJSON(path.join(cwd, '.roo', 'mcp.json'), 'mcpServers');
21
+ case 'windsurf':
22
+ return writeJSON(path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json'), 'mcpServers');
23
+ case 'goose':
24
+ return writeGooseYAML(path.join(cwd, '.goose', 'config.yaml'));
25
+ case 'zed':
26
+ return writeJSON(path.join(cwd, '.zed', 'settings.json'), 'context_servers');
27
+ case 'continue':
28
+ return writeContinueYAML(path.join(cwd, '.continue', 'mcpServers', 'booklib.yaml'));
29
+ default:
30
+ return null; // not MCP-capable
31
+ }
32
+ }
33
+
34
+ function writeJSON(filePath, rootKey) {
35
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
36
+ let config = {};
37
+ if (fs.existsSync(filePath)) {
38
+ try { config = JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { config = {}; }
39
+ }
40
+ if (!config[rootKey]) config[rootKey] = {};
41
+ config[rootKey].booklib = BOOKLIB_MCP_ENTRY;
42
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
43
+ return filePath;
44
+ }
45
+
46
+ function writeTOML(filePath) {
47
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
48
+ const section = '\n[mcp_servers.booklib]\ncommand = "booklib-mcp"\nargs = []\n';
49
+ if (fs.existsSync(filePath)) {
50
+ const content = fs.readFileSync(filePath, 'utf8');
51
+ if (content.includes('[mcp_servers.booklib]')) return filePath; // already exists
52
+ fs.appendFileSync(filePath, section);
53
+ } else {
54
+ fs.writeFileSync(filePath, section.trim() + '\n');
55
+ }
56
+ return filePath;
57
+ }
58
+
59
+ function writeGooseYAML(filePath) {
60
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
61
+ const entry = '\nmcp_servers:\n booklib:\n command: booklib-mcp\n args: []\n';
62
+ if (fs.existsSync(filePath)) {
63
+ const content = fs.readFileSync(filePath, 'utf8');
64
+ if (content.includes('booklib:')) return filePath; // already exists
65
+ if (content.includes('mcp_servers:')) {
66
+ // Append under existing mcp_servers section
67
+ const updated = content.replace('mcp_servers:', 'mcp_servers:\n booklib:\n command: booklib-mcp\n args: []');
68
+ fs.writeFileSync(filePath, updated);
69
+ } else {
70
+ fs.appendFileSync(filePath, entry);
71
+ }
72
+ } else {
73
+ fs.writeFileSync(filePath, entry.trim() + '\n');
74
+ }
75
+ return filePath;
76
+ }
77
+
78
+ function writeContinueYAML(filePath) {
79
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
80
+ const content = 'name: booklib\ncommand: booklib-mcp\nargs: []\n';
81
+ fs.writeFileSync(filePath, content);
82
+ return filePath;
83
+ }
84
+
85
+ export const MCP_CAPABLE = new Set([
86
+ 'claude', 'cursor', 'copilot', 'gemini', 'codex',
87
+ 'windsurf', 'roo-code', 'goose', 'zed', 'continue',
88
+ ]);