@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,170 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
4
+ import matter from 'gray-matter';
5
+ import { resolveBookLibPaths } from '../paths.js';
6
+
7
+ export function resolveKnowledgePaths() {
8
+ const { indexPath } = resolveBookLibPaths();
9
+ const bookLibDir = path.dirname(indexPath);
10
+ return {
11
+ nodesDir: path.join(bookLibDir, 'knowledge', 'nodes'),
12
+ graphFile: path.join(bookLibDir, 'knowledge', 'graph.jsonl'),
13
+ };
14
+ }
15
+
16
+ export const EDGE_TYPES = Object.freeze([
17
+ 'implements', 'contradicts', 'extends', 'applies-to',
18
+ 'see-also', 'inspired-by', 'supersedes', 'depends-on',
19
+ ]);
20
+
21
+ export function generateNodeId(prefix = 'node') {
22
+ return `${prefix}_${randomBytes(4).toString('hex')}`;
23
+ }
24
+
25
+ /** Serializes a knowledge node to a gray-matter markdown string. */
26
+ export function serializeNode({
27
+ id, type, title, content = '',
28
+ sources = [], tags = [], area = null,
29
+ confidence = 'high', nodePaths = [],
30
+ raw = null,
31
+ }) {
32
+ const data = { id, type, title, created: new Date().toISOString().split('T')[0] };
33
+ if (sources.length) data.sources = sources;
34
+ if (tags.length) data.tags = tags;
35
+ if (area) data.area = area;
36
+ if (confidence !== 'high') data.confidence = confidence;
37
+ if (nodePaths.length) data.paths = nodePaths;
38
+ if (raw) data.raw = raw;
39
+ return matter.stringify(content.trim(), data);
40
+ }
41
+
42
+ export function saveNode(nodeContent, id, { nodesDir } = {}) {
43
+ const dir = nodesDir ?? resolveKnowledgePaths().nodesDir;
44
+ fs.mkdirSync(dir, { recursive: true });
45
+ const filePath = path.join(dir, `${id}.md`);
46
+ fs.writeFileSync(filePath, nodeContent, 'utf8');
47
+ return filePath;
48
+ }
49
+
50
+ export function loadNode(id, { nodesDir } = {}) {
51
+ const dir = nodesDir ?? resolveKnowledgePaths().nodesDir;
52
+ const filePath = path.join(dir, `${id}.md`);
53
+ if (!fs.existsSync(filePath)) return null;
54
+ return fs.readFileSync(filePath, 'utf8');
55
+ }
56
+
57
+ /** Parses a node markdown file, returning frontmatter fields plus a `body` property. */
58
+ export function parseNodeFrontmatter(content) {
59
+ const { data, content: body } = matter(content);
60
+ return { ...data, body: body.trim() };
61
+ }
62
+
63
+ export function listNodes({ nodesDir } = {}) {
64
+ const { nodesDir: defaultDir } = resolveKnowledgePaths();
65
+ const dir = nodesDir ?? defaultDir;
66
+ if (!fs.existsSync(dir)) return [];
67
+ return fs.readdirSync(dir)
68
+ .filter(f => f.endsWith('.md'))
69
+ .map(f => f.replace(/\.md$/, ''));
70
+ }
71
+
72
+ // ── Edge primitives ──────────────────────────────────────────────────────────
73
+
74
+ export function appendEdge(edge, { graphFile } = {}) {
75
+ const file = graphFile ?? resolveKnowledgePaths().graphFile;
76
+ fs.mkdirSync(path.dirname(file), { recursive: true });
77
+ fs.appendFileSync(file, JSON.stringify(edge) + '\n', 'utf8');
78
+ }
79
+
80
+ export function loadEdges({ graphFile } = {}) {
81
+ const file = graphFile ?? resolveKnowledgePaths().graphFile;
82
+ if (!fs.existsSync(file)) return [];
83
+ const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
84
+ const edges = [];
85
+ for (const line of lines) {
86
+ try {
87
+ edges.push(JSON.parse(line));
88
+ } catch {
89
+ // Skip corrupt/truncated lines — one bad byte should not break all graph callers
90
+ if (process.env.BOOKLIB_DEBUG) {
91
+ console.warn(`[graph] Skipped corrupt edge line: ${line.slice(0, 80)}...`);
92
+ }
93
+ }
94
+ }
95
+ return edges;
96
+ }
97
+
98
+ /**
99
+ * Resolves a node reference (exact ID or partial case-insensitive title) to a node ID.
100
+ * Returns the matched node ID, or throws an Error with a helpful message.
101
+ * @param {string} ref - Exact node ID or partial title string.
102
+ * @param {{ nodesDir?: string }} [opts]
103
+ */
104
+ export function resolveNodeRef(ref, { nodesDir } = {}) {
105
+ const dir = nodesDir ?? resolveKnowledgePaths().nodesDir;
106
+ const allIds = listNodes({ nodesDir: dir });
107
+
108
+ // Exact ID match
109
+ if (allIds.includes(ref)) return ref;
110
+
111
+ // Title match (case-insensitive, partial)
112
+ const matches = allIds
113
+ .map(id => {
114
+ const raw = loadNode(id, { nodesDir: dir });
115
+ if (!raw) return null;
116
+ const parsed = parseNodeFrontmatter(raw);
117
+ return { id, title: parsed.title ?? '', type: parsed.type ?? '' };
118
+ })
119
+ .filter(n => n && n.title.toLowerCase().includes(ref.toLowerCase()));
120
+
121
+ if (matches.length === 1) return matches[0].id;
122
+
123
+ if (matches.length === 0) {
124
+ throw new Error(`No node found matching "${ref}". Run: booklib nodes list`);
125
+ }
126
+
127
+ const list = matches.map(m => ` ${m.id} [${m.type}] ${m.title}`).join('\n');
128
+ throw new Error(`Multiple nodes match "${ref}" — use the exact ID:\n${list}`);
129
+ }
130
+
131
+ // ── Graph traversal (BFS, max 2 hops by default) ────────────────────────────
132
+
133
+ export function traverseEdges(startId, edges, maxHops = 2) {
134
+ const results = [];
135
+ const visited = new Set([startId]);
136
+ const queue = [{ id: startId, hop: 0 }];
137
+
138
+ while (queue.length > 0) {
139
+ const { id, hop } = queue.shift();
140
+ if (hop >= maxHops) continue;
141
+
142
+ const connected = edges.filter(e => e.from === id || e.to === id);
143
+ for (const edge of connected) {
144
+ const neighborId = edge.from === id ? edge.to : edge.from;
145
+ if (!visited.has(neighborId)) {
146
+ visited.add(neighborId);
147
+ results.push({ id: neighborId, edge, hop: hop + 1 });
148
+ queue.push({ id: neighborId, hop: hop + 1 });
149
+ }
150
+ }
151
+ }
152
+ return results;
153
+ }
154
+
155
+ /**
156
+ * Parses a links argument string like "effective-kotlin:applies-to,design-patterns:see-also"
157
+ * into an array of { to, type } pairs. Skips entries without a colon separator.
158
+ * @param {string} linksArg
159
+ * @returns {{ to: string, type: string }[]}
160
+ */
161
+ export function parseCaptureLinkArgs(linksArg) {
162
+ if (!linksArg) return [];
163
+ return linksArg.split(',')
164
+ .map(pair => {
165
+ const colonIdx = pair.lastIndexOf(':');
166
+ if (colonIdx === -1) return null;
167
+ return { to: pair.slice(0, colonIdx).trim(), type: pair.slice(colonIdx + 1).trim() };
168
+ })
169
+ .filter(Boolean);
170
+ }
@@ -0,0 +1,411 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { execSync } from 'child_process';
4
+
5
+ /**
6
+ * Manages agent session snapshots for multi-agent handoffs.
7
+ */
8
+ export class BookLibHandoff {
9
+ constructor(handoffDir = path.join(process.cwd(), '.booklib', 'sessions')) {
10
+ this.handoffDir = handoffDir;
11
+ }
12
+
13
+ /**
14
+ * Automatically detects a session ID based on Git branch or folder name + timestamp.
15
+ */
16
+ getAutoSessionId() {
17
+ try {
18
+ // 1. Try Git Branch
19
+ return execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
20
+ } catch {
21
+ // 2. Fallback to Folder Name + Date (for non-git or unrelated chats)
22
+ const folder = path.basename(process.cwd());
23
+ const date = new Date().toISOString().split('T')[0];
24
+ return `${folder}-${date}`;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Resolves the file path for a session.
30
+ */
31
+ getSessionPath(name) {
32
+ const sessionName = name || this.getAutoSessionId();
33
+ return path.join(this.handoffDir, `${sessionName}.md`);
34
+ }
35
+
36
+ /**
37
+ * Saves the current agent session state to a file.
38
+ * Enhanced with git state, uncommitted changes, and recent commits tracking.
39
+ * For long-running chats: includes recent commit history as implicit memory.
40
+ */
41
+ saveState({ name, goal, next, progress, skills }) {
42
+ if (!fs.existsSync(this.handoffDir)) {
43
+ fs.mkdirSync(this.handoffDir, { recursive: true });
44
+ }
45
+
46
+ // Auto-populate active knowledge from project's .booklib/
47
+ let knowledgeItems = [];
48
+ try {
49
+ const booklibDir = path.join(process.cwd(), '.booklib');
50
+
51
+ // Team decisions and post-training gaps from context map — include actual content
52
+ const mapPath = path.join(booklibDir, 'context-map.json');
53
+ if (fs.existsSync(mapPath)) {
54
+ const map = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
55
+ if (map.items) {
56
+ const decisions = map.items.filter(i => i.type === 'decision');
57
+ const gapCount = map.items.filter(i => i.type === 'post-training').length;
58
+ decisions.forEach(d => {
59
+ const text = d.injection?.constraint || d.injection?.correction || d.id;
60
+ knowledgeItems.push(`<decision source="${d.source || ''}">${text}</decision>`);
61
+ });
62
+ if (gapCount > 0) {
63
+ knowledgeItems.push(`<post_training_gaps count="${gapCount}">Run \`booklib analyze\` for details. Hooks inject corrections automatically.</post_training_gaps>`);
64
+ }
65
+ }
66
+ }
67
+
68
+ // Connected sources
69
+ const sourcesPath = path.join(booklibDir, 'sources.json');
70
+ if (fs.existsSync(sourcesPath)) {
71
+ const { sources } = JSON.parse(fs.readFileSync(sourcesPath, 'utf8'));
72
+ sources.filter(s => s.chunk_count > 0).forEach(s => {
73
+ knowledgeItems.push(`<source name="${s.name}" type="${s.type}" chunks="${s.chunk_count}" />`);
74
+ });
75
+ }
76
+
77
+ // Knowledge nodes (team decisions, insights)
78
+ const nodesDir = path.join(booklibDir, 'knowledge', 'nodes');
79
+ if (fs.existsSync(nodesDir)) {
80
+ const nodeFiles = fs.readdirSync(nodesDir).filter(f => f.endsWith('.md'));
81
+ nodeFiles.forEach(f => {
82
+ const content = fs.readFileSync(path.join(nodesDir, f), 'utf8');
83
+ const titleMatch = content.match(/title:\s*(.+)/);
84
+ const typeMatch = content.match(/type:\s*(.+)/);
85
+ if (titleMatch) {
86
+ knowledgeItems.push(`<knowledge type="${(typeMatch?.[1] || 'note').trim()}">${titleMatch[1].trim()}</knowledge>`);
87
+ }
88
+ });
89
+ }
90
+ } catch { /* best-effort — don't fail the save */ }
91
+
92
+ // Capture git state
93
+ let gitInfo = '';
94
+ let recentCommits = '';
95
+ try {
96
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
97
+ const lastCommit = execSync('git log -1 --format=%H%n%s', { stdio: 'pipe' }).toString().trim().split('\n');
98
+ const stagedFiles = execSync('git diff --name-only --cached', { stdio: 'pipe' }).toString().trim().split('\n').filter(Boolean);
99
+ const unstagedFiles = execSync('git diff --name-only', { stdio: 'pipe' }).toString().trim().split('\n').filter(Boolean);
100
+
101
+ // Capture last 10 commits (implicit chat memory via commit messages)
102
+ const commits = execSync('git log --oneline -10', { stdio: 'pipe' }).toString().trim().split('\n');
103
+ recentCommits = commits.map(c => ` ${c}`).join('\n');
104
+
105
+ gitInfo = `
106
+ <git_state>
107
+ <branch>${branch}</branch>
108
+ <last_commit_sha>${lastCommit[0]}</last_commit_sha>
109
+ <last_commit_msg>${lastCommit[1] || 'N/A'}</last_commit_msg>
110
+ <uncommitted_changes>
111
+ <staged_files>${stagedFiles.length > 0 ? stagedFiles.join(', ') : 'None'}</staged_files>
112
+ <modified_files>${unstagedFiles.length > 0 ? unstagedFiles.join(', ') : 'None'}</modified_files>
113
+ </uncommitted_changes>
114
+ <recent_commit_history>
115
+ <note>Use this as implicit context: each commit message documents decisions made</note>
116
+ ${recentCommits}
117
+ </recent_commit_history>
118
+ </git_state>`;
119
+ } catch (err) {
120
+ gitInfo = `
121
+ <git_state>
122
+ <warning>Could not capture git state. Verify changes are committed before resuming.</warning>
123
+ </git_state>`;
124
+ }
125
+
126
+ const sessionPath = this.getSessionPath(name);
127
+ const content = `
128
+ <session_handoff>
129
+ <metadata>
130
+ <timestamp>${new Date().toISOString()}</timestamp>
131
+ <session_id>${name || this.getAutoSessionId()}</session_id>
132
+ <working_directory>${process.cwd()}</working_directory>
133
+ </metadata>
134
+
135
+ <context>
136
+ <goal>${goal || 'Not specified'}</goal>
137
+ <progress>${progress || 'Just started'}</progress>
138
+ <pending_tasks>${next || 'Determine next steps'}</pending_tasks>
139
+ <note>For long chats: review recent_commit_history below for detailed reasoning and decisions</note>
140
+ </context>
141
+
142
+ <active_knowledge>
143
+ ${(skills || []).map(s => `<skill id="${s}" />`).join('\n ')}
144
+ ${knowledgeItems.join('\n ')}
145
+ </active_knowledge>
146
+ ${gitInfo}
147
+
148
+ <recovery_instructions>
149
+ <step1>Run: \`node bin/booklib.js resume ${name || this.getAutoSessionId()}\` to load this context</step1>
150
+ <step2>Review the pending_tasks above</step2>
151
+ <step3>CHECK RECENT COMMIT HISTORY (git_state/recent_commit_history) for chat reasoning</step3>
152
+ <step4>If resuming in a different session, ensure you're in the correct working_directory</step4>
153
+ <step5>Run: \`git log --oneline -20\` to see full history if needed</step5>
154
+ </recovery_instructions>
155
+
156
+ <long_chat_recovery_guide>
157
+ <context_source>Since conversation transcripts aren't saved, use these sources:</context_source>
158
+ <source1>Recent commit messages (above) document each decision</source1>
159
+ <source2>Run: \`git show\` on recent commits to see code changes + reasoning</source2>
160
+ <source3>Run: \`git log -p --follow -- &lt;file&gt;\` to see file evolution</source3>
161
+ <source4>Pending_tasks above shows immediate next steps</source4>
162
+ <source5>Active skills tell you which frameworks were being applied</source5>
163
+ </long_chat_recovery_guide>
164
+ </session_handoff>
165
+ `;
166
+
167
+ fs.writeFileSync(sessionPath, content.trim());
168
+ console.log(`✅ Session snapshot saved to ${sessionPath}`);
169
+ console.log(`📝 Git state captured: branch, commits, uncommitted changes`);
170
+ console.log(`📚 Recent 10 commits saved for implicit chat memory`);
171
+ }
172
+
173
+ /**
174
+ * Resumes the session by reading the handoff file.
175
+ */
176
+ resume(name) {
177
+ const sessionPath = this.getSessionPath(name);
178
+
179
+ if (!fs.existsSync(sessionPath)) {
180
+ const sessions = this.listSessions();
181
+ if (sessions.length > 0) {
182
+ return `No snapshot found for "${name || this.getAutoSessionId()}". \nAvailable sessions: ${sessions.join(', ')}`;
183
+ }
184
+ return 'No handoff files found in .booklib/sessions/. Starting a fresh session.';
185
+ }
186
+
187
+ const content = fs.readFileSync(sessionPath, 'utf8');
188
+ return `
189
+ === RESUMING SESSION [${name || this.getAutoSessionId()}] ===
190
+ An previous agent has left a context snapshot for you.
191
+ Please read the following handoff details and continue the work:
192
+
193
+ ${content}
194
+ `;
195
+ }
196
+
197
+ /**
198
+ * Lists all available handoff sessions.
199
+ */
200
+ listSessions() {
201
+ if (!fs.existsSync(this.handoffDir)) return [];
202
+ return fs.readdirSync(this.handoffDir)
203
+ .filter(f => f.endsWith('.md'))
204
+ .map(f => f.replace('.md', ''));
205
+ }
206
+
207
+ /**
208
+ * Recovers handoff state from session files OR git (100% coverage).
209
+ *
210
+ * Priority order:
211
+ * 1. Explicit session file for current branch
212
+ * 2. Parent session (via lineage)
213
+ * 3. Most recent session on same branch
214
+ * 4. Git-based recovery (fallback)
215
+ *
216
+ * Usage:
217
+ * const recovered = handoff.recoverFromSessionOrGit();
218
+ * console.log(recovered);
219
+ */
220
+ recoverFromSessionOrGit() {
221
+ const branch = this._getCurrentBranch();
222
+ const sessionPath = this.getSessionPath(branch);
223
+
224
+ // Try 1: Explicit session file for current branch
225
+ if (fs.existsSync(sessionPath)) {
226
+ const content = fs.readFileSync(sessionPath, 'utf8');
227
+ return `
228
+ SESSION-BASED RECOVERY (found matching session)
229
+ ═══════════════════════════════════════════════════
230
+
231
+ Branch: ${branch}
232
+ Status: Session file found for this branch ✅
233
+
234
+ ${content}
235
+
236
+ NEXT STEPS:
237
+ 1. Review the context above from the previous agent
238
+ 2. Check git status for any uncommitted work
239
+ 3. Run: git log --oneline -5 to see recent commits
240
+ 4. Continue work as indicated in pending_tasks above
241
+ `;
242
+ }
243
+
244
+ // Try 2: Check lineage for parent session
245
+ const parentSession = this._getParentSession(branch);
246
+ if (parentSession) {
247
+ const parentPath = this.getSessionPath(parentSession);
248
+ if (fs.existsSync(parentPath)) {
249
+ const content = fs.readFileSync(parentPath, 'utf8');
250
+ return `
251
+ SESSION-BASED RECOVERY (found parent session in lineage)
252
+ ═════════════════════════════════════════════════════════
253
+
254
+ Branch: ${branch}
255
+ Status: No explicit session for this branch, but parent "${parentSession}" found ✅
256
+
257
+ PARENT SESSION CONTEXT:
258
+ ${content}
259
+
260
+ RECOVERY INTERPRETATION:
261
+ - This branch was created from: ${parentSession}
262
+ - Parent agent's work provides the base context
263
+ - Any commits since parent session show your additional work
264
+ - Run: git log ${parentSession}..HEAD to see your new commits
265
+
266
+ NEXT STEPS:
267
+ 1. Review parent session context above
268
+ 2. Run: git diff ${parentSession}..HEAD to see your changes
269
+ 3. Review pending_tasks from parent session
270
+ 4. Continue or adjust based on what you've added
271
+ `;
272
+ }
273
+ }
274
+
275
+ // Try 3: Most recent session on same branch
276
+ const recentSession = this._getMostRecentSessionOnBranch(branch);
277
+ if (recentSession) {
278
+ const recentPath = this.getSessionPath(recentSession);
279
+ if (fs.existsSync(recentPath)) {
280
+ const content = fs.readFileSync(recentPath, 'utf8');
281
+ return `
282
+ SESSION-BASED RECOVERY (found recent session on branch)
283
+ ═══════════════════════════════════════════════════════
284
+
285
+ Branch: ${branch}
286
+ Status: Using most recent session on this branch: "${recentSession}" ✅
287
+
288
+ ${content}
289
+
290
+ RECOVERY INTERPRETATION:
291
+ - This is the most recent session saved on this branch
292
+ - Any new commits since this session represent your continued work
293
+ - Run: git log ${recentSession}.. to see what you did after this session
294
+
295
+ NEXT STEPS:
296
+ 1. Review the session context above
297
+ 2. Check your recent commits for what you accomplished
298
+ 3. Review pending_tasks from the session
299
+ 4. Continue from where you left off
300
+ `;
301
+ }
302
+ }
303
+
304
+ // Fallback: Git-based recovery
305
+ return this.recoverFromGit();
306
+ }
307
+
308
+ // ─── HELPER METHODS FOR ENHANCED RECOVERY ───
309
+
310
+ /**
311
+ * Gets current git branch.
312
+ */
313
+ _getCurrentBranch() {
314
+ try {
315
+ return execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
316
+ } catch {
317
+ return 'unknown';
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Gets parent session from lineage file.
323
+ */
324
+ _getParentSession(branch) {
325
+ const lineageFile = path.join(this.handoffDir, '_lineage.json');
326
+ if (!fs.existsSync(lineageFile)) return null;
327
+
328
+ try {
329
+ const lineage = JSON.parse(fs.readFileSync(lineageFile, 'utf8'));
330
+ return lineage[branch]?.parent || null;
331
+ } catch {
332
+ return null;
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Finds most recent session saved on current branch.
338
+ */
339
+ _getMostRecentSessionOnBranch(branch) {
340
+ if (!fs.existsSync(this.handoffDir)) return null;
341
+
342
+ const sessions = fs.readdirSync(this.handoffDir)
343
+ .filter(f => f.endsWith('.md') && f !== '_lineage.json')
344
+ .map(f => {
345
+ const filePath = path.join(this.handoffDir, f);
346
+ const content = fs.readFileSync(filePath, 'utf8');
347
+ const branchMatch = content.match(/<branch>(.*?)<\/branch>/);
348
+ const sessionBranch = branchMatch ? branchMatch[1] : null;
349
+
350
+ return {
351
+ name: f.replace('.md', ''),
352
+ branch: sessionBranch,
353
+ mtime: fs.statSync(filePath).mtime
354
+ };
355
+ })
356
+ .filter(s => s.branch === branch)
357
+ .sort((a, b) => b.mtime - a.mtime);
358
+
359
+ return sessions.length > 0 ? sessions[0].name : null;
360
+ }
361
+
362
+ /**
363
+ * Recovers handoff state from git when explicit save file is missing.
364
+ * Useful if user forgets to save-state before quota hit.
365
+ *
366
+ * Usage:
367
+ * const recovered = handoff.recoverFromGit();
368
+ * console.log(recovered);
369
+ */
370
+ recoverFromGit() {
371
+ try {
372
+ const branch = this._getCurrentBranch();
373
+ const lastCommit = execSync('git log -1 --format=%H%n%s%n%b', { stdio: 'pipe' }).toString().trim().split('\n');
374
+ const commits = execSync('git log --oneline -10', { stdio: 'pipe' }).toString().trim();
375
+ const status = execSync('git status --short', { stdio: 'pipe' }).toString().trim();
376
+
377
+ return `
378
+ GIT-BASED RECOVERY (no session files found)
379
+ ═══════════════════════════════════════════
380
+
381
+ Branch: ${branch}
382
+
383
+ Last Commit: ${lastCommit[0]}
384
+ Message: ${lastCommit[1] || 'N/A'}
385
+
386
+ Recent 10 Commits:
387
+ ${commits}
388
+
389
+ Uncommitted Changes:
390
+ ${status || 'None'}
391
+
392
+ RECOVERY STEPS:
393
+ 1. Review recent commits above to understand what was done
394
+ 2. Run: git show <commit-sha> to see code + reasoning
395
+ 3. Run: git log -p -- <file> to trace file evolution
396
+ 4. Run: git status to see current work in progress
397
+ 5. Create explicit handoff for next agent: booklib save-state
398
+
399
+ SAVE FOR NEXT TIME:
400
+ Call "booklib save-state" before quota exhaustion to preserve:
401
+ - Goal statement
402
+ - Progress summary
403
+ - Next tasks
404
+ - Active skills
405
+ - Recovery instructions
406
+ `;
407
+ } catch (err) {
408
+ return `Could not recover from git: ${err.message}. Check manual git history.`;
409
+ }
410
+ }
411
+ }