@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,194 @@
1
+ // lib/engine/corrections.js
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { createEmbeddingPipeline } from './embedding-provider.js';
7
+
8
+ // ── Constants ────────────────────────────────────────────────────────────────
9
+
10
+ export const LEVEL_THRESHOLDS = [
11
+ { level: 4, min: 10 },
12
+ { level: 3, min: 5 },
13
+ { level: 2, min: 3 },
14
+ { level: 1, min: 1 },
15
+ ];
16
+
17
+ const DEDUP_THRESHOLD = 0.85;
18
+ const MAX_INJECTED = 20;
19
+
20
+ export const MARKER_START = '<!-- booklib-learned-start -->';
21
+ export const MARKER_END = '<!-- booklib-learned-end -->';
22
+
23
+ // ── Embedding model (lazy-loaded, module-level singleton) ────────────────────
24
+
25
+ async function _getEmbedding(text) {
26
+ const { extractor } = await createEmbeddingPipeline({ quiet: true });
27
+ const output = await extractor(text, { pooling: 'mean', normalize: true });
28
+ return Array.from(output.data);
29
+ }
30
+
31
+ // ── Pure functions ────────────────────────────────────────────────────────────
32
+
33
+ export function levelFromMentions(n) {
34
+ for (const { level, min } of LEVEL_THRESHOLDS) {
35
+ if (n >= min) return level;
36
+ }
37
+ return 1;
38
+ }
39
+
40
+ export function cosine(a, b) {
41
+ let dot = 0, na = 0, nb = 0;
42
+ for (let i = 0; i < a.length; i++) {
43
+ dot += a[i] * b[i];
44
+ na += a[i] * a[i];
45
+ nb += b[i] * b[i];
46
+ }
47
+ const denom = Math.sqrt(na) * Math.sqrt(nb);
48
+ return denom === 0 ? 0 : dot / denom;
49
+ }
50
+
51
+ function _generateId() {
52
+ return randomUUID().replace(/-/g, '').slice(0, 8);
53
+ }
54
+
55
+ function _escapeRegex(s) {
56
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
57
+ }
58
+
59
+ // ── File paths ────────────────────────────────────────────────────────────────
60
+
61
+ function _correctionsPath(home) {
62
+ return path.join(home, '.booklib', 'corrections.jsonl');
63
+ }
64
+
65
+ function _claudeMdPath(home) {
66
+ return path.join(home, '.claude', 'CLAUDE.md');
67
+ }
68
+
69
+ // ── Storage ───────────────────────────────────────────────────────────────────
70
+
71
+ export function loadCorrections(home = os.homedir()) {
72
+ const p = _correctionsPath(home);
73
+ if (!fs.existsSync(p)) return [];
74
+ return fs.readFileSync(p, 'utf8')
75
+ .split('\n')
76
+ .filter(Boolean)
77
+ .reduce((acc, line) => {
78
+ try {
79
+ acc.push(JSON.parse(line));
80
+ } catch {
81
+ process.stderr.write(`Warning: skipping corrupt line in corrections.jsonl: ${line.slice(0, 40)}\n`);
82
+ }
83
+ return acc;
84
+ }, []);
85
+ }
86
+
87
+ function _saveCorrections(corrections, home) {
88
+ const p = _correctionsPath(home);
89
+ fs.mkdirSync(path.dirname(p), { recursive: true });
90
+ const lines = corrections.map(c => JSON.stringify(c)).join('\n');
91
+ fs.writeFileSync(p, corrections.length ? lines + '\n' : '');
92
+ }
93
+
94
+ // ── CLAUDE.md injection ────────────────────────────────────────────────────────
95
+
96
+ export function rebuildLearnedSection(home = os.homedir()) {
97
+ const corrections = loadCorrections(home);
98
+ const active = corrections
99
+ .filter(c => c.level >= 3)
100
+ .sort((a, b) => b.mentions - a.mentions)
101
+ .slice(0, MAX_INJECTED);
102
+
103
+ const claudeFile = _claudeMdPath(home);
104
+ fs.mkdirSync(path.dirname(claudeFile), { recursive: true });
105
+
106
+ let existing = '';
107
+ try { existing = fs.readFileSync(claudeFile, 'utf8'); } catch (e) {
108
+ if (e.code !== 'ENOENT') throw e;
109
+ }
110
+
111
+ if (active.length === 0) {
112
+ const re = new RegExp(
113
+ `\\n?${_escapeRegex(MARKER_START)}[\\s\\S]*?${_escapeRegex(MARKER_END)}\\n?`
114
+ );
115
+ const updated = existing.replace(re, '').trimEnd();
116
+ fs.writeFileSync(claudeFile, updated ? updated + '\n' : '');
117
+ return;
118
+ }
119
+
120
+ const bullets = active.map(c => `- ${c.text.slice(0, 120)}`).join('\n');
121
+ const section = [
122
+ MARKER_START,
123
+ '## Learned Corrections (BookLib)',
124
+ '',
125
+ '> When the user corrects your approach, run: booklib correction add "brief rule"',
126
+ '',
127
+ bullets,
128
+ '',
129
+ MARKER_END,
130
+ ].join('\n');
131
+
132
+ const re = new RegExp(
133
+ `${_escapeRegex(MARKER_START)}[\\s\\S]*?${_escapeRegex(MARKER_END)}`
134
+ );
135
+ const updated = existing.includes(MARKER_START)
136
+ ? existing.replace(re, section)
137
+ : (existing.trimEnd() ? `${existing.trimEnd()}\n\n${section}\n` : `${section}\n`);
138
+
139
+ fs.writeFileSync(claudeFile, updated);
140
+ }
141
+
142
+ // ── Public API ────────────────────────────────────────────────────────────────
143
+
144
+ export function listCorrections(home = os.homedir()) {
145
+ return loadCorrections(home).sort((a, b) => b.mentions - a.mentions);
146
+ }
147
+
148
+ export function removeCorrection(id, home = os.homedir()) {
149
+ const corrections = loadCorrections(home);
150
+ const idx = corrections.findIndex(c => c.id === id);
151
+ if (idx === -1) return null;
152
+ const [removed] = corrections.splice(idx, 1);
153
+ _saveCorrections(corrections, home);
154
+ if (removed.level >= 3) rebuildLearnedSection(home);
155
+ return removed;
156
+ }
157
+
158
+ export async function addCorrection(text, home = os.homedir(), embedFn = _getEmbedding) {
159
+ const corrections = loadCorrections(home);
160
+ const now = new Date().toISOString();
161
+ const newVec = await embedFn(text);
162
+
163
+ for (const c of corrections) {
164
+ const existVec = c.embedding ?? await embedFn(c.text);
165
+ if (!c.embedding) c.embedding = existVec; // backfill on first encounter
166
+ const sim = cosine(newVec, existVec);
167
+ if (sim >= DEDUP_THRESHOLD) {
168
+ const oldLevel = c.level;
169
+ c.mentions += 1;
170
+ c.level = levelFromMentions(c.mentions);
171
+ c.lastSeen = now;
172
+ c.sessions.push(now);
173
+ _saveCorrections(corrections, home);
174
+ if (c.level >= 3 && c.level !== oldLevel) rebuildLearnedSection(home);
175
+ const { embedding: _emb, ...rest } = c;
176
+ return { ...rest, wasExisting: true };
177
+ }
178
+ }
179
+
180
+ const entry = {
181
+ id: _generateId(),
182
+ text,
183
+ mentions: 1,
184
+ level: levelFromMentions(1),
185
+ sessions: [now],
186
+ firstSeen: now,
187
+ lastSeen: now,
188
+ embedding: newVec,
189
+ };
190
+ corrections.push(entry);
191
+ _saveCorrections(corrections, home);
192
+ const { embedding: _emb, ...rest } = entry;
193
+ return { ...rest, wasExisting: false };
194
+ }
@@ -0,0 +1,203 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parseImports, detectLanguage } from './import-parser.js';
4
+
5
+ const MAX_FILE_SIZE = 1_000_000;
6
+
7
+ /**
8
+ * Prohibition patterns -- regex that captures what's being prohibited.
9
+ * Group 1 should capture the prohibited thing.
10
+ */
11
+ const PROHIBITION_PATTERNS = [
12
+ // "do not use X", "don't use X", "never use X", "avoid X"
13
+ /(?:do\s+not|don't|never|avoid)\s+(?:use|using)\s+['"`]?([^'"`.,;\n]+)/gi,
14
+ // "deprecated: X", "X is deprecated"
15
+ /deprecated:?\s+['"`]?([^'"`.,;\n]+)/gi,
16
+ /(['"`]?[A-Z][\w.]*['"`]?)\s+is\s+deprecated/gi,
17
+ // "prefer X over Y" -- Y is prohibited
18
+ /prefer\s+\S+\s+over\s+['"`]?([^'"`.,;\n]+)/gi,
19
+ // "replaced X with Y" -- X is prohibited
20
+ /replaced?\s+['"`]?([^'"`.,;\n]+?)['"`]?\s+with\s/gi,
21
+ // "decided against X"
22
+ /decided\s+against\s+['"`]?([^'"`.,;\n]+)/gi,
23
+ // "instead of X" — only after a decision verb to reduce false positives
24
+ /(?:decided|agreed|chose|chosen|use)\s+\S+\s+instead\s+of\s+['"`]?([^'"`.,;\n]+)/gi,
25
+ // "must not X", "should not X"
26
+ /(?:must|should)\s+not\s+(?:use|import|call|reference)\s+['"`]?([^'"`.,;\n]+)/gi,
27
+ ];
28
+
29
+ export class DecisionChecker {
30
+ /**
31
+ * @param {object} [opts]
32
+ * @param {object} [opts.searcher] - BookLibSearcher instance
33
+ * @param {number} [opts.minScore] - minimum search relevance score
34
+ */
35
+ constructor(opts = {}) {
36
+ this.searcher = opts.searcher ?? null;
37
+ this.minScore = opts.minScore ?? 0.4;
38
+ }
39
+
40
+ /**
41
+ * Check a file for contradictions against indexed team decisions.
42
+ * @param {string} filePath
43
+ * @returns {Promise<{contradictions: Array<{identifier: string, decision: string, source: string, pattern: string}>, checked: number}>}
44
+ */
45
+ async checkFile(filePath) {
46
+ if (!this.searcher) return { contradictions: [], checked: 0 };
47
+
48
+ const language = detectLanguage(filePath);
49
+ if (!language) return { contradictions: [], checked: 0 };
50
+
51
+ const resolved = path.resolve(filePath);
52
+ const stat = fs.statSync(resolved, { throwIfNoEntry: false });
53
+ if (!stat || stat.size > MAX_FILE_SIZE) return { contradictions: [], checked: 0 };
54
+
55
+ const code = fs.readFileSync(resolved, 'utf8');
56
+ const identifiers = this._extractIdentifiers(code, language);
57
+ if (identifiers.length === 0) return { contradictions: [], checked: 0 };
58
+
59
+ return this._findContradictions(identifiers);
60
+ }
61
+
62
+ /**
63
+ * Extract meaningful identifiers from code -- import names, API calls, etc.
64
+ * @param {string} code
65
+ * @param {string} language
66
+ * @returns {string[]} unique identifiers
67
+ */
68
+ _extractIdentifiers(code, language) {
69
+ const imports = parseImports(code, language).map(i => i.module);
70
+ const apiCalls = [];
71
+ // Match dot-notation API calls: stripe.charges.create(), db.collection.find()
72
+ const dotPattern = /\b([a-zA-Z_$][\w]*(?:\.[a-zA-Z_$]\w*)+)\s*\(/g;
73
+ let match;
74
+ while ((match = dotPattern.exec(code)) !== null) {
75
+ apiCalls.push(match[1]);
76
+ }
77
+ return [...new Set([...imports, ...apiCalls])];
78
+ }
79
+
80
+ /**
81
+ * Search the index for each identifier and check for contradictions.
82
+ * @param {string[]} identifiers
83
+ * @returns {Promise<{contradictions: Array, checked: number}>}
84
+ */
85
+ async _findContradictions(identifiers) {
86
+ const contradictions = [];
87
+ const seen = new Set();
88
+
89
+ for (const id of identifiers) {
90
+ try {
91
+ const results = await this.searcher.search(id, 3, this.minScore);
92
+ for (const result of results) {
93
+ this._checkResult(result, identifiers, seen, contradictions);
94
+ }
95
+ } catch {
96
+ // Search failure -- skip this identifier
97
+ }
98
+ }
99
+
100
+ return { contradictions, checked: identifiers.length };
101
+ }
102
+
103
+ /**
104
+ * Check a single search result for prohibition matches.
105
+ * @param {object} result - search result with text and metadata
106
+ * @param {string[]} identifiers - code identifiers to match against
107
+ * @param {Set} seen - dedup tracker
108
+ * @param {Array} contradictions - output array
109
+ */
110
+ _checkResult(result, identifiers, seen, contradictions) {
111
+ const text = result.item?.text ?? result.text ?? '';
112
+ const prohibited = this._extractProhibitions(text);
113
+
114
+ for (const { target, pattern } of prohibited) {
115
+ const matchedId = this._matchesIdentifier(target, identifiers);
116
+ const key = `${matchedId}:${text.slice(0, 50)}`;
117
+ if (matchedId && !seen.has(key)) {
118
+ seen.add(key);
119
+ contradictions.push({
120
+ identifier: matchedId,
121
+ decision: text.slice(0, 300),
122
+ source: result.metadata?.sourceName ?? result.metadata?.title ?? 'unknown',
123
+ pattern,
124
+ });
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Extract prohibition targets from a text snippet.
131
+ * @param {string} text
132
+ * @returns {Array<{target: string, pattern: string}>}
133
+ */
134
+ _extractProhibitions(text) {
135
+ const results = [];
136
+ for (const pattern of PROHIBITION_PATTERNS) {
137
+ const regex = new RegExp(pattern.source, pattern.flags);
138
+ let match;
139
+ while ((match = regex.exec(text)) !== null) {
140
+ const target = match[1].trim();
141
+ if (target.length >= 2 && target.length <= 100) {
142
+ results.push({ target, pattern: pattern.source.slice(0, 40) });
143
+ }
144
+ }
145
+ }
146
+ return results;
147
+ }
148
+
149
+ /**
150
+ * Check if a prohibition target matches any code identifier.
151
+ * Splits both on word boundaries (dots, spaces) for segment-level matching.
152
+ * "Charges API" matches "stripe.charges" because "charges" overlaps.
153
+ * @param {string} target - the prohibited thing from decision text
154
+ * @param {string[]} identifiers - identifiers from the code
155
+ * @returns {string|null} the matched identifier, or null
156
+ */
157
+ _matchesIdentifier(target, identifiers) {
158
+ const targetSegments = this._toSegments(target);
159
+ for (const id of identifiers) {
160
+ if (this._segmentsOverlap(targetSegments, this._toSegments(id))) {
161
+ return id;
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+
167
+ /**
168
+ * Split a string into lowercase segments by dots, spaces, and camelCase.
169
+ * @param {string} str
170
+ * @returns {string[]}
171
+ */
172
+ _toSegments(str) {
173
+ return str
174
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
175
+ .toLowerCase()
176
+ .split(/[\s./:@-]+/)
177
+ .filter(s => s.length >= 2);
178
+ }
179
+
180
+ /**
181
+ * Check if any significant segment from A exactly matches one from B.
182
+ * Exact equality only — substring matching produces too many false positives
183
+ * on short common segments like "api", "sql", "log".
184
+ * @param {string[]} segsA
185
+ * @param {string[]} segsB
186
+ * @returns {boolean}
187
+ */
188
+ _segmentsOverlap(segsA, segsB) {
189
+ const setB = new Set(segsB);
190
+ // Require exact match on segments of 4+ chars, or at least 2 short-segment matches
191
+ let shortMatches = 0;
192
+ for (const a of segsA) {
193
+ if (setB.has(a)) {
194
+ if (a.length >= 4) return true;
195
+ shortMatches++;
196
+ if (shortMatches >= 2) return true;
197
+ }
198
+ }
199
+ return false;
200
+ }
201
+ }
202
+
203
+ export { PROHIBITION_PATTERNS };
@@ -0,0 +1,207 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { resolveBookLibPaths } from '../paths.js';
5
+ import { SKILL_LIMIT } from '../wizard/skill-recommender.js';
6
+ import { countAllSlots, countInstalledSlots, listInstalledSkillNames } from '../skill-fetcher.js';
7
+
8
+ const MARKER_START = '<!-- booklib-standards-start -->';
9
+
10
+ /**
11
+ * Runs all diagnostic checks and returns an array of findings.
12
+ * @param {string} cwd - project root
13
+ * @returns {Array<{ check: string, severity: 'error'|'warning'|'info', message: string, fixable: boolean, data?: object }>}
14
+ */
15
+ export function runDiagnostics(cwd = process.cwd()) {
16
+ const findings = [];
17
+
18
+ checkSlotOverload(findings);
19
+ checkOversizedConfigs(findings, cwd);
20
+ checkMissingIndex(findings, cwd);
21
+ checkMissingConfigFiles(findings, cwd);
22
+ checkStaleSkills(findings);
23
+ checkOrphanedSkills(findings);
24
+
25
+ return findings;
26
+ }
27
+
28
+ /** Check 1: Slot overload */
29
+ function checkSlotOverload(findings) {
30
+ const slotsUsed = countAllSlots();
31
+ if (slotsUsed > SKILL_LIMIT) {
32
+ findings.push({
33
+ check: 'slot-overload',
34
+ severity: 'warning',
35
+ message: `${slotsUsed} skills installed (limit: ${SKILL_LIMIT}). Agent context is overloaded. Run: booklib doctor --usage`,
36
+ fixable: false,
37
+ data: { slotsUsed },
38
+ });
39
+ }
40
+ }
41
+
42
+ /** Check 2: Oversized config files */
43
+ function checkOversizedConfigs(findings, cwd) {
44
+ const configFiles = [
45
+ { file: 'CLAUDE.md', path: path.join(cwd, 'CLAUDE.md') },
46
+ { file: '.github/copilot-instructions.md', path: path.join(cwd, '.github', 'copilot-instructions.md') },
47
+ { file: '.gemini/context.md', path: path.join(cwd, '.gemini', 'context.md') },
48
+ ];
49
+
50
+ for (const cf of configFiles) {
51
+ if (fs.existsSync(cf.path)) {
52
+ const content = fs.readFileSync(cf.path, 'utf8');
53
+ const lines = content.split('\n').length;
54
+ if (lines > 500 && content.includes(MARKER_START)) {
55
+ findings.push({
56
+ check: 'oversized-config',
57
+ severity: 'warning',
58
+ message: `${cf.file} is ${lines} lines. Recommended: under 200.`,
59
+ fixable: true,
60
+ data: { file: cf.file, absPath: cf.path, lines },
61
+ });
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ /** Check 3: Missing index */
68
+ function checkMissingIndex(findings, cwd) {
69
+ try {
70
+ const { indexPath } = resolveBookLibPaths(cwd);
71
+ if (!fs.existsSync(indexPath)) {
72
+ findings.push({
73
+ check: 'missing-index',
74
+ severity: 'error',
75
+ message: 'No search index found. Search and recommendations won\'t work.',
76
+ fixable: true,
77
+ });
78
+ }
79
+ } catch {
80
+ findings.push({
81
+ check: 'missing-index',
82
+ severity: 'error',
83
+ message: 'No search index found. Search and recommendations won\'t work.',
84
+ fixable: true,
85
+ });
86
+ }
87
+ }
88
+
89
+ /** Check 4: Missing config files for configured tools */
90
+ function checkMissingConfigFiles(findings, cwd) {
91
+ const configPath = path.join(cwd, 'booklib.config.json');
92
+ if (!fs.existsSync(configPath)) return;
93
+
94
+ try {
95
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
96
+ const toolFileMap = {
97
+ claude: 'CLAUDE.md',
98
+ copilot: '.github/copilot-instructions.md',
99
+ gemini: '.gemini/context.md',
100
+ codex: 'AGENTS.md',
101
+ cursor: '.cursor/rules/booklib-standards.mdc',
102
+ };
103
+
104
+ for (const tool of (config.tools ?? [])) {
105
+ const expectedFile = toolFileMap[tool];
106
+ if (expectedFile && !fs.existsSync(path.join(cwd, expectedFile))) {
107
+ findings.push({
108
+ check: 'missing-config',
109
+ severity: 'warning',
110
+ message: `${tool} configured but ${expectedFile} not found.`,
111
+ fixable: true,
112
+ data: { tool, file: expectedFile },
113
+ });
114
+ }
115
+ }
116
+ } catch { /* malformed config, skip */ }
117
+ }
118
+
119
+ /** Check 5: Stale skills (installed but never used) */
120
+ function checkStaleSkills(findings) {
121
+ const usagePath = path.join(os.homedir(), '.booklib', 'usage.json');
122
+ if (!fs.existsSync(usagePath)) return;
123
+
124
+ try {
125
+ const usage = JSON.parse(fs.readFileSync(usagePath, 'utf8'));
126
+ const usedNames = new Set(
127
+ usage.map(u => u.skill?.toLowerCase()).filter(Boolean),
128
+ );
129
+ const installed = listInstalledSkillNames();
130
+ const stale = installed.filter(n => !usedNames.has(n.toLowerCase()));
131
+
132
+ if (stale.length > 5) {
133
+ findings.push({
134
+ check: 'stale-skills',
135
+ severity: 'info',
136
+ message: `${stale.length} skills have no recorded usage.`,
137
+ fixable: true,
138
+ data: { staleNames: stale },
139
+ });
140
+ }
141
+ } catch { /* no usage data, skip */ }
142
+ }
143
+
144
+ /** Check 6: Orphaned skills (have .booklib marker but not in any catalog/index) */
145
+ function checkOrphanedSkills(findings) {
146
+ const claudeSkillsDir = path.join(os.homedir(), '.claude', 'skills');
147
+ if (!fs.existsSync(claudeSkillsDir)) return;
148
+
149
+ try {
150
+ const installed = listInstalledSkillNames();
151
+ // Read the bundled skills directory to find known skill names
152
+ const packageRoot = path.resolve(
153
+ new URL('.', import.meta.url).pathname, '..', '..',
154
+ );
155
+ const bundledSkillsDir = path.join(packageRoot, 'skills');
156
+ const knownNames = new Set();
157
+ if (fs.existsSync(bundledSkillsDir)) {
158
+ for (const entry of fs.readdirSync(bundledSkillsDir, { withFileTypes: true })) {
159
+ if (entry.isDirectory()) knownNames.add(entry.name);
160
+ }
161
+ }
162
+
163
+ // Also load community registry skill names
164
+ const registryPath = path.join(packageRoot, 'community', 'registry.json');
165
+ if (fs.existsSync(registryPath)) {
166
+ try {
167
+ const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
168
+ for (const skill of (registry.skills ?? [])) {
169
+ knownNames.add(skill.name);
170
+ }
171
+ } catch { /* malformed registry, skip */ }
172
+ }
173
+
174
+ const orphaned = installed.filter(n => !knownNames.has(n));
175
+ for (const name of orphaned) {
176
+ findings.push({
177
+ check: 'orphaned-skill',
178
+ severity: 'info',
179
+ message: `${name} -- not found in any catalog. May be outdated.`,
180
+ fixable: false,
181
+ });
182
+ }
183
+ } catch { /* skip orphan check on error */ }
184
+ }
185
+
186
+ /**
187
+ * Prints diagnostic results to stdout.
188
+ * @param {Array<{ check: string, severity: string, message: string, fixable: boolean }>} findings
189
+ */
190
+ export function printDiagnostics(findings) {
191
+ if (findings.length === 0) {
192
+ console.log(' No issues found. BookLib is healthy.\n');
193
+ return;
194
+ }
195
+
196
+ for (const f of findings) {
197
+ const icon = f.severity === 'error' ? '\u2717' : f.severity === 'warning' ? '\u26A0' : '\u2139';
198
+ console.log(` ${icon} ${f.message}`);
199
+ }
200
+
201
+ const fixable = findings.filter(f => f.fixable).length;
202
+ if (fixable > 0) {
203
+ console.log(`\n ${fixable} issue(s) fixable. Run: booklib doctor --cure\n`);
204
+ } else {
205
+ console.log('');
206
+ }
207
+ }
@@ -0,0 +1,72 @@
1
+ // lib/engine/embedding-provider.js
2
+ // Suppress ONNX runtime JS-level warnings
3
+ process.env.ORT_LOG_LEVEL = 'ERROR';
4
+
5
+ import { pipeline } from '@huggingface/transformers';
6
+
7
+ const MODEL_NAME = 'Xenova/all-MiniLM-L6-v2';
8
+ const BATCH_SIZE = 32;
9
+
10
+ let _cachedPipeline = null;
11
+
12
+ /**
13
+ * Returns a shared embedding pipeline (singleton per process).
14
+ * First call downloads/loads the model; subsequent calls return the cached instance.
15
+ *
16
+ * @param {Object} [opts]
17
+ * @param {boolean} [opts.quiet=false] - Suppress loading message.
18
+ * @returns {Promise<{ extractor: Function }>}
19
+ */
20
+ export async function createEmbeddingPipeline(opts = {}) {
21
+ if (_cachedPipeline) return _cachedPipeline;
22
+
23
+ const { quiet = false } = opts;
24
+ if (!quiet) {
25
+ console.log('Loading local embedding model...');
26
+ }
27
+
28
+ // Try CoreML (Apple Silicon) with stderr suppressed during load
29
+ // CoreML spams native warnings ("Context leak", "IsInputSupported") — harmless but noisy
30
+ let extractor;
31
+ const isMac = process.platform === 'darwin';
32
+
33
+ // CPU int8 quantization is faster than CoreML for this model (73ms vs 1550ms).
34
+ // CoreML partitions 231/323 nodes to GPU, but the CPU↔GPU transfer overhead
35
+ // makes it slower than pure CPU for MiniLM-L6 (384-dim, small model).
36
+ // CPU q8 also produces zero native warnings.
37
+ extractor = await pipeline('feature-extraction', MODEL_NAME, { dtype: 'q8' });
38
+
39
+ // Warm up the model with a dummy embedding — triggers native CoreML warnings NOW
40
+ // so they don't appear during user-facing operations (search, lookup, index).
41
+ try {
42
+ await extractor('warmup', { pooling: 'mean', normalize: true });
43
+ } catch { /* warmup is best-effort */ }
44
+
45
+ _cachedPipeline = { extractor };
46
+ return _cachedPipeline;
47
+ }
48
+
49
+ /**
50
+ * Embed multiple texts in batches.
51
+ * @param {Function} extractor - the pipeline function
52
+ * @param {string[]} texts - array of texts to embed
53
+ * @param {number} [batchSize=32]
54
+ * @param {Function} [onBatch] - called after each batch with { done, total }
55
+ * @returns {Promise<number[][]>} array of embedding vectors
56
+ */
57
+ export async function batchEmbed(extractor, texts, batchSize = BATCH_SIZE, onBatch) {
58
+ const allVectors = [];
59
+ for (let i = 0; i < texts.length; i += batchSize) {
60
+ const batch = texts.slice(i, i + batchSize);
61
+ const outputs = await extractor(batch, { pooling: 'mean', normalize: true });
62
+ const dims = outputs.dims[outputs.dims.length - 1];
63
+ for (let j = 0; j < batch.length; j++) {
64
+ const start = j * dims;
65
+ allVectors.push(Array.from(outputs.data.slice(start, start + dims)));
66
+ }
67
+ onBatch?.({ done: Math.min(i + batchSize, texts.length), total: texts.length });
68
+ }
69
+ return allVectors;
70
+ }
71
+
72
+ export { BATCH_SIZE, MODEL_NAME };