@mseep/core 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/CHANGELOG.md +285 -0
  2. package/LICENSE +21 -0
  3. package/README.ja.md +14 -0
  4. package/README.ko.md +14 -0
  5. package/README.md +227 -0
  6. package/README.pt-BR.md +14 -0
  7. package/README.skills.md +50 -0
  8. package/README.uk.md +14 -0
  9. package/README.zh-CN.md +14 -0
  10. package/bin/booklib-mcp.js +458 -0
  11. package/bin/booklib.js +2394 -0
  12. package/bin/skills.cjs +1292 -0
  13. package/community/registry.json +1616 -0
  14. package/hooks/hooks.json +52 -0
  15. package/hooks/posttooluse-capture.mjs +67 -0
  16. package/hooks/posttooluse-contradict.mjs +76 -0
  17. package/hooks/posttooluse-imports.mjs +67 -0
  18. package/hooks/pretooluse-inject.mjs +82 -0
  19. package/hooks/suggest.js +153 -0
  20. package/lib/agent-detector.js +96 -0
  21. package/lib/config-loader.js +39 -0
  22. package/lib/conflict-resolver.js +148 -0
  23. package/lib/connectors/context7.js +167 -0
  24. package/lib/connectors/github.js +223 -0
  25. package/lib/connectors/local.js +120 -0
  26. package/lib/connectors/notion.js +436 -0
  27. package/lib/connectors/web.js +134 -0
  28. package/lib/context-builder.js +574 -0
  29. package/lib/discovery-engine.js +298 -0
  30. package/lib/doctor/hook-installer.js +83 -0
  31. package/lib/doctor/usage-tracker.js +87 -0
  32. package/lib/engine/auditor.js +103 -0
  33. package/lib/engine/auto-linker.js +177 -0
  34. package/lib/engine/bm25-index.js +178 -0
  35. package/lib/engine/capture.js +120 -0
  36. package/lib/engine/context-map.js +641 -0
  37. package/lib/engine/corrections.js +194 -0
  38. package/lib/engine/decision-checker.js +203 -0
  39. package/lib/engine/doctor.js +207 -0
  40. package/lib/engine/embedding-provider.js +72 -0
  41. package/lib/engine/gap-detector.js +138 -0
  42. package/lib/engine/gap-resolver.js +135 -0
  43. package/lib/engine/graph-injector.js +137 -0
  44. package/lib/engine/graph-search.js +183 -0
  45. package/lib/engine/graph.js +170 -0
  46. package/lib/engine/handoff.js +411 -0
  47. package/lib/engine/import-checker.js +249 -0
  48. package/lib/engine/import-parser.js +145 -0
  49. package/lib/engine/indexer.js +334 -0
  50. package/lib/engine/lookup-priority.js +15 -0
  51. package/lib/engine/parser.js +257 -0
  52. package/lib/engine/principle-extractor.js +116 -0
  53. package/lib/engine/project-analyzer.js +353 -0
  54. package/lib/engine/query-expander.js +42 -0
  55. package/lib/engine/reasoning-modes.js +353 -0
  56. package/lib/engine/registries.js +524 -0
  57. package/lib/engine/reranker.js +45 -0
  58. package/lib/engine/rrf.js +59 -0
  59. package/lib/engine/scanner.js +151 -0
  60. package/lib/engine/searcher.js +223 -0
  61. package/lib/engine/session-coordinator.js +291 -0
  62. package/lib/engine/session-manager.js +375 -0
  63. package/lib/engine/source-detector.js +240 -0
  64. package/lib/engine/source-manager.js +142 -0
  65. package/lib/engine/structured-response.js +47 -0
  66. package/lib/engine/synthesis-templates.js +364 -0
  67. package/lib/installer.js +70 -0
  68. package/lib/instinct-block.js +21 -0
  69. package/lib/mcp-config-writer.js +107 -0
  70. package/lib/paths.js +62 -0
  71. package/lib/project-initializer.js +856 -0
  72. package/lib/registry/skills.js +102 -0
  73. package/lib/registry-searcher.js +107 -0
  74. package/lib/rules/rules-manager.js +169 -0
  75. package/lib/skill-fetcher.js +333 -0
  76. package/lib/well-known-builder.js +74 -0
  77. package/lib/wizard/index.js +1389 -0
  78. package/lib/wizard/integration-detector.js +41 -0
  79. package/lib/wizard/project-detector.js +146 -0
  80. package/lib/wizard/prompt.js +221 -0
  81. package/lib/wizard/registry-embeddings.js +107 -0
  82. package/lib/wizard/skill-recommender.js +69 -0
  83. package/package.json +70 -0
  84. package/skills/animation-at-work/SKILL.md +270 -0
  85. package/skills/animation-at-work/assets/example_asset.txt +1 -0
  86. package/skills/animation-at-work/evals/evals.json +44 -0
  87. package/skills/animation-at-work/evals/results.json +13 -0
  88. package/skills/animation-at-work/examples/after.md +64 -0
  89. package/skills/animation-at-work/examples/before.md +35 -0
  90. package/skills/animation-at-work/references/api_reference.md +369 -0
  91. package/skills/animation-at-work/references/review-checklist.md +79 -0
  92. package/skills/animation-at-work/scripts/audit_animations.py +295 -0
  93. package/skills/animation-at-work/scripts/example.py +1 -0
  94. package/skills/booklib-mcp-guide/SKILL.md +129 -0
  95. package/skills/booklib-mcp-guide/evals/evals.json +37 -0
  96. package/skills/booklib-mcp-guide/examples/after.md +34 -0
  97. package/skills/booklib-mcp-guide/examples/before.md +27 -0
  98. package/skills/booklib-mcp-guide/references/tool-catalog.md +9 -0
  99. package/skills/clean-code-reviewer/SKILL.md +444 -0
  100. package/skills/clean-code-reviewer/audit.json +35 -0
  101. package/skills/clean-code-reviewer/evals/evals.json +185 -0
  102. package/skills/clean-code-reviewer/evals/results.json +13 -0
  103. package/skills/clean-code-reviewer/examples/after.md +48 -0
  104. package/skills/clean-code-reviewer/examples/before.md +33 -0
  105. package/skills/clean-code-reviewer/references/api_reference.md +158 -0
  106. package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
  107. package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
  108. package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
  109. package/skills/data-intensive-patterns/SKILL.md +267 -0
  110. package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
  111. package/skills/data-intensive-patterns/evals/evals.json +54 -0
  112. package/skills/data-intensive-patterns/evals/results.json +13 -0
  113. package/skills/data-intensive-patterns/examples/after.md +61 -0
  114. package/skills/data-intensive-patterns/examples/before.md +38 -0
  115. package/skills/data-intensive-patterns/references/api_reference.md +34 -0
  116. package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
  117. package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
  118. package/skills/data-intensive-patterns/scripts/adr.py +213 -0
  119. package/skills/data-intensive-patterns/scripts/example.py +1 -0
  120. package/skills/data-pipelines/SKILL.md +259 -0
  121. package/skills/data-pipelines/assets/example_asset.txt +1 -0
  122. package/skills/data-pipelines/evals/evals.json +45 -0
  123. package/skills/data-pipelines/evals/results.json +13 -0
  124. package/skills/data-pipelines/examples/after.md +97 -0
  125. package/skills/data-pipelines/examples/before.md +37 -0
  126. package/skills/data-pipelines/references/api_reference.md +301 -0
  127. package/skills/data-pipelines/references/review-checklist.md +181 -0
  128. package/skills/data-pipelines/scripts/example.py +1 -0
  129. package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
  130. package/skills/design-patterns/SKILL.md +271 -0
  131. package/skills/design-patterns/assets/example_asset.txt +1 -0
  132. package/skills/design-patterns/evals/evals.json +46 -0
  133. package/skills/design-patterns/evals/results.json +13 -0
  134. package/skills/design-patterns/examples/after.md +52 -0
  135. package/skills/design-patterns/examples/before.md +29 -0
  136. package/skills/design-patterns/references/api_reference.md +1 -0
  137. package/skills/design-patterns/references/patterns-catalog.md +726 -0
  138. package/skills/design-patterns/references/review-checklist.md +173 -0
  139. package/skills/design-patterns/scripts/example.py +1 -0
  140. package/skills/design-patterns/scripts/scaffold.py +807 -0
  141. package/skills/domain-driven-design/SKILL.md +142 -0
  142. package/skills/domain-driven-design/assets/example_asset.txt +1 -0
  143. package/skills/domain-driven-design/evals/evals.json +48 -0
  144. package/skills/domain-driven-design/evals/results.json +13 -0
  145. package/skills/domain-driven-design/examples/after.md +80 -0
  146. package/skills/domain-driven-design/examples/before.md +43 -0
  147. package/skills/domain-driven-design/references/api_reference.md +1 -0
  148. package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
  149. package/skills/domain-driven-design/references/review-checklist.md +158 -0
  150. package/skills/domain-driven-design/scripts/example.py +1 -0
  151. package/skills/domain-driven-design/scripts/scaffold.py +421 -0
  152. package/skills/effective-java/SKILL.md +227 -0
  153. package/skills/effective-java/assets/example_asset.txt +1 -0
  154. package/skills/effective-java/evals/evals.json +46 -0
  155. package/skills/effective-java/evals/results.json +13 -0
  156. package/skills/effective-java/examples/after.md +83 -0
  157. package/skills/effective-java/examples/before.md +37 -0
  158. package/skills/effective-java/references/api_reference.md +1 -0
  159. package/skills/effective-java/references/items-catalog.md +955 -0
  160. package/skills/effective-java/references/review-checklist.md +216 -0
  161. package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
  162. package/skills/effective-java/scripts/example.py +1 -0
  163. package/skills/effective-kotlin/SKILL.md +271 -0
  164. package/skills/effective-kotlin/assets/example_asset.txt +1 -0
  165. package/skills/effective-kotlin/audit.json +29 -0
  166. package/skills/effective-kotlin/evals/evals.json +45 -0
  167. package/skills/effective-kotlin/evals/results.json +13 -0
  168. package/skills/effective-kotlin/examples/after.md +36 -0
  169. package/skills/effective-kotlin/examples/before.md +38 -0
  170. package/skills/effective-kotlin/references/api_reference.md +1 -0
  171. package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
  172. package/skills/effective-kotlin/references/review-checklist.md +126 -0
  173. package/skills/effective-kotlin/scripts/example.py +1 -0
  174. package/skills/effective-python/SKILL.md +441 -0
  175. package/skills/effective-python/evals/evals.json +44 -0
  176. package/skills/effective-python/evals/results.json +13 -0
  177. package/skills/effective-python/examples/after.md +56 -0
  178. package/skills/effective-python/examples/before.md +40 -0
  179. package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
  180. package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
  181. package/skills/effective-python/ref-03-functions.md +186 -0
  182. package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
  183. package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
  184. package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
  185. package/skills/effective-python/ref-07-concurrency.md +213 -0
  186. package/skills/effective-python/ref-08-robustness-performance.md +248 -0
  187. package/skills/effective-python/ref-09-testing-debugging.md +253 -0
  188. package/skills/effective-python/ref-10-collaboration.md +175 -0
  189. package/skills/effective-python/references/api_reference.md +218 -0
  190. package/skills/effective-python/references/practices-catalog.md +483 -0
  191. package/skills/effective-python/references/review-checklist.md +190 -0
  192. package/skills/effective-python/scripts/lint.py +173 -0
  193. package/skills/effective-typescript/SKILL.md +262 -0
  194. package/skills/effective-typescript/audit.json +29 -0
  195. package/skills/effective-typescript/evals/evals.json +37 -0
  196. package/skills/effective-typescript/evals/results.json +13 -0
  197. package/skills/effective-typescript/examples/after.md +70 -0
  198. package/skills/effective-typescript/examples/before.md +47 -0
  199. package/skills/effective-typescript/references/api_reference.md +118 -0
  200. package/skills/effective-typescript/references/practices-catalog.md +371 -0
  201. package/skills/effective-typescript/scripts/review.py +169 -0
  202. package/skills/kotlin-in-action/SKILL.md +261 -0
  203. package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
  204. package/skills/kotlin-in-action/evals/evals.json +43 -0
  205. package/skills/kotlin-in-action/evals/results.json +13 -0
  206. package/skills/kotlin-in-action/examples/after.md +53 -0
  207. package/skills/kotlin-in-action/examples/before.md +39 -0
  208. package/skills/kotlin-in-action/references/api_reference.md +1 -0
  209. package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
  210. package/skills/kotlin-in-action/references/review-checklist.md +204 -0
  211. package/skills/kotlin-in-action/scripts/example.py +1 -0
  212. package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
  213. package/skills/lean-startup/SKILL.md +160 -0
  214. package/skills/lean-startup/assets/example_asset.txt +1 -0
  215. package/skills/lean-startup/evals/evals.json +43 -0
  216. package/skills/lean-startup/evals/results.json +13 -0
  217. package/skills/lean-startup/examples/after.md +80 -0
  218. package/skills/lean-startup/examples/before.md +34 -0
  219. package/skills/lean-startup/references/api_reference.md +319 -0
  220. package/skills/lean-startup/references/review-checklist.md +137 -0
  221. package/skills/lean-startup/scripts/example.py +1 -0
  222. package/skills/lean-startup/scripts/new_experiment.py +286 -0
  223. package/skills/microservices-patterns/SKILL.md +384 -0
  224. package/skills/microservices-patterns/evals/evals.json +45 -0
  225. package/skills/microservices-patterns/evals/results.json +13 -0
  226. package/skills/microservices-patterns/examples/after.md +69 -0
  227. package/skills/microservices-patterns/examples/before.md +40 -0
  228. package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
  229. package/skills/microservices-patterns/references/review-checklist.md +169 -0
  230. package/skills/microservices-patterns/scripts/new_service.py +583 -0
  231. package/skills/programming-with-rust/SKILL.md +209 -0
  232. package/skills/programming-with-rust/evals/evals.json +37 -0
  233. package/skills/programming-with-rust/evals/results.json +13 -0
  234. package/skills/programming-with-rust/examples/after.md +107 -0
  235. package/skills/programming-with-rust/examples/before.md +59 -0
  236. package/skills/programming-with-rust/references/api_reference.md +152 -0
  237. package/skills/programming-with-rust/references/practices-catalog.md +335 -0
  238. package/skills/programming-with-rust/scripts/review.py +142 -0
  239. package/skills/refactoring-ui/SKILL.md +362 -0
  240. package/skills/refactoring-ui/assets/example_asset.txt +1 -0
  241. package/skills/refactoring-ui/evals/evals.json +45 -0
  242. package/skills/refactoring-ui/evals/results.json +13 -0
  243. package/skills/refactoring-ui/examples/after.md +85 -0
  244. package/skills/refactoring-ui/examples/before.md +58 -0
  245. package/skills/refactoring-ui/references/api_reference.md +355 -0
  246. package/skills/refactoring-ui/references/review-checklist.md +114 -0
  247. package/skills/refactoring-ui/scripts/audit_css.py +250 -0
  248. package/skills/refactoring-ui/scripts/example.py +1 -0
  249. package/skills/rust-in-action/SKILL.md +350 -0
  250. package/skills/rust-in-action/evals/evals.json +38 -0
  251. package/skills/rust-in-action/evals/results.json +13 -0
  252. package/skills/rust-in-action/examples/after.md +156 -0
  253. package/skills/rust-in-action/examples/before.md +56 -0
  254. package/skills/rust-in-action/references/practices-catalog.md +346 -0
  255. package/skills/rust-in-action/scripts/review.py +147 -0
  256. package/skills/skill-router/SKILL.md +186 -0
  257. package/skills/skill-router/evals/evals.json +38 -0
  258. package/skills/skill-router/evals/results.json +13 -0
  259. package/skills/skill-router/examples/after.md +63 -0
  260. package/skills/skill-router/examples/before.md +39 -0
  261. package/skills/skill-router/references/api_reference.md +24 -0
  262. package/skills/skill-router/references/routing-heuristics.md +89 -0
  263. package/skills/skill-router/references/skill-catalog.md +174 -0
  264. package/skills/skill-router/scripts/route.py +266 -0
  265. package/skills/spring-boot-in-action/SKILL.md +340 -0
  266. package/skills/spring-boot-in-action/evals/evals.json +39 -0
  267. package/skills/spring-boot-in-action/evals/results.json +13 -0
  268. package/skills/spring-boot-in-action/examples/after.md +185 -0
  269. package/skills/spring-boot-in-action/examples/before.md +84 -0
  270. package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
  271. package/skills/spring-boot-in-action/scripts/review.py +184 -0
  272. package/skills/storytelling-with-data/SKILL.md +241 -0
  273. package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
  274. package/skills/storytelling-with-data/evals/evals.json +47 -0
  275. package/skills/storytelling-with-data/evals/results.json +13 -0
  276. package/skills/storytelling-with-data/examples/after.md +50 -0
  277. package/skills/storytelling-with-data/examples/before.md +33 -0
  278. package/skills/storytelling-with-data/references/api_reference.md +379 -0
  279. package/skills/storytelling-with-data/references/review-checklist.md +111 -0
  280. package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
  281. package/skills/storytelling-with-data/scripts/example.py +1 -0
  282. package/skills/system-design-interview/SKILL.md +233 -0
  283. package/skills/system-design-interview/assets/example_asset.txt +1 -0
  284. package/skills/system-design-interview/evals/evals.json +46 -0
  285. package/skills/system-design-interview/evals/results.json +13 -0
  286. package/skills/system-design-interview/examples/after.md +94 -0
  287. package/skills/system-design-interview/examples/before.md +27 -0
  288. package/skills/system-design-interview/references/api_reference.md +582 -0
  289. package/skills/system-design-interview/references/review-checklist.md +201 -0
  290. package/skills/system-design-interview/scripts/example.py +1 -0
  291. package/skills/system-design-interview/scripts/new_design.py +421 -0
  292. package/skills/using-asyncio-python/SKILL.md +290 -0
  293. package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
  294. package/skills/using-asyncio-python/evals/evals.json +43 -0
  295. package/skills/using-asyncio-python/evals/results.json +13 -0
  296. package/skills/using-asyncio-python/examples/after.md +68 -0
  297. package/skills/using-asyncio-python/examples/before.md +39 -0
  298. package/skills/using-asyncio-python/references/api_reference.md +267 -0
  299. package/skills/using-asyncio-python/references/review-checklist.md +149 -0
  300. package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
  301. package/skills/using-asyncio-python/scripts/example.py +1 -0
  302. package/skills/web-scraping-python/SKILL.md +280 -0
  303. package/skills/web-scraping-python/assets/example_asset.txt +1 -0
  304. package/skills/web-scraping-python/evals/evals.json +46 -0
  305. package/skills/web-scraping-python/evals/results.json +13 -0
  306. package/skills/web-scraping-python/examples/after.md +109 -0
  307. package/skills/web-scraping-python/examples/before.md +40 -0
  308. package/skills/web-scraping-python/references/api_reference.md +393 -0
  309. package/skills/web-scraping-python/references/review-checklist.md +163 -0
  310. package/skills/web-scraping-python/scripts/example.py +1 -0
  311. package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
  312. package/skills/writing-plans/audit.json +34 -0
