@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,334 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { LocalIndex } from 'vectra';
4
+ import { parseSkillFile } from './parser.js';
5
+ import { resolveBookLibPaths } from '../paths.js';
6
+ import { BM25Index } from './bm25-index.js';
7
+ import { createEmbeddingPipeline, batchEmbed, BATCH_SIZE } from './embedding-provider.js';
8
+
9
+ /**
10
+ * Builds a structured metadata prefix for SRAG-style embeddings.
11
+ * Prepended to chunk text before vector embedding so the model encodes domain context.
12
+ * @param {object} metadata - Chunk metadata (name/title, type, tags).
13
+ * @returns {string} Prefix string like "[skill:X] [type:Y] [tags:a,b] " or "".
14
+ */
15
+ export function buildMetadataPrefix(metadata) {
16
+ const parts = [];
17
+ const label = metadata.name ?? metadata.title;
18
+ if (label) parts.push(`[skill:${label}]`);
19
+ if (metadata.type) parts.push(`[type:${metadata.type}]`);
20
+ if (Array.isArray(metadata.tags) && metadata.tags.length > 0) {
21
+ parts.push(`[tags:${metadata.tags.join(',')}]`);
22
+ }
23
+ return parts.length > 0 ? parts.join(' ') + ' ' : '';
24
+ }
25
+
26
+ /**
27
+ * Handles the creation and updating of the semantic index for the BookLib library.
28
+ */
29
+ export class BookLibIndexer {
30
+ constructor(indexPath) {
31
+ this.indexPath = indexPath ?? resolveBookLibPaths().indexPath;
32
+ fs.mkdirSync(this.indexPath, { recursive: true });
33
+ this.index = new LocalIndex(this.indexPath);
34
+ this.extractor = null;
35
+ }
36
+
37
+ get bm25Path() {
38
+ return path.join(path.dirname(this.indexPath), 'bm25.json');
39
+ }
40
+
41
+ _loadOrCreateBM25() {
42
+ if (!fs.existsSync(this.bm25Path)) return new BM25Index();
43
+ try {
44
+ return BM25Index.load(this.bm25Path);
45
+ } catch {
46
+ // Corrupt BM25 — rebuild from scratch
47
+ return new BM25Index();
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Loads the embedding model with GPU auto-detection (lazy-loaded).
53
+ * @param {Object} opts
54
+ * @param {boolean} [opts.quiet=false] - Suppress the "Loading local embedding model..." message.
55
+ */
56
+ async loadModel(opts = {}) {
57
+ const { quiet = false } = opts;
58
+ if (!this.extractor) {
59
+ const indexExists = await this.index.isIndexCreated().catch(() => false);
60
+ if (!indexExists) {
61
+ console.log('First run: downloading embedding model (~25 MB, ~1 min)...');
62
+ } else if (!quiet) {
63
+ console.log('Loading local embedding model...');
64
+ }
65
+ const { extractor, providerInfo } = await createEmbeddingPipeline({ quiet });
66
+ this.extractor = extractor;
67
+ this.providerInfo = providerInfo;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Generates a vector embedding for a single string.
73
+ * Used for search queries and single-node indexing.
74
+ */
75
+ async getEmbedding(text) {
76
+ await this.loadModel({ quiet: true });
77
+ const output = await this.extractor(text, { pooling: 'mean', normalize: true });
78
+ return Array.from(output.data);
79
+ }
80
+
81
+ /**
82
+ * Indexes a directory of skills.
83
+ *
84
+ * @param {string} dirPath - The root directory of the skills library.
85
+ * @param {boolean} clearFirst - Whether to clear the index before starting.
86
+ * @param {Object} opts
87
+ * @param {boolean} [opts.quiet=false] - Suppress per-file output; print a single summary instead.
88
+ */
89
+ async indexDirectory(dirPath, clearFirst = false, opts = {}) {
90
+ const { quiet = false, onProgress, onFileProgress, onStatus, sourceName } = opts;
91
+
92
+ if (clearFirst && fs.existsSync(this.indexPath)) {
93
+ fs.rmSync(this.indexPath, { recursive: true, force: true });
94
+ }
95
+
96
+ // Validate vectra index.json — if corrupt (0 bytes or truncated JSON), delete and recreate.
97
+ const vectraIndexFile = path.join(this.indexPath, 'index.json');
98
+ if (fs.existsSync(vectraIndexFile)) {
99
+ let corrupt = false;
100
+ const stat = fs.statSync(vectraIndexFile);
101
+ if (stat.size === 0) {
102
+ corrupt = true;
103
+ } else {
104
+ try {
105
+ JSON.parse(fs.readFileSync(vectraIndexFile, 'utf8'));
106
+ } catch {
107
+ corrupt = true;
108
+ }
109
+ }
110
+ if (corrupt) {
111
+ fs.rmSync(this.indexPath, { recursive: true, force: true });
112
+ fs.mkdirSync(this.indexPath, { recursive: true });
113
+ this.index = new LocalIndex(this.indexPath);
114
+ }
115
+ }
116
+
117
+ if (!(await this.index.isIndexCreated())) {
118
+ await this.index.createIndex();
119
+ }
120
+
121
+ const files = this.getFiles(dirPath, ['.md', '.mdc']);
122
+ if (!quiet) console.log(`Found ${files.length} skill files to index in ${dirPath}.`);
123
+
124
+ // Pre-warm the model so the load message respects the quiet flag.
125
+ await this.loadModel({ quiet });
126
+ const bm25Chunks = [];
127
+
128
+ // Phase 1: Parse all files and collect chunks
129
+ let totalFiles = 0;
130
+ let skipped = 0;
131
+ const allChunks = [];
132
+
133
+ for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
134
+ const file = files[fileIndex];
135
+ const content = fs.readFileSync(file, 'utf8');
136
+ const relativePath = path.relative(dirPath, file);
137
+ let chunks;
138
+ try {
139
+ chunks = parseSkillFile(content, relativePath);
140
+ } catch (err) {
141
+ if (quiet) {
142
+ skipped++;
143
+ } else {
144
+ process.stderr.write(`Skipping ${relativePath}: ${err.message}\n`);
145
+ }
146
+ continue;
147
+ }
148
+
149
+ if (quiet) {
150
+ totalFiles++;
151
+ } else {
152
+ console.log(`Parsing ${relativePath} (${chunks.length} chunks)...`);
153
+ }
154
+ onFileProgress?.({ current: fileIndex + 1, total: files.length, file: relativePath });
155
+
156
+ bm25Chunks.push(...chunks);
157
+ allChunks.push(...chunks);
158
+ }
159
+
160
+ // Phase 2: Batch-embed all chunks for throughput
161
+ const texts = allChunks.map(c => buildMetadataPrefix(c.metadata) + c.text);
162
+ const vectors = await batchEmbed(this.extractor, texts, BATCH_SIZE, ({ done, total }) => {
163
+ onProgress?.({ current: done, total, file: `embedding ${done}/${total}` });
164
+ });
165
+
166
+ // Phase 3: Batch-insert into vectra (single disk write instead of N)
167
+ onStatus?.('saving');
168
+ const items = allChunks.map((chunk, i) => {
169
+ const meta = { ...chunk.metadata, text: chunk.text };
170
+ if (sourceName) meta.sourceName = sourceName;
171
+ return { vector: vectors[i], metadata: meta };
172
+ });
173
+ await this.index.beginUpdate();
174
+ for (const item of items) {
175
+ await this.index.insertItem(item);
176
+ }
177
+ await this.index.endUpdate();
178
+
179
+ // Tag BM25 chunks with sourceName so disconnect can filter them out
180
+ if (sourceName) {
181
+ for (const chunk of bm25Chunks) {
182
+ chunk.metadata = { ...chunk.metadata, sourceName };
183
+ }
184
+ }
185
+
186
+ if (clearFirst) {
187
+ // Full rebuild from scratch
188
+ const bm25 = new BM25Index();
189
+ bm25.build(bm25Chunks);
190
+ bm25.save(this.bm25Path);
191
+ } else {
192
+ // Append to existing — don't rebuild 19K docs for 200 new chunks
193
+ const bm25 = this._loadOrCreateBM25();
194
+ for (const chunk of bm25Chunks) {
195
+ bm25.add(chunk);
196
+ }
197
+ bm25.save(this.bm25Path);
198
+ }
199
+
200
+ if (quiet) {
201
+ console.log(` Indexed ${totalFiles} files (${allChunks.length} chunks)`);
202
+ if (skipped > 0) {
203
+ console.log(` ${skipped} file(s) skipped (malformed frontmatter)`);
204
+ }
205
+ } else {
206
+ console.log('Indexing complete.');
207
+ }
208
+
209
+ return { files: totalFiles, chunks: allChunks.length };
210
+ }
211
+
212
+ /**
213
+ * Indexes a single knowledge node file into the existing index.
214
+ * Safe to call after each capture command — only adds the new node.
215
+ * @param {string} filePath - Absolute path to the node .md file.
216
+ * @param {string} nodesDir - Root nodes directory (used for relative path in metadata).
217
+ */
218
+ async indexNodeFile(filePath, nodesDir) {
219
+ if (!(await this.index.isIndexCreated())) {
220
+ await this.index.createIndex();
221
+ }
222
+
223
+ const content = fs.readFileSync(filePath, 'utf8');
224
+ const relativePath = path.relative(nodesDir, filePath);
225
+ let chunks;
226
+ try {
227
+ chunks = parseSkillFile(content, relativePath);
228
+ } catch (err) {
229
+ process.stderr.write(`⚠ Skipping node ${relativePath}: ${err.message}\n`);
230
+ return;
231
+ }
232
+
233
+ // If body is empty, index the frontmatter fields so the node is still findable by title/tags
234
+ if (chunks.length === 0) {
235
+ const matter = (await import('gray-matter')).default;
236
+ const { data } = matter.read(filePath);
237
+ const fallbackText = [data.title, data.type, ...(data.tags ?? [])].filter(Boolean).join(' ');
238
+ if (!fallbackText) return;
239
+ const prefixedFallback = buildMetadataPrefix(data) + fallbackText;
240
+ const vector = await this.getEmbedding(prefixedFallback);
241
+ await this.index.insertItem({
242
+ vector,
243
+ metadata: { text: fallbackText, id: data.id, title: data.title, type: data.type, nodeKind: 'knowledge', nodeFile: filePath }
244
+ });
245
+ const bm25 = this._loadOrCreateBM25();
246
+ bm25.add({ text: fallbackText, metadata: { id: data.id, title: data.title, type: data.type, nodeKind: 'knowledge', nodeFile: filePath } });
247
+ bm25.save(this.bm25Path);
248
+ return;
249
+ }
250
+
251
+ for (const chunk of chunks) {
252
+ const prefixedText = buildMetadataPrefix(chunk.metadata) + chunk.text;
253
+ const vector = await this.getEmbedding(prefixedText);
254
+ await this.index.insertItem({
255
+ vector,
256
+ metadata: { ...chunk.metadata, text: chunk.text, nodeKind: 'knowledge', nodeFile: filePath }
257
+ });
258
+ }
259
+
260
+ const bm25 = this._loadOrCreateBM25();
261
+ for (const chunk of chunks) bm25.add(chunk);
262
+ bm25.save(this.bm25Path);
263
+ }
264
+
265
+ /**
266
+ * Indexes all knowledge nodes from .booklib/knowledge/nodes/.
267
+ * Used by `booklib index` to rebuild the full knowledge portion of the index.
268
+ * @param {string} nodesDir - Path to the nodes directory.
269
+ */
270
+ async indexKnowledgeNodes(nodesDir) {
271
+ if (!fs.existsSync(nodesDir)) return;
272
+
273
+ const files = this.getFiles(nodesDir, ['.md']);
274
+ if (files.length === 0) return;
275
+ console.log(`Indexing ${files.length} knowledge node(s)...`);
276
+
277
+ for (const file of files) {
278
+ await this.indexNodeFile(file, nodesDir);
279
+ }
280
+
281
+ // Auto-link knowledge nodes to matching skills
282
+ try {
283
+ const { autoLinkSkills } = await import('./auto-linker.js');
284
+ const matterLib = (await import('gray-matter')).default;
285
+
286
+ const knowledgeNodes = files.map(file => {
287
+ const content = fs.readFileSync(file, 'utf8');
288
+ const { data, content: body } = matterLib(content);
289
+ return { id: data.id, title: data.title ?? '', tags: data.tags ?? [], body: body?.slice(0, 500) ?? '' };
290
+ }).filter(n => n.id);
291
+
292
+ // Collect skill tags + descriptions from the skills directory
293
+ const skillsPath = path.resolve(this.indexPath, '..', '..', 'skills');
294
+ const skillTags = [];
295
+ if (fs.existsSync(skillsPath)) {
296
+ for (const dir of fs.readdirSync(skillsPath)) {
297
+ const skillFile = path.join(skillsPath, dir, 'SKILL.md');
298
+ if (!fs.existsSync(skillFile)) continue;
299
+ const { data } = matterLib(fs.readFileSync(skillFile, 'utf8'));
300
+ if (data.name) {
301
+ skillTags.push({ name: data.name, tags: data.tags ?? [], description: data.description ?? '' });
302
+ }
303
+ }
304
+ }
305
+
306
+ if (knowledgeNodes.length > 0 && skillTags.length > 0) {
307
+ const links = await autoLinkSkills({ knowledgeNodes, skillTags });
308
+ if (links.length > 0) {
309
+ console.log(` Auto-linked ${links.length} knowledge↔skill edge(s)`);
310
+ }
311
+ }
312
+ } catch { /* auto-linking is best-effort */ }
313
+ }
314
+
315
+ /**
316
+ * Helper to recursively list files with specific extensions.
317
+ */
318
+ getFiles(dir, extensions) {
319
+ let results = [];
320
+ const list = fs.readdirSync(dir);
321
+ list.forEach(file => {
322
+ file = path.join(dir, file);
323
+ const stat = fs.statSync(file);
324
+ if (stat && stat.isDirectory()) {
325
+ results = results.concat(this.getFiles(file, extensions));
326
+ } else {
327
+ if (extensions.some(ext => file.endsWith(ext))) {
328
+ results.push(file);
329
+ }
330
+ }
331
+ });
332
+ return results;
333
+ }
334
+ }
@@ -0,0 +1,15 @@
1
+ // lib/engine/lookup-priority.js — prioritize lookup results by source type
2
+
3
+ /**
4
+ * Prioritize lookup results: post-training > team > niche skills.
5
+ *
6
+ * @param {{ gapResults?: Array, teamResults?: Array, nicheResults?: Array }} sources
7
+ * @returns {Array} merged results, ordered by priority
8
+ */
9
+ export function prioritizeLookupResults({ gapResults = [], teamResults = [], nicheResults = [] }) {
10
+ const priority = [...gapResults, ...teamResults];
11
+ if (priority.length < 2) {
12
+ priority.push(...nicheResults);
13
+ }
14
+ return priority;
15
+ }
@@ -0,0 +1,257 @@
1
+ import { createHash } from 'node:crypto';
2
+ import matter from 'gray-matter';
3
+
4
+ const NEGATION_WORDS = ['never', 'not', "don't", 'instead', 'but', 'however', 'avoid', 'do not'];
5
+
6
+ const XML_TAG_SECTION_MAP = {
7
+ core_principles: 'framework',
8
+ anti_patterns: 'pitfalls',
9
+ examples: 'case_studies',
10
+ };
11
+
12
+ /**
13
+ * Generates a stable parentId from document name and header text.
14
+ */
15
+ function makeParentId(docName, headerText) {
16
+ return createHash('sha256')
17
+ .update(`${docName}::${headerText}`)
18
+ .digest('hex')
19
+ .slice(0, 12);
20
+ }
21
+
22
+ /**
23
+ * Infer section type from header text or surrounding XML tag.
24
+ */
25
+ function inferSection(headerText, xmlTag) {
26
+ if (xmlTag && XML_TAG_SECTION_MAP[xmlTag]) return XML_TAG_SECTION_MAP[xmlTag];
27
+
28
+ const lower = headerText.toLowerCase();
29
+ if (/principle|rule/.test(lower)) return 'framework';
30
+ if (/anti|avoid|pitfall/.test(lower)) return 'pitfalls';
31
+ if (/example|case/.test(lower)) return 'case_studies';
32
+ return 'content';
33
+ }
34
+
35
+ /**
36
+ * Detect which XML tag (if any) wraps a line range within the body.
37
+ */
38
+ function detectXmlTag(body, sectionText) {
39
+ for (const [tag] of Object.entries(XML_TAG_SECTION_MAP)) {
40
+ const openTag = `<${tag}>`;
41
+ const closeTag = `</${tag}>`;
42
+ const openIdx = body.indexOf(openTag);
43
+ const closeIdx = body.indexOf(closeTag);
44
+ if (openIdx !== -1 && closeIdx !== -1) {
45
+ const tagContent = body.slice(openIdx + openTag.length, closeIdx);
46
+ if (tagContent.includes(sectionText)) return tag;
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+
52
+ /**
53
+ * Extracts keywords (>3 chars) from text for contradiction detection.
54
+ */
55
+ function extractKeywords(text) {
56
+ return new Set(
57
+ text.toLowerCase().match(/\b[a-z]{4,}\b/g) || []
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Returns true if chunkB starts with a negation word and shares
63
+ * a keyword (>3 chars) with chunkA.
64
+ */
65
+ function shouldMerge(textA, textB) {
66
+ const lowerB = textB.toLowerCase().trimStart();
67
+ const startsWithNegation = NEGATION_WORDS.some(w => lowerB.startsWith(w));
68
+ if (!startsWithNegation) return false;
69
+
70
+ const kwA = extractKeywords(textA);
71
+ const kwB = extractKeywords(textB);
72
+ for (const kw of kwB) {
73
+ if (kwA.has(kw)) return true;
74
+ }
75
+ return false;
76
+ }
77
+
78
+ /**
79
+ * Splits a section body into atomic chunks: bullets, numbered items,
80
+ * bold-headed paragraphs, or plain paragraphs.
81
+ */
82
+ function splitSectionBody(body) {
83
+ const lines = body.split('\n');
84
+ const rawChunks = [];
85
+ let current = null;
86
+
87
+ for (const line of lines) {
88
+ const trimmed = line.trimStart();
89
+ const isBullet = /^[-*] /.test(trimmed);
90
+ const isNumbered = /^\d+\.\s/.test(trimmed);
91
+ const isBoldHead = /^\*\*[^*]+\*\*/.test(trimmed);
92
+ const isContinuation = !isBullet && !isNumbered && !isBoldHead
93
+ && current !== null && trimmed.length > 0
94
+ && (line.startsWith(' ') || line.startsWith('\t'));
95
+
96
+ if (isBullet || isNumbered || isBoldHead) {
97
+ if (current !== null) rawChunks.push(current);
98
+ const cleaned = trimmed
99
+ .replace(/^[-*]\s+/, '')
100
+ .replace(/^\d+\.\s+/, '');
101
+ current = cleaned;
102
+ } else if (isContinuation) {
103
+ current += ' ' + trimmed;
104
+ } else if (trimmed.length > 0) {
105
+ // Plain paragraph line that isn't a continuation
106
+ if (current !== null) rawChunks.push(current);
107
+ current = trimmed;
108
+ }
109
+ // Skip blank lines (they just separate things)
110
+ }
111
+ if (current !== null) rawChunks.push(current);
112
+ return rawChunks;
113
+ }
114
+
115
+ /**
116
+ * Apply contradiction guard: merge adjacent chunks where the second
117
+ * negates the first.
118
+ */
119
+ function applyContradictionGuard(chunks) {
120
+ if (chunks.length <= 1) return chunks;
121
+ const merged = [chunks[0]];
122
+ for (let i = 1; i < chunks.length; i++) {
123
+ const prev = merged[merged.length - 1];
124
+ if (shouldMerge(prev, chunks[i])) {
125
+ merged[merged.length - 1] = prev + '\n' + chunks[i];
126
+ } else {
127
+ merged.push(chunks[i]);
128
+ }
129
+ }
130
+ return merged;
131
+ }
132
+
133
+ /**
134
+ * Strip XML tags from text, returning the inner content.
135
+ */
136
+ function stripXmlTags(text) {
137
+ return text.replace(/<\/?[a-z_]+>/g, '').trim();
138
+ }
139
+
140
+ /**
141
+ * Splits any markdown document into atomic chunks with parent references.
142
+ *
143
+ * @param {string} content - Raw markdown content (may include YAML frontmatter)
144
+ * @param {string} filePath - Path to the source file
145
+ * @returns {Array<{text: string, parentId: string, parentTitle: string, siblingIndex: number, siblingCount: number, section: string, metadata: object}>}
146
+ */
147
+ export function splitMarkdown(content, filePath) {
148
+ const { data: frontmatter, content: body } = matter(content);
149
+ const docName = frontmatter.name || filePath;
150
+ const allChunks = [];
151
+
152
+ // Split body by ## headers
153
+ const headerRegex = /^## (.+)$/gm;
154
+ const headers = [];
155
+ let match;
156
+ while ((match = headerRegex.exec(body)) !== null) {
157
+ headers.push({ title: match[1].trim(), index: match.index });
158
+ }
159
+
160
+ // Content before first header is the summary
161
+ const preHeaderText = headers.length > 0
162
+ ? body.slice(0, headers[0].index).trim()
163
+ : body.trim();
164
+
165
+ if (preHeaderText && headers.length > 0) {
166
+ const cleaned = stripXmlTags(preHeaderText);
167
+ if (cleaned) {
168
+ allChunks.push({
169
+ text: cleaned,
170
+ parentId: makeParentId(docName, 'summary'),
171
+ parentTitle: docName,
172
+ siblingIndex: 0,
173
+ siblingCount: 1,
174
+ section: 'summary',
175
+ metadata: { ...frontmatter, filePath },
176
+ });
177
+ }
178
+ }
179
+
180
+ // No headers: whole document under title parent
181
+ if (headers.length === 0) {
182
+ const cleaned = stripXmlTags(preHeaderText);
183
+ if (cleaned) {
184
+ const bodyChunks = splitSectionBody(cleaned);
185
+ const merged = applyContradictionGuard(bodyChunks);
186
+ const parentId = makeParentId(docName, docName);
187
+ for (let i = 0; i < merged.length; i++) {
188
+ allChunks.push({
189
+ text: merged[i],
190
+ parentId,
191
+ parentTitle: docName,
192
+ siblingIndex: i,
193
+ siblingCount: merged.length,
194
+ section: 'content',
195
+ metadata: { ...frontmatter, filePath },
196
+ });
197
+ }
198
+ }
199
+ return allChunks;
200
+ }
201
+
202
+ // Process each header section
203
+ for (let h = 0; h < headers.length; h++) {
204
+ const header = headers[h];
205
+ const nextStart = h + 1 < headers.length ? headers[h + 1].index : body.length;
206
+ const sectionRaw = body.slice(
207
+ header.index + body.slice(header.index).indexOf('\n') + 1,
208
+ nextStart
209
+ ).trim();
210
+
211
+ const xmlTag = detectXmlTag(body, sectionRaw.slice(0, 80));
212
+ const section = inferSection(header.title, xmlTag);
213
+ const cleaned = stripXmlTags(sectionRaw);
214
+ if (!cleaned) continue;
215
+
216
+ const bodyChunks = splitSectionBody(cleaned);
217
+ const merged = applyContradictionGuard(bodyChunks);
218
+ const parentId = makeParentId(docName, header.title);
219
+
220
+ for (let i = 0; i < merged.length; i++) {
221
+ allChunks.push({
222
+ text: merged[i],
223
+ parentId,
224
+ parentTitle: header.title,
225
+ siblingIndex: i,
226
+ siblingCount: merged.length,
227
+ section,
228
+ metadata: { ...frontmatter, filePath },
229
+ });
230
+ }
231
+ }
232
+
233
+ return allChunks;
234
+ }
235
+
236
+ /**
237
+ * Backwards-compatible wrapper. Returns [{text, metadata}] format
238
+ * with parent fields included in metadata.
239
+ *
240
+ * @param {string} content - Raw file content
241
+ * @param {string} filePath - Path to the file
242
+ * @returns {Array<{text: string, metadata: object}>}
243
+ */
244
+ export function parseSkillFile(content, filePath) {
245
+ const chunks = splitMarkdown(content, filePath);
246
+ return chunks.map(chunk => ({
247
+ text: chunk.text,
248
+ metadata: {
249
+ ...chunk.metadata,
250
+ type: chunk.section,
251
+ parentId: chunk.parentId,
252
+ parentTitle: chunk.parentTitle,
253
+ siblingIndex: chunk.siblingIndex,
254
+ siblingCount: chunk.siblingCount,
255
+ },
256
+ }));
257
+ }