@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,641 @@
1
+ // lib/engine/context-map.js — keyword extraction, map building, and matching for runtime context injection
2
+
3
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+
6
+ /**
7
+ * Stopwords filtered from code terms. Common English words that
8
+ * carry no signal for matching knowledge to source files.
9
+ */
10
+ const STOPWORDS = new Set([
11
+ 'a', 'an', 'the', 'in', 'on', 'at', 'to', 'for', 'of', 'and', 'or',
12
+ 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
13
+ 'do', 'does', 'did', 'will', 'would', 'shall', 'should', 'may', 'might',
14
+ 'must', 'can', 'could', 'not', 'no', 'nor', 'so', 'yet', 'both', 'with',
15
+ 'about', 'from', 'up', 'down', 'out', 'how', 'what', 'when', 'where',
16
+ 'who', 'why', 'which', 'by', 'as', 'if', 'then', 'than', 'too', 'very',
17
+ 'just', 'more', 'also', 'its', 'it', 'all', 'use', 'that', 'this',
18
+ 'each', 'every', 'any', 'some', 'such',
19
+ ]);
20
+
21
+ /**
22
+ * Terms that map to file glob patterns. When these appear in
23
+ * knowledge text, they hint which source files the rule applies to.
24
+ */
25
+ const TERM_TO_GLOB = {
26
+ api: '**/api/**',
27
+ admin: '**/admin/**',
28
+ auth: '**/auth/**',
29
+ middleware: '**/middleware/**',
30
+ config: '**/config/**',
31
+ migration: '**/migrations/**',
32
+ test: '**/test*/**',
33
+ route: '**/routes/**',
34
+ controller: '**/controllers/**',
35
+ model: '**/models/**',
36
+ service: '**/services/**',
37
+ component: '**/components/**',
38
+ hook: '**/hooks/**',
39
+ util: '**/utils/**',
40
+ helper: '**/helpers/**',
41
+ schema: '**/schema*/**',
42
+ handler: '**/handlers/**',
43
+ worker: '**/workers/**',
44
+ job: '**/jobs/**',
45
+ plugin: '**/plugins/**',
46
+ endpoint: '**/api/**',
47
+ };
48
+
49
+ /**
50
+ * Known packages whose mention in knowledge text signals an import trigger.
51
+ * Checked case-insensitively against words in the text.
52
+ */
53
+ const KNOWN_PACKAGES = new Set([
54
+ 'stripe', 'express', 'next', 'react', 'supabase', 'prisma',
55
+ 'drizzle', 'zod', 'joi', 'lodash', 'axios', 'fastify', 'hono',
56
+ 'trpc', 'graphql', 'apollo', 'sequelize', 'mongoose', 'redis',
57
+ 'bullmq', 'kafkajs', 'socket.io', 'passport', 'jest', 'vitest',
58
+ 'playwright', 'cypress', 'tailwindcss', 'webpack', 'vite', 'esbuild',
59
+ 'turborepo', 'pino', 'winston', 'sentry', 'datadog', 'knex',
60
+ ]);
61
+
62
+ /** Regex to capture quoted package names like '@scope/pkg' or "pkg-name" */
63
+ const QUOTED_PKG_RE = /['"](@[\w-]+\/[\w.-]+|[\w][\w.-]*)['"`]/g;
64
+
65
+ /**
66
+ * Extract three types of keywords from knowledge text.
67
+ *
68
+ * @param {string} text - knowledge item text (markdown, notes, decisions)
69
+ * @returns {{ codeTerms: string[], filePatterns: string[], importTriggers: string[] }}
70
+ */
71
+ export function extractKeywords(text) {
72
+ if (!text) return { codeTerms: [], filePatterns: [], importTriggers: [] };
73
+
74
+ const lowerText = text.toLowerCase();
75
+ const words = lowerText.split(/[^a-z0-9@/._-]+/).filter(Boolean);
76
+
77
+ const codeTerms = extractCodeTerms(words);
78
+ const filePatterns = extractFilePatterns(words);
79
+ const importTriggers = extractImportTriggers(words, text);
80
+
81
+ return { codeTerms, filePatterns, importTriggers };
82
+ }
83
+
84
+ /**
85
+ * Nouns and identifiers: filter stopwords, min 3 chars, deduplicated, lowercased.
86
+ * @param {string[]} words - pre-split lowercased tokens
87
+ * @returns {string[]}
88
+ */
89
+ function extractCodeTerms(words) {
90
+ const seen = new Set();
91
+ return words.filter(w => {
92
+ if (w.length < 3 || STOPWORDS.has(w) || seen.has(w)) return false;
93
+ seen.add(w);
94
+ return true;
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Glob patterns inferred from path-like terms in text.
100
+ * @param {string[]} words - pre-split lowercased tokens
101
+ * @returns {string[]}
102
+ */
103
+ function extractFilePatterns(words) {
104
+ const patterns = new Set();
105
+ for (const word of words) {
106
+ // Strip trailing 's' for simple plurals (e.g., "endpoints" -> "endpoint")
107
+ const singular = word.endsWith('s') ? word.slice(0, -1) : word;
108
+ const glob = TERM_TO_GLOB[word] ?? TERM_TO_GLOB[singular];
109
+ if (glob) patterns.add(glob);
110
+ }
111
+ return [...patterns];
112
+ }
113
+
114
+ /**
115
+ * Package names found by checking known packages and extracting quoted strings.
116
+ * @param {string[]} words - pre-split lowercased tokens
117
+ * @param {string} rawText - original text for quoted-string extraction
118
+ * @returns {string[]}
119
+ */
120
+ function extractImportTriggers(words, rawText) {
121
+ const triggers = new Set();
122
+
123
+ for (const word of words) {
124
+ if (KNOWN_PACKAGES.has(word)) triggers.add(word);
125
+ }
126
+
127
+ for (const match of rawText.matchAll(QUOTED_PKG_RE)) {
128
+ triggers.add(match[1]);
129
+ }
130
+
131
+ return [...triggers];
132
+ }
133
+
134
+ // ── Code block extraction ────────────────────────────────────────────────────
135
+
136
+ const CODE_BLOCK_RE = /```[\w]*\n([\s\S]*?)```/;
137
+
138
+ /**
139
+ * Extract the first markdown code block from text.
140
+ * @param {string} text
141
+ * @returns {string | null}
142
+ */
143
+ function extractCodeBlock(text) {
144
+ const match = text.match(CODE_BLOCK_RE);
145
+ return match ? match[1].trim() : null;
146
+ }
147
+
148
+ // ── Injection text builder ───────────────────────────────────────────────────
149
+
150
+ /**
151
+ * Build pre-computed injection text for a knowledge item.
152
+ * For decisions/notes: constraint is first 200 chars, correction is null,
153
+ * and example is extracted from any markdown code block.
154
+ *
155
+ * @param {{ text: string }} item - knowledge item with a .text property
156
+ * @returns {{ correction: null, constraint: string, example: string | null }}
157
+ */
158
+ export function buildInjectionText(item) {
159
+ const text = item?.text ?? '';
160
+ return {
161
+ correction: null,
162
+ constraint: text.slice(0, 200),
163
+ example: extractCodeBlock(text),
164
+ };
165
+ }
166
+
167
+ // ── Simple glob matching (no external deps) ─────────────────────────────────
168
+
169
+ /**
170
+ * Convert a file-glob pattern to a regex and test it against a file path.
171
+ * Supports **, *, and ? wildcards.
172
+ *
173
+ * @param {string} pattern - glob pattern (e.g., '** /api/**')
174
+ * @param {string} filePath - file path to test
175
+ * @returns {boolean}
176
+ */
177
+ function simpleGlob(pattern, filePath) {
178
+ const escaped = pattern
179
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
180
+ .replace(/\*\*/g, '\x00')
181
+ .replace(/\*/g, '[^/]*')
182
+ .replace(/\?/g, '[^/]')
183
+ .replace(/\x00/g, '.*');
184
+ return new RegExp(`^${escaped}$`).test(filePath);
185
+ }
186
+
187
+ // ── Match strength constants ────────────────────────────────────────────────
188
+
189
+ const STRENGTH_IMPORT = 4;
190
+ const STRENGTH_FUNCTION = 3;
191
+ const STRENGTH_CODE_TERM = 2;
192
+ const STRENGTH_FILE_PATTERN = 1;
193
+ // Team knowledge needs stronger signal than a single codeTerm match
194
+ const MIN_STRENGTH_TEAM = 3; // require file pattern + code term, or function/import match
195
+
196
+ const MAX_MATCHES = 5;
197
+
198
+ // ── LLM batch size ──────────────────────────────────────────────────────────
199
+
200
+ const LLM_BATCH_SIZE = 20;
201
+
202
+ // ── ContextMapBuilder ───────────────────────────────────────────────────────
203
+
204
+ export class ContextMapBuilder {
205
+ constructor(opts = {}) {
206
+ this.processingMode = opts.processingMode ?? 'fast';
207
+ this.apiKey = opts.apiKey;
208
+ this.ollamaModel = opts.ollamaModel;
209
+ }
210
+
211
+ /**
212
+ * Build a context map from knowledge items.
213
+ * @param {Array<{id: string, text: string, source?: string, type?: string}>} items
214
+ * @returns {Promise<{version: number, builtAt: string, items: Array}>}
215
+ */
216
+ async buildFromKnowledge(items) {
217
+ if (!items?.length) return this._emptyMap();
218
+
219
+ const mapped = items.map(item => ({
220
+ id: item.id,
221
+ source: item.source ?? null,
222
+ type: item.type ?? null,
223
+ match: {
224
+ ...extractKeywords(item.text),
225
+ functionPatterns: [],
226
+ },
227
+ injection: buildInjectionText(item),
228
+ }));
229
+
230
+ if (this.processingMode !== 'fast') {
231
+ await this._inferScopes(mapped, items);
232
+ }
233
+
234
+ return { version: 1, builtAt: new Date().toISOString(), items: mapped };
235
+ }
236
+
237
+ /**
238
+ * Build a context map from post-training knowledge gaps.
239
+ * @param {Array<{name: string, version?: string, ecosystem?: string, publishDate?: string}>} gaps
240
+ * @returns {Promise<{version: number, builtAt: string, items: Array}>}
241
+ */
242
+ async buildFromGaps(gaps, opts = {}) {
243
+ if (!gaps?.length) return this._emptyMap();
244
+ const { booklibDir } = opts;
245
+
246
+ const mapped = gaps.map(gap => {
247
+ const thinCorrection = `${gap.name}@${gap.version ?? 'latest'} (published ${gap.publishDate ?? 'unknown'}). Post-training.`;
248
+
249
+ // Try to enrich correction from resolved docs (Context7/GitHub)
250
+ let richCorrection = thinCorrection;
251
+ if (booklibDir) {
252
+ try {
253
+ const safeName = gap.name.replace(/[@/]/g, '_').replace(/^_+/, '');
254
+ const sourceDirs = [
255
+ join(booklibDir, 'sources', `ctx7-${safeName}`),
256
+ join(booklibDir, 'sources', `gh-${safeName}`),
257
+ ];
258
+
259
+ for (const dir of sourceDirs) {
260
+ if (!existsSync(dir)) continue;
261
+ const files = readdirSync(dir).filter(f => f.endsWith('.md'));
262
+ if (files.length === 0) continue;
263
+
264
+ // Extract a short summary — hooks inject 3-10 lines max.
265
+ // Full docs are available via MCP lookup tool.
266
+ const allContent = files.slice(0, 5).map(f =>
267
+ readFileSync(join(dir, f), 'utf8')
268
+ ).join('\n');
269
+
270
+ // Pull key identifiers: imports, function names, config keys
271
+ const imports = [...allContent.matchAll(/import\s+\{([^}]+)\}/g)].map(m => m[1].trim()).join(', ');
272
+ const firstCodeBlock = allContent.match(/```\w*\n([\s\S]*?)```/)?.[1]?.slice(0, 200)?.trim();
273
+
274
+ if (imports || firstCodeBlock) {
275
+ const parts = [thinCorrection];
276
+ if (imports) parts.push(`Key exports: ${imports.slice(0, 150)}`);
277
+ if (firstCodeBlock) parts.push(`Usage: ${firstCodeBlock.split('\n').slice(0, 3).join(' ').slice(0, 150)}`);
278
+ parts.push('If unsure, call lookup with a specific question.');
279
+ richCorrection = parts.join(' | ');
280
+ break;
281
+ }
282
+ }
283
+ } catch { /* best-effort — fall back to thin correction */ }
284
+ }
285
+
286
+ return {
287
+ id: `gap:${gap.name}`,
288
+ source: 'gap-detector',
289
+ type: 'post-training',
290
+ match: {
291
+ codeTerms: [],
292
+ filePatterns: ['**'],
293
+ importTriggers: [gap.name],
294
+ functionPatterns: [],
295
+ },
296
+ injection: {
297
+ correction: richCorrection,
298
+ constraint: null,
299
+ example: null,
300
+ },
301
+ };
302
+ });
303
+
304
+ return { version: 1, builtAt: new Date().toISOString(), items: mapped };
305
+ }
306
+
307
+ /**
308
+ * Add a single item to an existing context map.
309
+ * @param {{version: number, builtAt: string, items: Array}} map
310
+ * @param {{id: string, text: string, source?: string, type?: string}} item
311
+ * @returns {Promise<{version: number, builtAt: string, items: Array}>}
312
+ */
313
+ async addItem(map, item) {
314
+ const entry = {
315
+ id: item.id,
316
+ source: item.source ?? null,
317
+ type: item.type ?? null,
318
+ match: {
319
+ ...extractKeywords(item.text),
320
+ functionPatterns: [],
321
+ },
322
+ injection: buildInjectionText(item),
323
+ };
324
+
325
+ if (this.processingMode !== 'fast') {
326
+ await this._inferScopes([entry], [item]);
327
+ }
328
+
329
+ map.items.push(entry);
330
+ return map;
331
+ }
332
+
333
+ /**
334
+ * Write a context map to disk as JSON.
335
+ * @param {string} filePath
336
+ * @param {{version: number, builtAt: string, items: Array}} map
337
+ */
338
+ save(filePath, map) {
339
+ mkdirSync(dirname(filePath), { recursive: true });
340
+ writeFileSync(filePath, JSON.stringify(map, null, 2), 'utf8');
341
+ }
342
+
343
+ /**
344
+ * Load a context map from disk. Returns null if missing or corrupt.
345
+ * @param {string} filePath
346
+ * @returns {{version: number, builtAt: string, items: Array} | null}
347
+ */
348
+ static load(filePath) {
349
+ try {
350
+ const raw = readFileSync(filePath, 'utf8');
351
+ return JSON.parse(raw);
352
+ } catch {
353
+ return null;
354
+ }
355
+ }
356
+
357
+ // ── Private helpers ─────────────────────────────────────────────────────
358
+
359
+ /** @returns {{version: number, builtAt: string, items: Array}} */
360
+ _emptyMap() {
361
+ return { version: 1, builtAt: new Date().toISOString(), items: [] };
362
+ }
363
+
364
+ /**
365
+ * Batch LLM inference to populate functionPatterns and importTriggers.
366
+ * Processes items in batches of LLM_BATCH_SIZE.
367
+ */
368
+ async _inferScopes(mapped, sourceItems) {
369
+ for (let i = 0; i < mapped.length; i += LLM_BATCH_SIZE) {
370
+ const batchMapped = mapped.slice(i, i + LLM_BATCH_SIZE);
371
+ const batchSource = sourceItems.slice(i, i + LLM_BATCH_SIZE);
372
+
373
+ const prompt = this._buildScopePrompt(batchSource);
374
+ const response = await this._callLLM(prompt);
375
+ this._applyScopeResponse(batchMapped, response);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Build a prompt asking the LLM for functionPatterns and importTriggers.
381
+ * @param {Array<{id: string, text: string}>} items
382
+ * @returns {string}
383
+ */
384
+ _buildScopePrompt(items) {
385
+ const itemList = items
386
+ .map((it, idx) => `[${idx}] id="${it.id}": ${it.text.slice(0, 300)}`)
387
+ .join('\n');
388
+
389
+ return [
390
+ 'For each knowledge item below, return JSON: an array of objects with',
391
+ '{ "index": number, "functionPatterns": string[], "importTriggers": string[] }.',
392
+ 'functionPatterns: regex patterns for function/method names this rule applies to.',
393
+ 'importTriggers: npm/pip package names that signal this rule is relevant.',
394
+ 'Return ONLY valid JSON array, no markdown fences.',
395
+ '',
396
+ itemList,
397
+ ].join('\n');
398
+ }
399
+
400
+ /**
401
+ * Parse LLM response and merge functionPatterns/importTriggers into mapped items.
402
+ */
403
+ _applyScopeResponse(mapped, response) {
404
+ if (!response) return;
405
+
406
+ try {
407
+ const cleaned = response.replace(/```[\w]*\n?/g, '').trim();
408
+ const parsed = JSON.parse(cleaned);
409
+ if (!Array.isArray(parsed)) return;
410
+
411
+ for (const entry of parsed) {
412
+ const target = mapped[entry.index];
413
+ if (!target) continue;
414
+ if (Array.isArray(entry.functionPatterns)) {
415
+ target.match.functionPatterns = entry.functionPatterns;
416
+ }
417
+ if (Array.isArray(entry.importTriggers)) {
418
+ const merged = new Set([...target.match.importTriggers, ...entry.importTriggers]);
419
+ target.match.importTriggers = [...merged];
420
+ }
421
+ }
422
+ } catch {
423
+ // LLM returned unparseable response; skip gracefully
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Call the configured LLM. Override this method in tests.
429
+ * @param {string} prompt
430
+ * @returns {Promise<string | null>}
431
+ */
432
+ async _callLLM(prompt) {
433
+ const mode = this.processingMode;
434
+
435
+ if (mode === 'api' && this.apiKey) {
436
+ return this._callApiLLM(prompt);
437
+ }
438
+ if (mode === 'local') {
439
+ return this._callOllamaLLM(prompt);
440
+ }
441
+ return null;
442
+ }
443
+
444
+ /** Call Anthropic or OpenAI API based on key prefix. */
445
+ async _callApiLLM(prompt) {
446
+ const isAnthropic = this.apiKey.startsWith('sk-ant-');
447
+ const url = isAnthropic
448
+ ? 'https://api.anthropic.com/v1/messages'
449
+ : 'https://api.openai.com/v1/chat/completions';
450
+
451
+ const body = isAnthropic
452
+ ? { model: 'claude-sonnet-4-20250514', max_tokens: 2048, messages: [{ role: 'user', content: prompt }] }
453
+ : { model: 'gpt-4o-mini', messages: [{ role: 'user', content: prompt }], max_tokens: 2048 };
454
+
455
+ const headers = isAnthropic
456
+ ? { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, 'anthropic-version': '2023-06-01' }
457
+ : { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}` };
458
+
459
+ const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) });
460
+ if (!res.ok) return null;
461
+
462
+ const data = await res.json();
463
+ return isAnthropic ? data.content?.[0]?.text : data.choices?.[0]?.message?.content;
464
+ }
465
+
466
+ /** Call local Ollama instance. */
467
+ async _callOllamaLLM(prompt) {
468
+ const model = this.ollamaModel ?? 'llama3';
469
+ const url = 'http://localhost:11434/api/generate';
470
+ const body = { model, prompt, stream: false };
471
+
472
+ try {
473
+ const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
474
+ if (!res.ok) return null;
475
+ const data = await res.json();
476
+ return data.response ?? null;
477
+ } catch {
478
+ return null;
479
+ }
480
+ }
481
+ }
482
+
483
+ // ── ContextMapMatcher ───────────────────────────────────────────────────────
484
+
485
+ /** Prohibition patterns for contradiction checking. */
486
+ const PROHIBITION_RE = /(?:do not use|don't use|never use|avoid|deprecated|prefer\s+(\S+)\s+over)\s+(\S+)/gi;
487
+
488
+ export class ContextMapMatcher {
489
+ /**
490
+ * @param {Array} items - items from a loaded context map
491
+ */
492
+ constructor(items) {
493
+ this.items = items ?? [];
494
+ }
495
+
496
+ /**
497
+ * Match context map items against a file path, code block, and imports.
498
+ * Returns matched items sorted by strength (desc), capped at MAX_MATCHES.
499
+ *
500
+ * @param {string} filePath - current file being edited
501
+ * @param {string} codeBlock - code content
502
+ * @param {string[]} imports - imported package names
503
+ * @returns {Array} matched items with _strength property
504
+ */
505
+ match(filePath, codeBlock, imports) {
506
+ const importSet = new Set((imports ?? []).map(i => i.toLowerCase()));
507
+ const codeLower = (codeBlock ?? '').toLowerCase();
508
+
509
+ const scored = [];
510
+ for (const item of this.items) {
511
+ const strength = this._scoreItem(item, filePath, codeLower, importSet);
512
+ // Post-training items: any match is enough (import trigger required by the check below)
513
+ // Team knowledge: require stronger signal to avoid broad keyword noise
514
+ const minStrength = item.type === 'post-training' ? 1 : MIN_STRENGTH_TEAM;
515
+ if (strength >= minStrength) scored.push({ ...item, _strength: strength });
516
+ }
517
+
518
+ scored.sort((a, b) => b._strength - a._strength);
519
+ return scored.slice(0, MAX_MATCHES);
520
+ }
521
+
522
+ /**
523
+ * Check for contradictions between new code and matched knowledge items.
524
+ * @param {string} newCode - code being written
525
+ * @param {Array} matchedItems - items returned by match()
526
+ * @returns {Array<{id: string, constraint: string, example: string | null, source: string | null}>}
527
+ */
528
+ checkContradictions(newCode, matchedItems) {
529
+ if (!newCode || !matchedItems?.length) return [];
530
+
531
+ const codeLower = newCode.toLowerCase();
532
+ const violations = [];
533
+
534
+ for (const item of matchedItems) {
535
+ const text = item.injection?.constraint ?? '';
536
+ const found = this._findProhibitions(text, codeLower);
537
+ if (found.length > 0) {
538
+ violations.push({
539
+ id: item.id,
540
+ constraint: found.join('; '),
541
+ example: item.injection?.example ?? null,
542
+ source: item.source ?? null,
543
+ });
544
+ }
545
+ }
546
+
547
+ return violations;
548
+ }
549
+
550
+ // ── Private helpers ─────────────────────────────────────────────────────
551
+
552
+ /**
553
+ * Score a single item against the current context.
554
+ * Post-training items with '**' filePattern require importTriggers to match.
555
+ */
556
+ _scoreItem(item, filePath, codeLower, importSet) {
557
+ let strength = 0;
558
+
559
+ const m = item.match ?? {};
560
+ const importMatch = this._matchImports(m.importTriggers, importSet);
561
+ if (importMatch) strength += STRENGTH_IMPORT;
562
+
563
+ const fnMatch = this._matchFunctionPatterns(m.functionPatterns, codeLower);
564
+ if (fnMatch) strength += STRENGTH_FUNCTION;
565
+
566
+ const termMatch = this._matchCodeTerms(m.codeTerms, codeLower);
567
+ if (termMatch) strength += STRENGTH_CODE_TERM;
568
+
569
+ const fileMatch = this._matchFilePatterns(m.filePatterns, filePath);
570
+ if (fileMatch) strength += STRENGTH_FILE_PATTERN;
571
+
572
+ // Post-training items with only '**' glob must have importTriggers match
573
+ if (item.type === 'post-training' && this._hasOnlyWildcard(m.filePatterns) && !importMatch) {
574
+ return 0;
575
+ }
576
+
577
+ return strength;
578
+ }
579
+
580
+ /** Check if any importTrigger matches the import set. */
581
+ _matchImports(triggers, importSet) {
582
+ if (!triggers?.length) return false;
583
+ return triggers.some(t => {
584
+ const tLower = t.toLowerCase();
585
+ // Exact match: "next" === "next"
586
+ if (importSet.has(tLower)) return true;
587
+ // Subpath match: trigger "next" matches import "next/navigation"
588
+ for (const imp of importSet) {
589
+ if (imp.startsWith(tLower + '/')) return true;
590
+ }
591
+ return false;
592
+ });
593
+ }
594
+
595
+ /** Check if any functionPattern regex matches the code. */
596
+ _matchFunctionPatterns(patterns, codeLower) {
597
+ if (!patterns?.length) return false;
598
+ return patterns.some(p => {
599
+ try {
600
+ return new RegExp(p, 'i').test(codeLower);
601
+ } catch {
602
+ return false;
603
+ }
604
+ });
605
+ }
606
+
607
+ /** Check if any code term appears in the code block. */
608
+ _matchCodeTerms(terms, codeLower) {
609
+ if (!terms?.length) return false;
610
+ return terms.some(t => codeLower.includes(t.toLowerCase()));
611
+ }
612
+
613
+ /** Check if any file pattern matches the file path. */
614
+ _matchFilePatterns(patterns, filePath) {
615
+ if (!patterns?.length || !filePath) return false;
616
+ return patterns.some(p => simpleGlob(p, filePath));
617
+ }
618
+
619
+ /** True when filePatterns is exactly ['**'] or empty. */
620
+ _hasOnlyWildcard(patterns) {
621
+ if (!patterns?.length) return true;
622
+ return patterns.length === 1 && patterns[0] === '**';
623
+ }
624
+
625
+ /** Extract prohibition patterns and check if code violates them. */
626
+ _findProhibitions(constraintText, codeLower) {
627
+ const violations = [];
628
+ PROHIBITION_RE.lastIndex = 0;
629
+
630
+ let match;
631
+ while ((match = PROHIBITION_RE.exec(constraintText)) !== null) {
632
+ // match[1] = preferred term (from "prefer X over Y"), match[2] = prohibited term
633
+ const prohibited = (match[2] ?? '').toLowerCase().replace(/[.,;:!?]/g, '');
634
+ if (prohibited && codeLower.includes(prohibited)) {
635
+ violations.push(match[0].trim());
636
+ }
637
+ }
638
+
639
+ return violations;
640
+ }
641
+ }