@@ -0,0 +1,52 @@
1
+ {
2
+ "UserPromptSubmit": [
3
+ {
4
+ "hooks": [
5
+ {
6
+ "type": "command",
7
+ "command": "node \"$HOME/.claude/booklib-suggest.js\""
8
+ }
9
+ ]
10
+ }
11
+ ],
12
+ "PreToolUse": [
13
+ {
14
+ "matcher": "Edit|Write",
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "node \"${BOOKLIB_ROOT}/hooks/pretooluse-inject.mjs\""
19
+ }
20
+ ]
21
+ }
22
+ ],
23
+ "PostToolUse": [
24
+ {
25
+ "matcher": "WebFetch|WebSearch",
26
+ "hooks": [
27
+ {
28
+ "type": "command",
29
+ "command": "node \"${BOOKLIB_ROOT}/hooks/posttooluse-capture.mjs\""
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ "matcher": "Edit|Write",
35
+ "hooks": [
36
+ {
37
+ "type": "command",
38
+ "command": "node \"${BOOKLIB_ROOT}/hooks/posttooluse-contradict.mjs\""
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ "matcher": "Edit|Write",
44
+ "hooks": [
45
+ {
46
+ "type": "command",
47
+ "command": "node \"${BOOKLIB_ROOT}/hooks/posttooluse-imports.mjs\""
48
+ }
49
+ ]
50
+ }
51
+ ]
52
+ }
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ // PostToolUse hook — suggests saving knowledge after WebFetch or WebSearch.
3
+ // Reads tool info from stdin (JSON), writes a hint to stdout when relevant.
4
+
5
+ "use strict";
6
+
7
+ process.exitCode = 0;
8
+
9
+ let input = '';
10
+ process.stdin.setEncoding('utf8');
11
+ process.stdin.on('data', chunk => { input += chunk; });
12
+ process.stdin.on('end', () => {
13
+ let toolName = '';
14
+ let toolInput = {};
15
+
16
+ try {
17
+ const parsed = JSON.parse(input);
18
+ toolName = parsed.tool_name ?? parsed.toolName ?? '';
19
+ toolInput = parsed.tool_input ?? parsed.toolInput ?? {};
20
+ } catch {
21
+ process.exit(0);
22
+ }
23
+
24
+ const captureTools = ['WebFetch', 'WebSearch', 'web_fetch', 'web_search'];
25
+ if (!captureTools.includes(toolName)) process.exit(0);
26
+
27
+ const isSearch = ['WebSearch', 'web_search'].includes(toolName);
28
+ const url = toolInput.url ?? '';
29
+ const query = toolInput.query ?? toolInput.input ?? '';
30
+
31
+ let suggestedTitle;
32
+ let sourceDesc;
33
+
34
+ if (isSearch && query) {
35
+ suggestedTitle = query.slice(0, 60);
36
+ sourceDesc = `search: "${query}"`;
37
+ } else if (url) {
38
+ try {
39
+ const u = new URL(url);
40
+ const lastSegment = u.pathname.split('/').filter(Boolean).pop() ?? '';
41
+ const readable = lastSegment.replace(/[-_]/g, ' ').replace(/\.\w+$/, '').trim();
42
+ suggestedTitle = (readable ? `${readable} (${u.hostname})` : u.hostname).slice(0, 60);
43
+ } catch {
44
+ suggestedTitle = url.slice(0, 60);
45
+ }
46
+ sourceDesc = url;
47
+ } else {
48
+ process.stdout.write('\n[booklib] To save what you found: booklib note "<title>"\n');
49
+ process.exit(0);
50
+ }
51
+
52
+ // Strip shell metacharacters so the displayed command is always safe to copy-paste
53
+ const safeTitle = suggestedTitle.replace(/["$`\\]/g, '');
54
+
55
+ const hint = [
56
+ '',
57
+ `[booklib] Knowledge capture — ${sourceDesc}`,
58
+ ` Save what you found:`,
59
+ ` echo "paste key findings here" | booklib note "${safeTitle}"`,
60
+ ` Or create a research template:`,
61
+ ` booklib research "${safeTitle}"`,
62
+ '',
63
+ ].join('\n');
64
+
65
+ process.stdout.write(hint);
66
+ process.exit(0);
67
+ });
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ // PostToolUse hook — checks written code against context map constraints
3
+ // after Edit/Write tool use. Outputs a hint when contradictions are found.
4
+
5
+ import { readFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { ContextMapMatcher } from '../lib/engine/context-map.js';
8
+
9
+ /**
10
+ * Format contradictions into a warning hint.
11
+ * @param {Array<{id: string, constraint: string, example: string|null, source: string|null}>} contradictions
12
+ * @returns {string}
13
+ */
14
+ export function formatContradiction(contradictions) {
15
+ if (!contradictions?.length) return '';
16
+
17
+ const lines = ['[BookLib] Contradiction detected:'];
18
+
19
+ for (const v of contradictions) {
20
+ lines.push(` ${v.constraint}`);
21
+ if (v.example) {
22
+ lines.push(` Fix: ${v.example}`);
23
+ }
24
+ }
25
+
26
+ return lines.join('\n');
27
+ }
28
+
29
+ // Only run stdin reading when executed as a script, not when imported for testing
30
+ if (process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/'))) {
31
+ process.exitCode = 0;
32
+
33
+ let input = '';
34
+ process.stdin.setEncoding('utf8');
35
+ process.stdin.on('data', chunk => { input += chunk; });
36
+ process.stdin.on('end', async () => {
37
+ try {
38
+ const parsed = JSON.parse(input);
39
+ const toolName = parsed.tool_name ?? parsed.toolName ?? '';
40
+ const toolInput = parsed.tool_input ?? parsed.toolInput ?? {};
41
+
42
+ const writeTools = ['Write', 'Edit', 'write', 'edit'];
43
+ if (!writeTools.includes(toolName)) process.exit(0);
44
+
45
+ const filePath = toolInput.file_path ?? toolInput.filePath ?? '';
46
+ if (!filePath) process.exit(0);
47
+
48
+ const mapPath = join(process.cwd(), '.booklib', 'context-map.json');
49
+ const raw = readFileSync(mapPath, 'utf8');
50
+ const map = JSON.parse(raw);
51
+ if (!map?.items?.length) process.exit(0);
52
+
53
+ const newCode = toolInput.new_string ?? toolInput.content ?? '';
54
+ if (!newCode) process.exit(0);
55
+
56
+ const importRe = /from\s+['"]([^'"]+)['"]/g;
57
+ const imports = [];
58
+ let match;
59
+ while ((match = importRe.exec(newCode)) !== null) {
60
+ imports.push(match[1]);
61
+ }
62
+
63
+ const matcher = new ContextMapMatcher(map.items);
64
+ const matched = matcher.match(filePath, newCode, imports);
65
+ const contradictions = matcher.checkContradictions(newCode, matched);
66
+
67
+ if (contradictions.length > 0) {
68
+ process.stdout.write(formatContradiction(contradictions));
69
+ }
70
+ } catch {
71
+ // Best effort — don't break the hook chain
72
+ }
73
+
74
+ process.exit(0);
75
+ });
76
+ }
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ // PostToolUse hook — checks imports after Write/Edit tool use.
3
+ // Reads tool info from stdin (JSON), runs ImportChecker on written files,
4
+ // outputs a hint when unknown imports are found.
5
+
6
+ import path from 'node:path';
7
+ import { detectLanguage } from '../lib/engine/import-parser.js';
8
+
9
+ process.exitCode = 0;
10
+
11
+ let input = '';
12
+ process.stdin.setEncoding('utf8');
13
+ process.stdin.on('data', chunk => { input += chunk; });
14
+ process.stdin.on('end', async () => {
15
+ let toolName = '';
16
+ let toolInput = {};
17
+
18
+ try {
19
+ const parsed = JSON.parse(input);
20
+ toolName = parsed.tool_name ?? parsed.toolName ?? '';
21
+ toolInput = parsed.tool_input ?? parsed.toolInput ?? {};
22
+ } catch {
23
+ process.exit(0);
24
+ }
25
+
26
+ const writeTools = ['Write', 'Edit', 'write', 'edit'];
27
+ if (!writeTools.includes(toolName)) process.exit(0);
28
+
29
+ const filePath = toolInput.file_path ?? toolInput.filePath ?? '';
30
+ if (!filePath) process.exit(0);
31
+
32
+ // Use the parser's language detection instead of duplicating extension list
33
+ if (!detectLanguage(filePath)) process.exit(0);
34
+
35
+ try {
36
+ const { ImportChecker } = await import('../lib/engine/import-checker.js');
37
+ const checker = new ImportChecker();
38
+ const result = await checker.checkFile(filePath);
39
+
40
+ if (result.unknown.length === 0) process.exit(0);
41
+
42
+ const lines = [
43
+ '',
44
+ `[booklib] ${result.unknown.length} unknown import(s) in ${path.basename(filePath)}:`,
45
+ ];
46
+
47
+ for (const imp of result.unknown.slice(0, 3)) {
48
+ lines.push(` ${imp.module} — not in BookLib index`);
49
+ }
50
+ if (result.unknown.length > 3) {
51
+ lines.push(` ... and ${result.unknown.length - 3} more`);
52
+ }
53
+
54
+ lines.push(` Run: booklib check-imports ${filePath}`);
55
+ lines.push('');
56
+ process.stdout.write(lines.join('\n'));
57
+ } catch {
58
+ // Best effort — don't break the hook chain
59
+ }
60
+
61
+ // Future integration point: decision contradiction checking.
62
+ // DecisionChecker requires a BookLibSearcher with the embedding model loaded,
63
+ // which is too heavy for a PostToolUse hook. Will activate when hooks can
64
+ // access a persistent searcher instance (e.g., via MCP or shared process).
65
+
66
+ process.exit(0);
67
+ });
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse hook — injects relevant context from the BookLib context map
3
+ // before Edit/Write tool use. Reads tool info from stdin (JSON), matches
4
+ // file + code against context map, outputs injection hints.
5
+
6
+ import { readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { ContextMapMatcher } from '../lib/engine/context-map.js';
9
+
10
+ /**
11
+ * Format matched context map items into an injection hint.
12
+ * @param {Array<{injection: {correction: string|null, constraint: string|null, example: string|null}, source?: string|null}>} items
13
+ * @returns {string}
14
+ */
15
+ export function formatInjection(items) {
16
+ if (!items?.length) return '';
17
+
18
+ const lines = ['[BookLib] Context for this edit:'];
19
+
20
+ for (const item of items) {
21
+ const { correction, constraint, example } = item.injection ?? {};
22
+
23
+ if (correction) {
24
+ lines.push(` ${correction}`);
25
+ } else if (constraint) {
26
+ const team = item.source ? `${item.source}: ` : '';
27
+ lines.push(` ${team}${constraint}`);
28
+ }
29
+
30
+ if (example) {
31
+ lines.push(` ${example}`);
32
+ }
33
+ }
34
+
35
+ return lines.join('\n');
36
+ }
37
+
38
+ // Only run stdin reading when executed as a script, not when imported for testing
39
+ if (process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/'))) {
40
+ process.exitCode = 0;
41
+
42
+ let input = '';
43
+ process.stdin.setEncoding('utf8');
44
+ process.stdin.on('data', chunk => { input += chunk; });
45
+ process.stdin.on('end', async () => {
46
+ try {
47
+ const parsed = JSON.parse(input);
48
+ const toolName = parsed.tool_name ?? parsed.toolName ?? '';
49
+ const toolInput = parsed.tool_input ?? parsed.toolInput ?? {};
50
+
51
+ const writeTools = ['Write', 'Edit', 'write', 'edit'];
52
+ if (!writeTools.includes(toolName)) process.exit(0);
53
+
54
+ const filePath = toolInput.file_path ?? toolInput.filePath ?? '';
55
+ if (!filePath) process.exit(0);
56
+
57
+ const mapPath = join(process.cwd(), '.booklib', 'context-map.json');
58
+ const raw = readFileSync(mapPath, 'utf8');
59
+ const map = JSON.parse(raw);
60
+ if (!map?.items?.length) process.exit(0);
61
+
62
+ const fileContent = toolInput.old_string ?? toolInput.content ?? '';
63
+ const importRe = /from\s+['"]([^'"]+)['"]/g;
64
+ const imports = [];
65
+ let match;
66
+ while ((match = importRe.exec(fileContent)) !== null) {
67
+ imports.push(match[1]);
68
+ }
69
+
70
+ const matcher = new ContextMapMatcher(map.items);
71
+ const matched = matcher.match(filePath, fileContent, imports);
72
+
73
+ if (matched.length > 0) {
74
+ process.stdout.write(formatInjection(matched));
75
+ }
76
+ } catch {
77
+ // Best effort — don't break the hook chain
78
+ }
79
+
80
+ process.exit(0);
81
+ });
82
+ }
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * booklib-suggest.js
4
+ * Claude Code UserPromptSubmit hook — suggests a relevant booklib skill
5
+ * when the prompt contains a review intent AND a language/domain signal.
6
+ *
7
+ * Install: copy (or symlink) this file to ~/.claude/booklib-suggest.js
8
+ * Hook config (hooks.json):
9
+ * { "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "node \"$HOME/.claude/booklib-suggest.js\"" }] }] }
10
+ */
11
+
12
+ "use strict";
13
+
14
+ process.exitCode = 0;
15
+
16
+ const REVIEW_KEYWORDS = [
17
+ "review", "check", "improve", "refactor", "fix", "audit",
18
+ "analyse", "analyze", "critique", "lint",
19
+ ];
20
+
21
+ const LANGUAGE_SIGNALS = {
22
+ python_async: [".py", "asyncio", "async def", "await "],
23
+ python_scraping: [".py", "python", "beautifulsoup", "scrapy", "requests.get", "web scraping"],
24
+ python: [".py", "python", "def ", "async def", "import ", "asyncio", "beautifulsoup", "scrapy"],
25
+ typescript: [".ts", ".tsx", ".js", "typescript", "interface ", "type ", "const ", "function "],
26
+ java: [".java", "java", "class ", "@override", "public static"],
27
+ kotlin: [".kt", "kotlin", "fun ", "val ", "var ", "data class"],
28
+ rust: [".rs", "rust", "fn ", "impl ", "struct ", "enum ", "let mut"],
29
+ ui_animation: [".css", ".scss", "animation", "transition", "@keyframes", "styled", "tailwind"],
30
+ ui: [".css", ".scss", "animation", "transition", "@keyframes", "styled", "tailwind"],
31
+ data: ["pipeline", "etl", "dataframe", "schema", "migration", "replication"],
32
+ architecture: ["microservice", "aggregate", "bounded context", "saga", "event sourcing"],
33
+ };
34
+
35
+ const SKILL_MAP = {
36
+ python_async: { skill: "/using-asyncio-python", reason: "Async patterns and asyncio best practices in Python" },
37
+ python_scraping: { skill: "/web-scraping-python", reason: "Web scraping techniques and patterns with Python" },
38
+ python: { skill: "/effective-python", reason: "Pythonic idioms and best practices" },
39
+ typescript: { skill: "/effective-typescript", reason: "TypeScript type safety and idiomatic patterns" },
40
+ java: { skill: "/effective-java", reason: "Effective Java patterns and API design" },
41
+ kotlin: { skill: "/effective-kotlin", reason: "Idiomatic Kotlin and best practices" },
42
+ rust: { skill: "/programming-with-rust", reason: "Rust ownership, safety, and idiomatic patterns" },
43
+ ui_animation: { skill: "/animation-at-work", reason: "Web animation principles and best practices" },
44
+ ui: { skill: "/refactoring-ui", reason: "UI design principles and visual hierarchy" },
45
+ data: { skill: "/data-pipelines", reason: "Data pipeline design and ETL best practices" },
46
+ architecture: { skill: "/skill-router", reason: "Routes to the right skill for architecture concerns" },
47
+ };
48
+
49
+ function extractPrompt(raw) {
50
+ if (!raw || raw.trim() === "") return "";
51
+ try {
52
+ const parsed = JSON.parse(raw);
53
+ // Claude Code may send { prompt: "..." } or { message: "..." }
54
+ if (parsed && typeof parsed === "object") {
55
+ return String(parsed.prompt || parsed.message || parsed.text || "");
56
+ }
57
+ } catch (_) {
58
+ // Not JSON — treat as raw prompt text
59
+ }
60
+ return raw;
61
+ }
62
+
63
+ function hasReviewIntent(text) {
64
+ const lower = text.toLowerCase();
65
+ return REVIEW_KEYWORDS.some((kw) => lower.includes(kw));
66
+ }
67
+
68
+ /**
69
+ * Returns the most specific language key that matches, or null.
70
+ * Order matters: more specific keys (python_async, python_scraping) are checked first.
71
+ */
72
+ function detectLanguage(text) {
73
+ const lower = text.toLowerCase();
74
+
75
+ const orderedKeys = [
76
+ "python_async",
77
+ "python_scraping",
78
+ "python",
79
+ "typescript",
80
+ "java",
81
+ "kotlin",
82
+ "rust",
83
+ "ui_animation",
84
+ "ui",
85
+ "data",
86
+ "architecture",
87
+ ];
88
+
89
+ for (const key of orderedKeys) {
90
+ const signals = LANGUAGE_SIGNALS[key];
91
+ // For compound keys, require at least 2 signals to avoid false positives
92
+ // (e.g. python_async needs both a python marker AND an async marker)
93
+ if (key === "python_async") {
94
+ const hasPython = [".py", "python", "def ", "import "].some((s) => lower.includes(s));
95
+ const hasAsync = ["asyncio", "async def", "await "].some((s) => lower.includes(s));
96
+ if (hasPython && hasAsync) return key;
97
+ continue;
98
+ }
99
+ if (key === "python_scraping") {
100
+ const hasPython = [".py", "python", "def ", "import "].some((s) => lower.includes(s));
101
+ const hasScraping = ["beautifulsoup", "scrapy", "web scraping", "requests.get"].some((s) => lower.includes(s));
102
+ if (hasPython && hasScraping) return key;
103
+ continue;
104
+ }
105
+ if (signals.some((s) => lower.includes(s.toLowerCase()))) {
106
+ return key;
107
+ }
108
+ }
109
+
110
+ return null;
111
+ }
112
+
113
+ function main() {
114
+ let raw = "";
115
+ try {
116
+ // Read all of stdin synchronously
117
+ const fd = require("fs").openSync("/dev/stdin", "r");
118
+ const chunks = [];
119
+ const buf = Buffer.alloc(4096);
120
+ let bytesRead;
121
+ while ((bytesRead = require("fs").readSync(fd, buf, 0, buf.length, null)) > 0) {
122
+ chunks.push(buf.slice(0, bytesRead));
123
+ }
124
+ require("fs").closeSync(fd);
125
+ raw = Buffer.concat(chunks).toString("utf8");
126
+ } catch (_) {
127
+ // stdin unavailable or empty — exit silently
128
+ process.exit(0);
129
+ }
130
+
131
+ const prompt = extractPrompt(raw);
132
+ if (!prompt) process.exit(0);
133
+
134
+ if (!hasReviewIntent(prompt)) process.exit(0);
135
+
136
+ const langKey = detectLanguage(prompt);
137
+
138
+ if (!langKey) {
139
+ // Review intent but no specific language — suggest clean-code-reviewer
140
+ process.stdout.write(
141
+ "💡 booklib: try /clean-code-reviewer — General clean code review principles\n"
142
+ );
143
+ process.exit(0);
144
+ }
145
+
146
+ const match = SKILL_MAP[langKey];
147
+ if (!match) process.exit(0);
148
+
149
+ process.stdout.write(`💡 booklib: try ${match.skill} — ${match.reason}\n`);
150
+ process.exit(0);
151
+ }
152
+
153
+ main();
@@ -0,0 +1,96 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { execSync } from 'node:child_process';
5
+
6
+ /**
7
+ * Detects which AI coding agents are present in the project or on $PATH.
8
+ * Always returns at least ['claude'].
9
+ */
10
+ export class AgentDetector {
11
+ /**
12
+ * @param {object} opts
13
+ * @param {string} [opts.cwd] - project root to check for config dirs/files
14
+ * @param {boolean} [opts.checkPath] - whether to check $PATH (default true)
15
+ */
16
+ constructor({ cwd = process.cwd(), checkPath = true, home } = {}) {
17
+ this.cwd = cwd;
18
+ this.checkPath = checkPath;
19
+ this.home = home ?? os.homedir();
20
+ }
21
+
22
+ detect() {
23
+ const found = new Set(['claude']); // always present
24
+
25
+ const DIR_SIGNALS = {
26
+ cursor: ['.cursor'],
27
+ 'roo-code': ['.roo'],
28
+ openhands: ['.openhands'],
29
+ junie: ['.junie'],
30
+ goose: ['.goose'],
31
+ letta: ['.letta'],
32
+ windsurf: ['.windsurf'],
33
+ gemini: ['.gemini'],
34
+ };
35
+
36
+ const FILE_SIGNALS = {
37
+ opencode: ['opencode.toml'],
38
+ copilot: ['.github/copilot-instructions.md'],
39
+ };
40
+
41
+ const EXTENSION_SIGNALS = {
42
+ copilot: ['github.copilot', 'github.copilot-chat'],
43
+ };
44
+
45
+ const PATH_SIGNALS = {
46
+ cursor: 'cursor',
47
+ codex: 'codex',
48
+ windsurf: 'windsurf',
49
+ gemini: 'gemini',
50
+ goose: 'goose',
51
+ opencode: 'opencode',
52
+ };
53
+
54
+ for (const [agent, dirs] of Object.entries(DIR_SIGNALS)) {
55
+ if (dirs.some(dir => fs.existsSync(path.join(this.cwd, dir)))) {
56
+ found.add(agent);
57
+ }
58
+ }
59
+
60
+ for (const [agent, files] of Object.entries(FILE_SIGNALS)) {
61
+ if (files.some(file => fs.existsSync(path.join(this.cwd, file)))) {
62
+ found.add(agent);
63
+ }
64
+ }
65
+
66
+ // VS Code extension detection
67
+ const extDir = path.join(this.home, '.vscode', 'extensions');
68
+ if (fs.existsSync(extDir)) {
69
+ let entries;
70
+ try { entries = fs.readdirSync(extDir); } catch { entries = []; }
71
+ for (const [agent, prefixes] of Object.entries(EXTENSION_SIGNALS)) {
72
+ if (prefixes.some(p => entries.some(e => e.startsWith(p)))) {
73
+ found.add(agent);
74
+ }
75
+ }
76
+ }
77
+
78
+ if (this.checkPath) {
79
+ for (const [agent, bin] of Object.entries(PATH_SIGNALS)) {
80
+ if (this._inPath(bin)) found.add(agent);
81
+ }
82
+ }
83
+
84
+ return [...found];
85
+ }
86
+
87
+ _inPath(bin) {
88
+ try {
89
+ const cmd = process.platform === 'win32' ? `where ${bin}` : `which ${bin}`;
90
+ execSync(cmd, { stdio: 'ignore' });
91
+ return true;
92
+ } catch {
93
+ return false;
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,39 @@
1
+ import fs from 'fs';
2
+ import { resolveBookLibPaths } from './paths.js';
3
+
4
+ const DEFAULT_CONFIG = {
5
+ sources: [
6
+ { type: 'registry', trusted: true }
7
+ ],
8
+ discovery: {
9
+ ttlHours: 24
10
+ },
11
+ search: {
12
+ minScore: 0.3,
13
+ registryFallbackThreshold: 0.4
14
+ }
15
+ };
16
+
17
+ /**
18
+ * Loads booklib.config.json from project-local or global user dir.
19
+ * Returns DEFAULT_CONFIG if no config file is found.
20
+ * User config is shallow-merged with defaults so missing keys always have values.
21
+ */
22
+ export function loadConfig(projectCwd) {
23
+ const { configPath } = resolveBookLibPaths(projectCwd);
24
+
25
+ let userConfig = {};
26
+ if (fs.existsSync(configPath)) {
27
+ try {
28
+ userConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
29
+ } catch {
30
+ // malformed config — fall back to defaults silently
31
+ }
32
+ }
33
+
34
+ return {
35
+ sources: userConfig.sources ?? DEFAULT_CONFIG.sources,
36
+ discovery: { ...DEFAULT_CONFIG.discovery, ...(userConfig.discovery ?? {}) },
37
+ search: { ...DEFAULT_CONFIG.search, ...(userConfig.search ?? {}) },
38
+ };
39
+ }