@booklib/core 2.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 (374) hide show
  1. package/.cursor/rules/booklib-standards.mdc +40 -0
  2. package/.gemini/context.md +372 -0
  3. package/AGENTS.md +166 -0
  4. package/CHANGELOG.md +226 -0
  5. package/CLAUDE.md +81 -0
  6. package/CODE_OF_CONDUCT.md +31 -0
  7. package/CONTRIBUTING.md +304 -0
  8. package/LICENSE +21 -0
  9. package/PLAN.md +28 -0
  10. package/README.ja.md +198 -0
  11. package/README.ko.md +198 -0
  12. package/README.md +503 -0
  13. package/README.pt-BR.md +198 -0
  14. package/README.uk.md +241 -0
  15. package/README.zh-CN.md +198 -0
  16. package/SECURITY.md +9 -0
  17. package/agents/architecture-reviewer.md +136 -0
  18. package/agents/booklib-reviewer.md +90 -0
  19. package/agents/data-reviewer.md +107 -0
  20. package/agents/jvm-reviewer.md +146 -0
  21. package/agents/python-reviewer.md +128 -0
  22. package/agents/rust-reviewer.md +115 -0
  23. package/agents/ts-reviewer.md +110 -0
  24. package/agents/ui-reviewer.md +117 -0
  25. package/assets/logo.svg +36 -0
  26. package/bin/booklib-mcp.js +304 -0
  27. package/bin/booklib.js +1705 -0
  28. package/bin/skills.cjs +1292 -0
  29. package/booklib-router.mdc +36 -0
  30. package/booklib.config.json +19 -0
  31. package/commands/animation-at-work.md +10 -0
  32. package/commands/clean-code-reviewer.md +10 -0
  33. package/commands/data-intensive-patterns.md +10 -0
  34. package/commands/data-pipelines.md +10 -0
  35. package/commands/design-patterns.md +10 -0
  36. package/commands/domain-driven-design.md +10 -0
  37. package/commands/effective-java.md +10 -0
  38. package/commands/effective-kotlin.md +10 -0
  39. package/commands/effective-python.md +10 -0
  40. package/commands/effective-typescript.md +10 -0
  41. package/commands/kotlin-in-action.md +10 -0
  42. package/commands/lean-startup.md +10 -0
  43. package/commands/microservices-patterns.md +10 -0
  44. package/commands/programming-with-rust.md +10 -0
  45. package/commands/refactoring-ui.md +10 -0
  46. package/commands/rust-in-action.md +10 -0
  47. package/commands/skill-router.md +10 -0
  48. package/commands/spring-boot-in-action.md +10 -0
  49. package/commands/storytelling-with-data.md +10 -0
  50. package/commands/system-design-interview.md +10 -0
  51. package/commands/using-asyncio-python.md +10 -0
  52. package/commands/web-scraping-python.md +10 -0
  53. package/community/registry.json +1616 -0
  54. package/hooks/hooks.json +23 -0
  55. package/hooks/posttooluse-capture.mjs +67 -0
  56. package/hooks/suggest.js +153 -0
  57. package/lib/agent-behaviors.js +40 -0
  58. package/lib/agent-detector.js +96 -0
  59. package/lib/config-loader.js +39 -0
  60. package/lib/conflict-resolver.js +148 -0
  61. package/lib/context-builder.js +574 -0
  62. package/lib/discovery-engine.js +298 -0
  63. package/lib/doctor/hook-installer.js +83 -0
  64. package/lib/doctor/usage-tracker.js +87 -0
  65. package/lib/engine/ai-features.js +253 -0
  66. package/lib/engine/auditor.js +103 -0
  67. package/lib/engine/bm25-index.js +178 -0
  68. package/lib/engine/capture.js +120 -0
  69. package/lib/engine/corrections.js +198 -0
  70. package/lib/engine/doctor.js +195 -0
  71. package/lib/engine/graph-injector.js +137 -0
  72. package/lib/engine/graph.js +161 -0
  73. package/lib/engine/handoff.js +405 -0
  74. package/lib/engine/indexer.js +242 -0
  75. package/lib/engine/parser.js +53 -0
  76. package/lib/engine/query-expander.js +42 -0
  77. package/lib/engine/reranker.js +40 -0
  78. package/lib/engine/rrf.js +59 -0
  79. package/lib/engine/scanner.js +151 -0
  80. package/lib/engine/searcher.js +139 -0
  81. package/lib/engine/session-coordinator.js +306 -0
  82. package/lib/engine/session-manager.js +429 -0
  83. package/lib/engine/synthesizer.js +70 -0
  84. package/lib/installer.js +70 -0
  85. package/lib/instinct-block.js +33 -0
  86. package/lib/mcp-config-writer.js +88 -0
  87. package/lib/paths.js +57 -0
  88. package/lib/profiles/design.md +19 -0
  89. package/lib/profiles/general.md +16 -0
  90. package/lib/profiles/research-analysis.md +22 -0
  91. package/lib/profiles/software-development.md +23 -0
  92. package/lib/profiles/writing-content.md +19 -0
  93. package/lib/project-initializer.js +916 -0
  94. package/lib/registry/skills.js +102 -0
  95. package/lib/registry-searcher.js +99 -0
  96. package/lib/rules/rules-manager.js +169 -0
  97. package/lib/skill-fetcher.js +333 -0
  98. package/lib/well-known-builder.js +70 -0
  99. package/lib/wizard/index.js +404 -0
  100. package/lib/wizard/integration-detector.js +41 -0
  101. package/lib/wizard/project-detector.js +100 -0
  102. package/lib/wizard/prompt.js +156 -0
  103. package/lib/wizard/registry-embeddings.js +107 -0
  104. package/lib/wizard/skill-recommender.js +69 -0
  105. package/llms-full.txt +254 -0
  106. package/llms.txt +70 -0
  107. package/package.json +45 -0
  108. package/research-reports/2026-04-01-current-architecture.md +160 -0
  109. package/research-reports/IDEAS.md +93 -0
  110. package/rules/common/clean-code.md +42 -0
  111. package/rules/java/effective-java.md +42 -0
  112. package/rules/kotlin/effective-kotlin.md +37 -0
  113. package/rules/python/effective-python.md +38 -0
  114. package/rules/rust/rust.md +37 -0
  115. package/rules/typescript/effective-typescript.md +42 -0
  116. package/scripts/gen-llms-full.mjs +36 -0
  117. package/scripts/gen-og.mjs +142 -0
  118. package/scripts/validate-frontmatter.js +25 -0
  119. package/skills/animation-at-work/SKILL.md +270 -0
  120. package/skills/animation-at-work/assets/example_asset.txt +1 -0
  121. package/skills/animation-at-work/evals/evals.json +44 -0
  122. package/skills/animation-at-work/evals/results.json +13 -0
  123. package/skills/animation-at-work/examples/after.md +64 -0
  124. package/skills/animation-at-work/examples/before.md +35 -0
  125. package/skills/animation-at-work/references/api_reference.md +369 -0
  126. package/skills/animation-at-work/references/review-checklist.md +79 -0
  127. package/skills/animation-at-work/scripts/audit_animations.py +295 -0
  128. package/skills/animation-at-work/scripts/example.py +1 -0
  129. package/skills/clean-code-reviewer/SKILL.md +444 -0
  130. package/skills/clean-code-reviewer/audit.json +35 -0
  131. package/skills/clean-code-reviewer/evals/evals.json +185 -0
  132. package/skills/clean-code-reviewer/evals/results.json +13 -0
  133. package/skills/clean-code-reviewer/examples/after.md +48 -0
  134. package/skills/clean-code-reviewer/examples/before.md +33 -0
  135. package/skills/clean-code-reviewer/references/api_reference.md +158 -0
  136. package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
  137. package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
  138. package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
  139. package/skills/data-intensive-patterns/SKILL.md +267 -0
  140. package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
  141. package/skills/data-intensive-patterns/evals/evals.json +54 -0
  142. package/skills/data-intensive-patterns/evals/results.json +13 -0
  143. package/skills/data-intensive-patterns/examples/after.md +61 -0
  144. package/skills/data-intensive-patterns/examples/before.md +38 -0
  145. package/skills/data-intensive-patterns/references/api_reference.md +34 -0
  146. package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
  147. package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
  148. package/skills/data-intensive-patterns/scripts/adr.py +213 -0
  149. package/skills/data-intensive-patterns/scripts/example.py +1 -0
  150. package/skills/data-pipelines/SKILL.md +259 -0
  151. package/skills/data-pipelines/assets/example_asset.txt +1 -0
  152. package/skills/data-pipelines/evals/evals.json +45 -0
  153. package/skills/data-pipelines/evals/results.json +13 -0
  154. package/skills/data-pipelines/examples/after.md +97 -0
  155. package/skills/data-pipelines/examples/before.md +37 -0
  156. package/skills/data-pipelines/references/api_reference.md +301 -0
  157. package/skills/data-pipelines/references/review-checklist.md +181 -0
  158. package/skills/data-pipelines/scripts/example.py +1 -0
  159. package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
  160. package/skills/design-patterns/SKILL.md +271 -0
  161. package/skills/design-patterns/assets/example_asset.txt +1 -0
  162. package/skills/design-patterns/evals/evals.json +46 -0
  163. package/skills/design-patterns/evals/results.json +13 -0
  164. package/skills/design-patterns/examples/after.md +52 -0
  165. package/skills/design-patterns/examples/before.md +29 -0
  166. package/skills/design-patterns/references/api_reference.md +1 -0
  167. package/skills/design-patterns/references/patterns-catalog.md +726 -0
  168. package/skills/design-patterns/references/review-checklist.md +173 -0
  169. package/skills/design-patterns/scripts/example.py +1 -0
  170. package/skills/design-patterns/scripts/scaffold.py +807 -0
  171. package/skills/domain-driven-design/SKILL.md +142 -0
  172. package/skills/domain-driven-design/assets/example_asset.txt +1 -0
  173. package/skills/domain-driven-design/evals/evals.json +48 -0
  174. package/skills/domain-driven-design/evals/results.json +13 -0
  175. package/skills/domain-driven-design/examples/after.md +80 -0
  176. package/skills/domain-driven-design/examples/before.md +43 -0
  177. package/skills/domain-driven-design/references/api_reference.md +1 -0
  178. package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
  179. package/skills/domain-driven-design/references/review-checklist.md +158 -0
  180. package/skills/domain-driven-design/scripts/example.py +1 -0
  181. package/skills/domain-driven-design/scripts/scaffold.py +421 -0
  182. package/skills/effective-java/SKILL.md +227 -0
  183. package/skills/effective-java/assets/example_asset.txt +1 -0
  184. package/skills/effective-java/evals/evals.json +46 -0
  185. package/skills/effective-java/evals/results.json +13 -0
  186. package/skills/effective-java/examples/after.md +83 -0
  187. package/skills/effective-java/examples/before.md +37 -0
  188. package/skills/effective-java/references/api_reference.md +1 -0
  189. package/skills/effective-java/references/items-catalog.md +955 -0
  190. package/skills/effective-java/references/review-checklist.md +216 -0
  191. package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
  192. package/skills/effective-java/scripts/example.py +1 -0
  193. package/skills/effective-kotlin/SKILL.md +271 -0
  194. package/skills/effective-kotlin/assets/example_asset.txt +1 -0
  195. package/skills/effective-kotlin/audit.json +29 -0
  196. package/skills/effective-kotlin/evals/evals.json +45 -0
  197. package/skills/effective-kotlin/evals/results.json +13 -0
  198. package/skills/effective-kotlin/examples/after.md +36 -0
  199. package/skills/effective-kotlin/examples/before.md +38 -0
  200. package/skills/effective-kotlin/references/api_reference.md +1 -0
  201. package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
  202. package/skills/effective-kotlin/references/review-checklist.md +126 -0
  203. package/skills/effective-kotlin/scripts/example.py +1 -0
  204. package/skills/effective-python/SKILL.md +441 -0
  205. package/skills/effective-python/evals/evals.json +44 -0
  206. package/skills/effective-python/evals/results.json +13 -0
  207. package/skills/effective-python/examples/after.md +56 -0
  208. package/skills/effective-python/examples/before.md +40 -0
  209. package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
  210. package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
  211. package/skills/effective-python/ref-03-functions.md +186 -0
  212. package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
  213. package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
  214. package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
  215. package/skills/effective-python/ref-07-concurrency.md +213 -0
  216. package/skills/effective-python/ref-08-robustness-performance.md +248 -0
  217. package/skills/effective-python/ref-09-testing-debugging.md +253 -0
  218. package/skills/effective-python/ref-10-collaboration.md +175 -0
  219. package/skills/effective-python/references/api_reference.md +218 -0
  220. package/skills/effective-python/references/practices-catalog.md +483 -0
  221. package/skills/effective-python/references/review-checklist.md +190 -0
  222. package/skills/effective-python/scripts/lint.py +173 -0
  223. package/skills/effective-typescript/SKILL.md +262 -0
  224. package/skills/effective-typescript/audit.json +29 -0
  225. package/skills/effective-typescript/evals/evals.json +37 -0
  226. package/skills/effective-typescript/evals/results.json +13 -0
  227. package/skills/effective-typescript/examples/after.md +70 -0
  228. package/skills/effective-typescript/examples/before.md +47 -0
  229. package/skills/effective-typescript/references/api_reference.md +118 -0
  230. package/skills/effective-typescript/references/practices-catalog.md +371 -0
  231. package/skills/effective-typescript/scripts/review.py +169 -0
  232. package/skills/kotlin-in-action/SKILL.md +261 -0
  233. package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
  234. package/skills/kotlin-in-action/evals/evals.json +43 -0
  235. package/skills/kotlin-in-action/evals/results.json +13 -0
  236. package/skills/kotlin-in-action/examples/after.md +53 -0
  237. package/skills/kotlin-in-action/examples/before.md +39 -0
  238. package/skills/kotlin-in-action/references/api_reference.md +1 -0
  239. package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
  240. package/skills/kotlin-in-action/references/review-checklist.md +204 -0
  241. package/skills/kotlin-in-action/scripts/example.py +1 -0
  242. package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
  243. package/skills/lean-startup/SKILL.md +160 -0
  244. package/skills/lean-startup/assets/example_asset.txt +1 -0
  245. package/skills/lean-startup/evals/evals.json +43 -0
  246. package/skills/lean-startup/evals/results.json +13 -0
  247. package/skills/lean-startup/examples/after.md +80 -0
  248. package/skills/lean-startup/examples/before.md +34 -0
  249. package/skills/lean-startup/references/api_reference.md +319 -0
  250. package/skills/lean-startup/references/review-checklist.md +137 -0
  251. package/skills/lean-startup/scripts/example.py +1 -0
  252. package/skills/lean-startup/scripts/new_experiment.py +286 -0
  253. package/skills/microservices-patterns/SKILL.md +384 -0
  254. package/skills/microservices-patterns/evals/evals.json +45 -0
  255. package/skills/microservices-patterns/evals/results.json +13 -0
  256. package/skills/microservices-patterns/examples/after.md +69 -0
  257. package/skills/microservices-patterns/examples/before.md +40 -0
  258. package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
  259. package/skills/microservices-patterns/references/review-checklist.md +169 -0
  260. package/skills/microservices-patterns/scripts/new_service.py +583 -0
  261. package/skills/programming-with-rust/SKILL.md +209 -0
  262. package/skills/programming-with-rust/evals/evals.json +37 -0
  263. package/skills/programming-with-rust/evals/results.json +13 -0
  264. package/skills/programming-with-rust/examples/after.md +107 -0
  265. package/skills/programming-with-rust/examples/before.md +59 -0
  266. package/skills/programming-with-rust/references/api_reference.md +152 -0
  267. package/skills/programming-with-rust/references/practices-catalog.md +335 -0
  268. package/skills/programming-with-rust/scripts/review.py +142 -0
  269. package/skills/refactoring-ui/SKILL.md +362 -0
  270. package/skills/refactoring-ui/assets/example_asset.txt +1 -0
  271. package/skills/refactoring-ui/evals/evals.json +45 -0
  272. package/skills/refactoring-ui/evals/results.json +13 -0
  273. package/skills/refactoring-ui/examples/after.md +85 -0
  274. package/skills/refactoring-ui/examples/before.md +58 -0
  275. package/skills/refactoring-ui/references/api_reference.md +355 -0
  276. package/skills/refactoring-ui/references/review-checklist.md +114 -0
  277. package/skills/refactoring-ui/scripts/audit_css.py +250 -0
  278. package/skills/refactoring-ui/scripts/example.py +1 -0
  279. package/skills/rust-in-action/SKILL.md +350 -0
  280. package/skills/rust-in-action/evals/evals.json +38 -0
  281. package/skills/rust-in-action/evals/results.json +13 -0
  282. package/skills/rust-in-action/examples/after.md +156 -0
  283. package/skills/rust-in-action/examples/before.md +56 -0
  284. package/skills/rust-in-action/references/practices-catalog.md +346 -0
  285. package/skills/rust-in-action/scripts/review.py +147 -0
  286. package/skills/skill-router/SKILL.md +186 -0
  287. package/skills/skill-router/evals/evals.json +38 -0
  288. package/skills/skill-router/evals/results.json +13 -0
  289. package/skills/skill-router/examples/after.md +63 -0
  290. package/skills/skill-router/examples/before.md +39 -0
  291. package/skills/skill-router/references/api_reference.md +24 -0
  292. package/skills/skill-router/references/routing-heuristics.md +89 -0
  293. package/skills/skill-router/references/skill-catalog.md +174 -0
  294. package/skills/skill-router/scripts/route.py +266 -0
  295. package/skills/spring-boot-in-action/SKILL.md +340 -0
  296. package/skills/spring-boot-in-action/evals/evals.json +39 -0
  297. package/skills/spring-boot-in-action/evals/results.json +13 -0
  298. package/skills/spring-boot-in-action/examples/after.md +185 -0
  299. package/skills/spring-boot-in-action/examples/before.md +84 -0
  300. package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
  301. package/skills/spring-boot-in-action/scripts/review.py +184 -0
  302. package/skills/storytelling-with-data/SKILL.md +241 -0
  303. package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
  304. package/skills/storytelling-with-data/evals/evals.json +47 -0
  305. package/skills/storytelling-with-data/evals/results.json +13 -0
  306. package/skills/storytelling-with-data/examples/after.md +50 -0
  307. package/skills/storytelling-with-data/examples/before.md +33 -0
  308. package/skills/storytelling-with-data/references/api_reference.md +379 -0
  309. package/skills/storytelling-with-data/references/review-checklist.md +111 -0
  310. package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
  311. package/skills/storytelling-with-data/scripts/example.py +1 -0
  312. package/skills/system-design-interview/SKILL.md +233 -0
  313. package/skills/system-design-interview/assets/example_asset.txt +1 -0
  314. package/skills/system-design-interview/evals/evals.json +46 -0
  315. package/skills/system-design-interview/evals/results.json +13 -0
  316. package/skills/system-design-interview/examples/after.md +94 -0
  317. package/skills/system-design-interview/examples/before.md +27 -0
  318. package/skills/system-design-interview/references/api_reference.md +582 -0
  319. package/skills/system-design-interview/references/review-checklist.md +201 -0
  320. package/skills/system-design-interview/scripts/example.py +1 -0
  321. package/skills/system-design-interview/scripts/new_design.py +421 -0
  322. package/skills/using-asyncio-python/SKILL.md +290 -0
  323. package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
  324. package/skills/using-asyncio-python/evals/evals.json +43 -0
  325. package/skills/using-asyncio-python/evals/results.json +13 -0
  326. package/skills/using-asyncio-python/examples/after.md +68 -0
  327. package/skills/using-asyncio-python/examples/before.md +39 -0
  328. package/skills/using-asyncio-python/references/api_reference.md +267 -0
  329. package/skills/using-asyncio-python/references/review-checklist.md +149 -0
  330. package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
  331. package/skills/using-asyncio-python/scripts/example.py +1 -0
  332. package/skills/web-scraping-python/SKILL.md +280 -0
  333. package/skills/web-scraping-python/assets/example_asset.txt +1 -0
  334. package/skills/web-scraping-python/evals/evals.json +46 -0
  335. package/skills/web-scraping-python/evals/results.json +13 -0
  336. package/skills/web-scraping-python/examples/after.md +109 -0
  337. package/skills/web-scraping-python/examples/before.md +40 -0
  338. package/skills/web-scraping-python/references/api_reference.md +393 -0
  339. package/skills/web-scraping-python/references/review-checklist.md +163 -0
  340. package/skills/web-scraping-python/scripts/example.py +1 -0
  341. package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
  342. package/skills/writing-plans/audit.json +34 -0
  343. package/tests/agent-detector.test.js +83 -0
  344. package/tests/corrections.test.js +245 -0
  345. package/tests/doctor/hook-installer.test.js +72 -0
  346. package/tests/doctor/usage-tracker.test.js +140 -0
  347. package/tests/engine/benchmark-eval.test.js +31 -0
  348. package/tests/engine/bm25-index.test.js +85 -0
  349. package/tests/engine/capture-command.test.js +35 -0
  350. package/tests/engine/capture.test.js +17 -0
  351. package/tests/engine/graph-augmented-search.test.js +107 -0
  352. package/tests/engine/graph-injector.test.js +44 -0
  353. package/tests/engine/graph.test.js +216 -0
  354. package/tests/engine/hybrid-searcher.test.js +74 -0
  355. package/tests/engine/indexer-bm25.test.js +37 -0
  356. package/tests/engine/mcp-tools.test.js +73 -0
  357. package/tests/engine/project-initializer-mcp.test.js +99 -0
  358. package/tests/engine/query-expander.test.js +36 -0
  359. package/tests/engine/reranker.test.js +51 -0
  360. package/tests/engine/rrf.test.js +49 -0
  361. package/tests/engine/srag-prefix.test.js +47 -0
  362. package/tests/instinct-block.test.js +23 -0
  363. package/tests/mcp-config-writer.test.js +60 -0
  364. package/tests/project-initializer-new-agents.test.js +48 -0
  365. package/tests/rules/rules-manager.test.js +230 -0
  366. package/tests/well-known-builder.test.js +40 -0
  367. package/tests/wizard/integration-detector.test.js +31 -0
  368. package/tests/wizard/project-detector.test.js +51 -0
  369. package/tests/wizard/prompt-session.test.js +61 -0
  370. package/tests/wizard/prompt.test.js +16 -0
  371. package/tests/wizard/registry-embeddings.test.js +35 -0
  372. package/tests/wizard/skill-recommender.test.js +34 -0
  373. package/tests/wizard/slot-count.test.js +25 -0
  374. package/vercel.json +21 -0
