@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,404 @@
1
+ // lib/wizard/index.js
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { createWizardUI, sep } from './prompt.js';
6
+ import { detect as detectProject } from './project-detector.js';
7
+ import { SKILL_LIMIT } from './skill-recommender.js';
8
+ import { detectIntegrations } from './integration-detector.js';
9
+ import { SkillFetcher, countAllSlots, countInstalledSlots, listInstalledSkillNames, installSkill } from '../skill-fetcher.js';
10
+ import { BookLibIndexer } from '../engine/indexer.js';
11
+ import { BookLibSearcher } from '../engine/searcher.js';
12
+ import { AgentDetector } from '../agent-detector.js';
13
+ import { ProjectInitializer } from '../project-initializer.js';
14
+ import { resolveBookLibPaths } from '../paths.js';
15
+ import { writeMCPConfig, MCP_CAPABLE } from '../mcp-config-writer.js';
16
+
17
+ const AGENT_LABELS = {
18
+ claude: 'Claude Code', cursor: 'Cursor', copilot: 'Copilot',
19
+ gemini: 'Gemini CLI', codex: 'Codex', windsurf: 'Windsurf',
20
+ 'roo-code': 'Roo Code', openhands: 'OpenHands', junie: 'Junie',
21
+ goose: 'Goose', opencode: 'OpenCode', letta: 'Letta',
22
+ };
23
+ const ALL_AGENTS = Object.keys(AGENT_LABELS);
24
+
25
+ export async function runWizard(cwd = process.cwd(), opts = {}) {
26
+ const markerPath = path.join(cwd, '.booklib', 'initialized');
27
+
28
+ if (opts.reset) {
29
+ if (fs.existsSync(markerPath)) fs.unlinkSync(markerPath);
30
+ return runSetup(cwd);
31
+ }
32
+
33
+ if (fs.existsSync(markerPath)) {
34
+ console.log('\n Already initialized. Running relevance check...');
35
+ console.log(' (to re-run full setup: rm -rf .booklib && booklib init)\n');
36
+ return runRelevanceAudit(cwd);
37
+ }
38
+ return runSetup(cwd);
39
+ }
40
+
41
+ // ── Setup flow ───────────────────────────────────────────────────────────────
42
+
43
+ async function runSetup(cwd) {
44
+ const ui = createWizardUI();
45
+
46
+ // Banner
47
+ console.log('');
48
+ console.log(' \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2726');
49
+ console.log(' \u2502 \u2500\u2500\u2500 \u2502 \u2500\u2500\u2500 \u2502');
50
+ console.log(' \u2502 \u2500\u2500 \u2502 \u2500\u2500 \u2502 BookLib');
51
+ console.log(' \u2502 \u2500\u2500\u2500 \u2502 \u2500\u2500\u2500 \u2502');
52
+ console.log(' \u2502 \u2500\u2500 \u2502 \u2500\u2500 \u2502 AI-agent skills from');
53
+ console.log(' \u2502 \u2500\u2500\u2500 \u2502 \u2500\u2500\u2500 \u2502 expert knowledge');
54
+ console.log(' \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2518');
55
+ console.log('');
56
+
57
+ ui.intro('Setup Wizard');
58
+
59
+ // Step 1: Project detection
60
+ const project = await stepProjectDetection(ui, cwd);
61
+
62
+ // Step 2: Profile selection
63
+ const profile = await stepProfileSelection(ui);
64
+
65
+ // Save profile to config
66
+ try {
67
+ const { configPath } = resolveBookLibPaths(cwd);
68
+ let savedConfig = {};
69
+ try { savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { /* no config */ }
70
+ savedConfig.profile = profile;
71
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
72
+ fs.writeFileSync(configPath, JSON.stringify(savedConfig, null, 2));
73
+ } catch { /* best-effort */ }
74
+
75
+ // Step 3: Health check
76
+ const slotsUsed = countAllSlots();
77
+ const installedNames = listInstalledSkillNames();
78
+ if (slotsUsed > SKILL_LIMIT) {
79
+ ui.log.warn(
80
+ `${slotsUsed} skills installed (limit: ${SKILL_LIMIT}).\n` +
81
+ 'Agent context is overloaded \u2014 most skills get truncated.\n' +
82
+ 'After indexing, I\'ll find the best skills for your stack and help clean up.'
83
+ );
84
+ }
85
+
86
+ // Step 4: Tool detection
87
+ const selectedAgents = await stepToolSelection(ui, cwd);
88
+
89
+ // Step 5: Index build with spinner
90
+ await stepIndexBuild(ui);
91
+
92
+ // Step 6: Recommend + install + cleanup
93
+ const selectedSkills = await stepRecommendAndInstall(ui, project, slotsUsed, installedNames);
94
+
95
+ // Step 7: Write config files
96
+ const skillsForConfig = selectedSkills.length > 0 ? selectedSkills : installedNames.slice(0, 10);
97
+ const stack = project.languages.join(', ');
98
+ await stepWriteConfigs(ui, cwd, selectedAgents, skillsForConfig, profile, stack);
99
+
100
+ // Step 8: Summary
101
+ ui.outro('BookLib is ready');
102
+
103
+ const finalSlots = countInstalledSlots();
104
+ console.log('');
105
+ if (selectedSkills.length > 0) console.log(` \u2713 ${selectedSkills.length} skills configured`);
106
+ console.log(` \u2713 ${finalSlots} total skills loaded`);
107
+ console.log('');
108
+ console.log(` ${sep()}`);
109
+ console.log(' Quick reference:');
110
+ console.log('');
111
+ console.log(' booklib search "query" find patterns');
112
+ console.log(' booklib search "q" --graph include graph links');
113
+ console.log(' booklib capture --title "..." save knowledge');
114
+ console.log(' booklib doctor health check');
115
+ console.log(' booklib doctor --cure auto-fix issues');
116
+ console.log(' booklib init --reset re-run setup');
117
+ console.log(` ${sep()}`);
118
+ console.log('');
119
+
120
+ // Mark initialized
121
+ const markerPath = path.join(cwd, '.booklib', 'initialized');
122
+ fs.mkdirSync(path.dirname(markerPath), { recursive: true });
123
+ fs.writeFileSync(markerPath, new Date().toISOString());
124
+ }
125
+
126
+ async function stepProjectDetection(ui, cwd) {
127
+ const project = detectProject(cwd);
128
+
129
+ if (project.languages.length > 0) {
130
+ const langs = project.languages.join(', ');
131
+ const fw = project.frameworks.length ? ` (${project.frameworks.join(', ')})` : '';
132
+ const ok = await ui.confirm(`Detected: ${langs}${fw}. Correct?`, true);
133
+ if (!ok) {
134
+ const answer = await ui.text('Describe your stack:', 'e.g. React + Node.js, Kotlin Android');
135
+ return { languages: [answer], frameworks: [], signals: [] };
136
+ }
137
+ } else {
138
+ const answer = await ui.text('What are you building?', 'e.g. React + Node.js, Kotlin Android');
139
+ return { languages: [answer], frameworks: [], signals: [] };
140
+ }
141
+
142
+ return project;
143
+ }
144
+
145
+ async function stepProfileSelection(ui) {
146
+ const profile = await ui.select('What kind of work is this project for?', [
147
+ { value: 'software-development', label: 'Software development', hint: 'recommended' },
148
+ { value: 'writing-content', label: 'Writing & content' },
149
+ { value: 'research-analysis', label: 'Research & analysis' },
150
+ { value: 'design', label: 'Design' },
151
+ { value: 'general', label: 'General / other' },
152
+ ]);
153
+ return profile;
154
+ }
155
+
156
+ async function stepToolSelection(ui, cwd) {
157
+ const detector = new AgentDetector({ cwd });
158
+ const detected = detector.detect();
159
+ const detectedSet = new Set(detected);
160
+
161
+ const options = ALL_AGENTS.map(a => ({
162
+ value: a,
163
+ label: AGENT_LABELS[a],
164
+ hint: detectedSet.has(a) ? 'detected' : undefined,
165
+ }));
166
+
167
+ const selected = await ui.multiselect('Which AI tools do you use?', options, {
168
+ initialValues: detected,
169
+ });
170
+
171
+ // Save to config
172
+ try {
173
+ const { configPath } = resolveBookLibPaths(cwd);
174
+ let savedConfig = {};
175
+ try { savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { /* no config */ }
176
+ savedConfig.tools = selected;
177
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
178
+ fs.writeFileSync(configPath, JSON.stringify(savedConfig, null, 2));
179
+ } catch { /* best-effort */ }
180
+
181
+ const integrations = detectIntegrations({ cwd });
182
+ if (integrations.superpowers) {
183
+ ui.log.info('Detected: obra/superpowers plugin (skills auto-synced)');
184
+ }
185
+
186
+ return selected;
187
+ }
188
+
189
+ async function stepIndexBuild(ui) {
190
+ const s = ui.spinner();
191
+ s.start('Building knowledge index...');
192
+ const indexer = new BookLibIndexer();
193
+
194
+ try {
195
+ const { skillsPath } = resolveBookLibPaths();
196
+ let lastFile = '';
197
+ await indexer.indexDirectory(skillsPath, false, {
198
+ quiet: true,
199
+ onProgress({ current, total, file }) {
200
+ lastFile = file.split('/')[0] ?? file;
201
+ s.message(`Building knowledge index... [${current}/${total}] ${lastFile}`);
202
+ },
203
+ });
204
+ s.stop('Index ready');
205
+ } catch (err) {
206
+ s.stop(`Index build failed: ${err.message}`);
207
+ }
208
+ }
209
+
210
+ async function stepRecommendAndInstall(ui, project, slotsUsed, installedNames) {
211
+ const s = ui.spinner();
212
+ s.start('Finding best skills for your project...');
213
+
214
+ const searcher = new BookLibSearcher();
215
+ const queryText = project.languages.join(' ') + ' best practices';
216
+ let results;
217
+ try {
218
+ results = await searcher.search(queryText, 20, 0);
219
+ } catch {
220
+ s.stop('Search index not available');
221
+ return [];
222
+ }
223
+
224
+ const bySkill = new Map();
225
+ for (const r of results) {
226
+ const name = r.metadata?.name;
227
+ if (!name) continue;
228
+ if (!bySkill.has(name) || r.score > bySkill.get(name).score) {
229
+ const snippet = (r.text ?? '').replace(/\n/g, ' ').slice(0, 80).trim();
230
+ bySkill.set(name, { name, score: r.score, displayScore: r.displayScore, snippet, description: r.metadata?.description ?? '' });
231
+ }
232
+ }
233
+ const recommended = [...bySkill.values()].sort((a, b) => b.score - a.score).slice(0, 10);
234
+ s.stop(`Found ${recommended.length} matching skills`);
235
+
236
+ if (recommended.length === 0) return [];
237
+
238
+ const installedSet = new Set(installedNames.map(n => n.toLowerCase()));
239
+
240
+ const options = recommended.map(sk => ({
241
+ value: sk.name,
242
+ label: `${sk.name} [${sk.displayScore ?? Math.round((sk.score ?? 0) * 100)}%]`,
243
+ hint: installedSet.has(sk.name.toLowerCase()) ? 'installed' : (sk.snippet || sk.description.slice(0, 50)),
244
+ }));
245
+
246
+ const selected = await ui.multiselect('Top skills for your project:', options);
247
+
248
+ if (selected.length === 0) return installedNames;
249
+
250
+ // Install selected skills
251
+ const toInstall = selected.filter(n => !installedSet.has(n.toLowerCase()));
252
+ const installed = [];
253
+
254
+ for (const name of selected) {
255
+ if (installedSet.has(name.toLowerCase())) {
256
+ ui.log.info(`${name} (already installed)`);
257
+ installed.push(name);
258
+ } else {
259
+ const result = installSkill(name);
260
+ if (result === 'installed') {
261
+ ui.log.success(`${name}`);
262
+ installed.push(name);
263
+ } else if (result === 'already-installed') {
264
+ ui.log.info(`${name} (already installed)`);
265
+ installed.push(name);
266
+ } else {
267
+ ui.log.warn(`${name}: not found in any catalog`);
268
+ }
269
+ }
270
+ }
271
+
272
+ // Cleanup offer
273
+ if (slotsUsed > SKILL_LIMIT && installed.length > 0) {
274
+ const toRemove = installedNames.filter(n => !selected.includes(n));
275
+ const cleanup = await ui.select(`You have ${slotsUsed} skills but only need ~${installed.length}.`, [
276
+ { value: 'clean', label: `Clean up \u2014 keep only recommended (remove ${toRemove.length})` },
277
+ { value: 'keep', label: 'Keep all + add recommended' },
278
+ { value: 'skip', label: 'Skip \u2014 I\'ll handle it manually' },
279
+ ]);
280
+
281
+ if (cleanup === 'clean') {
282
+ const fetcher = new SkillFetcher();
283
+ let removed = 0;
284
+ for (const name of toRemove) {
285
+ fetcher.desyncFromClaudeSkills({ name });
286
+ removed++;
287
+ }
288
+ ui.log.success(`Removed ${removed} skills. Kept ${installed.length}.`);
289
+ }
290
+ }
291
+
292
+ return installed;
293
+ }
294
+
295
+ async function stepWriteConfigs(ui, cwd, selectedAgents, skillNames, profile, stack) {
296
+ if (selectedAgents.length === 0 || skillNames.length === 0) return;
297
+
298
+ const s = ui.spinner();
299
+ s.start('Writing config files...');
300
+
301
+ const initializer = new ProjectInitializer({ projectCwd: cwd });
302
+ const target = selectedAgents.length === ALL_AGENTS.length ? 'all' : selectedAgents.join(',');
303
+
304
+ try {
305
+ const written = await initializer.init({ skills: skillNames, target, dryRun: false, quiet: true, profile, stack });
306
+ if (written.length > 0) {
307
+ s.stop('Config files written');
308
+ for (const file of written) {
309
+ ui.log.success(file);
310
+ }
311
+ } else {
312
+ s.stop('No config files needed');
313
+ }
314
+ } catch (err) {
315
+ s.stop(`Config write failed: ${err.message}`);
316
+ }
317
+
318
+ // Write MCP configs for capable tools
319
+ const mcpWritten = [];
320
+ for (const tool of selectedAgents) {
321
+ if (MCP_CAPABLE.has(tool)) {
322
+ try {
323
+ const mcpPath = writeMCPConfig(tool, cwd);
324
+ if (mcpPath) mcpWritten.push(mcpPath);
325
+ } catch { /* best-effort */ }
326
+ }
327
+ }
328
+ if (mcpWritten.length > 0) {
329
+ for (const p of mcpWritten) {
330
+ const rel = path.relative(cwd, p);
331
+ ui.log.success(`${rel} (MCP)`);
332
+ }
333
+ }
334
+ }
335
+
336
+ // ── Re-run flow (already initialized) ────────────────────────────────────────
337
+
338
+ async function runRelevanceAudit(cwd) {
339
+ const { cosine } = await import('./skill-recommender.js');
340
+ const { getEmbeddings } = await import('./registry-embeddings.js');
341
+ const { detect: detectProj } = await import('./project-detector.js');
342
+
343
+ console.log('\n BookLib \u2014 Relevance Check\n');
344
+
345
+ const project = detectProj(cwd);
346
+ const installedNames = listInstalledSkillNames();
347
+
348
+ if (installedNames.length === 0) {
349
+ console.log(' No BookLib-managed skills installed. Run "booklib init" to set up.');
350
+ return;
351
+ }
352
+
353
+ process.stdout.write(`\u25ba Scoring ${installedNames.length} skill(s) against your project`);
354
+ const dotInterval = setInterval(() => { process.stdout.write('.'); }, 300);
355
+
356
+ const embeddings = await getEmbeddings();
357
+ const searcher = new BookLibSearcher();
358
+ const queryText = project.languages.map(l => `${l} programming`).join('. ') || 'software engineering';
359
+ const queryVec = await searcher.getEmbedding(queryText);
360
+
361
+ clearInterval(dotInterval);
362
+ process.stdout.write('\n\n');
363
+
364
+ const RELEVANCE_THRESHOLD = 0.35;
365
+ const scored = installedNames
366
+ .map(name => ({ name, score: embeddings.has(name) ? cosine(queryVec, embeddings.get(name)) : null }))
367
+ .filter(s => s.score !== null)
368
+ .sort((a, b) => b.score - a.score);
369
+
370
+ const unindexedCount = installedNames.length - scored.length;
371
+ if (unindexedCount > 0) {
372
+ process.stdout.write(` ${unindexedCount} skill(s) not yet indexed \u2014 run "booklib index" to score them\n\n`);
373
+ }
374
+
375
+ if (scored.length === 0) {
376
+ process.stdout.write(' Nothing to score yet. Run "booklib index" first.\n\n');
377
+ return;
378
+ }
379
+
380
+ const relevant = scored.filter(s => s.score >= RELEVANCE_THRESHOLD);
381
+ const lowRelevance = scored.filter(s => s.score < RELEVANCE_THRESHOLD);
382
+
383
+ for (const { name, score } of relevant.slice(0, 5)) {
384
+ process.stdout.write(` \u2713 ${name.padEnd(30)} ${(score * 100).toFixed(0)}% match\n`);
385
+ }
386
+ if (relevant.length > 5) {
387
+ process.stdout.write(` \u2026 and ${relevant.length - 5} more relevant skill(s)\n`);
388
+ }
389
+
390
+ if (lowRelevance.length === 0) {
391
+ process.stdout.write(`\n All ${scored.length} scored skill(s) are relevant to this project.\n\n`);
392
+ return;
393
+ }
394
+
395
+ process.stdout.write('\n Low relevance for this project:\n');
396
+ for (const { name, score } of lowRelevance.slice(0, 10)) {
397
+ process.stdout.write(` \u00b7 ${name.padEnd(30)} ${(score * 100).toFixed(0)}% match\n`);
398
+ }
399
+ if (lowRelevance.length > 10) {
400
+ process.stdout.write(` \u2026 and ${lowRelevance.length - 10} more\n`);
401
+ }
402
+
403
+ process.stdout.write('\n Tip: run "booklib uninstall <skill>" to free slots for more relevant skills.\n\n');
404
+ }
@@ -0,0 +1,41 @@
1
+ // lib/wizard/integration-detector.js
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+
6
+ /**
7
+ * Detects installed integrations and tools.
8
+ *
9
+ * @param {{ cwd?: string, home?: string }} opts - override for testing
10
+ * @returns {{ superpowers: boolean, ruflo: boolean, claudeCode: boolean }}
11
+ */
12
+ export function detectIntegrations({ cwd = process.cwd(), home = os.homedir() } = {}) {
13
+ return {
14
+ superpowers: _detectSuperpowers(home),
15
+ ruflo: _detectRuflo(cwd, home),
16
+ claudeCode: _detectClaudeCode(home),
17
+ };
18
+ }
19
+
20
+ function _detectSuperpowers(home) {
21
+ const pluginsDir = path.join(home, '.claude', 'plugins');
22
+ if (!fs.existsSync(pluginsDir)) return false;
23
+ try {
24
+ return fs.readdirSync(pluginsDir).some(d => d.toLowerCase().includes('superpowers'));
25
+ } catch { return false; }
26
+ }
27
+
28
+ function _detectRuflo(cwd, home) {
29
+ const candidates = [
30
+ path.join(cwd, 'ruflo.config.js'),
31
+ path.join(cwd, 'ruflo.config.ts'),
32
+ path.join(cwd, 'ruflo.config.json'),
33
+ path.join(cwd, '.ruflo'),
34
+ path.join(home, '.ruflo'),
35
+ ];
36
+ return candidates.some(p => fs.existsSync(p));
37
+ }
38
+
39
+ function _detectClaudeCode(home) {
40
+ return fs.existsSync(path.join(home, '.claude'));
41
+ }
@@ -0,0 +1,100 @@
1
+ // lib/wizard/project-detector.js
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ const FILE_SIGNALS = [
6
+ { file: 'go.mod', lang: 'go' },
7
+ { file: 'Cargo.toml', lang: 'rust' },
8
+ { file: 'pom.xml', lang: 'java' },
9
+ { file: 'build.gradle', lang: 'java' },
10
+ { file: 'build.gradle.kts', lang: 'kotlin' },
11
+ { file: 'pyproject.toml', lang: 'python' },
12
+ { file: 'requirements.txt', lang: 'python' },
13
+ ];
14
+
15
+ const EXT_SIGNALS = {
16
+ '.kt': 'kotlin', '.kts': 'kotlin',
17
+ '.java': 'java',
18
+ '.py': 'python',
19
+ '.ts': 'typescript', '.tsx': 'typescript',
20
+ '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript',
21
+ '.rs': 'rust',
22
+ '.go': 'go',
23
+ '.rb': 'ruby',
24
+ '.swift': 'swift',
25
+ '.dart': 'dart',
26
+ '.cs': 'csharp',
27
+ };
28
+
29
+ const FRAMEWORK_SIGNALS = {
30
+ python: { fastapi: 'FastAPI', django: 'Django', flask: 'Flask', pytest: 'testing' },
31
+ javascript: { express: 'Express', next: 'Next.js', react: 'React', vue: 'Vue', '@nestjs': 'NestJS' },
32
+ typescript: { express: 'Express', next: 'Next.js', react: 'React', vue: 'Vue', '@nestjs': 'NestJS' },
33
+ java: { 'springframework': 'Spring Boot', micronaut: 'Micronaut' },
34
+ kotlin: { 'springframework': 'Spring Boot', ktor: 'Ktor' },
35
+ };
36
+
37
+ /**
38
+ * Scans cwd for language and framework signals.
39
+ * @returns {{ languages: string[], frameworks: string[], signals: string[] }}
40
+ */
41
+ export function detect(cwd = process.cwd()) {
42
+ const languages = new Set();
43
+ const frameworks = new Set();
44
+ const signals = [];
45
+
46
+ for (const { file, lang } of FILE_SIGNALS) {
47
+ if (fs.existsSync(path.join(cwd, file))) {
48
+ languages.add(lang);
49
+ signals.push(file);
50
+ }
51
+ }
52
+
53
+ // Scan cwd + one level deep for extension signals
54
+ try {
55
+ for (const entry of fs.readdirSync(cwd, { withFileTypes: true })) {
56
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
57
+ const ext = path.extname(entry.name).toLowerCase();
58
+ if (EXT_SIGNALS[ext]) languages.add(EXT_SIGNALS[ext]);
59
+ if (entry.isDirectory()) {
60
+ try {
61
+ for (const sub of fs.readdirSync(path.join(cwd, entry.name))) {
62
+ const subExt = path.extname(sub).toLowerCase();
63
+ if (EXT_SIGNALS[subExt]) languages.add(EXT_SIGNALS[subExt]);
64
+ }
65
+ } catch { /* skip */ }
66
+ }
67
+ }
68
+ } catch { /* skip */ }
69
+
70
+ // Detect frameworks from package.json
71
+ const pkgPath = path.join(cwd, 'package.json');
72
+ if (fs.existsSync(pkgPath)) {
73
+ try {
74
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
75
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
76
+ for (const lang of ['javascript', 'typescript']) {
77
+ if (!languages.has(lang)) continue;
78
+ for (const [dep, label] of Object.entries(FRAMEWORK_SIGNALS[lang] ?? {})) {
79
+ if (Object.keys(deps).some(d => d.includes(dep))) frameworks.add(label);
80
+ }
81
+ }
82
+ if (Object.keys(deps).includes('typescript')) languages.add('typescript');
83
+ } catch { /* skip */ }
84
+ }
85
+
86
+ // Detect Python / JVM frameworks from text files
87
+ for (const lang of ['python', 'java', 'kotlin']) {
88
+ if (!languages.has(lang)) continue;
89
+ for (const f of ['requirements.txt', 'pyproject.toml', 'pom.xml', 'build.gradle', 'build.gradle.kts']) {
90
+ try {
91
+ const content = fs.readFileSync(path.join(cwd, f), 'utf8').toLowerCase();
92
+ for (const [dep, label] of Object.entries(FRAMEWORK_SIGNALS[lang] ?? {})) {
93
+ if (content.includes(dep.toLowerCase())) frameworks.add(label);
94
+ }
95
+ } catch { /* skip */ }
96
+ }
97
+ }
98
+
99
+ return { languages: [...languages], frameworks: [...frameworks], signals };
100
+ }