@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,253 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export class BookLibAIFeatures {
5
+ constructor(projectRoot = process.cwd()) {
6
+ this.projectRoot = projectRoot;
7
+ this.sessionsDir = path.join(projectRoot, '.booklib/sessions');
8
+ }
9
+
10
+ async generateSessionSummary(sessionName, modelProvider = 'claude') {
11
+ const sessionPath = path.join(this.sessionsDir, `${sessionName}.md`);
12
+ if (!fs.existsSync(sessionPath)) {
13
+ return { error: 'Session not found' };
14
+ }
15
+
16
+ const content = fs.readFileSync(sessionPath, 'utf8');
17
+ const data = this._parseSession(content);
18
+
19
+ const summary = this._generateMockSummary(data);
20
+
21
+ return {
22
+ session: sessionName,
23
+ summary,
24
+ tokens_estimated: 100,
25
+ model: modelProvider
26
+ };
27
+ }
28
+
29
+ recommendSkills(goal, availableSkills = []) {
30
+ const skillKeywords = {
31
+ 'effective-typescript': ['typescript', 'type', 'interface', 'generic', 'null safety'],
32
+ 'clean-code-reviewer': ['code quality', 'refactor', 'clean', 'maintainability'],
33
+ 'microservices-patterns': ['service', 'distributed', 'saga', 'cqrs', 'message queue'],
34
+ 'effective-python': ['python', 'async', 'decorator', 'context manager'],
35
+ 'design-patterns': ['pattern', 'factory', 'observer', 'singleton', 'decorator'],
36
+ 'domain-driven-design': ['domain', 'entity', 'aggregate', 'ubiquitous language'],
37
+ 'system-design-interview': ['scale', 'load balance', 'cache', 'database sharding'],
38
+ 'refactoring-ui': ['ui', 'design', 'layout', 'typography', 'color palette'],
39
+ 'data-pipelines': ['data', 'etl', 'pipeline', 'orchestration', 'warehouse'],
40
+ 'animation-at-work': ['animation', 'transition', 'motion', 'keyframe']
41
+ };
42
+
43
+ const lowerGoal = goal.toLowerCase();
44
+ const recommendations = {};
45
+
46
+ for (const [skill, keywords] of Object.entries(skillKeywords)) {
47
+ const matchCount = keywords.filter(kw => lowerGoal.includes(kw)).length;
48
+ if (matchCount > 0) {
49
+ recommendations[skill] = matchCount;
50
+ }
51
+ }
52
+
53
+ const sorted = Object.entries(recommendations)
54
+ .sort((a, b) => b[1] - a[1])
55
+ .slice(0, 5)
56
+ .map(([skill, score]) => ({
57
+ skill,
58
+ confidence: Math.min(100, score * 20),
59
+ reason: this._getSkillReason(skill, goal)
60
+ }));
61
+
62
+ return { recommendations: sorted };
63
+ }
64
+
65
+ getExtensionData() {
66
+ const sessions = this._loadAllSessions();
67
+
68
+ return {
69
+ version: '1.0.0',
70
+ sessions: sessions.map(s => ({
71
+ id: s.session_id,
72
+ name: s.session_id,
73
+ goal: s.goal,
74
+ branch: s.branch,
75
+ timestamp: s.timestamp
76
+ })),
77
+ status_bar_data: sessions[0] ? {
78
+ current_session: sessions[0].session_id,
79
+ branch: sessions[0].branch,
80
+ pending_tasks: (sessions[0].pending_tasks || '').split('\n').length
81
+ } : null,
82
+ quick_actions: [
83
+ { command: 'booklib.saveSession', title: 'Save Session', icon: 'save' },
84
+ { command: 'booklib.recoverSession', title: 'Recover Session', icon: 'refresh' },
85
+ { command: 'booklib.viewTasks', title: 'View Tasks', icon: 'list' },
86
+ { command: 'booklib.switchSession', title: 'Switch Session', icon: 'branch' }
87
+ ]
88
+ };
89
+ }
90
+
91
+ getGitHubIntegrationData(sessionName) {
92
+ const sessionPath = path.join(this.sessionsDir, `${sessionName}.md`);
93
+ if (!fs.existsSync(sessionPath)) {
94
+ return { error: 'Session not found' };
95
+ }
96
+
97
+ const content = fs.readFileSync(sessionPath, 'utf8');
98
+ const data = this._parseSession(content);
99
+
100
+ return {
101
+ wiki_page: {
102
+ title: `Session: ${data.session_id}`,
103
+ content: this._generateWikiMarkdown(data),
104
+ slug: `session-${data.session_id}`
105
+ },
106
+ issues_to_create: (data.pending_tasks || '')
107
+ .split('\n')
108
+ .filter(t => t.trim())
109
+ .map((task, i) => ({
110
+ title: task.trim(),
111
+ body: `From session: ${data.session_id}\n\nContext: ${data.goal}`,
112
+ labels: ['from-session', ...data.skills],
113
+ priority: i === 0 ? 'high' : 'medium'
114
+ })),
115
+ pr_context: {
116
+ session: data.session_id,
117
+ goal: data.goal,
118
+ branch: data.branch,
119
+ summary: `Working on: ${data.goal}`
120
+ }
121
+ };
122
+ }
123
+
124
+ getSlackIntegrationData(sessionName) {
125
+ const sessionPath = path.join(this.sessionsDir, `${sessionName}.md`);
126
+ if (!fs.existsSync(sessionPath)) {
127
+ return { error: 'Session not found' };
128
+ }
129
+
130
+ const content = fs.readFileSync(sessionPath, 'utf8');
131
+ const data = this._parseSession(content);
132
+
133
+ return {
134
+ webhook_enabled: !!process.env.SLACK_WEBHOOK,
135
+ message: {
136
+ text: `📝 Session Update: ${data.session_id}`,
137
+ blocks: [
138
+ {
139
+ type: 'header',
140
+ text: {
141
+ type: 'plain_text',
142
+ text: `📝 ${data.session_id}`
143
+ }
144
+ },
145
+ {
146
+ type: 'section',
147
+ fields: [
148
+ {
149
+ type: 'mrkdwn',
150
+ text: `*Goal:*\n${data.goal}`
151
+ },
152
+ {
153
+ type: 'mrkdwn',
154
+ text: `*Progress:*\n${data.progress.substring(0, 100)}...`
155
+ }
156
+ ]
157
+ },
158
+ {
159
+ type: 'section',
160
+ text: {
161
+ type: 'mrkdwn',
162
+ text: `*Skills:* ${data.skills.join(', ')}`
163
+ }
164
+ }
165
+ ]
166
+ },
167
+ notification_types: {
168
+ on_save: true,
169
+ on_complete: true,
170
+ team_mention: data.pending_tasks ? '@team' : null,
171
+ branch_mention: `#${data.branch}`
172
+ }
173
+ };
174
+ }
175
+
176
+ _parseSession(content) {
177
+ const extract = (tag) => {
178
+ const regex = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`);
179
+ const match = content.match(regex);
180
+ return match ? match[1].trim() : '';
181
+ };
182
+
183
+ const skillRegex = /<skill id="([^"]+)"/g;
184
+ const skills = [];
185
+ let match;
186
+ while ((match = skillRegex.exec(content)) !== null) {
187
+ skills.push(match[1]);
188
+ }
189
+
190
+ return {
191
+ session_id: extract('session_id'),
192
+ timestamp: extract('timestamp'),
193
+ goal: extract('goal'),
194
+ progress: extract('progress'),
195
+ pending_tasks: extract('pending_tasks'),
196
+ branch: extract('branch'),
197
+ skills
198
+ };
199
+ }
200
+
201
+ _generateMockSummary(data) {
202
+ const parts = [
203
+ `Building: ${data.goal.split(' ').slice(0, 3).join(' ')}`,
204
+ `Progress: ${data.progress.split('\n')[0]}`,
205
+ data.pending_tasks ? `Next: ${data.pending_tasks.split('\n')[0]}` : 'Ready for review'
206
+ ];
207
+ return parts.join('. ');
208
+ }
209
+
210
+ _getSkillReason(skill, goal) {
211
+ const reasons = {
212
+ 'effective-typescript': 'Goal mentions TypeScript or type safety concerns',
213
+ 'clean-code-reviewer': 'Goal includes code quality or refactoring',
214
+ 'microservices-patterns': 'Goal mentions distributed systems or services',
215
+ 'system-design-interview': 'Goal includes scaling or architecture',
216
+ 'design-patterns': 'Goal mentions patterns or architecture',
217
+ 'refactoring-ui': 'Goal includes UI/design work'
218
+ };
219
+ return reasons[skill] || 'Relevant to development goals';
220
+ }
221
+
222
+ _generateWikiMarkdown(data) {
223
+ return `# Session: ${data.session_id}
224
+
225
+ ## Goal
226
+ ${data.goal}
227
+
228
+ ## Progress
229
+ ${data.progress}
230
+
231
+ ## Next Tasks
232
+ ${data.pending_tasks || 'No tasks defined'}
233
+
234
+ ## Skills
235
+ - ${data.skills.join('\n- ')}
236
+
237
+ ## Metadata
238
+ - **Branch:** ${data.branch}
239
+ - **Created:** ${new Date(data.timestamp).toLocaleString()}
240
+ `;
241
+ }
242
+
243
+ _loadAllSessions() {
244
+ if (!fs.existsSync(this.sessionsDir)) return [];
245
+
246
+ return fs.readdirSync(this.sessionsDir)
247
+ .filter(f => f.endsWith('.md') && !f.startsWith('_'))
248
+ .map(file => {
249
+ const content = fs.readFileSync(path.join(this.sessionsDir, file), 'utf8');
250
+ return this._parseSession(content);
251
+ });
252
+ }
253
+ }
@@ -0,0 +1,103 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { parseSkillFile } from './parser.js';
4
+
5
+ /**
6
+ * Handles systematic audits of content against BookLib skills.
7
+ */
8
+ export class BookLibAuditor {
9
+ constructor() {}
10
+
11
+ /**
12
+ * Performs an audit of a file against a specific skill.
13
+ */
14
+ async audit(skillPath, targetFilePath) {
15
+ if (!fs.existsSync(skillPath)) {
16
+ throw new Error(`Skill not found at ${skillPath}`);
17
+ }
18
+
19
+ if (!fs.existsSync(targetFilePath)) {
20
+ throw new Error(`Target file not found at ${targetFilePath}`);
21
+ }
22
+
23
+ const skillMdPath = fs.statSync(skillPath).isDirectory()
24
+ ? path.join(skillPath, 'SKILL.md')
25
+ : skillPath;
26
+
27
+ const skillContent = fs.readFileSync(skillMdPath, 'utf8');
28
+ const chunks = parseSkillFile(skillContent, skillMdPath);
29
+
30
+ const targetContent = fs.readFileSync(targetFilePath, 'utf8');
31
+ const targetLines = targetContent.split('\n');
32
+
33
+ // Support both legacy and universal tags
34
+ const principles = chunks.find(c => c.metadata.type === 'framework' || c.metadata.type === 'core_principles')?.text || 'See metadata';
35
+ const pitfalls = chunks.find(c => c.metadata.type === 'pitfalls' || c.metadata.type === 'anti_patterns')?.text || 'See metadata';
36
+
37
+ // 1. Static Analysis (Domain-specific low-hanging fruit)
38
+ const auditJsonPath = path.join(path.dirname(skillMdPath), 'audit.json');
39
+ let staticFindings = '';
40
+ if (fs.existsSync(auditJsonPath)) {
41
+ const { rules } = JSON.parse(fs.readFileSync(auditJsonPath, 'utf8'));
42
+ const findings = [];
43
+
44
+ for (const rule of rules) {
45
+ const regex = new RegExp(rule.pattern, 'g');
46
+ targetLines.forEach((line, index) => {
47
+ if (regex.test(line)) {
48
+ findings.push(`- ${rule.severity} **${rule.id}**: ${rule.message} (Line ${index + 1}: \`${line.trim()}\`)`);
49
+ }
50
+ });
51
+ }
52
+
53
+ if (findings.length > 0) {
54
+ staticFindings = `### 🤖 Automated Detections\n${findings.join('\n')}\n`;
55
+ } else {
56
+ staticFindings = `### 🤖 Automated Detections\n*No common pitfalls detected automatically.*\n`;
57
+ }
58
+ }
59
+
60
+ return `
61
+ # BookLib Active Audit: ${path.basename(skillPath)}
62
+ **Target Content**: \`${targetFilePath}\`
63
+
64
+ ## 🛠 Instructions for the Agent
65
+ You are tasked with a systematic audit of the provided content against expert frameworks.
66
+ Follow these steps:
67
+ 1. **Analyze**: Read the target content carefully.
68
+ 2. **Evaluate**: Compare the work against the "Expert Framework" and "Pitfalls to Avoid".
69
+ 3. **Report**: Document where the content aligns or violates these expert standards.
70
+
71
+ ---
72
+
73
+ ## 📜 Expert Framework
74
+ ${principles}
75
+
76
+ ## 🚫 Pitfalls to Avoid
77
+ ${pitfalls}
78
+
79
+ ---
80
+
81
+ ## 🔍 Audit Findings
82
+
83
+ ${staticFindings}
84
+
85
+ ### 🔴 Critical Misalignments (Manual)
86
+ * (List major deviations from the expert framework)
87
+
88
+ ### 🟡 Opportunities for Improvement
89
+ * (List areas where the content can be sharpened)
90
+
91
+ ### 🟢 Strengths
92
+ * (Acknowledge where the content perfectly embodies the principles)
93
+
94
+ ### 🚀 Recommended Refinement
95
+ \`\`\`
96
+ // Provide a surgical improvement or rewritten section here
97
+ \`\`\`
98
+
99
+ > **Note**: This audit was generated by the BookLib Universal Engine.
100
+ > Always cite the specific expert items in your findings.
101
+ `;
102
+ }
103
+ }
@@ -0,0 +1,178 @@
1
+ import fs from 'node:fs';
2
+
3
+ const K1 = 1.5;
4
+ const B = 0.75;
5
+ const MIN_TOKEN_LENGTH = 2;
6
+
7
+ /**
8
+ * Tokenizes text into lowercase alphanumeric terms of at least 2 characters.
9
+ * @param {string} text
10
+ * @returns {string[]}
11
+ */
12
+ function tokenize(text) {
13
+ return text
14
+ .toLowerCase()
15
+ .split(/[^a-z0-9]+/)
16
+ .filter(token => token.length >= MIN_TOKEN_LENGTH);
17
+ }
18
+
19
+ /**
20
+ * Counts term frequencies in a token array.
21
+ * @param {string[]} tokens
22
+ * @returns {Map<string, number>}
23
+ */
24
+ function countTermFrequencies(tokens) {
25
+ const freq = new Map();
26
+ for (const token of tokens) {
27
+ freq.set(token, (freq.get(token) ?? 0) + 1);
28
+ }
29
+ return freq;
30
+ }
31
+
32
+ /**
33
+ * Computes BM25 IDF for a term.
34
+ * Formula: log((N - df + 0.5) / (df + 0.5) + 1)
35
+ * @param {number} docCount - Total number of documents
36
+ * @param {number} docFrequency - Number of documents containing the term
37
+ * @returns {number}
38
+ */
39
+ function computeIdf(docCount, docFrequency) {
40
+ return Math.log((docCount - docFrequency + 0.5) / (docFrequency + 0.5) + 1);
41
+ }
42
+
43
+ /**
44
+ * Scores a single document against a set of query terms using BM25.
45
+ * @param {Object} doc - Document with freq map and len
46
+ * @param {string[]} queryTerms
47
+ * @param {Map<string, number>} df - Document frequency map
48
+ * @param {number} docCount
49
+ * @param {number} avgDocLen
50
+ * @returns {number}
51
+ */
52
+ function scoreBm25(doc, queryTerms, df, docCount, avgDocLen) {
53
+ let score = 0;
54
+ for (const term of queryTerms) {
55
+ const termFreq = doc.freq[term] ?? 0;
56
+ if (termFreq === 0) continue;
57
+
58
+ const docFrequency = df[term] ?? 0;
59
+ const idf = computeIdf(docCount, docFrequency);
60
+ const normalizedTf =
61
+ (termFreq * (K1 + 1)) /
62
+ (termFreq + K1 * (1 - B + B * (doc.len / avgDocLen)));
63
+
64
+ score += idf * normalizedTf;
65
+ }
66
+ return score;
67
+ }
68
+
69
+ /**
70
+ * BM25 full-text search index.
71
+ *
72
+ * Supports incremental document addition, JSON persistence, and
73
+ * Robertson BM25 ranking.
74
+ */
75
+ export class BM25Index {
76
+ constructor() {
77
+ this._docs = [];
78
+ this._df = {};
79
+ this._avgLen = 0;
80
+ this._totalLen = 0;
81
+ }
82
+
83
+ /**
84
+ * Builds the index from scratch from an array of chunks.
85
+ * @param {{ text: string, metadata: object }[]} chunks
86
+ */
87
+ build(chunks) {
88
+ this._docs = [];
89
+ this._df = {};
90
+ this._avgLen = 0;
91
+ this._totalLen = 0;
92
+
93
+ for (const chunk of chunks) {
94
+ this._appendDoc(chunk);
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Incrementally adds one document to the index, updating avgLen and df.
100
+ * @param {{ text: string, metadata: object }} chunk
101
+ */
102
+ add(chunk) {
103
+ this._appendDoc(chunk);
104
+ }
105
+
106
+ /**
107
+ * Searches the index and returns top-K results sorted by score descending.
108
+ * @param {string} query
109
+ * @param {number} topK
110
+ * @returns {{ score: number, text: string, metadata: object }[]}
111
+ */
112
+ search(query, topK = 20) {
113
+ if (this._docs.length === 0) return [];
114
+
115
+ const queryTerms = tokenize(query);
116
+ if (queryTerms.length === 0) return [];
117
+
118
+ const scoredDocs = this._docs.map(doc => ({
119
+ score: scoreBm25(doc, queryTerms, this._df, this._docs.length, this._avgLen),
120
+ text: doc.text,
121
+ metadata: doc.metadata,
122
+ }));
123
+
124
+ const matchingDocs = scoredDocs.filter(r => r.score > 0);
125
+ return matchingDocs
126
+ .sort((a, b) => b.score - a.score)
127
+ .slice(0, topK);
128
+ }
129
+
130
+ /**
131
+ * Persists the index to a JSON file.
132
+ * @param {string} filePath
133
+ */
134
+ save(filePath) {
135
+ const serialized = JSON.stringify({
136
+ docs: this._docs,
137
+ df: this._df,
138
+ avgLen: this._avgLen,
139
+ });
140
+ fs.writeFileSync(filePath, serialized, 'utf8');
141
+ }
142
+
143
+ /**
144
+ * Restores a BM25Index instance from a JSON file.
145
+ * @param {string} filePath
146
+ * @returns {BM25Index}
147
+ */
148
+ static load(filePath) {
149
+ const raw = fs.readFileSync(filePath, 'utf8');
150
+ const { docs, df, avgLen } = JSON.parse(raw);
151
+ const idx = new BM25Index();
152
+ idx._docs = docs;
153
+ idx._df = df;
154
+ idx._avgLen = avgLen;
155
+ idx._totalLen = docs.reduce((sum, d) => sum + d.len, 0);
156
+ return idx;
157
+ }
158
+
159
+ /**
160
+ * Internal: adds one document and updates df and avgLen.
161
+ * @param {{ text: string, metadata: object }} chunk
162
+ */
163
+ _appendDoc(chunk) {
164
+ const tokens = tokenize(chunk.text);
165
+ const freqMap = countTermFrequencies(tokens);
166
+ const freqObj = Object.fromEntries(freqMap);
167
+
168
+ for (const term of freqMap.keys()) {
169
+ this._df[term] = (this._df[term] ?? 0) + 1;
170
+ }
171
+
172
+ const len = tokens.length;
173
+ this._docs.push({ text: chunk.text, metadata: chunk.metadata, freq: freqObj, len });
174
+
175
+ this._totalLen += len;
176
+ this._avgLen = this._totalLen / this._docs.length;
177
+ }
178
+ }
@@ -0,0 +1,120 @@
1
+ // lib/engine/capture.js
2
+ import { spawnSync } from 'node:child_process';
3
+ import { writeFileSync, readFileSync, unlinkSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+
7
+ // ── AI prompt builders (exported for testing) ─────────────────────────────────
8
+
9
+ /** Returns the prompt used to ask Claude to structure raw dictation into a clean note. */
10
+ export function buildDictatePrompt(rawText) {
11
+ return `You are a knowledge management assistant. Structure the following raw notes into a clean markdown knowledge note.
12
+
13
+ Raw input:
14
+ ${rawText}
15
+
16
+ Return ONLY valid YAML frontmatter + markdown. The frontmatter must include:
17
+ - title: (concise, descriptive title extracted or inferred from the content)
18
+ - tags: (2-5 relevant tags as a YAML list)
19
+ - type: note
20
+
21
+ Fix grammar and typos. Preserve all meaning. Do not add information not present in the input.
22
+ Return nothing except the markdown document starting with ---.`;
23
+ }
24
+
25
+ /** Returns the prompt used to summarize a conversation transcript into a structured note. */
26
+ export function buildSummarizePrompt(transcript, title = '') {
27
+ const titleLine = title ? `Title: ${title}\n\n` : '';
28
+ return `You are a knowledge management assistant. Summarize this AI conversation into a structured knowledge note.
29
+
30
+ ${titleLine}Conversation:
31
+ ${transcript}
32
+
33
+ Return ONLY a markdown document starting with ---. Include this frontmatter:
34
+ - title: (descriptive title${title ? ` — use "${title}" as basis` : ''})
35
+ - tags: (2-5 relevant tags)
36
+ - type: note
37
+
38
+ And these sections in the body:
39
+ ## Key Decisions
40
+ (bullet list of decisions made)
41
+
42
+ ## Findings
43
+ (bullet list of key findings or conclusions)
44
+
45
+ ## Context
46
+ (1-2 sentences of background)
47
+
48
+ Then append the full transcript inside a details element:
49
+ <details>
50
+ <summary>Full conversation transcript</summary>
51
+
52
+ ${transcript}
53
+
54
+ </details>`;
55
+ }
56
+
57
+ // ── AI call (requires ANTHROPIC_API_KEY env var) ──────────────────────────────
58
+
59
+ /** Calls claude-haiku via the Anthropic Messages API. Requires ANTHROPIC_API_KEY. */
60
+ export async function callAnthropicAPI(prompt) {
61
+ const apiKey = process.env.ANTHROPIC_API_KEY;
62
+ if (!apiKey) {
63
+ throw new Error('ANTHROPIC_API_KEY not set. Export it or add to .env.local');
64
+ }
65
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
66
+ method: 'POST',
67
+ headers: {
68
+ 'Content-Type': 'application/json',
69
+ 'x-api-key': apiKey,
70
+ 'anthropic-version': '2023-06-01',
71
+ },
72
+ body: JSON.stringify({
73
+ model: 'claude-haiku-4-5-20251001',
74
+ max_tokens: 2048,
75
+ messages: [{ role: 'user', content: prompt }],
76
+ }),
77
+ });
78
+ if (!response.ok) {
79
+ const body = await response.text();
80
+ throw new Error(`Anthropic API ${response.status}: ${body}`);
81
+ }
82
+ const data = await response.json();
83
+ return data.content[0].text;
84
+ }
85
+
86
+ // ── Input helpers ─────────────────────────────────────────────────────────────
87
+
88
+ /** Opens $EDITOR (or vi) with optional initial content. Returns edited content. */
89
+ export function openEditor(initialContent = '') {
90
+ const tmpFile = join(tmpdir(), `booklib-edit-${Date.now()}.md`);
91
+ writeFileSync(tmpFile, initialContent, 'utf8');
92
+ const editor = process.env.EDITOR ?? 'vi';
93
+ spawnSync(editor, [tmpFile], { stdio: 'inherit' });
94
+ const content = readFileSync(tmpFile, 'utf8');
95
+ unlinkSync(tmpFile);
96
+ return content.trim();
97
+ }
98
+
99
+ /** Reads all of stdin when piped. Returns empty string when stdin is a TTY. */
100
+ export async function readStdin() {
101
+ if (process.stdin.isTTY) return '';
102
+ return new Promise(resolve => {
103
+ let data = '';
104
+ process.stdin.setEncoding('utf8');
105
+ process.stdin.on('data', chunk => { data += chunk; });
106
+ process.stdin.on('end', () => resolve(data.trim()));
107
+ });
108
+ }
109
+
110
+ /** Prompts user to type input interactively until Ctrl+D. */
111
+ export async function readInteractive(prompt = 'Type your note (Ctrl+D when done):') {
112
+ process.stdout.write(prompt + '\n');
113
+ return new Promise(resolve => {
114
+ let data = '';
115
+ process.stdin.setEncoding('utf8');
116
+ process.stdin.resume();
117
+ process.stdin.on('data', chunk => { data += chunk; });
118
+ process.stdin.on('end', () => resolve(data.trim()));
119
+ });
120
+ }