@@ -0,0 +1,156 @@
1
+ # After: Rust in Action
2
+
3
+ The same utility rewritten with idiomatic systems Rust — explicit endianness, buffered I/O, a domain error type, checksum validation, thread pool, and safe shared state.
4
+
5
+ ```rust
6
+ use std::fmt;
7
+ use std::fs::File;
8
+ use std::io::{BufReader, Read};
9
+ use std::path::Path;
10
+ use std::sync::{Arc, Mutex};
11
+ use std::sync::mpsc;
12
+ use std::thread;
13
+
14
+ // Domain error type — wraps all downstream errors (Ch 3, 8)
15
+ #[derive(Debug)]
16
+ pub enum LogError {
17
+ Io(std::io::Error),
18
+ InvalidRecord { offset: usize, reason: &'static str },
19
+ ChecksumMismatch { expected: u32, got: u32 },
20
+ }
21
+
22
+ impl fmt::Display for LogError {
23
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24
+ match self {
25
+ LogError::Io(e) => write!(f, "I/O: {e}"),
26
+ LogError::InvalidRecord { offset, reason } =>
27
+ write!(f, "invalid record at {offset}: {reason}"),
28
+ LogError::ChecksumMismatch { expected, got } =>
29
+ write!(f, "checksum mismatch: expected {expected:#010x}, got {got:#010x}"),
30
+ }
31
+ }
32
+ }
33
+
34
+ impl std::error::Error for LogError {}
35
+ impl From<std::io::Error> for LogError {
36
+ fn from(e: std::io::Error) -> Self { LogError::Io(e) }
37
+ }
38
+
39
+ // BufReader for batched I/O syscalls; &Path for type-safe path (Ch 7)
40
+ // Returns Result — caller decides how to handle failure (Ch 3)
41
+ fn read_log(path: &Path) -> Result<Vec<u8>, LogError> {
42
+ let file = File::open(path)?; // ? converts io::Error → LogError (Ch 8)
43
+ let mut reader = BufReader::new(file); // batched reads (Ch 7)
44
+ let mut buf = Vec::new();
45
+ reader.read_to_end(&mut buf)?;
46
+ Ok(buf)
47
+ }
48
+
49
+ // Explicit little-endian — deterministic across all hosts (Ch 5, 7)
50
+ fn parse_record_id(bytes: &[u8], offset: usize) -> Result<u32, LogError> {
51
+ bytes.get(offset..offset + 4)
52
+ .ok_or(LogError::InvalidRecord { offset, reason: "not enough bytes for id" })
53
+ .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
54
+ }
55
+
56
+ // Simulate a simple CRC32-like checksum (Ch 7)
57
+ fn checksum(data: &[u8]) -> u32 {
58
+ data.iter().fold(0u32, |acc, &b| acc.wrapping_add(b as u32))
59
+ }
60
+
61
+ // Explicit little-endian write + checksum (Ch 5, 7)
62
+ fn write_record(id: u32, data: &[u8]) -> Vec<u8> {
63
+ let mut out = Vec::with_capacity(4 + data.len() + 4);
64
+ out.extend_from_slice(&id.to_le_bytes()); // SAFETY: LE is the protocol spec
65
+ out.extend_from_slice(data);
66
+ let crc = checksum(&out);
67
+ out.extend_from_slice(&crc.to_le_bytes()); // append checksum
68
+ out
69
+ }
70
+
71
+ fn read_record(buf: &[u8]) -> Result<(u32, &[u8]), LogError> {
72
+ let id = parse_record_id(buf, 0)?;
73
+ let payload = buf.get(4..buf.len() - 4)
74
+ .ok_or(LogError::InvalidRecord { offset: 4, reason: "too short for payload + checksum" })?;
75
+ let stored_crc = u32::from_le_bytes(buf[buf.len() - 4..].try_into().unwrap());
76
+ let computed = checksum(&buf[..buf.len() - 4]);
77
+ if computed != stored_crc {
78
+ return Err(LogError::ChecksumMismatch { expected: stored_crc, got: computed });
79
+ }
80
+ Ok((id, payload))
81
+ }
82
+
83
+ // Thread pool via channel — no unbounded thread spawning (Ch 10)
84
+ type Job = Box<dyn FnOnce() + Send + 'static>;
85
+
86
+ struct Pool {
87
+ tx: mpsc::Sender<Job>,
88
+ }
89
+
90
+ impl Pool {
91
+ fn new(workers: usize) -> Self {
92
+ let (tx, rx) = mpsc::channel::<Job>();
93
+ let rx = Arc::new(Mutex::new(rx));
94
+ for _ in 0..workers {
95
+ let rx = Arc::clone(&rx);
96
+ thread::spawn(move || loop {
97
+ match rx.lock().expect("mutex poisoned").recv() {
98
+ Ok(job) => job(),
99
+ Err(_) => break, // sender dropped — shut down
100
+ }
101
+ });
102
+ }
103
+ Pool { tx }
104
+ }
105
+
106
+ fn submit(&self, job: impl FnOnce() + Send + 'static) {
107
+ self.tx.send(Box::new(job)).expect("pool closed");
108
+ }
109
+ }
110
+
111
+ // Safe shared error counter via Arc<Mutex<T>> (Ch 6, 10)
112
+ fn make_error_counter() -> Arc<Mutex<u32>> {
113
+ Arc::new(Mutex::new(0))
114
+ }
115
+
116
+ fn record_error(counter: &Arc<Mutex<u32>>) {
117
+ *counter.lock().expect("mutex poisoned") += 1;
118
+ }
119
+
120
+ fn main() -> Result<(), LogError> {
121
+ let path = Path::new("data.log");
122
+ let bytes = read_log(path)?;
123
+
124
+ let id = parse_record_id(&bytes, 0)?;
125
+ println!("id: {id}");
126
+
127
+ let pool = Pool::new(4);
128
+ let errors = make_error_counter();
129
+
130
+ // move — closure owns record + error counter clone (Ch 10)
131
+ let record = bytes.clone();
132
+ let err_counter = Arc::clone(&errors);
133
+ pool.submit(move || {
134
+ println!("processing {} bytes, id={}", record.len(), id);
135
+ if record.len() < 8 {
136
+ record_error(&err_counter);
137
+ }
138
+ });
139
+
140
+ // Give threads time to finish (production code would use join handles)
141
+ std::thread::sleep(std::time::Duration::from_millis(10));
142
+ println!("errors: {}", errors.lock().unwrap());
143
+
144
+ Ok(())
145
+ }
146
+ ```
147
+
148
+ **Key improvements:**
149
+ - `LogError` wraps all downstream errors with `From` impls — `?` converts automatically (Ch 3, 8)
150
+ - `BufReader` batches file I/O syscalls — essential for large files (Ch 7)
151
+ - `u32::from_le_bytes()` / `to_le_bytes()` — explicit endianness, correct across all hosts (Ch 5)
152
+ - Checksum appended and verified on read — corruption is detectable (Ch 7)
153
+ - Thread pool via `mpsc::channel` + `Arc<Mutex<Receiver>>` — bounded concurrency (Ch 10)
154
+ - `Arc<Mutex<u32>>` replaces `unsafe static mut` — safe shared mutable state (Ch 6, 10)
155
+ - `move` closures transfer ownership into threads — required for `'static` bound (Ch 10)
156
+ - `&Path` instead of `String` for file path — type-safe, works with literals (Ch 7)
@@ -0,0 +1,56 @@
1
+ # Before: Rust in Action
2
+
3
+ A systems utility that reads a binary log file, parses records, and processes them concurrently — with common systems-level anti-patterns.
4
+
5
+ ```rust
6
+ use std::fs::File;
7
+ use std::io::Read;
8
+ use std::thread;
9
+
10
+ // Silently panics on any I/O failure; no error type
11
+ fn read_log(path: String) -> Vec<u8> {
12
+ let mut file = File::open(path).unwrap();
13
+ let mut buf = vec![];
14
+ file.read_to_end(&mut buf).unwrap();
15
+ buf
16
+ }
17
+
18
+ // Assumes native endianness — corrupts data on big-endian hosts
19
+ fn parse_record_id(bytes: &[u8]) -> u32 {
20
+ let arr = [bytes[0], bytes[1], bytes[2], bytes[3]];
21
+ u32::from_ne_bytes(arr) // native endian — wrong for protocol
22
+ }
23
+
24
+ // Spawns one thread per record — no pooling
25
+ fn process_all(records: Vec<Vec<u8>>) {
26
+ for record in records {
27
+ thread::spawn(|| {
28
+ println!("processing {} bytes", record.len());
29
+ });
30
+ }
31
+ // No join — threads may not finish before main exits
32
+ }
33
+
34
+ // Shared mutable state with unsafe global
35
+ static mut ERROR_COUNT: u32 = 0;
36
+
37
+ fn record_error() {
38
+ unsafe {
39
+ ERROR_COUNT += 1; // data race — undefined behavior
40
+ }
41
+ }
42
+
43
+ // No endianness comment, no checksum, no error type
44
+ fn write_record(id: u32, data: &[u8]) -> Vec<u8> {
45
+ let mut out = vec![];
46
+ out.extend_from_slice(&id.to_ne_bytes()); // native endian — wrong
47
+ out.extend_from_slice(data);
48
+ out // no checksum — corruption undetectable
49
+ }
50
+
51
+ fn main() {
52
+ let bytes = read_log(String::from("data.log"));
53
+ let id = parse_record_id(&bytes);
54
+ println!("id: {}", id);
55
+ }
56
+ ```
@@ -0,0 +1,346 @@
1
+ # Rust in Action — Practices Catalog
2
+
3
+ Systems-focused before/after examples from each chapter group.
4
+
5
+ ---
6
+
7
+ ## Ownership: Use References, Not Moves (Ch 4)
8
+
9
+ **Before:**
10
+ ```rust
11
+ fn print_log(data: Vec<u8>) { // consumes — caller loses data
12
+ println!("{} bytes", data.len());
13
+ }
14
+ let log = vec![1u8, 2, 3];
15
+ print_log(log);
16
+ // log is gone — can't use it again
17
+ ```
18
+ **After:**
19
+ ```rust
20
+ fn print_log(data: &[u8]) { // borrows a slice — Vec<u8> derefs to &[u8]
21
+ println!("{} bytes", data.len());
22
+ }
23
+ let log = vec![1u8, 2, 3];
24
+ print_log(&log);
25
+ println!("{log:?}"); // still valid
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Ownership: Resolving Lifetime Issues (Ch 4)
31
+
32
+ **Before:**
33
+ ```rust
34
+ struct Config {
35
+ name: String, // owned — always heap-allocates
36
+ }
37
+ ```
38
+ **After (borrow when caller controls data):**
39
+ ```rust
40
+ struct Config<'a> {
41
+ name: &'a str, // borrows — zero allocation if caller has the string
42
+ }
43
+ // Use when Config doesn't outlive the string it references
44
+ ```
45
+ **Or (own when Config must be independent):**
46
+ ```rust
47
+ struct Config {
48
+ name: String, // owned — correct when Config outlives the source string
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Smart Pointers: Choosing the Right Type (Ch 6)
55
+
56
+ ```rust
57
+ // Box<T>: heap allocation, single owner
58
+ let boxed: Box<[u8]> = vec![1, 2, 3].into_boxed_slice();
59
+
60
+ // Rc<T>: shared ownership, single thread only
61
+ use std::rc::Rc;
62
+ let shared = Rc::new(vec![1, 2, 3]);
63
+ let clone1 = Rc::clone(&shared); // cheap — increments refcount
64
+ // let _ = thread::spawn(move || { clone1; }); // COMPILE ERROR: Rc is not Send
65
+
66
+ // Arc<T>: shared ownership, multi-thread safe
67
+ use std::sync::Arc;
68
+ let arc = Arc::new(vec![1, 2, 3]);
69
+ let arc2 = Arc::clone(&arc); // idiomatic — explicit about cheapness
70
+ thread::spawn(move || println!("{arc2:?}")).join().unwrap();
71
+
72
+ // RefCell<T>: interior mutability, single thread, runtime checks
73
+ use std::cell::RefCell;
74
+ let cache: RefCell<Option<String>> = RefCell::new(None);
75
+ *cache.borrow_mut() = Some("computed".into());
76
+
77
+ // Cow<T>: clone-on-write — avoids allocation when only reading
78
+ use std::borrow::Cow;
79
+ fn process(input: &str) -> Cow<str> {
80
+ if input.contains("bad") {
81
+ Cow::Owned(input.replace("bad", "good")) // allocates only when needed
82
+ } else {
83
+ Cow::Borrowed(input) // zero allocation
84
+ }
85
+ }
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Data: Explicit Endianness (Ch 5, 7)
91
+
92
+ **Before:**
93
+ ```rust
94
+ fn parse_id(bytes: &[u8]) -> u32 {
95
+ u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) // native — wrong on BE hosts
96
+ }
97
+ fn write_id(id: u32) -> [u8; 4] {
98
+ id.to_ne_bytes() // corrupts protocol on big-endian
99
+ }
100
+ ```
101
+ **After:**
102
+ ```rust
103
+ // Protocol says: little-endian. Be explicit regardless of host.
104
+ fn parse_id(bytes: &[u8], offset: usize) -> Result<u32, &'static str> {
105
+ bytes.get(offset..offset + 4)
106
+ .ok_or("buffer too short")
107
+ .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
108
+ }
109
+
110
+ fn write_id(id: u32) -> [u8; 4] {
111
+ id.to_le_bytes() // always little-endian — deterministic on all hosts
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Data: Bit Manipulation (Ch 5)
118
+
119
+ ```rust
120
+ // Named constants for masks — self-documenting (Ch 5)
121
+ const SIGN_BIT: u32 = 0x8000_0000;
122
+ const EXPONENT_MASK: u32 = 0x7F80_0000;
123
+ const MANTISSA_MASK: u32 = 0x007F_FFFF;
124
+
125
+ fn dissect_f32(n: f32) -> (u32, u32, u32) {
126
+ let bits = n.to_bits();
127
+ let sign = (bits & SIGN_BIT) >> 31;
128
+ let exponent = (bits & EXPONENT_MASK) >> 23;
129
+ let mantissa = bits & MANTISSA_MASK;
130
+ (sign, exponent, mantissa)
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Files: Buffered I/O + serde (Ch 7)
137
+
138
+ **Before:**
139
+ ```rust
140
+ use std::fs::File;
141
+ use std::io::Read;
142
+ let mut f = File::open("data.bin").unwrap();
143
+ let mut buf = vec![];
144
+ f.read_to_end(&mut buf).unwrap(); // unbuffered + panics
145
+ ```
146
+ **After:**
147
+ ```rust
148
+ use std::fs::File;
149
+ use std::io::{BufReader, Read};
150
+ use std::path::Path;
151
+
152
+ fn read_file(path: &Path) -> Result<Vec<u8>, std::io::Error> {
153
+ let f = File::open(path)?;
154
+ let mut reader = BufReader::new(f); // batched syscalls
155
+ let mut buf = Vec::new();
156
+ reader.read_to_end(&mut buf)?;
157
+ Ok(buf)
158
+ }
159
+
160
+ // Structured serialization with serde + bincode (Ch 7)
161
+ use serde::{Deserialize, Serialize};
162
+
163
+ #[derive(Serialize, Deserialize, Debug)]
164
+ struct Record {
165
+ id: u64,
166
+ payload: Vec<u8>,
167
+ }
168
+
169
+ fn write_record(rec: &Record, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
170
+ let f = File::create(path)?;
171
+ let writer = std::io::BufWriter::new(f);
172
+ bincode::serialize_into(writer, rec)?;
173
+ Ok(())
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Error Handling: Library Error Wrapping (Ch 8)
180
+
181
+ ```rust
182
+ // Wrapping multiple downstream error types in one domain error (Ch 8)
183
+ #[derive(Debug)]
184
+ pub enum NetworkError {
185
+ Io(std::io::Error),
186
+ AddrParse(std::net::AddrParseError),
187
+ Timeout,
188
+ }
189
+
190
+ impl std::fmt::Display for NetworkError {
191
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192
+ match self {
193
+ NetworkError::Io(e) => write!(f, "I/O: {e}"),
194
+ NetworkError::AddrParse(e) => write!(f, "address parse: {e}"),
195
+ NetworkError::Timeout => write!(f, "connection timed out"),
196
+ }
197
+ }
198
+ }
199
+
200
+ impl std::error::Error for NetworkError {}
201
+
202
+ impl From<std::io::Error> for NetworkError {
203
+ fn from(e: std::io::Error) -> Self { NetworkError::Io(e) }
204
+ }
205
+ impl From<std::net::AddrParseError> for NetworkError {
206
+ fn from(e: std::net::AddrParseError) -> Self { NetworkError::AddrParse(e) }
207
+ }
208
+
209
+ fn connect(addr: &str) -> Result<std::net::TcpStream, NetworkError> {
210
+ let addr: std::net::SocketAddr = addr.parse()?; // AddrParseError → NetworkError
211
+ let stream = std::net::TcpStream::connect(addr)?; // io::Error → NetworkError
212
+ Ok(stream)
213
+ }
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Networking: State Machines with Enums (Ch 8)
219
+
220
+ ```rust
221
+ #[derive(Debug, Clone, PartialEq)]
222
+ enum TcpState {
223
+ Closed,
224
+ Listen,
225
+ SynReceived,
226
+ Established,
227
+ FinWait1,
228
+ Closed_,
229
+ }
230
+
231
+ impl TcpState {
232
+ fn on_syn(self) -> Result<Self, &'static str> {
233
+ match self {
234
+ TcpState::Listen => Ok(TcpState::SynReceived),
235
+ other => Err("SYN received in invalid state"),
236
+ }
237
+ }
238
+
239
+ fn on_ack(self) -> Result<Self, &'static str> {
240
+ match self {
241
+ TcpState::SynReceived => Ok(TcpState::Established),
242
+ other => Err("ACK received in invalid state"),
243
+ }
244
+ }
245
+ }
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Concurrency: Thread Pool via Channels (Ch 10)
251
+
252
+ ```rust
253
+ use std::sync::{Arc, Mutex, mpsc};
254
+ use std::thread;
255
+
256
+ // move closure required — captures must be 'static (Ch 10)
257
+ fn spawn_worker(id: usize, rx: Arc<Mutex<mpsc::Receiver<String>>>) {
258
+ thread::spawn(move || loop { // move transfers rx into thread
259
+ let msg = rx.lock().expect("mutex poisoned").recv();
260
+ match msg {
261
+ Ok(s) => println!("worker {id}: {s}"),
262
+ Err(_) => break,
263
+ }
264
+ });
265
+ }
266
+
267
+ fn main() {
268
+ let (tx, rx) = mpsc::channel::<String>();
269
+ let rx = Arc::new(Mutex::new(rx));
270
+
271
+ for i in 0..4 {
272
+ spawn_worker(i, Arc::clone(&rx));
273
+ }
274
+
275
+ tx.send("hello".into()).unwrap();
276
+ tx.send("world".into()).unwrap();
277
+ drop(tx); // close channel — workers will exit their loops
278
+ thread::sleep(std::time::Duration::from_millis(10));
279
+ }
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Time: Instant vs SystemTime + NTP Offset (Ch 9)
285
+
286
+ ```rust
287
+ use std::time::{Instant, SystemTime, UNIX_EPOCH};
288
+
289
+ // Instant for elapsed — monotonic, cannot go backwards (Ch 9)
290
+ let start = Instant::now();
291
+ do_work();
292
+ println!("elapsed: {:?}", start.elapsed());
293
+
294
+ // SystemTime for wall clock (can go backwards — don't use for elapsed)
295
+ let unix_ts = SystemTime::now()
296
+ .duration_since(UNIX_EPOCH)
297
+ .expect("system clock before Unix epoch")
298
+ .as_secs();
299
+
300
+ // NTP epoch conversion (Ch 9)
301
+ // NTP counts from 1900-01-01; Unix counts from 1970-01-01
302
+ const NTP_UNIX_OFFSET: u64 = 2_208_988_800;
303
+
304
+ fn ntp_to_unix(ntp_seconds: u64) -> u64 {
305
+ ntp_seconds.saturating_sub(NTP_UNIX_OFFSET)
306
+ }
307
+
308
+ fn unix_to_ntp(unix_seconds: u64) -> u64 {
309
+ unix_seconds + NTP_UNIX_OFFSET
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Unsafe: Safe Abstraction Pattern (Ch 6)
316
+
317
+ ```rust
318
+ // Wrap unsafe in a safe API — callers need not use unsafe (Ch 6)
319
+ pub struct AlignedBuffer {
320
+ ptr: *mut u8,
321
+ len: usize,
322
+ }
323
+
324
+ impl AlignedBuffer {
325
+ pub fn new(len: usize) -> Self {
326
+ // SAFETY: len > 0, alignment is a power of 2, ptr checked for null
327
+ let layout = std::alloc::Layout::from_size_align(len, 64).unwrap();
328
+ let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
329
+ assert!(!ptr.is_null(), "allocation failed");
330
+ AlignedBuffer { ptr, len }
331
+ }
332
+
333
+ pub fn as_slice(&self) -> &[u8] {
334
+ // SAFETY: ptr valid, len accurate, no mutable alias exists
335
+ unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
336
+ }
337
+ }
338
+
339
+ impl Drop for AlignedBuffer {
340
+ fn drop(&mut self) {
341
+ // SAFETY: same layout as alloc, ptr not yet freed
342
+ let layout = std::alloc::Layout::from_size_align(self.len, 64).unwrap();
343
+ unsafe { std::alloc::dealloc(self.ptr, layout) }
344
+ }
345
+ }
346
+ ```
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ review.py — Pre-analysis script for Rust in Action reviews.
4
+ Usage: python review.py <file.rs>
5
+
6
+ Scans a Rust source file for systems-programming anti-patterns from the book:
7
+ endianness issues, unsafe shared state, missing buffered I/O, unwrap misuse,
8
+ unbounded thread spawning, and incorrect smart pointer choices.
9
+ """
10
+
11
+ import re
12
+ import sys
13
+ from pathlib import Path
14
+
15
+
16
+ CHECKS = [
17
+ (
18
+ r"from_ne_bytes|to_ne_bytes",
19
+ "Ch 5/7: Native endianness",
20
+ "use from_le_bytes/to_le_bytes or from_be_bytes/to_be_bytes to match the protocol spec",
21
+ ),
22
+ (
23
+ r"static\s+mut\s+\w+",
24
+ "Ch 6/10: static mut",
25
+ "data race risk — replace with Arc<Mutex<T>> or std::sync::atomic",
26
+ ),
27
+ (
28
+ r"\.unwrap\(\)",
29
+ "Ch 3: .unwrap()",
30
+ "panics on failure — use ?, .expect(\"reason\"), or match in production paths",
31
+ ),
32
+ (
33
+ r"unsafe\s*\{",
34
+ "Ch 6: unsafe block",
35
+ "ensure a safe abstraction wraps this; add a // SAFETY: comment explaining invariants",
36
+ ),
37
+ (
38
+ r"File::open|File::create",
39
+ "Ch 7: Unbuffered file I/O",
40
+ "wrap in BufReader::new()/BufWriter::new() to batch syscalls",
41
+ ),
42
+ (
43
+ r"thread::spawn",
44
+ "Ch 10: thread::spawn",
45
+ "if inside a loop, consider a thread pool (channel + Arc<Mutex<Receiver>>) instead of one thread per task",
46
+ ),
47
+ (
48
+ r"\bRc\s*::\s*(new|clone)\b",
49
+ "Ch 6: Rc usage",
50
+ "Rc is not Send — if shared across threads, replace with Arc",
51
+ ),
52
+ (
53
+ r"Box::new\(Vec\b|Box::new\(vec!",
54
+ "Ch 6: Box<Vec<T>>",
55
+ "Vec already heap-allocates — Box<Vec<T>> is double-indirection with no benefit; return Vec directly",
56
+ ),
57
+ (
58
+ r"\bexpect\s*\(\s*\)",
59
+ "Ch 3: .expect() with empty string",
60
+ "add a meaningful reason: .expect(\"what invariant was violated\")",
61
+ ),
62
+ ]
63
+
64
+
65
+ def scan(source: str) -> list[dict]:
66
+ findings = []
67
+ lines = source.splitlines()
68
+ for lineno, line in enumerate(lines, start=1):
69
+ stripped = line.strip()
70
+ if stripped.startswith("//"):
71
+ continue # skip comments
72
+ for pattern, label, advice in CHECKS:
73
+ if re.search(pattern, line):
74
+ findings.append({
75
+ "line": lineno,
76
+ "text": line.rstrip(),
77
+ "label": label,
78
+ "advice": advice,
79
+ })
80
+ return findings
81
+
82
+
83
+ def group_by_label(findings: list[dict]) -> dict:
84
+ groups: dict[str, list] = {}
85
+ for f in findings:
86
+ groups.setdefault(f["label"], []).append(f)
87
+ return groups
88
+
89
+
90
+ def sep(char="-", width=70) -> str:
91
+ return char * width
92
+
93
+
94
+ def main() -> None:
95
+ if len(sys.argv) < 2:
96
+ print("Usage: python review.py <file.rs>")
97
+ sys.exit(1)
98
+
99
+ path = Path(sys.argv[1])
100
+ if not path.exists():
101
+ print(f"Error: file not found: {path}")
102
+ sys.exit(1)
103
+
104
+ if path.suffix.lower() != ".rs":
105
+ print(f"Warning: expected a .rs file, got '{path.suffix}' — continuing anyway")
106
+
107
+ source = path.read_text(encoding="utf-8", errors="replace")
108
+ findings = scan(source)
109
+ groups = group_by_label(findings)
110
+
111
+ print(sep("="))
112
+ print("RUST IN ACTION — PRE-REVIEW REPORT")
113
+ print(sep("="))
114
+ print(f"File : {path}")
115
+ print(f"Lines : {len(source.splitlines())}")
116
+ print(f"Issues : {len(findings)} potential anti-patterns across {len(groups)} categories")
117
+ print()
118
+
119
+ if not findings:
120
+ print(" [OK] No common anti-patterns detected.")
121
+ print()
122
+ else:
123
+ for label, items in groups.items():
124
+ print(sep())
125
+ print(f" {label} ({len(items)} occurrence{'s' if len(items) != 1 else ''})")
126
+ print(sep())
127
+ print(f" Advice: {items[0]['advice']}")
128
+ print()
129
+ for item in items[:5]: # cap display at 5 per category
130
+ print(f" line {item['line']:>4}: {item['text'][:100]}")
131
+ if len(items) > 5:
132
+ print(f" ... and {len(items) - 5} more occurrence(s)")
133
+ print()
134
+
135
+ severity = (
136
+ "HIGH" if len(findings) >= 5
137
+ else "MEDIUM" if len(findings) >= 2
138
+ else "LOW" if findings
139
+ else "NONE"
140
+ )
141
+ print(sep("="))
142
+ print(f"SEVERITY: {severity} | Review chapters: Ch 3 (errors), Ch 5-7 (data/files), Ch 6 (pointers), Ch 10 (concurrency)")
143
+ print(sep("="))
144
+
145
+
146
+ if __name__ == "__main__":
147
+ main()