@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,158 @@
1
+ # Domain-Driven Design — Code Review Checklist
2
+
3
+ Systematic checklist for reviewing code against DDD principles. Organized by
4
+ category with specific items to inspect.
5
+
6
+ ---
7
+
8
+ ## 1. Ubiquitous Language
9
+
10
+ - [ ] **Class names match domain terms** — Classes are named after domain concepts the business recognizes, not technical abstractions (e.g., `Policy` not `RuleEngine`, `Shipment` not `DataTransferObject`)
11
+ - [ ] **Method names describe domain operations** — Methods read like domain actions (e.g., `order.cancel()` not `order.setStatus(CANCELLED)`, `account.debit(amount)` not `account.updateBalance(-amount)`)
12
+ - [ ] **No technical jargon in domain layer** — Avoid names like Manager, Handler, Processor, Helper, Util, Data, Info in the domain layer
13
+ - [ ] **Consistent vocabulary** — Same concept uses the same name everywhere. No synonyms (e.g., don't mix "client" and "customer" for the same concept)
14
+ - [ ] **Language reflects current understanding** — Terms have been refined as the model evolved, not stuck with initial naive names
15
+ - [ ] **Module/package names tell a domain story** — Packages are organized by domain concept, not by pattern type (not `entities/`, `services/`, `repositories/`)
16
+
17
+ ## 2. Layered Architecture
18
+
19
+ - [ ] **Four layers identifiable** — UI/Presentation, Application, Domain, Infrastructure are clearly separated
20
+ - [ ] **Domain layer has zero outward dependencies** — Domain classes don't import UI, Application, or Infrastructure classes
21
+ - [ ] **Infrastructure implements domain interfaces** — Dependency Inversion: domain defines interfaces (e.g., `OrderRepository`), infrastructure implements them (e.g., `JpaOrderRepository`)
22
+ - [ ] **Application layer is thin** — Application services coordinate but contain NO business logic. They manage transactions, security, and use-case flow
23
+ - [ ] **No domain logic in controllers** — UI/API controllers delegate to application services, never contain business rules
24
+ - [ ] **No domain logic in infrastructure** — Persistence logic doesn't enforce business rules; it only stores and retrieves
25
+ - [ ] **No circular dependencies** — Lower layers never depend on higher layers
26
+
27
+ ## 3. Entities
28
+
29
+ - [ ] **Identity is explicit** — Entity has a clear, typed identifier (preferably a Value Object like `OrderId`, not a raw `String` or `long`)
30
+ - [ ] **Equality based on identity** — `equals()` and `hashCode()` use the identifier, not attributes
31
+ - [ ] **Encapsulates behavior** — Entity contains domain logic, not just getters and setters
32
+ - [ ] **Lifecycle management** — Entity handles its own state transitions and lifecycle events
33
+ - [ ] **Not overloaded** — Entity is focused on identity and core behavior; ancillary logic is in Value Objects or Services
34
+ - [ ] **Attributes that change are still same entity** — Design acknowledges that attributes can change while identity persists
35
+
36
+ ## 4. Value Objects
37
+
38
+ - [ ] **Immutable** — No setters, no mutable state. All fields are final/readonly
39
+ - [ ] **Equality by attributes** — `equals()` compares all attributes, not reference identity
40
+ - [ ] **Rich behavior** — Value Objects contain domain logic (calculations, validations, transformations), not just data
41
+ - [ ] **Side-effect-free methods** — Operations return new Value Objects rather than modifying state
42
+ - [ ] **Used instead of primitives** — Domain concepts like money, dates, addresses, quantities are Value Objects, not raw types (no `double price`, `String email`, `int quantity`)
43
+ - [ ] **Freely shareable** — No aliasing bugs because they're immutable
44
+ - [ ] **Self-validating** — Value Object constructor validates invariants (e.g., `Email` rejects invalid format)
45
+
46
+ ## 5. Aggregates
47
+
48
+ - [ ] **Clear root entity** — One Entity is the root with global identity; all access goes through it
49
+ - [ ] **Boundary is defined** — It's clear which Entities and Value Objects are inside the Aggregate
50
+ - [ ] **Invariants enforced by root** — The root ensures all business rules within the boundary are consistent
51
+ - [ ] **No external references to internals** — Outside code cannot hold direct references to internal entities. References are through the root only
52
+ - [ ] **Kept small** — Aggregate contains only what's needed for invariant enforcement. Other Aggregates are referenced by ID
53
+ - [ ] **One Aggregate per transaction** — Changes to one Aggregate are committed in one transaction. Cross-Aggregate changes are eventually consistent
54
+ - [ ] **Delete cascades from root** — Removing the root removes everything inside the boundary
55
+ - [ ] **Internal entities have local identity** — IDs of internal entities are meaningful only within the Aggregate
56
+
57
+ ## 6. Repositories
58
+
59
+ - [ ] **Only for Aggregate roots** — No Repository exists for internal Aggregate entities or Value Objects
60
+ - [ ] **Collection-like interface** — API resembles an in-memory collection: add, remove, find, query
61
+ - [ ] **Interface in domain layer** — The Repository interface is defined in the domain layer
62
+ - [ ] **Implementation in infrastructure** — The concrete class (JPA, JDBC, etc.) is in the infrastructure layer
63
+ - [ ] **Returns whole Aggregates** — Queries return fully reconstituted Aggregates, not partial objects or DTOs
64
+ - [ ] **No persistence leakage** — Domain code has no awareness of SQL, ORM annotations, or storage details
65
+ - [ ] **Encapsulates query strategy** — Complex queries are behind well-named methods, not scattered SQL
66
+ - [ ] **Delegates to Factory for reconstitution** — Complex object rebuilding is handled by a Factory, not in the Repository itself
67
+
68
+ ## 7. Factories
69
+
70
+ - [ ] **Used for complex creation** — If creation requires more than a simple constructor, a Factory exists
71
+ - [ ] **Atomic creation** — Factory produces a valid, consistent object or fails entirely (no partially created objects)
72
+ - [ ] **Invariants enforced at creation** — All Aggregate invariants are validated during Factory creation
73
+ - [ ] **Abstracts concrete types** — Client code depends on abstractions, not on the specific class the Factory creates
74
+ - [ ] **Reconstitution vs. creation distinguished** — Factories for loading from persistence don't re-validate business rules that only apply at creation time, and don't generate new IDs
75
+
76
+ ## 8. Domain Services
77
+
78
+ - [ ] **Stateless** — Domain Services hold no mutable state between calls
79
+ - [ ] **Named in Ubiquitous Language** — Service name describes a domain operation (e.g., `TransferService`, `PricingService`)
80
+ - [ ] **Not overused** — Domain Services are the exception, not the rule. Most behavior belongs on Entities and Value Objects
81
+ - [ ] **Parameters and return types are domain objects** — Service interfaces use domain types, not primitives or infrastructure types
82
+ - [ ] **No anemic domain model** — If all logic is in Services and Entities are just data bags, the model is anemic
83
+ - [ ] **Distinguished from Application Services** — Domain Services contain business logic; Application Services coordinate use cases
84
+
85
+ ## 9. Supple Design
86
+
87
+ - [ ] **Intention-Revealing Interfaces** — Can a developer understand what a class/method does without reading the implementation?
88
+ - [ ] **Side-Effect-Free Functions** — Complex logic lives in pure functions (especially on Value Objects). Commands are simple state changes
89
+ - [ ] **Assertions / Post-conditions** — Critical invariants are documented or enforced. Tests verify post-conditions
90
+ - [ ] **Conceptual Contours** — Object boundaries align with natural domain concepts. Things that change together are together
91
+ - [ ] **Standalone Classes** — Dependencies are minimized. Each class can be understood with minimal context
92
+ - [ ] **Closure of Operations** — Operations on a type return the same type where natural (e.g., `Money.add(Money): Money`)
93
+ - [ ] **Declarative style** — Where possible, the code describes WHAT should happen, not HOW (e.g., specifications, rules)
94
+
95
+ ## 10. Specification Pattern
96
+
97
+ - [ ] **Business rules as objects** — Complex boolean conditions are modeled as Specification objects, not inline conditionals
98
+ - [ ] **isSatisfiedBy method** — Each Specification has a clear test method
99
+ - [ ] **Composable** — Specifications can be combined with AND, OR, NOT
100
+ - [ ] **Three use cases considered** — Validation (testing), Selection (querying), Building-to-order (generation)
101
+ - [ ] **Named in domain language** — `OverdueInvoiceSpecification`, not `InvoiceFilter42`
102
+ - [ ] **Implemented as Value Objects** — Specifications are immutable and side-effect-free
103
+
104
+ ## 11. Strategic Design — Bounded Contexts
105
+
106
+ - [ ] **Contexts are explicitly defined** — Each Bounded Context has a clear name and boundary
107
+ - [ ] **One model per context** — No concept is defined differently within the same context
108
+ - [ ] **Context Map exists** — Relationships between contexts are documented
109
+ - [ ] **Integration patterns identified** — Each context-to-context relationship uses a named pattern (Shared Kernel, ACL, Conformist, etc.)
110
+ - [ ] **No model bleeding** — Types from one context don't appear in another context's domain layer
111
+ - [ ] **Team boundaries align** — Ideally, one team per Bounded Context
112
+
113
+ ## 12. Integration Patterns
114
+
115
+ - [ ] **Anticorruption Layer present where needed** — External systems and legacy integrations are wrapped in ACL
116
+ - [ ] **ACL components identifiable** — Façade, Adapter, and Translator are present as needed
117
+ - [ ] **Domain interfaces define integration** — The domain layer defines what it needs; infrastructure implements it
118
+ - [ ] **Shared Kernel is minimal** — If two contexts share code, the shared portion is as small as possible with joint CI
119
+ - [ ] **Open Host Service is well-documented** — Public APIs have clear protocols and versioning
120
+ - [ ] **Published Language is formal** — Shared data formats are documented and standardized
121
+ - [ ] **Conformist choice is conscious** — If a team conforms to an upstream model, it's a deliberate decision with documented trade-offs
122
+
123
+ ## 13. Distillation
124
+
125
+ - [ ] **Core Domain identified** — The team knows which part of the system is the competitive differentiator
126
+ - [ ] **Best talent on Core** — The most skilled developers are working on the Core Domain
127
+ - [ ] **Generic Subdomains simplified** — Non-core parts use off-the-shelf solutions, simpler models, or outsourced implementations
128
+ - [ ] **Domain Vision Statement exists** — A short document describes the Core Domain's value proposition
129
+ - [ ] **Core is highlighted** — Developers can quickly identify which code is Core Domain (via packages, annotations, or documentation)
130
+ - [ ] **Segregated Core** — Core Domain code is in its own module with minimal dependencies on supporting code
131
+
132
+ ## 14. Large-Scale Structure
133
+
134
+ - [ ] **Structure serves the team** — If a large-scale structure exists, it helps developers navigate and make decisions
135
+ - [ ] **Not over-engineered** — Structure is as simple as possible. No structure is better than bad structure
136
+ - [ ] **Evolving** — Structure is allowed to change as understanding deepens
137
+ - [ ] **Responsibility Layers (if used)** — Layers have clear domain-level responsibilities and dependencies flow downward
138
+ - [ ] **Knowledge Level (if used)** — Configuration rules are separated from operational data in a meta-model
139
+
140
+ ---
141
+
142
+ ## Quick Review Workflow
143
+
144
+ 1. **Start with Ubiquitous Language** — Read the code. Does it speak the domain? Can a domain expert recognize the concepts?
145
+ 2. **Check architecture** — Are layers separated? Does the domain depend on nothing?
146
+ 3. **Inspect building blocks** — Are Entities, Value Objects, Aggregates, Repositories, and Services used correctly?
147
+ 4. **Evaluate design quality** — Is the design supple? Intention-revealing? Side-effect-free where possible?
148
+ 5. **Assess strategic alignment** — Are Bounded Contexts defined? Is the Core Domain getting the most attention?
149
+ 6. **Flag anti-patterns** — Look for anemic domain model, God Aggregates, primitive obsession, leaking infrastructure, missing ACL
150
+
151
+ ## Severity Levels
152
+
153
+ | Severity | Description | Example |
154
+ |----------|------------|---------|
155
+ | **Critical** | Broken invariants, data corruption risk, no domain model | Anemic domain model, no Aggregate boundaries, infrastructure in domain |
156
+ | **High** | Incorrect pattern application, model integrity issues | Repository for non-root, mutable Value Objects, leaked internals |
157
+ | **Medium** | Design improvement opportunities | Missing Specifications, primitive types for concepts, overly large Aggregates |
158
+ | **Low** | Polish and naming refinements | Inconsistent naming, missing documentation, suboptimal module organization |
@@ -0,0 +1,421 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ DDD Scaffold — generates aggregate building blocks for a bounded context.
4
+ Usage: python scaffold.py <AggregateName> [--lang python|kotlin|java] [--output-dir ./]
5
+ """
6
+
7
+ import argparse
8
+ import sys
9
+ from pathlib import Path
10
+ from string import Template
11
+
12
+ # ---------------------------------------------------------------------------
13
+ # Templates
14
+ # ---------------------------------------------------------------------------
15
+
16
+ PYTHON_AGGREGATE = Template('''\
17
+ from __future__ import annotations
18
+ from dataclasses import dataclass, field
19
+ from typing import List
20
+ from .${name}Id import ${name}Id
21
+ from .${name}Events import ${name}Created, ${name}Event
22
+
23
+
24
+ @dataclass
25
+ class ${name}:
26
+ """Aggregate root for ${name}."""
27
+
28
+ id: ${name}Id
29
+ _events: List[${name}Event] = field(default_factory=list, init=False, repr=False)
30
+
31
+ # ------------------------------------------------------------------
32
+ # Factory method — the only sanctioned way to create a ${name}.
33
+ # ------------------------------------------------------------------
34
+ @classmethod
35
+ def create(cls, id: ${name}Id, **kwargs) -> "${name}":
36
+ instance = cls(id=id)
37
+ instance._check_invariants()
38
+ instance._record(${name}Created(aggregate_id=id))
39
+ return instance
40
+
41
+ # ------------------------------------------------------------------
42
+ # Commands — each mutates state and records an event.
43
+ # ------------------------------------------------------------------
44
+
45
+ # def rename(self, new_name: str) -> None:
46
+ # if not new_name.strip():
47
+ # raise ValueError("Name must not be blank.")
48
+ # self.name = new_name
49
+ # self._record(${name}Renamed(aggregate_id=self.id, name=new_name))
50
+
51
+ # ------------------------------------------------------------------
52
+ # Domain events
53
+ # ------------------------------------------------------------------
54
+ def pull_events(self) -> List[${name}Event]:
55
+ """Return and clear pending domain events."""
56
+ events, self._events = self._events, []
57
+ return events
58
+
59
+ def _record(self, event: ${name}Event) -> None:
60
+ self._events.append(event)
61
+
62
+ # ------------------------------------------------------------------
63
+ # Invariants
64
+ # ------------------------------------------------------------------
65
+ def _check_invariants(self) -> None:
66
+ """Raise if the aggregate is in an invalid state."""
67
+ if self.id is None:
68
+ raise ValueError("${name} must have an ID.")
69
+ ''')
70
+
71
+ PYTHON_ID = Template('''\
72
+ from __future__ import annotations
73
+ from dataclasses import dataclass
74
+ import uuid
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class ${name}Id:
79
+ """Value Object — identity of a ${name} aggregate."""
80
+
81
+ value: str
82
+
83
+ def __post_init__(self) -> None:
84
+ if not self.value or not self.value.strip():
85
+ raise ValueError("${name}Id must not be blank.")
86
+
87
+ @classmethod
88
+ def generate(cls) -> "${name}Id":
89
+ return cls(value=str(uuid.uuid4()))
90
+
91
+ @classmethod
92
+ def from_string(cls, raw: str) -> "${name}Id":
93
+ return cls(value=raw)
94
+
95
+ def __str__(self) -> str:
96
+ return self.value
97
+ ''')
98
+
99
+ PYTHON_REPOSITORY = Template('''\
100
+ from __future__ import annotations
101
+ from abc import ABC, abstractmethod
102
+ from typing import Optional
103
+ from .${name}Id import ${name}Id
104
+ from .${name} import ${name}
105
+
106
+
107
+ class ${name}Repository(ABC):
108
+ """Port (interface) for ${name} persistence.
109
+
110
+ Concrete adapters live in the infrastructure layer.
111
+ """
112
+
113
+ @abstractmethod
114
+ def find_by_id(self, id: ${name}Id) -> Optional[${name}]:
115
+ """Return the aggregate or None if not found."""
116
+
117
+ @abstractmethod
118
+ def save(self, aggregate: ${name}) -> None:
119
+ """Persist all state changes."""
120
+
121
+ @abstractmethod
122
+ def delete(self, id: ${name}Id) -> None:
123
+ """Remove the aggregate."""
124
+
125
+ @abstractmethod
126
+ def exists(self, id: ${name}Id) -> bool:
127
+ """Check existence without loading the aggregate."""
128
+ ''')
129
+
130
+ PYTHON_EVENTS = Template('''\
131
+ from __future__ import annotations
132
+ from dataclasses import dataclass, field
133
+ from datetime import datetime, timezone
134
+ from typing import ClassVar
135
+ from .${name}Id import ${name}Id
136
+
137
+
138
+ @dataclass(frozen=True)
139
+ class ${name}Event:
140
+ """Base class for all ${name} domain events."""
141
+
142
+ event_type: ClassVar[str]
143
+ aggregate_id: ${name}Id
144
+ occurred_at: datetime = field(
145
+ default_factory=lambda: datetime.now(tz=timezone.utc)
146
+ )
147
+
148
+
149
+ @dataclass(frozen=True)
150
+ class ${name}Created(${name}Event):
151
+ """Raised when a new ${name} is created."""
152
+
153
+ event_type: ClassVar[str] = "${name}Created"
154
+
155
+
156
+ # Add more events below as your domain grows, for example:
157
+ # @dataclass(frozen=True)
158
+ # class ${name}Renamed(${name}Event):
159
+ # event_type: ClassVar[str] = "${name}Renamed"
160
+ # name: str = ""
161
+ ''')
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # Kotlin templates
165
+ # ---------------------------------------------------------------------------
166
+
167
+ KOTLIN_AGGREGATE = Template('''\
168
+ package com.example.${lname}
169
+
170
+ import java.time.Instant
171
+
172
+ class ${name}(val id: ${name}Id) {
173
+
174
+ private val _events: MutableList<${name}Event> = mutableListOf()
175
+ val events: List<${name}Event> get() = _events.toList()
176
+
177
+ companion object {
178
+ /** Factory — the only way to create a valid ${name}. */
179
+ fun create(id: ${name}Id): ${name} {
180
+ val agg = ${name}(id)
181
+ agg.checkInvariants()
182
+ agg.record(${name}Created(aggregateId = id))
183
+ return agg
184
+ }
185
+ }
186
+
187
+ /** Pull and clear pending domain events. */
188
+ fun pullEvents(): List<${name}Event> {
189
+ val copy = _events.toList()
190
+ _events.clear()
191
+ return copy
192
+ }
193
+
194
+ private fun record(event: ${name}Event) { _events.add(event) }
195
+
196
+ private fun checkInvariants() {
197
+ // Add invariant assertions here.
198
+ }
199
+ }
200
+ ''')
201
+
202
+ KOTLIN_ID = Template('''\
203
+ package com.example.${lname}
204
+
205
+ import java.util.UUID
206
+
207
+ @JvmInline
208
+ value class ${name}Id(val value: String) {
209
+ init {
210
+ require(value.isNotBlank()) { "${name}Id must not be blank." }
211
+ }
212
+
213
+ companion object {
214
+ fun generate(): ${name}Id = ${name}Id(UUID.randomUUID().toString())
215
+ fun of(raw: String): ${name}Id = ${name}Id(raw)
216
+ }
217
+
218
+ override fun toString(): String = value
219
+ }
220
+ ''')
221
+
222
+ KOTLIN_REPOSITORY = Template('''\
223
+ package com.example.${lname}
224
+
225
+ interface ${name}Repository {
226
+ fun findById(id: ${name}Id): ${name}?
227
+ fun save(aggregate: ${name})
228
+ fun delete(id: ${name}Id)
229
+ fun exists(id: ${name}Id): Boolean
230
+ }
231
+ ''')
232
+
233
+ KOTLIN_EVENTS = Template('''\
234
+ package com.example.${lname}
235
+
236
+ import java.time.Instant
237
+
238
+ sealed class ${name}Event {
239
+ abstract val aggregateId: ${name}Id
240
+ abstract val occurredAt: Instant
241
+ }
242
+
243
+ data class ${name}Created(
244
+ override val aggregateId: ${name}Id,
245
+ override val occurredAt: Instant = Instant.now()
246
+ ) : ${name}Event()
247
+
248
+ // Add more events as the domain grows:
249
+ // data class ${name}Renamed(
250
+ // override val aggregateId: ${name}Id,
251
+ // val name: String,
252
+ // override val occurredAt: Instant = Instant.now()
253
+ // ) : ${name}Event()
254
+ ''')
255
+
256
+ # ---------------------------------------------------------------------------
257
+ # Java templates
258
+ # ---------------------------------------------------------------------------
259
+
260
+ JAVA_AGGREGATE = Template('''\
261
+ package com.example.${lname};
262
+
263
+ import java.util.ArrayList;
264
+ import java.util.Collections;
265
+ import java.util.List;
266
+ import java.util.Objects;
267
+
268
+ public final class ${name} {
269
+
270
+ private final ${name}Id id;
271
+ private final List<${name}Event> events = new ArrayList<>();
272
+
273
+ private ${name}(${name}Id id) {
274
+ this.id = Objects.requireNonNull(id, "id must not be null");
275
+ }
276
+
277
+ /** Factory — the only way to create a valid ${name}. */
278
+ public static ${name} create(${name}Id id) {
279
+ var agg = new ${name}(id);
280
+ agg.checkInvariants();
281
+ agg.record(new ${name}Created(id));
282
+ return agg;
283
+ }
284
+
285
+ public ${name}Id getId() { return id; }
286
+
287
+ /** Pull and clear pending domain events. */
288
+ public List<${name}Event> pullEvents() {
289
+ var copy = List.copyOf(events);
290
+ events.clear();
291
+ return copy;
292
+ }
293
+
294
+ private void record(${name}Event event) { events.add(event); }
295
+
296
+ private void checkInvariants() {
297
+ // Add invariant assertions here.
298
+ }
299
+ }
300
+ ''')
301
+
302
+ JAVA_ID = Template('''\
303
+ package com.example.${lname};
304
+
305
+ import java.util.Objects;
306
+ import java.util.UUID;
307
+
308
+ public record ${name}Id(String value) {
309
+
310
+ public ${name}Id {
311
+ Objects.requireNonNull(value, "value must not be null");
312
+ if (value.isBlank()) throw new IllegalArgumentException("${name}Id must not be blank.");
313
+ }
314
+
315
+ public static ${name}Id generate() { return new ${name}Id(UUID.randomUUID().toString()); }
316
+ public static ${name}Id of(String raw) { return new ${name}Id(raw); }
317
+
318
+ @Override public String toString() { return value; }
319
+ }
320
+ ''')
321
+
322
+ JAVA_REPOSITORY = Template('''\
323
+ package com.example.${lname};
324
+
325
+ import java.util.Optional;
326
+
327
+ public interface ${name}Repository {
328
+ Optional<${name}> findById(${name}Id id);
329
+ void save(${name} aggregate);
330
+ void delete(${name}Id id);
331
+ boolean exists(${name}Id id);
332
+ }
333
+ ''')
334
+
335
+ JAVA_EVENTS = Template('''\
336
+ package com.example.${lname};
337
+
338
+ import java.time.Instant;
339
+
340
+ public sealed interface ${name}Event permits ${name}Created {
341
+ ${name}Id aggregateId();
342
+ Instant occurredAt();
343
+ }
344
+
345
+ record ${name}Created(${name}Id aggregateId, Instant occurredAt) implements ${name}Event {
346
+ ${name}Created(${name}Id aggregateId) { this(aggregateId, Instant.now()); }
347
+ }
348
+
349
+ // Add more events as the domain grows:
350
+ // record ${name}Renamed(${name}Id aggregateId, String name, Instant occurredAt)
351
+ // implements ${name}Event { ... }
352
+ ''')
353
+
354
+ TEMPLATES = {
355
+ "python": {
356
+ "ext": "py",
357
+ "aggregate": PYTHON_AGGREGATE,
358
+ "id": PYTHON_ID,
359
+ "repository": PYTHON_REPOSITORY,
360
+ "events": PYTHON_EVENTS,
361
+ },
362
+ "kotlin": {
363
+ "ext": "kt",
364
+ "aggregate": KOTLIN_AGGREGATE,
365
+ "id": KOTLIN_ID,
366
+ "repository": KOTLIN_REPOSITORY,
367
+ "events": KOTLIN_EVENTS,
368
+ },
369
+ "java": {
370
+ "ext": "java",
371
+ "aggregate": JAVA_AGGREGATE,
372
+ "id": JAVA_ID,
373
+ "repository": JAVA_REPOSITORY,
374
+ "events": JAVA_EVENTS,
375
+ },
376
+ }
377
+
378
+
379
+ def write(path: Path, content: str) -> None:
380
+ path.parent.mkdir(parents=True, exist_ok=True)
381
+ path.write_text(content)
382
+ print(f" Created: {path}")
383
+
384
+
385
+ def scaffold(name: str, lang: str, output_dir: Path) -> None:
386
+ t = TEMPLATES[lang]
387
+ ext = t["ext"]
388
+ ctx = {"name": name, "lname": name.lower()}
389
+ files = {
390
+ f"{name}.{ext}": t["aggregate"],
391
+ f"{name}Id.{ext}": t["id"],
392
+ f"{name}Repository.{ext}": t["repository"],
393
+ f"{name}Events.{ext}": t["events"],
394
+ }
395
+ print(f"\nScaffolding DDD aggregate '{name}' ({lang}) in {output_dir}/\n")
396
+ for filename, tmpl in files.items():
397
+ write(output_dir / filename, tmpl.substitute(ctx))
398
+
399
+ print("\nNext steps:")
400
+ print(f" 1. Implement your domain commands inside {name}.{ext}")
401
+ print(f" 2. Add concrete repository in infrastructure/ (implements {name}Repository)")
402
+ print(f" 3. Publish events from {name}Events.{ext} via a message broker")
403
+ print(f" 4. Keep the aggregate free of infrastructure concerns\n")
404
+
405
+
406
+ def main() -> None:
407
+ parser = argparse.ArgumentParser(description="Scaffold DDD aggregate building blocks.")
408
+ parser.add_argument("name", help="Aggregate name (PascalCase), e.g. Order")
409
+ parser.add_argument("--lang", choices=["python", "kotlin", "java"], default="python")
410
+ parser.add_argument("--output-dir", default=".", type=Path)
411
+ args = parser.parse_args()
412
+
413
+ if not args.name[0].isupper():
414
+ print(f"ERROR: AggregateName should be PascalCase (got '{args.name}').", file=sys.stderr)
415
+ sys.exit(1)
416
+
417
+ scaffold(args.name, args.lang, args.output_dir)
418
+
419
+
420
+ if __name__ == "__main__":
421
+ main()