@robbiesrobotics/alice-agents 1.5.8 → 1.5.10

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 (591) hide show
  1. package/README.md +152 -129
  2. package/bin/alice-install.mjs +27 -35
  3. package/lib/hermes-agent.mjs +449 -0
  4. package/lib/hermes-installer.mjs +338 -0
  5. package/lib/installer.mjs +254 -19
  6. package/lib/skills.mjs +128 -4
  7. package/package.json +3 -3
  8. package/templates/skills/acculynx/SKILL.md +183 -0
  9. package/templates/skills/acculynx/references/analysis_template.py +116 -0
  10. package/templates/skills/acculynx/references/dashboard_page.tsx +641 -0
  11. package/templates/skills/claude-code/SKILL.md +2 -2
  12. package/templates/skills/coding-agent/SKILL.md +68 -0
  13. package/templates/skills/crawl4ai/SKILL.md +119 -0
  14. package/templates/skills/crawl4ai/scripts/crwl +3 -0
  15. package/templates/workspaces/_shared/AGENTS-hermes.md +54 -0
  16. package/templates/workspaces/_shared/AGENTS.md +25 -0
  17. package/templates/workspaces/_shared/SOUL-hermes.md +35 -0
  18. package/templates/workspaces/_shared/hermes-agent-skill.md +40 -0
  19. package/templates/workspaces/_shared/hermes-orchestrator-skill.md +150 -0
  20. package/templates/workspaces/_shared/hermes-specialist-skill.md +109 -0
  21. package/templates/workspaces/accuscope/AGENTS.md +38 -0
  22. package/templates/workspaces/accuscope/FEEDBACK.md +27 -0
  23. package/templates/workspaces/accuscope/HEARTBEAT.md +26 -0
  24. package/templates/workspaces/accuscope/IDENTITY.md +48 -0
  25. package/templates/workspaces/accuscope/LEARNINGS.md +46 -0
  26. package/templates/workspaces/accuscope/MEMORY.md +47 -0
  27. package/templates/workspaces/accuscope/PLAYBOOK.md +65 -0
  28. package/templates/workspaces/accuscope/SOUL.md +40 -0
  29. package/templates/workspaces/accuscope/TOOLS.md +63 -0
  30. package/templates/workspaces/accuscope/USER.md +39 -0
  31. package/templates/workspaces/aiden/AGENTS.md +52 -0
  32. package/templates/workspaces/aiden/FEEDBACK.md +12 -0
  33. package/templates/workspaces/aiden/HEARTBEAT.md +9 -0
  34. package/templates/workspaces/aiden/IDENTITY.md +6 -0
  35. package/templates/workspaces/aiden/LEARNINGS.md +6 -0
  36. package/templates/workspaces/aiden/MEMORY.md +22 -0
  37. package/templates/workspaces/aiden/PLAYBOOK.md +16 -0
  38. package/templates/workspaces/aiden/SOUL.md +1 -1
  39. package/templates/workspaces/aiden/USER.md +17 -0
  40. package/templates/workspaces/alex/AGENTS.md +52 -0
  41. package/templates/workspaces/alex/FEEDBACK.md +11 -0
  42. package/templates/workspaces/alex/HEARTBEAT.md +9 -0
  43. package/templates/workspaces/alex/IDENTITY.md +6 -0
  44. package/templates/workspaces/alex/LEARNINGS.md +5 -0
  45. package/templates/workspaces/alex/MEMORY.md +22 -0
  46. package/templates/workspaces/alex/PLAYBOOK.md +16 -0
  47. package/templates/workspaces/alex/SOUL.md +1 -1
  48. package/templates/workspaces/alex/USER.md +13 -0
  49. package/templates/workspaces/aria/AGENTS.md +18 -0
  50. package/templates/workspaces/aria/FEEDBACK.md +12 -0
  51. package/templates/workspaces/aria/HEARTBEAT.md +32 -0
  52. package/templates/workspaces/aria/IDENTITY.md +12 -0
  53. package/templates/workspaces/aria/LEARNINGS.md +31 -0
  54. package/templates/workspaces/aria/MEMORY.md +29 -0
  55. package/templates/workspaces/aria/PLAYBOOK.md +71 -0
  56. package/templates/workspaces/aria/SOUL.md +57 -0
  57. package/templates/workspaces/aria/TOOLS.md +47 -0
  58. package/templates/workspaces/aria/USER.md +18 -0
  59. package/templates/workspaces/audrey/AGENTS.md +59 -0
  60. package/templates/workspaces/audrey/FEEDBACK.md +11 -0
  61. package/templates/workspaces/audrey/HEARTBEAT.md +9 -0
  62. package/templates/workspaces/audrey/IDENTITY.md +6 -0
  63. package/templates/workspaces/audrey/LEARNINGS.md +5 -0
  64. package/templates/workspaces/audrey/MEMORY.md +22 -0
  65. package/templates/workspaces/audrey/PLAYBOOK.md +16 -0
  66. package/templates/workspaces/audrey/SOUL.md +1 -1
  67. package/templates/workspaces/audrey/TOOLS.md +15 -0
  68. package/templates/workspaces/audrey/USER.md +13 -0
  69. package/templates/workspaces/avery/AGENTS.md +52 -0
  70. package/templates/workspaces/avery/FEEDBACK.md +12 -0
  71. package/templates/workspaces/avery/HEARTBEAT.md +5 -0
  72. package/templates/workspaces/avery/IDENTITY.md +6 -0
  73. package/templates/workspaces/avery/LEARNINGS.md +6 -0
  74. package/templates/workspaces/avery/MEMORY.md +22 -0
  75. package/templates/workspaces/avery/PLAYBOOK.md +16 -0
  76. package/templates/workspaces/avery/SOUL.md +1 -1
  77. package/templates/workspaces/avery/USER.md +17 -0
  78. package/templates/workspaces/avery/skills/claude-code/SKILL.md +38 -0
  79. package/templates/workspaces/avery/skills/claude-code/claude_code +55 -0
  80. package/templates/workspaces/caleb/AGENTS.md +52 -0
  81. package/templates/workspaces/caleb/FEEDBACK.md +11 -0
  82. package/templates/workspaces/caleb/HEARTBEAT.md +9 -0
  83. package/templates/workspaces/caleb/IDENTITY.md +6 -0
  84. package/templates/workspaces/caleb/LEARNINGS.md +5 -0
  85. package/templates/workspaces/caleb/MEMORY.md +22 -0
  86. package/templates/workspaces/caleb/PLAYBOOK.md +16 -0
  87. package/templates/workspaces/caleb/SOUL.md +1 -1
  88. package/templates/workspaces/caleb/TOOLS.md +30 -0
  89. package/templates/workspaces/caleb/USER.md +13 -0
  90. package/templates/workspaces/clara/AGENTS.md +59 -0
  91. package/templates/workspaces/clara/FEEDBACK.md +12 -0
  92. package/templates/workspaces/clara/HEARTBEAT.md +5 -0
  93. package/templates/workspaces/clara/IDENTITY.md +6 -0
  94. package/templates/workspaces/clara/LEARNINGS.md +6 -0
  95. package/templates/workspaces/clara/MEMORY.md +22 -0
  96. package/templates/workspaces/clara/PLAYBOOK.md +16 -0
  97. package/templates/workspaces/clara/SOUL.md +1 -1
  98. package/templates/workspaces/clara/TOOLS.md +15 -0
  99. package/templates/workspaces/clara/USER.md +17 -0
  100. package/templates/workspaces/daphne/AGENTS.md +59 -0
  101. package/templates/workspaces/daphne/FEEDBACK.md +18 -0
  102. package/templates/workspaces/daphne/HEARTBEAT.md +5 -0
  103. package/templates/workspaces/daphne/IDENTITY.md +6 -0
  104. package/templates/workspaces/daphne/LEARNINGS.md +6 -0
  105. package/templates/workspaces/daphne/MEMORY.md +22 -0
  106. package/templates/workspaces/daphne/PLAYBOOK.md +48 -0
  107. package/templates/workspaces/daphne/SOUL.md +1 -1
  108. package/templates/workspaces/daphne/TOOLS.md +15 -0
  109. package/templates/workspaces/daphne/USER.md +17 -0
  110. package/templates/workspaces/darius/AGENTS.md +52 -0
  111. package/templates/workspaces/darius/FEEDBACK.md +12 -0
  112. package/templates/workspaces/darius/HEARTBEAT.md +5 -0
  113. package/templates/workspaces/darius/IDENTITY.md +6 -0
  114. package/templates/workspaces/darius/LEARNINGS.md +6 -0
  115. package/templates/workspaces/darius/MEMORY.md +22 -0
  116. package/templates/workspaces/darius/PLAYBOOK.md +16 -0
  117. package/templates/workspaces/darius/SOUL.md +1 -1
  118. package/templates/workspaces/darius/USER.md +17 -0
  119. package/templates/workspaces/darius/skills/claude-code/SKILL.md +38 -0
  120. package/templates/workspaces/darius/skills/claude-code/claude_code +55 -0
  121. package/templates/workspaces/devon/AGENTS.md +52 -0
  122. package/templates/workspaces/devon/FEEDBACK.md +11 -0
  123. package/templates/workspaces/devon/HEARTBEAT.md +5 -0
  124. package/templates/workspaces/devon/IDENTITY.md +6 -0
  125. package/templates/workspaces/devon/LEARNINGS.md +11 -0
  126. package/templates/workspaces/devon/MEMORY.md +22 -0
  127. package/templates/workspaces/devon/PLAYBOOK.md +16 -0
  128. package/templates/workspaces/devon/SOUL.md +1 -1
  129. package/templates/workspaces/devon/USER.md +13 -0
  130. package/templates/workspaces/devon/check_github.py +12 -0
  131. package/templates/workspaces/devon/check_mc_env.py +30 -0
  132. package/templates/workspaces/devon/check_sb.py +34 -0
  133. package/templates/workspaces/devon/check_vercel.py +12 -0
  134. package/templates/workspaces/devon/get_mc_files.py +17 -0
  135. package/templates/workspaces/devon/write_heartbeat.py +67 -0
  136. package/templates/workspaces/dylan/.env.example +33 -0
  137. package/templates/workspaces/dylan/00007_verify_licenses_table.sql +100 -0
  138. package/templates/workspaces/dylan/AGENTS.md +52 -0
  139. package/templates/workspaces/dylan/FEEDBACK.md +28 -0
  140. package/templates/workspaces/dylan/HEARTBEAT.md +5 -0
  141. package/templates/workspaces/dylan/IDENTITY.md +6 -0
  142. package/templates/workspaces/dylan/LEARNINGS.md +70 -0
  143. package/templates/workspaces/dylan/MEMORY.md +22 -0
  144. package/templates/workspaces/dylan/PLAYBOOK.md +16 -0
  145. package/templates/workspaces/dylan/SOUL.md +1 -1
  146. package/templates/workspaces/dylan/STRIPE_PIPELINE.md +185 -0
  147. package/templates/workspaces/dylan/USER.md +17 -0
  148. package/templates/workspaces/dylan/n8n-stripe-welcome-workflow.json +123 -0
  149. package/templates/workspaces/dylan/skills/claude-code/SKILL.md +38 -0
  150. package/templates/workspaces/dylan/skills/claude-code/claude_code +55 -0
  151. package/templates/workspaces/dylan/stripe-webhook-handler.py +433 -0
  152. package/templates/workspaces/dylan/test_mock_webhook.py +103 -0
  153. package/templates/workspaces/elena/AGENTS.md +59 -0
  154. package/templates/workspaces/elena/FEEDBACK.md +11 -0
  155. package/templates/workspaces/elena/HEARTBEAT.md +9 -0
  156. package/templates/workspaces/elena/IDENTITY.md +6 -0
  157. package/templates/workspaces/elena/LEARNINGS.md +5 -0
  158. package/templates/workspaces/elena/MEMORY.md +22 -0
  159. package/templates/workspaces/elena/PLAYBOOK.md +16 -0
  160. package/templates/workspaces/elena/SOUL.md +1 -1
  161. package/templates/workspaces/elena/TOOLS.md +15 -0
  162. package/templates/workspaces/elena/USER.md +13 -0
  163. package/templates/workspaces/eva/AGENTS.md +59 -0
  164. package/templates/workspaces/eva/FEEDBACK.md +11 -0
  165. package/templates/workspaces/eva/HEARTBEAT.md +9 -0
  166. package/templates/workspaces/eva/IDENTITY.md +6 -0
  167. package/templates/workspaces/eva/LEARNINGS.md +5 -0
  168. package/templates/workspaces/eva/MEMORY.md +22 -0
  169. package/templates/workspaces/eva/PLAYBOOK.md +16 -0
  170. package/templates/workspaces/eva/SOUL.md +1 -1
  171. package/templates/workspaces/eva/TOOLS.md +15 -0
  172. package/templates/workspaces/eva/USER.md +13 -0
  173. package/templates/workspaces/felix/AGENTS.md +52 -0
  174. package/templates/workspaces/felix/FEEDBACK.md +11 -0
  175. package/templates/workspaces/felix/HEARTBEAT.md +5 -0
  176. package/templates/workspaces/felix/IDENTITY.md +6 -0
  177. package/templates/workspaces/felix/LEARNINGS.md +17 -0
  178. package/templates/workspaces/felix/MEMORY.md +22 -0
  179. package/templates/workspaces/felix/PLAYBOOK.md +16 -0
  180. package/templates/workspaces/felix/SOUL.md +1 -1
  181. package/templates/workspaces/felix/USER.md +13 -0
  182. package/templates/workspaces/felix/fidelia-psychology.html +1594 -0
  183. package/templates/workspaces/felix/task.txt +164 -0
  184. package/templates/workspaces/hannah/AGENTS.md +59 -0
  185. package/templates/workspaces/hannah/FEEDBACK.md +12 -0
  186. package/templates/workspaces/hannah/HEARTBEAT.md +5 -0
  187. package/templates/workspaces/hannah/IDENTITY.md +6 -0
  188. package/templates/workspaces/hannah/LEARNINGS.md +6 -0
  189. package/templates/workspaces/hannah/MEMORY.md +22 -0
  190. package/templates/workspaces/hannah/PLAYBOOK.md +16 -0
  191. package/templates/workspaces/hannah/SOUL.md +1 -1
  192. package/templates/workspaces/hannah/TOOLS.md +15 -0
  193. package/templates/workspaces/hannah/USER.md +17 -0
  194. package/templates/workspaces/isaac/AGENTS.md +52 -0
  195. package/templates/workspaces/isaac/FEEDBACK.md +12 -0
  196. package/templates/workspaces/isaac/HEARTBEAT.md +9 -0
  197. package/templates/workspaces/isaac/IDENTITY.md +6 -0
  198. package/templates/workspaces/isaac/LEARNINGS.md +6 -0
  199. package/templates/workspaces/isaac/MEMORY.md +22 -0
  200. package/templates/workspaces/isaac/PLAYBOOK.md +16 -0
  201. package/templates/workspaces/isaac/SOUL.md +1 -1
  202. package/templates/workspaces/isaac/USER.md +17 -0
  203. package/templates/workspaces/isaac/skills/claude-code/SKILL.md +38 -0
  204. package/templates/workspaces/isaac/skills/claude-code/claude_code +55 -0
  205. package/templates/workspaces/logan/AGENTS.md +59 -0
  206. package/templates/workspaces/logan/FEEDBACK.md +11 -0
  207. package/templates/workspaces/logan/HEARTBEAT.md +9 -0
  208. package/templates/workspaces/logan/IDENTITY.md +6 -0
  209. package/templates/workspaces/logan/LEARNINGS.md +5 -0
  210. package/templates/workspaces/logan/MEMORY.md +22 -0
  211. package/templates/workspaces/logan/PLAYBOOK.md +16 -0
  212. package/templates/workspaces/logan/SOUL.md +1 -1
  213. package/templates/workspaces/logan/TOOLS.md +15 -0
  214. package/templates/workspaces/logan/USER.md +13 -0
  215. package/templates/workspaces/maxxipro/AGENTS.md +29 -0
  216. package/templates/workspaces/maxxipro/FEEDBACK.md +19 -0
  217. package/templates/workspaces/maxxipro/HEARTBEAT.md +22 -0
  218. package/templates/workspaces/maxxipro/IDENTITY.md +35 -0
  219. package/templates/workspaces/maxxipro/KNOWLEDGE.md +335 -0
  220. package/templates/workspaces/maxxipro/LEARNINGS.md +47 -0
  221. package/templates/workspaces/maxxipro/MEMORY.md +60 -0
  222. package/templates/workspaces/maxxipro/OUTREACH_TEMPLATES.md +143 -0
  223. package/templates/workspaces/maxxipro/PLAYBOOK.md +81 -0
  224. package/templates/workspaces/maxxipro/SOUL.md +146 -0
  225. package/templates/workspaces/maxxipro/TOOLS.md +81 -0
  226. package/templates/workspaces/maxxipro/USER.md +40 -0
  227. package/templates/workspaces/morgan/AGENTS.md +59 -0
  228. package/templates/workspaces/morgan/FEEDBACK.md +19 -0
  229. package/templates/workspaces/morgan/HEARTBEAT.md +5 -0
  230. package/templates/workspaces/morgan/IDENTITY.md +6 -0
  231. package/templates/workspaces/morgan/LEARNINGS.md +18 -0
  232. package/templates/workspaces/morgan/MEMORY.md +22 -0
  233. package/templates/workspaces/morgan/PLAYBOOK.md +16 -0
  234. package/templates/workspaces/morgan/SOUL.md +1 -1
  235. package/templates/workspaces/morgan/TOOLS.md +15 -0
  236. package/templates/workspaces/morgan/USER.md +13 -0
  237. package/templates/workspaces/nadia/AGENTS.md +59 -0
  238. package/templates/workspaces/nadia/FEEDBACK.md +12 -0
  239. package/templates/workspaces/nadia/HEARTBEAT.md +5 -0
  240. package/templates/workspaces/nadia/IDENTITY.md +6 -0
  241. package/templates/workspaces/nadia/LEARNINGS.md +6 -0
  242. package/templates/workspaces/nadia/MEMORY.md +22 -0
  243. package/templates/workspaces/nadia/PLAYBOOK.md +16 -0
  244. package/templates/workspaces/nadia/SOUL.md +1 -1
  245. package/templates/workspaces/nadia/TOOLS.md +15 -0
  246. package/templates/workspaces/nadia/USER.md +13 -0
  247. package/templates/workspaces/nate/AGENTS.md +24 -0
  248. package/templates/workspaces/nate/FEEDBACK.md +12 -0
  249. package/templates/workspaces/nate/HEARTBEAT.md +33 -0
  250. package/templates/workspaces/nate/IDENTITY.md +15 -0
  251. package/templates/workspaces/nate/LEARNINGS.md +33 -0
  252. package/templates/workspaces/nate/MEMORY.md +39 -0
  253. package/templates/workspaces/nate/PLAYBOOK.md +160 -0
  254. package/templates/workspaces/nate/SOUL.md +50 -0
  255. package/templates/workspaces/nate/TOOLS.md +111 -0
  256. package/templates/workspaces/nate/USER.md +32 -0
  257. package/templates/workspaces/olivia/.last-openclaw-version +1 -0
  258. package/templates/workspaces/olivia/.npmrc.tmp +0 -0
  259. package/templates/workspaces/olivia/AGENTS.md +77 -0
  260. package/templates/workspaces/olivia/ALPHA_CODING_BENCHMARK.txt +148 -0
  261. package/templates/workspaces/olivia/ALPHA_MODEL_GUIDE.md +393 -0
  262. package/templates/workspaces/olivia/FEEDBACK.md +13 -0
  263. package/templates/workspaces/olivia/HEADTOHEAD_BENCHMARK.txt +1289 -0
  264. package/templates/workspaces/olivia/HEARTBEAT.md +267 -0
  265. package/templates/workspaces/olivia/IDENTITY.md +6 -0
  266. package/templates/workspaces/olivia/LEARNINGS.md +708 -0
  267. package/templates/workspaces/olivia/MEMORY.md +202 -0
  268. package/templates/workspaces/olivia/MISSION_CONTROL_DESIGN_SPEC_v1.md +1143 -0
  269. package/templates/workspaces/olivia/MVP-COMPLETION-SUMMARY.md +175 -0
  270. package/templates/workspaces/olivia/NETWORK_IMPLEMENTATION_PLAN.md +1556 -0
  271. package/templates/workspaces/olivia/NEW_NODES_BENCHMARK.txt +947 -0
  272. package/templates/workspaces/olivia/PLAYBOOK.md +42 -0
  273. package/templates/workspaces/olivia/SELF-HEALING-COMPLETE.md +150 -0
  274. package/templates/workspaces/olivia/SOUL.md +8 -8
  275. package/templates/workspaces/olivia/TOOLS.md +15 -0
  276. package/templates/workspaces/olivia/USER.md +17 -0
  277. package/templates/workspaces/olivia/alicefleet-supabase-credentials.md +50 -0
  278. package/templates/workspaces/olivia/dzombo-copy-rewrite.md +115 -0
  279. package/templates/workspaces/olivia/dzombo-implementation-plan.md +1248 -0
  280. package/templates/workspaces/olivia/fidelia-psychology.html +1594 -0
  281. package/templates/workspaces/olivia/lead_debug.png +0 -0
  282. package/templates/workspaces/olivia/minimatch-10.2.4.tgz +0 -0
  283. package/templates/workspaces/olivia/operation-bllm-research.md +157 -0
  284. package/templates/workspaces/olivia/qa-audit-mission-control-v2.md +538 -0
  285. package/templates/workspaces/olivia/roofmaxx_logo.svg +1 -0
  286. package/templates/workspaces/olivia/roofmaxx_social.jpg +0 -0
  287. package/templates/workspaces/olivia/skills/1password/SKILL.md +53 -0
  288. package/templates/workspaces/olivia/skills/1password/_meta.json +6 -0
  289. package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/README.md +57 -0
  290. package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/SKILL.md +534 -0
  291. package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/_meta.json +6 -0
  292. package/templates/workspaces/olivia/skills/agent-security/SKILL.md +69 -0
  293. package/templates/workspaces/olivia/skills/agent-security/_meta.json +6 -0
  294. package/templates/workspaces/olivia/skills/agentic-security-audit/SKILL.md +855 -0
  295. package/templates/workspaces/olivia/skills/agentic-security-audit/_meta.json +6 -0
  296. package/templates/workspaces/olivia/skills/ai-automation-consulting/SKILL.md +67 -0
  297. package/templates/workspaces/olivia/skills/ai-automation-consulting/_meta.json +6 -0
  298. package/templates/workspaces/olivia/skills/ai-automation-consulting/skill.json +12 -0
  299. package/templates/workspaces/olivia/skills/ai-presentation-maker/SKILL.md +1104 -0
  300. package/templates/workspaces/olivia/skills/ai-presentation-maker/_meta.json +6 -0
  301. package/templates/workspaces/olivia/skills/ai-productivity-audit/SKILL.md +181 -0
  302. package/templates/workspaces/olivia/skills/ai-productivity-audit/_meta.json +6 -0
  303. package/templates/workspaces/olivia/skills/ai-researcher/README.md +31 -0
  304. package/templates/workspaces/olivia/skills/ai-researcher/SKILL.md +59 -0
  305. package/templates/workspaces/olivia/skills/ai-researcher/_meta.json +6 -0
  306. package/templates/workspaces/olivia/skills/ai-seo-writer/README.md +19 -0
  307. package/templates/workspaces/olivia/skills/ai-seo-writer/SKILL.md +100 -0
  308. package/templates/workspaces/olivia/skills/ai-seo-writer/_meta.json +6 -0
  309. package/templates/workspaces/olivia/skills/analytics-tracking-2/SKILL.md +309 -0
  310. package/templates/workspaces/olivia/skills/analytics-tracking-2/_meta.json +6 -0
  311. package/templates/workspaces/olivia/skills/api-doc-writer/SKILL.md +232 -0
  312. package/templates/workspaces/olivia/skills/api-doc-writer/_meta.json +6 -0
  313. package/templates/workspaces/olivia/skills/api-generator/SKILL.md +49 -0
  314. package/templates/workspaces/olivia/skills/api-generator/_meta.json +6 -0
  315. package/templates/workspaces/olivia/skills/api-generator/tips.md +10 -0
  316. package/templates/workspaces/olivia/skills/apple-notes/SKILL.md +50 -0
  317. package/templates/workspaces/olivia/skills/apple-notes/_meta.json +6 -0
  318. package/templates/workspaces/olivia/skills/apple-reminders/SKILL.md +67 -0
  319. package/templates/workspaces/olivia/skills/apple-reminders/_meta.json +6 -0
  320. package/templates/workspaces/olivia/skills/automation-workflows/SKILL.md +267 -0
  321. package/templates/workspaces/olivia/skills/automation-workflows/_meta.json +6 -0
  322. package/templates/workspaces/olivia/skills/autoresearch/SKILL.md +46 -0
  323. package/templates/workspaces/olivia/skills/autoresearch/aria_write.py +148 -0
  324. package/templates/workspaces/olivia/skills/autoresearch/autoresearch.py +75 -0
  325. package/templates/workspaces/olivia/skills/azure-devops/SKILL.md +115 -0
  326. package/templates/workspaces/olivia/skills/azure-devops/_meta.json +6 -0
  327. package/templates/workspaces/olivia/skills/blogwatcher/SKILL.md +46 -0
  328. package/templates/workspaces/olivia/skills/blogwatcher/_meta.json +6 -0
  329. package/templates/workspaces/olivia/skills/blucli/SKILL.md +27 -0
  330. package/templates/workspaces/olivia/skills/blucli/_meta.json +6 -0
  331. package/templates/workspaces/olivia/skills/check-analytics/SKILL.md +92 -0
  332. package/templates/workspaces/olivia/skills/check-analytics/_meta.json +6 -0
  333. package/templates/workspaces/olivia/skills/cloud-architect/SKILL.md +89 -0
  334. package/templates/workspaces/olivia/skills/cloud-architect/_meta.json +6 -0
  335. package/templates/workspaces/olivia/skills/cloud-infra-automation/SKILL.md +50 -0
  336. package/templates/workspaces/olivia/skills/cloud-infra-automation/_meta.json +6 -0
  337. package/templates/workspaces/olivia/skills/cloud-storage/SKILL.md +61 -0
  338. package/templates/workspaces/olivia/skills/cloud-storage/_meta.json +6 -0
  339. package/templates/workspaces/olivia/skills/cloud-storage/auth.md +97 -0
  340. package/templates/workspaces/olivia/skills/cloud-storage/costs.md +88 -0
  341. package/templates/workspaces/olivia/skills/cloud-storage/providers.md +55 -0
  342. package/templates/workspaces/olivia/skills/copywriting-pro/SKILL.md +107 -0
  343. package/templates/workspaces/olivia/skills/copywriting-pro/_meta.json +6 -0
  344. package/templates/workspaces/olivia/skills/data-analyst-pro/SKILL.md +21 -0
  345. package/templates/workspaces/olivia/skills/data-analyst-pro/_meta.json +6 -0
  346. package/templates/workspaces/olivia/skills/database-designer/README.md +388 -0
  347. package/templates/workspaces/olivia/skills/database-designer/SKILL.md +66 -0
  348. package/templates/workspaces/olivia/skills/database-designer/_meta.json +6 -0
  349. package/templates/workspaces/olivia/skills/database-designer/index_optimizer.py +926 -0
  350. package/templates/workspaces/olivia/skills/database-designer/migration_generator.py +1199 -0
  351. package/templates/workspaces/olivia/skills/database-designer/schema_analyzer.py +982 -0
  352. package/templates/workspaces/olivia/skills/deploy-agent/SKILL.md +255 -0
  353. package/templates/workspaces/olivia/skills/deploy-agent/_meta.json +6 -0
  354. package/templates/workspaces/olivia/skills/devops-automation-pack/SKILL.md +72 -0
  355. package/templates/workspaces/olivia/skills/devops-automation-pack/_meta.json +6 -0
  356. package/templates/workspaces/olivia/skills/devops-automation-pack/deploy.sh +0 -0
  357. package/templates/workspaces/olivia/skills/financial-analysis-agent/SKILL.md +489 -0
  358. package/templates/workspaces/olivia/skills/financial-analysis-agent/_meta.json +6 -0
  359. package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/README.md +72 -0
  360. package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/SKILL.md +226 -0
  361. package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/_meta.json +6 -0
  362. package/templates/workspaces/olivia/skills/gifgrep/SKILL.md +47 -0
  363. package/templates/workspaces/olivia/skills/gifgrep/_meta.json +6 -0
  364. package/templates/workspaces/olivia/skills/github/SKILL.md +47 -0
  365. package/templates/workspaces/olivia/skills/github/_meta.json +6 -0
  366. package/templates/workspaces/olivia/skills/gog/SKILL.md +36 -0
  367. package/templates/workspaces/olivia/skills/gog/_meta.json +6 -0
  368. package/templates/workspaces/olivia/skills/growth-strategy-hub/SKILL.md +135 -0
  369. package/templates/workspaces/olivia/skills/growth-strategy-hub/_meta.json +6 -0
  370. package/templates/workspaces/olivia/skills/growth-strategy-hub/metadata.json +4 -0
  371. package/templates/workspaces/olivia/skills/hetzner-cloud/SKILL.md +130 -0
  372. package/templates/workspaces/olivia/skills/hetzner-cloud/_meta.json +6 -0
  373. package/templates/workspaces/olivia/skills/himalaya/SKILL.md +217 -0
  374. package/templates/workspaces/olivia/skills/himalaya/_meta.json +6 -0
  375. package/templates/workspaces/olivia/skills/hotel-recommendation/SKILL.md +117 -0
  376. package/templates/workspaces/olivia/skills/hotel-recommendation/_meta.json +6 -0
  377. package/templates/workspaces/olivia/skills/hr-policy-generator/SKILL.md +54 -0
  378. package/templates/workspaces/olivia/skills/hr-policy-generator/_meta.json +6 -0
  379. package/templates/workspaces/olivia/skills/human-writing/SKILL.md +41 -0
  380. package/templates/workspaces/olivia/skills/human-writing/_meta.json +6 -0
  381. package/templates/workspaces/olivia/skills/imsg/SKILL.md +25 -0
  382. package/templates/workspaces/olivia/skills/imsg/_meta.json +6 -0
  383. package/templates/workspaces/olivia/skills/in-depth-research/SKILL.md +124 -0
  384. package/templates/workspaces/olivia/skills/in-depth-research/_meta.json +6 -0
  385. package/templates/workspaces/olivia/skills/in-depth-research/methodology.md +75 -0
  386. package/templates/workspaces/olivia/skills/in-depth-research/output-formats.md +168 -0
  387. package/templates/workspaces/olivia/skills/in-depth-research/sources.md +80 -0
  388. package/templates/workspaces/olivia/skills/javascript-skills/README.md +71 -0
  389. package/templates/workspaces/olivia/skills/javascript-skills/SKILL.md +746 -0
  390. package/templates/workspaces/olivia/skills/javascript-skills/_meta.json +6 -0
  391. package/templates/workspaces/olivia/skills/leadership-strategy-playbook/SKILL.md +147 -0
  392. package/templates/workspaces/olivia/skills/leadership-strategy-playbook/_meta.json +6 -0
  393. package/templates/workspaces/olivia/skills/market-research-agent/README.md +29 -0
  394. package/templates/workspaces/olivia/skills/market-research-agent/SKILL.md +52 -0
  395. package/templates/workspaces/olivia/skills/market-research-agent/_meta.json +6 -0
  396. package/templates/workspaces/olivia/skills/marketing-analytics/SKILL.md +74 -0
  397. package/templates/workspaces/olivia/skills/marketing-analytics/_meta.json +6 -0
  398. package/templates/workspaces/olivia/skills/marketing-master-io/SKILL.md +125 -0
  399. package/templates/workspaces/olivia/skills/marketing-master-io/_meta.json +6 -0
  400. package/templates/workspaces/olivia/skills/marketing-strategy-pmm/SKILL.md +398 -0
  401. package/templates/workspaces/olivia/skills/marketing-strategy-pmm/_meta.json +6 -0
  402. package/templates/workspaces/olivia/skills/meta-ads-analytics/SKILL.md +53 -0
  403. package/templates/workspaces/olivia/skills/meta-ads-analytics/_meta.json +6 -0
  404. package/templates/workspaces/olivia/skills/obsidian/SKILL.md +55 -0
  405. package/templates/workspaces/olivia/skills/obsidian/_meta.json +6 -0
  406. package/templates/workspaces/olivia/skills/openclaw-accounting/SKILL.md +125 -0
  407. package/templates/workspaces/olivia/skills/openclaw-accounting/_meta.json +6 -0
  408. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANGELOG.md +35 -0
  409. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANNELLOG.md +73 -0
  410. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/README.md +161 -0
  411. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/SKILL.md +130 -0
  412. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/_meta.json +6 -0
  413. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/config.json +36 -0
  414. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/metadata.json +19 -0
  415. package/templates/workspaces/olivia/skills/openhue/SKILL.md +30 -0
  416. package/templates/workspaces/olivia/skills/openhue/_meta.json +6 -0
  417. package/templates/workspaces/olivia/skills/orgx-operations-agent/SKILL.md +41 -0
  418. package/templates/workspaces/olivia/skills/orgx-operations-agent/_meta.json +6 -0
  419. package/templates/workspaces/olivia/skills/outreach/SKILL.md +84 -0
  420. package/templates/workspaces/olivia/skills/outreach/_meta.json +6 -0
  421. package/templates/workspaces/olivia/skills/outreach/by-type.md +166 -0
  422. package/templates/workspaces/olivia/skills/outreach/templates.md +154 -0
  423. package/templates/workspaces/olivia/skills/outreach/tracking.md +145 -0
  424. package/templates/workspaces/olivia/skills/persona-hr-coordinator/SKILL.md +38 -0
  425. package/templates/workspaces/olivia/skills/persona-hr-coordinator/_meta.json +6 -0
  426. package/templates/workspaces/olivia/skills/personal-productivity/SKILL.md +161 -0
  427. package/templates/workspaces/olivia/skills/personal-productivity/_meta.json +6 -0
  428. package/templates/workspaces/olivia/skills/personal-productivity/index.js +363 -0
  429. package/templates/workspaces/olivia/skills/personal-productivity/package.json +15 -0
  430. package/templates/workspaces/olivia/skills/personal-travel/README.md +34 -0
  431. package/templates/workspaces/olivia/skills/personal-travel/SKILL.md +46 -0
  432. package/templates/workspaces/olivia/skills/personal-travel/_meta.json +6 -0
  433. package/templates/workspaces/olivia/skills/presentation-html-generator-skill/SKILL.md +185 -0
  434. package/templates/workspaces/olivia/skills/presentation-html-generator-skill/_meta.json +6 -0
  435. package/templates/workspaces/olivia/skills/product-manager/SKILL.md +77 -0
  436. package/templates/workspaces/olivia/skills/product-manager/_meta.json +6 -0
  437. package/templates/workspaces/olivia/skills/quant-strategy/SKILL.md +28 -0
  438. package/templates/workspaces/olivia/skills/quant-strategy/_meta.json +6 -0
  439. package/templates/workspaces/olivia/skills/sales-pipeline-tracker/README.md +29 -0
  440. package/templates/workspaces/olivia/skills/sales-pipeline-tracker/SKILL.md +45 -0
  441. package/templates/workspaces/olivia/skills/sales-pipeline-tracker/_meta.json +6 -0
  442. package/templates/workspaces/olivia/skills/security-auditor/SKILL.md +399 -0
  443. package/templates/workspaces/olivia/skills/security-auditor/_meta.json +6 -0
  444. package/templates/workspaces/olivia/skills/security-hardening/SKILL.md +296 -0
  445. package/templates/workspaces/olivia/skills/security-hardening/_meta.json +6 -0
  446. package/templates/workspaces/olivia/skills/security-scanner/SKILL.md +67 -0
  447. package/templates/workspaces/olivia/skills/security-scanner/_meta.json +6 -0
  448. package/templates/workspaces/olivia/skills/seo-optimization/SKILL.md +31 -0
  449. package/templates/workspaces/olivia/skills/seo-optimization/_meta.json +6 -0
  450. package/templates/workspaces/olivia/skills/service-booking/SKILL.md +193 -0
  451. package/templates/workspaces/olivia/skills/service-booking/_meta.json +6 -0
  452. package/templates/workspaces/olivia/skills/sme-hr-automation/SKILL.md +131 -0
  453. package/templates/workspaces/olivia/skills/sme-hr-automation/_meta.json +6 -0
  454. package/templates/workspaces/olivia/skills/social-media-scheduler/README.md +29 -0
  455. package/templates/workspaces/olivia/skills/social-media-scheduler/SKILL.md +49 -0
  456. package/templates/workspaces/olivia/skills/social-media-scheduler/_meta.json +6 -0
  457. package/templates/workspaces/olivia/skills/sonoscli/SKILL.md +26 -0
  458. package/templates/workspaces/olivia/skills/sonoscli/_meta.json +6 -0
  459. package/templates/workspaces/olivia/skills/strategy-advisor/SKILL.md +33 -0
  460. package/templates/workspaces/olivia/skills/strategy-advisor/_meta.json +6 -0
  461. package/templates/workspaces/olivia/skills/summarize/SKILL.md +49 -0
  462. package/templates/workspaces/olivia/skills/summarize/_meta.json +6 -0
  463. package/templates/workspaces/olivia/skills/things-mac/SKILL.md +61 -0
  464. package/templates/workspaces/olivia/skills/things-mac/_meta.json +6 -0
  465. package/templates/workspaces/olivia/skills/travel-itinerary-planner/SKILL.md +121 -0
  466. package/templates/workspaces/olivia/skills/travel-itinerary-planner/_meta.json +6 -0
  467. package/templates/workspaces/olivia/skills/travel-manager/SKILL.md +36 -0
  468. package/templates/workspaces/olivia/skills/travel-manager/_meta.json +6 -0
  469. package/templates/workspaces/olivia/skills/travel-planning/SKILL.md +238 -0
  470. package/templates/workspaces/olivia/skills/travel-planning/_meta.json +6 -0
  471. package/templates/workspaces/olivia/skills/travel-planning/booking-guide.md +91 -0
  472. package/templates/workspaces/olivia/skills/travel-planning/memory-template.md +111 -0
  473. package/templates/workspaces/olivia/skills/travel-planning/multi-city.md +131 -0
  474. package/templates/workspaces/olivia/skills/travel-planning/packing-templates.md +155 -0
  475. package/templates/workspaces/olivia/skills/travel-planning/setup.md +66 -0
  476. package/templates/workspaces/olivia/skills/update-it-all/SKILL.md +143 -0
  477. package/templates/workspaces/olivia/skills/update-it-all/_meta.json +6 -0
  478. package/templates/workspaces/olivia/skills/voice/SKILL.md +62 -0
  479. package/templates/workspaces/olivia/skills/weather/SKILL.md +49 -0
  480. package/templates/workspaces/olivia/skills/weather/_meta.json +6 -0
  481. package/templates/workspaces/olivia/skills/web-researcher/SKILL.md +21 -0
  482. package/templates/workspaces/olivia/skills/web-researcher/_meta.json +6 -0
  483. package/templates/workspaces/olivia/skills/website-seo/SKILL.md +284 -0
  484. package/templates/workspaces/olivia/skills/website-seo/_meta.json +6 -0
  485. package/templates/workspaces/olivia/stripe-welcome-n8n.json +103 -0
  486. package/templates/workspaces/olivia/test2.wav.wav +0 -0
  487. package/templates/workspaces/olivia/test_speech.json +1 -0
  488. package/templates/workspaces/olivia/test_speech.srt +0 -0
  489. package/templates/workspaces/olivia/test_speech.tsv +1 -0
  490. package/templates/workspaces/olivia/test_speech.txt +0 -0
  491. package/templates/workspaces/olivia/test_speech.vtt +2 -0
  492. package/templates/workspaces/owen/AGENTS.md +59 -0
  493. package/templates/workspaces/owen/FEEDBACK.md +12 -0
  494. package/templates/workspaces/owen/HEARTBEAT.md +5 -0
  495. package/templates/workspaces/owen/IDENTITY.md +6 -0
  496. package/templates/workspaces/owen/LEARNINGS.md +46 -0
  497. package/templates/workspaces/owen/MEMORY.md +22 -0
  498. package/templates/workspaces/owen/PLAYBOOK.md +16 -0
  499. package/templates/workspaces/owen/SOUL.md +1 -1
  500. package/templates/workspaces/owen/TOOLS.md +15 -0
  501. package/templates/workspaces/owen/USER.md +17 -0
  502. package/templates/workspaces/parker/AGENTS.md +59 -0
  503. package/templates/workspaces/parker/FEEDBACK.md +11 -0
  504. package/templates/workspaces/parker/HEARTBEAT.md +5 -0
  505. package/templates/workspaces/parker/IDENTITY.md +6 -0
  506. package/templates/workspaces/parker/LEARNINGS.md +17 -0
  507. package/templates/workspaces/parker/MEMORY.md +22 -0
  508. package/templates/workspaces/parker/PLAYBOOK.md +16 -0
  509. package/templates/workspaces/parker/SOUL.md +1 -1
  510. package/templates/workspaces/parker/TOOLS.md +15 -0
  511. package/templates/workspaces/parker/USER.md +13 -0
  512. package/templates/workspaces/quinn/AGENTS.md +52 -0
  513. package/templates/workspaces/quinn/FEEDBACK.md +11 -0
  514. package/templates/workspaces/quinn/HEARTBEAT.md +5 -0
  515. package/templates/workspaces/quinn/IDENTITY.md +6 -0
  516. package/templates/workspaces/quinn/LEARNINGS.md +35 -0
  517. package/templates/workspaces/quinn/MEMORY.md +22 -0
  518. package/templates/workspaces/quinn/PLAYBOOK.md +16 -0
  519. package/templates/workspaces/quinn/SOUL.md +1 -1
  520. package/templates/workspaces/quinn/USER.md +17 -0
  521. package/templates/workspaces/quinn/alice-login-page.png +0 -0
  522. package/templates/workspaces/rowan/AGENTS.md +59 -0
  523. package/templates/workspaces/rowan/FEEDBACK.md +12 -0
  524. package/templates/workspaces/rowan/HEARTBEAT.md +5 -0
  525. package/templates/workspaces/rowan/IDENTITY.md +6 -0
  526. package/templates/workspaces/rowan/LEARNINGS.md +12 -0
  527. package/templates/workspaces/rowan/MEMORY.md +22 -0
  528. package/templates/workspaces/rowan/PLAYBOOK.md +16 -0
  529. package/templates/workspaces/rowan/SOUL.md +1 -1
  530. package/templates/workspaces/rowan/USER.md +17 -0
  531. package/templates/workspaces/selena/AGENTS.md +59 -0
  532. package/templates/workspaces/selena/FEEDBACK.md +12 -0
  533. package/templates/workspaces/selena/HEARTBEAT.md +5 -0
  534. package/templates/workspaces/selena/IDENTITY.md +6 -0
  535. package/templates/workspaces/selena/LEARNINGS.md +24 -0
  536. package/templates/workspaces/selena/MEMORY.md +22 -0
  537. package/templates/workspaces/selena/PLAYBOOK.md +16 -0
  538. package/templates/workspaces/selena/SOUL.md +1 -1
  539. package/templates/workspaces/selena/USER.md +17 -0
  540. package/templates/workspaces/selena/kids-ai-security-compliance-plan.md +791 -0
  541. package/templates/workspaces/selena/kidspark-coppa-compliance-audit.md +866 -0
  542. package/templates/workspaces/sloane/AGENTS.md +59 -0
  543. package/templates/workspaces/sloane/FEEDBACK.md +12 -0
  544. package/templates/workspaces/sloane/HEARTBEAT.md +9 -0
  545. package/templates/workspaces/sloane/IDENTITY.md +6 -0
  546. package/templates/workspaces/sloane/LEARNINGS.md +6 -0
  547. package/templates/workspaces/sloane/MEMORY.md +22 -0
  548. package/templates/workspaces/sloane/PLAYBOOK.md +16 -0
  549. package/templates/workspaces/sloane/SOUL.md +1 -1
  550. package/templates/workspaces/sloane/TOOLS.md +15 -0
  551. package/templates/workspaces/sloane/USER.md +13 -0
  552. package/templates/workspaces/smoketestagent/AGENTS.md +52 -0
  553. package/templates/workspaces/smoketestagent/FEEDBACK.md +3 -0
  554. package/templates/workspaces/smoketestagent/HEARTBEAT.md +14 -0
  555. package/templates/workspaces/smoketestagent/IDENTITY.md +6 -0
  556. package/templates/workspaces/smoketestagent/LEARNINGS.md +3 -0
  557. package/templates/workspaces/smoketestagent/MEMORY.md +24 -0
  558. package/templates/workspaces/smoketestagent/PLAYBOOK.md +7 -0
  559. package/templates/workspaces/smoketestagent/SOUL.md +32 -0
  560. package/templates/workspaces/smoketestagent/TOOLS.md +13 -0
  561. package/templates/workspaces/smoketestagent/USER.md +5 -0
  562. package/templates/workspaces/sophie/AGENTS.md +59 -0
  563. package/templates/workspaces/sophie/FEEDBACK.md +12 -0
  564. package/templates/workspaces/sophie/HEARTBEAT.md +9 -0
  565. package/templates/workspaces/sophie/IDENTITY.md +6 -0
  566. package/templates/workspaces/sophie/LEARNINGS.md +6 -0
  567. package/templates/workspaces/sophie/MEMORY.md +22 -0
  568. package/templates/workspaces/sophie/PLAYBOOK.md +16 -0
  569. package/templates/workspaces/sophie/SOUL.md +1 -1
  570. package/templates/workspaces/sophie/TOOLS.md +15 -0
  571. package/templates/workspaces/sophie/USER.md +17 -0
  572. package/templates/workspaces/tommy/AGENTS.md +59 -0
  573. package/templates/workspaces/tommy/FEEDBACK.md +12 -0
  574. package/templates/workspaces/tommy/HEARTBEAT.md +9 -0
  575. package/templates/workspaces/tommy/IDENTITY.md +6 -0
  576. package/templates/workspaces/tommy/LEARNINGS.md +6 -0
  577. package/templates/workspaces/tommy/MEMORY.md +22 -0
  578. package/templates/workspaces/tommy/PLAYBOOK.md +16 -0
  579. package/templates/workspaces/tommy/SOUL.md +1 -1
  580. package/templates/workspaces/tommy/TOOLS.md +15 -0
  581. package/templates/workspaces/tommy/USER.md +17 -0
  582. package/templates/workspaces/uma/AGENTS.md +59 -0
  583. package/templates/workspaces/uma/FEEDBACK.md +11 -0
  584. package/templates/workspaces/uma/HEARTBEAT.md +5 -0
  585. package/templates/workspaces/uma/IDENTITY.md +6 -0
  586. package/templates/workspaces/uma/LEARNINGS.md +11 -0
  587. package/templates/workspaces/uma/MEMORY.md +22 -0
  588. package/templates/workspaces/uma/PLAYBOOK.md +16 -0
  589. package/templates/workspaces/uma/SOUL.md +1 -1
  590. package/templates/workspaces/uma/TOOLS.md +15 -0
  591. package/templates/workspaces/uma/USER.md +13 -0
@@ -0,0 +1,982 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Database Schema Analyzer
4
+
5
+ Analyzes SQL DDL statements and JSON schema definitions for:
6
+ - Normalization level compliance (1NF-BCNF)
7
+ - Missing constraints (FK, NOT NULL, UNIQUE)
8
+ - Data type issues and antipatterns
9
+ - Naming convention violations
10
+ - Missing indexes on foreign key columns
11
+ - Table relationship mapping
12
+ - Generates Mermaid ERD diagrams
13
+
14
+ Input: SQL DDL file or JSON schema definition
15
+ Output: Analysis report + Mermaid ERD + recommendations
16
+
17
+ Usage:
18
+ python schema_analyzer.py --input schema.sql --output-format json
19
+ python schema_analyzer.py --input schema.json --output-format text
20
+ python schema_analyzer.py --input schema.sql --generate-erd --output analysis.json
21
+ """
22
+
23
+ import argparse
24
+ import json
25
+ import re
26
+ import sys
27
+ from collections import defaultdict, namedtuple
28
+ from typing import Dict, List, Set, Tuple, Optional, Any
29
+ from dataclasses import dataclass, asdict
30
+
31
+
32
+ @dataclass
33
+ class Column:
34
+ name: str
35
+ data_type: str
36
+ nullable: bool = True
37
+ primary_key: bool = False
38
+ unique: bool = False
39
+ foreign_key: Optional[str] = None
40
+ default_value: Optional[str] = None
41
+ check_constraint: Optional[str] = None
42
+
43
+
44
+ @dataclass
45
+ class Index:
46
+ name: str
47
+ table: str
48
+ columns: List[str]
49
+ unique: bool = False
50
+ index_type: str = "btree"
51
+
52
+
53
+ @dataclass
54
+ class Table:
55
+ name: str
56
+ columns: List[Column]
57
+ primary_key: List[str]
58
+ foreign_keys: List[Tuple[str, str]] # (column, referenced_table.column)
59
+ unique_constraints: List[List[str]]
60
+ check_constraints: Dict[str, str]
61
+ indexes: List[Index]
62
+
63
+
64
+ @dataclass
65
+ class NormalizationIssue:
66
+ table: str
67
+ issue_type: str
68
+ severity: str
69
+ description: str
70
+ suggestion: str
71
+ columns_affected: List[str]
72
+
73
+
74
+ @dataclass
75
+ class DataTypeIssue:
76
+ table: str
77
+ column: str
78
+ current_type: str
79
+ issue: str
80
+ suggested_type: str
81
+ rationale: str
82
+
83
+
84
+ @dataclass
85
+ class ConstraintIssue:
86
+ table: str
87
+ issue_type: str
88
+ severity: str
89
+ description: str
90
+ suggestion: str
91
+ columns_affected: List[str]
92
+
93
+
94
+ @dataclass
95
+ class NamingIssue:
96
+ table: str
97
+ column: Optional[str]
98
+ issue: str
99
+ current_name: str
100
+ suggested_name: str
101
+
102
+
103
+ class SchemaAnalyzer:
104
+ def __init__(self):
105
+ self.tables: Dict[str, Table] = {}
106
+ self.normalization_issues: List[NormalizationIssue] = []
107
+ self.datatype_issues: List[DataTypeIssue] = []
108
+ self.constraint_issues: List[ConstraintIssue] = []
109
+ self.naming_issues: List[NamingIssue] = []
110
+
111
+ # Data type antipatterns
112
+ self.varchar_255_pattern = re.compile(r'VARCHAR\(255\)', re.IGNORECASE)
113
+ self.bad_datetime_patterns = [
114
+ re.compile(r'VARCHAR\(\d+\)', re.IGNORECASE),
115
+ re.compile(r'CHAR\(\d+\)', re.IGNORECASE)
116
+ ]
117
+
118
+ # Naming conventions
119
+ self.table_naming_pattern = re.compile(r'^[a-z][a-z0-9_]*[a-z0-9]$')
120
+ self.column_naming_pattern = re.compile(r'^[a-z][a-z0-9_]*[a-z0-9]$')
121
+
122
+ def parse_sql_ddl(self, ddl_content: str) -> None:
123
+ """Parse SQL DDL statements and extract schema information."""
124
+ # Remove comments and normalize whitespace
125
+ ddl_content = re.sub(r'--.*$', '', ddl_content, flags=re.MULTILINE)
126
+ ddl_content = re.sub(r'/\*.*?\*/', '', ddl_content, flags=re.DOTALL)
127
+ ddl_content = re.sub(r'\s+', ' ', ddl_content.strip())
128
+
129
+ # Extract CREATE TABLE statements
130
+ create_table_pattern = re.compile(
131
+ r'CREATE\s+TABLE\s+(\w+)\s*\(\s*(.*?)\s*\)',
132
+ re.IGNORECASE | re.DOTALL
133
+ )
134
+
135
+ for match in create_table_pattern.finditer(ddl_content):
136
+ table_name = match.group(1).lower()
137
+ table_definition = match.group(2)
138
+
139
+ table = self._parse_table_definition(table_name, table_definition)
140
+ self.tables[table_name] = table
141
+
142
+ # Extract CREATE INDEX statements
143
+ self._parse_indexes(ddl_content)
144
+
145
+ def _parse_table_definition(self, table_name: str, definition: str) -> Table:
146
+ """Parse individual table definition."""
147
+ columns = []
148
+ primary_key = []
149
+ foreign_keys = []
150
+ unique_constraints = []
151
+ check_constraints = {}
152
+
153
+ # Split by commas, but handle nested parentheses
154
+ parts = self._split_table_parts(definition)
155
+
156
+ for part in parts:
157
+ part = part.strip()
158
+ if not part:
159
+ continue
160
+
161
+ if part.upper().startswith('PRIMARY KEY'):
162
+ primary_key = self._parse_primary_key(part)
163
+ elif part.upper().startswith('FOREIGN KEY'):
164
+ fk = self._parse_foreign_key(part)
165
+ if fk:
166
+ foreign_keys.append(fk)
167
+ elif part.upper().startswith('UNIQUE'):
168
+ unique = self._parse_unique_constraint(part)
169
+ if unique:
170
+ unique_constraints.append(unique)
171
+ elif part.upper().startswith('CHECK'):
172
+ check = self._parse_check_constraint(part)
173
+ if check:
174
+ check_constraints.update(check)
175
+ else:
176
+ # Column definition
177
+ column = self._parse_column_definition(part)
178
+ if column:
179
+ columns.append(column)
180
+ if column.primary_key:
181
+ primary_key.append(column.name)
182
+
183
+ return Table(
184
+ name=table_name,
185
+ columns=columns,
186
+ primary_key=primary_key,
187
+ foreign_keys=foreign_keys,
188
+ unique_constraints=unique_constraints,
189
+ check_constraints=check_constraints,
190
+ indexes=[]
191
+ )
192
+
193
+ def _split_table_parts(self, definition: str) -> List[str]:
194
+ """Split table definition by commas, respecting nested parentheses."""
195
+ parts = []
196
+ current_part = ""
197
+ paren_count = 0
198
+
199
+ for char in definition:
200
+ if char == '(':
201
+ paren_count += 1
202
+ elif char == ')':
203
+ paren_count -= 1
204
+ elif char == ',' and paren_count == 0:
205
+ parts.append(current_part.strip())
206
+ current_part = ""
207
+ continue
208
+
209
+ current_part += char
210
+
211
+ if current_part.strip():
212
+ parts.append(current_part.strip())
213
+
214
+ return parts
215
+
216
+ def _parse_column_definition(self, definition: str) -> Optional[Column]:
217
+ """Parse individual column definition."""
218
+ # Pattern for column definition
219
+ pattern = re.compile(
220
+ r'(\w+)\s+([A-Z]+(?:\(\d+(?:,\d+)?\))?)\s*(.*)',
221
+ re.IGNORECASE
222
+ )
223
+
224
+ match = pattern.match(definition.strip())
225
+ if not match:
226
+ return None
227
+
228
+ column_name = match.group(1).lower()
229
+ data_type = match.group(2).upper()
230
+ constraints = match.group(3).upper() if match.group(3) else ""
231
+
232
+ column = Column(
233
+ name=column_name,
234
+ data_type=data_type,
235
+ nullable='NOT NULL' not in constraints,
236
+ primary_key='PRIMARY KEY' in constraints,
237
+ unique='UNIQUE' in constraints
238
+ )
239
+
240
+ # Parse foreign key reference
241
+ fk_pattern = re.compile(r'REFERENCES\s+(\w+)\s*\(\s*(\w+)\s*\)', re.IGNORECASE)
242
+ fk_match = fk_pattern.search(constraints)
243
+ if fk_match:
244
+ column.foreign_key = f"{fk_match.group(1).lower()}.{fk_match.group(2).lower()}"
245
+
246
+ # Parse default value
247
+ default_pattern = re.compile(r'DEFAULT\s+([^,\s]+)', re.IGNORECASE)
248
+ default_match = default_pattern.search(constraints)
249
+ if default_match:
250
+ column.default_value = default_match.group(1)
251
+
252
+ return column
253
+
254
+ def _parse_primary_key(self, definition: str) -> List[str]:
255
+ """Parse PRIMARY KEY constraint."""
256
+ pattern = re.compile(r'PRIMARY\s+KEY\s*\(\s*(.*?)\s*\)', re.IGNORECASE)
257
+ match = pattern.search(definition)
258
+ if match:
259
+ columns = [col.strip().lower() for col in match.group(1).split(',')]
260
+ return columns
261
+ return []
262
+
263
+ def _parse_foreign_key(self, definition: str) -> Optional[Tuple[str, str]]:
264
+ """Parse FOREIGN KEY constraint."""
265
+ pattern = re.compile(
266
+ r'FOREIGN\s+KEY\s*\(\s*(\w+)\s*\)\s+REFERENCES\s+(\w+)\s*\(\s*(\w+)\s*\)',
267
+ re.IGNORECASE
268
+ )
269
+ match = pattern.search(definition)
270
+ if match:
271
+ column = match.group(1).lower()
272
+ ref_table = match.group(2).lower()
273
+ ref_column = match.group(3).lower()
274
+ return (column, f"{ref_table}.{ref_column}")
275
+ return None
276
+
277
+ def _parse_unique_constraint(self, definition: str) -> Optional[List[str]]:
278
+ """Parse UNIQUE constraint."""
279
+ pattern = re.compile(r'UNIQUE\s*\(\s*(.*?)\s*\)', re.IGNORECASE)
280
+ match = pattern.search(definition)
281
+ if match:
282
+ columns = [col.strip().lower() for col in match.group(1).split(',')]
283
+ return columns
284
+ return None
285
+
286
+ def _parse_check_constraint(self, definition: str) -> Optional[Dict[str, str]]:
287
+ """Parse CHECK constraint."""
288
+ pattern = re.compile(r'CHECK\s*\(\s*(.*?)\s*\)', re.IGNORECASE)
289
+ match = pattern.search(definition)
290
+ if match:
291
+ constraint_name = f"check_constraint_{len(self.tables)}"
292
+ return {constraint_name: match.group(1)}
293
+ return None
294
+
295
+ def _parse_indexes(self, ddl_content: str) -> None:
296
+ """Parse CREATE INDEX statements."""
297
+ index_pattern = re.compile(
298
+ r'CREATE\s+(?:(UNIQUE)\s+)?INDEX\s+(\w+)\s+ON\s+(\w+)\s*\(\s*(.*?)\s*\)',
299
+ re.IGNORECASE
300
+ )
301
+
302
+ for match in index_pattern.finditer(ddl_content):
303
+ unique = match.group(1) is not None
304
+ index_name = match.group(2).lower()
305
+ table_name = match.group(3).lower()
306
+ columns_str = match.group(4)
307
+
308
+ columns = [col.strip().lower() for col in columns_str.split(',')]
309
+
310
+ index = Index(
311
+ name=index_name,
312
+ table=table_name,
313
+ columns=columns,
314
+ unique=unique
315
+ )
316
+
317
+ if table_name in self.tables:
318
+ self.tables[table_name].indexes.append(index)
319
+
320
+ def parse_json_schema(self, json_content: str) -> None:
321
+ """Parse JSON schema definition."""
322
+ try:
323
+ schema = json.loads(json_content)
324
+
325
+ if 'tables' not in schema:
326
+ raise ValueError("JSON schema must contain 'tables' key")
327
+
328
+ for table_name, table_def in schema['tables'].items():
329
+ table = self._parse_json_table(table_name.lower(), table_def)
330
+ self.tables[table_name.lower()] = table
331
+
332
+ except json.JSONDecodeError as e:
333
+ raise ValueError(f"Invalid JSON: {e}")
334
+
335
+ def _parse_json_table(self, table_name: str, table_def: Dict[str, Any]) -> Table:
336
+ """Parse JSON table definition."""
337
+ columns = []
338
+ primary_key = table_def.get('primary_key', [])
339
+ foreign_keys = []
340
+ unique_constraints = table_def.get('unique_constraints', [])
341
+ check_constraints = table_def.get('check_constraints', {})
342
+
343
+ for col_name, col_def in table_def.get('columns', {}).items():
344
+ column = Column(
345
+ name=col_name.lower(),
346
+ data_type=col_def.get('type', 'VARCHAR(255)').upper(),
347
+ nullable=col_def.get('nullable', True),
348
+ primary_key=col_name.lower() in [pk.lower() for pk in primary_key],
349
+ unique=col_def.get('unique', False),
350
+ foreign_key=col_def.get('foreign_key'),
351
+ default_value=col_def.get('default')
352
+ )
353
+
354
+ columns.append(column)
355
+
356
+ if column.foreign_key:
357
+ foreign_keys.append((column.name, column.foreign_key))
358
+
359
+ return Table(
360
+ name=table_name,
361
+ columns=columns,
362
+ primary_key=[pk.lower() for pk in primary_key],
363
+ foreign_keys=foreign_keys,
364
+ unique_constraints=unique_constraints,
365
+ check_constraints=check_constraints,
366
+ indexes=[]
367
+ )
368
+
369
+ def analyze_normalization(self) -> None:
370
+ """Analyze normalization compliance."""
371
+ for table_name, table in self.tables.items():
372
+ self._check_first_normal_form(table)
373
+ self._check_second_normal_form(table)
374
+ self._check_third_normal_form(table)
375
+ self._check_bcnf(table)
376
+
377
+ def _check_first_normal_form(self, table: Table) -> None:
378
+ """Check First Normal Form compliance."""
379
+ # Check for atomic values (no arrays or delimited strings)
380
+ for column in table.columns:
381
+ if any(pattern in column.data_type.upper() for pattern in ['ARRAY', 'JSON', 'TEXT']):
382
+ if 'JSON' in column.data_type.upper():
383
+ # JSON columns can violate 1NF if storing arrays
384
+ self.normalization_issues.append(NormalizationIssue(
385
+ table=table.name,
386
+ issue_type="1NF_VIOLATION",
387
+ severity="WARNING",
388
+ description=f"Column '{column.name}' uses JSON type which may contain non-atomic values",
389
+ suggestion="Consider normalizing JSON arrays into separate tables",
390
+ columns_affected=[column.name]
391
+ ))
392
+
393
+ # Check for potential delimited values in VARCHAR/TEXT
394
+ if column.data_type.upper().startswith(('VARCHAR', 'CHAR', 'TEXT')):
395
+ if any(delimiter in column.name.lower() for delimiter in ['list', 'array', 'tags', 'items']):
396
+ self.normalization_issues.append(NormalizationIssue(
397
+ table=table.name,
398
+ issue_type="1NF_VIOLATION",
399
+ severity="HIGH",
400
+ description=f"Column '{column.name}' appears to store delimited values",
401
+ suggestion="Create separate table for individual values with foreign key relationship",
402
+ columns_affected=[column.name]
403
+ ))
404
+
405
+ def _check_second_normal_form(self, table: Table) -> None:
406
+ """Check Second Normal Form compliance."""
407
+ if len(table.primary_key) <= 1:
408
+ return # 2NF only applies to tables with composite primary keys
409
+
410
+ # Look for potential partial dependencies
411
+ non_key_columns = [col for col in table.columns if col.name not in table.primary_key]
412
+
413
+ for column in non_key_columns:
414
+ # Heuristic: columns that seem related to only part of the composite key
415
+ for pk_part in table.primary_key:
416
+ if pk_part in column.name or column.name.startswith(pk_part.split('_')[0]):
417
+ self.normalization_issues.append(NormalizationIssue(
418
+ table=table.name,
419
+ issue_type="2NF_VIOLATION",
420
+ severity="MEDIUM",
421
+ description=f"Column '{column.name}' may have partial dependency on '{pk_part}'",
422
+ suggestion=f"Consider moving '{column.name}' to a separate table related to '{pk_part}'",
423
+ columns_affected=[column.name, pk_part]
424
+ ))
425
+ break
426
+
427
+ def _check_third_normal_form(self, table: Table) -> None:
428
+ """Check Third Normal Form compliance."""
429
+ # Look for transitive dependencies
430
+ non_key_columns = [col for col in table.columns if col.name not in table.primary_key]
431
+
432
+ # Group columns by potential entities they describe
433
+ entity_groups = defaultdict(list)
434
+ for column in non_key_columns:
435
+ # Simple heuristic: group by prefix before underscore
436
+ prefix = column.name.split('_')[0]
437
+ if prefix != column.name: # Has underscore
438
+ entity_groups[prefix].append(column.name)
439
+
440
+ for entity, columns in entity_groups.items():
441
+ if len(columns) > 1 and entity != table.name.split('_')[0]:
442
+ # Potential entity that should be in its own table
443
+ id_column = f"{entity}_id"
444
+ if id_column in [col.name for col in table.columns]:
445
+ self.normalization_issues.append(NormalizationIssue(
446
+ table=table.name,
447
+ issue_type="3NF_VIOLATION",
448
+ severity="MEDIUM",
449
+ description=f"Columns {columns} may have transitive dependency through '{id_column}'",
450
+ suggestion=f"Consider creating separate '{entity}' table with these columns",
451
+ columns_affected=columns + [id_column]
452
+ ))
453
+
454
+ def _check_bcnf(self, table: Table) -> None:
455
+ """Check Boyce-Codd Normal Form compliance."""
456
+ # BCNF violations are complex to detect without functional dependencies
457
+ # Provide general guidance for composite keys
458
+ if len(table.primary_key) > 2:
459
+ self.normalization_issues.append(NormalizationIssue(
460
+ table=table.name,
461
+ issue_type="BCNF_WARNING",
462
+ severity="LOW",
463
+ description=f"Table has composite primary key with {len(table.primary_key)} columns",
464
+ suggestion="Review functional dependencies to ensure BCNF compliance",
465
+ columns_affected=table.primary_key
466
+ ))
467
+
468
+ def analyze_data_types(self) -> None:
469
+ """Analyze data type usage for antipatterns."""
470
+ for table_name, table in self.tables.items():
471
+ for column in table.columns:
472
+ self._check_varchar_255_antipattern(table.name, column)
473
+ self._check_inappropriate_types(table.name, column)
474
+ self._check_size_optimization(table.name, column)
475
+
476
+ def _check_varchar_255_antipattern(self, table_name: str, column: Column) -> None:
477
+ """Check for VARCHAR(255) antipattern."""
478
+ if self.varchar_255_pattern.match(column.data_type):
479
+ self.datatype_issues.append(DataTypeIssue(
480
+ table=table_name,
481
+ column=column.name,
482
+ current_type=column.data_type,
483
+ issue="VARCHAR(255) antipattern",
484
+ suggested_type="Appropriately sized VARCHAR or TEXT",
485
+ rationale="VARCHAR(255) is often used as default without considering actual data length requirements"
486
+ ))
487
+
488
+ def _check_inappropriate_types(self, table_name: str, column: Column) -> None:
489
+ """Check for inappropriate data types."""
490
+ # Date/time stored as string
491
+ if column.name.lower() in ['date', 'time', 'created', 'updated', 'modified', 'timestamp']:
492
+ if column.data_type.upper().startswith(('VARCHAR', 'CHAR', 'TEXT')):
493
+ self.datatype_issues.append(DataTypeIssue(
494
+ table=table_name,
495
+ column=column.name,
496
+ current_type=column.data_type,
497
+ issue="Date/time stored as string",
498
+ suggested_type="TIMESTAMP, DATE, or TIME",
499
+ rationale="Proper date/time types enable date arithmetic and indexing optimization"
500
+ ))
501
+
502
+ # Boolean stored as string/integer
503
+ if column.name.lower() in ['active', 'enabled', 'deleted', 'visible', 'published']:
504
+ if not column.data_type.upper().startswith('BOOL'):
505
+ self.datatype_issues.append(DataTypeIssue(
506
+ table=table_name,
507
+ column=column.name,
508
+ current_type=column.data_type,
509
+ issue="Boolean value stored as non-boolean type",
510
+ suggested_type="BOOLEAN",
511
+ rationale="Boolean type is more explicit and can be more storage efficient"
512
+ ))
513
+
514
+ # Numeric IDs as VARCHAR
515
+ if column.name.lower().endswith('_id') or column.name.lower() == 'id':
516
+ if column.data_type.upper().startswith(('VARCHAR', 'CHAR')):
517
+ self.datatype_issues.append(DataTypeIssue(
518
+ table=table_name,
519
+ column=column.name,
520
+ current_type=column.data_type,
521
+ issue="Numeric ID stored as string",
522
+ suggested_type="INTEGER, BIGINT, or UUID",
523
+ rationale="Numeric types are more efficient for ID columns and enable better indexing"
524
+ ))
525
+
526
+ def _check_size_optimization(self, table_name: str, column: Column) -> None:
527
+ """Check for size optimization opportunities."""
528
+ # Oversized integer types
529
+ if column.data_type.upper() == 'BIGINT':
530
+ if not any(keyword in column.name.lower() for keyword in ['timestamp', 'big', 'large', 'count']):
531
+ self.datatype_issues.append(DataTypeIssue(
532
+ table=table_name,
533
+ column=column.name,
534
+ current_type=column.data_type,
535
+ issue="Potentially oversized integer type",
536
+ suggested_type="INTEGER",
537
+ rationale="INTEGER is sufficient for most ID and count fields unless very large values are expected"
538
+ ))
539
+
540
+ def analyze_constraints(self) -> None:
541
+ """Analyze missing constraints."""
542
+ for table_name, table in self.tables.items():
543
+ self._check_missing_primary_key(table)
544
+ self._check_missing_foreign_key_constraints(table)
545
+ self._check_missing_not_null_constraints(table)
546
+ self._check_missing_unique_constraints(table)
547
+ self._check_missing_check_constraints(table)
548
+
549
+ def _check_missing_primary_key(self, table: Table) -> None:
550
+ """Check for missing primary key."""
551
+ if not table.primary_key:
552
+ self.constraint_issues.append(ConstraintIssue(
553
+ table=table.name,
554
+ issue_type="MISSING_PRIMARY_KEY",
555
+ severity="HIGH",
556
+ description="Table has no primary key defined",
557
+ suggestion="Add a primary key column (e.g., 'id' with auto-increment)",
558
+ columns_affected=[]
559
+ ))
560
+
561
+ def _check_missing_foreign_key_constraints(self, table: Table) -> None:
562
+ """Check for missing foreign key constraints."""
563
+ for column in table.columns:
564
+ if column.name.endswith('_id') and column.name != 'id':
565
+ # Potential foreign key column
566
+ if not column.foreign_key:
567
+ referenced_table = column.name[:-3] # Remove '_id' suffix
568
+ if referenced_table in self.tables or referenced_table + 's' in self.tables:
569
+ self.constraint_issues.append(ConstraintIssue(
570
+ table=table.name,
571
+ issue_type="MISSING_FOREIGN_KEY",
572
+ severity="MEDIUM",
573
+ description=f"Column '{column.name}' appears to be a foreign key but has no constraint",
574
+ suggestion=f"Add foreign key constraint referencing {referenced_table} table",
575
+ columns_affected=[column.name]
576
+ ))
577
+
578
+ def _check_missing_not_null_constraints(self, table: Table) -> None:
579
+ """Check for missing NOT NULL constraints."""
580
+ for column in table.columns:
581
+ if column.nullable and column.name in ['email', 'name', 'title', 'status']:
582
+ self.constraint_issues.append(ConstraintIssue(
583
+ table=table.name,
584
+ issue_type="MISSING_NOT_NULL",
585
+ severity="LOW",
586
+ description=f"Column '{column.name}' allows NULL but typically should not",
587
+ suggestion=f"Consider adding NOT NULL constraint to '{column.name}'",
588
+ columns_affected=[column.name]
589
+ ))
590
+
591
+ def _check_missing_unique_constraints(self, table: Table) -> None:
592
+ """Check for missing unique constraints."""
593
+ for column in table.columns:
594
+ if column.name in ['email', 'username', 'slug', 'code'] and not column.unique:
595
+ if column.name not in table.primary_key:
596
+ self.constraint_issues.append(ConstraintIssue(
597
+ table=table.name,
598
+ issue_type="MISSING_UNIQUE",
599
+ severity="MEDIUM",
600
+ description=f"Column '{column.name}' should likely have UNIQUE constraint",
601
+ suggestion=f"Add UNIQUE constraint to '{column.name}'",
602
+ columns_affected=[column.name]
603
+ ))
604
+
605
+ def _check_missing_check_constraints(self, table: Table) -> None:
606
+ """Check for missing check constraints."""
607
+ for column in table.columns:
608
+ # Email format validation
609
+ if column.name == 'email' and 'email' not in str(table.check_constraints):
610
+ self.constraint_issues.append(ConstraintIssue(
611
+ table=table.name,
612
+ issue_type="MISSING_CHECK_CONSTRAINT",
613
+ severity="LOW",
614
+ description=f"Email column lacks format validation",
615
+ suggestion="Add CHECK constraint for email format validation",
616
+ columns_affected=[column.name]
617
+ ))
618
+
619
+ # Positive values for counts, prices, etc.
620
+ if column.name.lower() in ['price', 'amount', 'count', 'quantity', 'age']:
621
+ if column.name not in str(table.check_constraints):
622
+ self.constraint_issues.append(ConstraintIssue(
623
+ table=table.name,
624
+ issue_type="MISSING_CHECK_CONSTRAINT",
625
+ severity="LOW",
626
+ description=f"Column '{column.name}' should validate positive values",
627
+ suggestion=f"Add CHECK constraint: {column.name} > 0",
628
+ columns_affected=[column.name]
629
+ ))
630
+
631
+ def analyze_naming_conventions(self) -> None:
632
+ """Analyze naming convention compliance."""
633
+ for table_name, table in self.tables.items():
634
+ self._check_table_naming(table_name)
635
+ for column in table.columns:
636
+ self._check_column_naming(table_name, column.name)
637
+
638
+ def _check_table_naming(self, table_name: str) -> None:
639
+ """Check table naming conventions."""
640
+ if not self.table_naming_pattern.match(table_name):
641
+ suggested_name = self._suggest_table_name(table_name)
642
+ self.naming_issues.append(NamingIssue(
643
+ table=table_name,
644
+ column=None,
645
+ issue="Invalid table naming convention",
646
+ current_name=table_name,
647
+ suggested_name=suggested_name
648
+ ))
649
+
650
+ # Check for plural naming
651
+ if not table_name.endswith('s') and table_name not in ['data', 'information']:
652
+ self.naming_issues.append(NamingIssue(
653
+ table=table_name,
654
+ column=None,
655
+ issue="Table name should be plural",
656
+ current_name=table_name,
657
+ suggested_name=table_name + 's'
658
+ ))
659
+
660
+ def _check_column_naming(self, table_name: str, column_name: str) -> None:
661
+ """Check column naming conventions."""
662
+ if not self.column_naming_pattern.match(column_name):
663
+ suggested_name = self._suggest_column_name(column_name)
664
+ self.naming_issues.append(NamingIssue(
665
+ table=table_name,
666
+ column=column_name,
667
+ issue="Invalid column naming convention",
668
+ current_name=column_name,
669
+ suggested_name=suggested_name
670
+ ))
671
+
672
+ def _suggest_table_name(self, table_name: str) -> str:
673
+ """Suggest corrected table name."""
674
+ # Convert to snake_case and make plural
675
+ name = re.sub(r'([A-Z])', r'_\1', table_name).lower().strip('_')
676
+ return name + 's' if not name.endswith('s') else name
677
+
678
+ def _suggest_column_name(self, column_name: str) -> str:
679
+ """Suggest corrected column name."""
680
+ # Convert to snake_case
681
+ return re.sub(r'([A-Z])', r'_\1', column_name).lower().strip('_')
682
+
683
+ def check_missing_indexes(self) -> List[Dict[str, Any]]:
684
+ """Check for missing indexes on foreign key columns."""
685
+ missing_indexes = []
686
+
687
+ for table_name, table in self.tables.items():
688
+ existing_indexed_columns = set()
689
+
690
+ # Collect existing indexed columns
691
+ for index in table.indexes:
692
+ existing_indexed_columns.update(index.columns)
693
+
694
+ # Primary key columns are automatically indexed
695
+ existing_indexed_columns.update(table.primary_key)
696
+
697
+ # Check foreign key columns
698
+ for column in table.columns:
699
+ if column.foreign_key and column.name not in existing_indexed_columns:
700
+ missing_indexes.append({
701
+ 'table': table_name,
702
+ 'column': column.name,
703
+ 'type': 'foreign_key',
704
+ 'suggestion': f"CREATE INDEX idx_{table_name}_{column.name} ON {table_name} ({column.name});"
705
+ })
706
+
707
+ return missing_indexes
708
+
709
+ def generate_mermaid_erd(self) -> str:
710
+ """Generate Mermaid ERD diagram."""
711
+ erd_lines = ["erDiagram"]
712
+
713
+ # Add table definitions
714
+ for table_name, table in self.tables.items():
715
+ erd_lines.append(f" {table_name.upper()} {{")
716
+
717
+ for column in table.columns:
718
+ data_type = column.data_type
719
+ constraints = []
720
+
721
+ if column.primary_key:
722
+ constraints.append("PK")
723
+ if column.foreign_key:
724
+ constraints.append("FK")
725
+ if not column.nullable:
726
+ constraints.append("NOT NULL")
727
+ if column.unique:
728
+ constraints.append("UNIQUE")
729
+
730
+ constraint_str = " ".join(constraints)
731
+ if constraint_str:
732
+ constraint_str = f" \"{constraint_str}\""
733
+
734
+ erd_lines.append(f" {data_type} {column.name}{constraint_str}")
735
+
736
+ erd_lines.append(" }")
737
+
738
+ # Add relationships
739
+ relationships = set()
740
+ for table_name, table in self.tables.items():
741
+ for column in table.columns:
742
+ if column.foreign_key:
743
+ ref_table = column.foreign_key.split('.')[0]
744
+ if ref_table in self.tables:
745
+ relationship = f" {ref_table.upper()} ||--o{{ {table_name.upper()} : has"
746
+ relationships.add(relationship)
747
+
748
+ erd_lines.extend(sorted(relationships))
749
+
750
+ return "\n".join(erd_lines)
751
+
752
+ def get_analysis_summary(self) -> Dict[str, Any]:
753
+ """Get comprehensive analysis summary."""
754
+ return {
755
+ "schema_overview": {
756
+ "total_tables": len(self.tables),
757
+ "total_columns": sum(len(table.columns) for table in self.tables.values()),
758
+ "tables_with_primary_keys": len([t for t in self.tables.values() if t.primary_key]),
759
+ "total_foreign_keys": sum(len(table.foreign_keys) for table in self.tables.values()),
760
+ "total_indexes": sum(len(table.indexes) for table in self.tables.values())
761
+ },
762
+ "normalization_analysis": {
763
+ "total_issues": len(self.normalization_issues),
764
+ "by_severity": {
765
+ "high": len([i for i in self.normalization_issues if i.severity == "HIGH"]),
766
+ "medium": len([i for i in self.normalization_issues if i.severity == "MEDIUM"]),
767
+ "low": len([i for i in self.normalization_issues if i.severity == "LOW"]),
768
+ "warning": len([i for i in self.normalization_issues if i.severity == "WARNING"])
769
+ },
770
+ "issues": [asdict(issue) for issue in self.normalization_issues]
771
+ },
772
+ "data_type_analysis": {
773
+ "total_issues": len(self.datatype_issues),
774
+ "issues": [asdict(issue) for issue in self.datatype_issues]
775
+ },
776
+ "constraint_analysis": {
777
+ "total_issues": len(self.constraint_issues),
778
+ "by_severity": {
779
+ "high": len([i for i in self.constraint_issues if i.severity == "HIGH"]),
780
+ "medium": len([i for i in self.constraint_issues if i.severity == "MEDIUM"]),
781
+ "low": len([i for i in self.constraint_issues if i.severity == "LOW"])
782
+ },
783
+ "issues": [asdict(issue) for issue in self.constraint_issues]
784
+ },
785
+ "naming_analysis": {
786
+ "total_issues": len(self.naming_issues),
787
+ "issues": [asdict(issue) for issue in self.naming_issues]
788
+ },
789
+ "missing_indexes": self.check_missing_indexes(),
790
+ "recommendations": self._generate_recommendations()
791
+ }
792
+
793
+ def _generate_recommendations(self) -> List[str]:
794
+ """Generate high-level recommendations."""
795
+ recommendations = []
796
+
797
+ # High severity issues
798
+ high_severity_issues = [
799
+ i for i in self.normalization_issues + self.constraint_issues
800
+ if i.severity == "HIGH"
801
+ ]
802
+
803
+ if high_severity_issues:
804
+ recommendations.append(f"Address {len(high_severity_issues)} high-severity issues immediately")
805
+
806
+ # Missing primary keys
807
+ tables_without_pk = [name for name, table in self.tables.items() if not table.primary_key]
808
+ if tables_without_pk:
809
+ recommendations.append(f"Add primary keys to tables: {', '.join(tables_without_pk)}")
810
+
811
+ # Data type improvements
812
+ varchar_255_issues = [i for i in self.datatype_issues if "VARCHAR(255)" in i.issue]
813
+ if varchar_255_issues:
814
+ recommendations.append(f"Review {len(varchar_255_issues)} VARCHAR(255) columns for right-sizing")
815
+
816
+ # Missing foreign keys
817
+ missing_fks = [i for i in self.constraint_issues if i.issue_type == "MISSING_FOREIGN_KEY"]
818
+ if missing_fks:
819
+ recommendations.append(f"Consider adding {len(missing_fks)} foreign key constraints for referential integrity")
820
+
821
+ # Normalization improvements
822
+ normalization_issues_count = len(self.normalization_issues)
823
+ if normalization_issues_count > 0:
824
+ recommendations.append(f"Review {normalization_issues_count} normalization issues for schema optimization")
825
+
826
+ return recommendations
827
+
828
+ def format_text_report(self, analysis: Dict[str, Any]) -> str:
829
+ """Format analysis as human-readable text report."""
830
+ lines = []
831
+ lines.append("DATABASE SCHEMA ANALYSIS REPORT")
832
+ lines.append("=" * 50)
833
+ lines.append("")
834
+
835
+ # Overview
836
+ overview = analysis["schema_overview"]
837
+ lines.append("SCHEMA OVERVIEW")
838
+ lines.append("-" * 15)
839
+ lines.append(f"Total Tables: {overview['total_tables']}")
840
+ lines.append(f"Total Columns: {overview['total_columns']}")
841
+ lines.append(f"Tables with Primary Keys: {overview['tables_with_primary_keys']}")
842
+ lines.append(f"Total Foreign Keys: {overview['total_foreign_keys']}")
843
+ lines.append(f"Total Indexes: {overview['total_indexes']}")
844
+ lines.append("")
845
+
846
+ # Recommendations
847
+ if analysis["recommendations"]:
848
+ lines.append("KEY RECOMMENDATIONS")
849
+ lines.append("-" * 18)
850
+ for i, rec in enumerate(analysis["recommendations"], 1):
851
+ lines.append(f"{i}. {rec}")
852
+ lines.append("")
853
+
854
+ # Normalization Issues
855
+ norm_analysis = analysis["normalization_analysis"]
856
+ if norm_analysis["total_issues"] > 0:
857
+ lines.append(f"NORMALIZATION ISSUES ({norm_analysis['total_issues']} total)")
858
+ lines.append("-" * 25)
859
+ severity_counts = norm_analysis["by_severity"]
860
+ lines.append(f"High: {severity_counts['high']}, Medium: {severity_counts['medium']}, "
861
+ f"Low: {severity_counts['low']}, Warning: {severity_counts['warning']}")
862
+ lines.append("")
863
+
864
+ for issue in norm_analysis["issues"][:5]: # Show first 5
865
+ lines.append(f"• {issue['table']}: {issue['description']}")
866
+ lines.append(f" Suggestion: {issue['suggestion']}")
867
+ lines.append("")
868
+
869
+ # Data Type Issues
870
+ dt_analysis = analysis["data_type_analysis"]
871
+ if dt_analysis["total_issues"] > 0:
872
+ lines.append(f"DATA TYPE ISSUES ({dt_analysis['total_issues']} total)")
873
+ lines.append("-" * 20)
874
+ for issue in dt_analysis["issues"][:5]: # Show first 5
875
+ lines.append(f"• {issue['table']}.{issue['column']}: {issue['issue']}")
876
+ lines.append(f" Current: {issue['current_type']} → Suggested: {issue['suggested_type']}")
877
+ lines.append(f" Rationale: {issue['rationale']}")
878
+ lines.append("")
879
+
880
+ # Constraint Issues
881
+ const_analysis = analysis["constraint_analysis"]
882
+ if const_analysis["total_issues"] > 0:
883
+ lines.append(f"CONSTRAINT ISSUES ({const_analysis['total_issues']} total)")
884
+ lines.append("-" * 20)
885
+ severity_counts = const_analysis["by_severity"]
886
+ lines.append(f"High: {severity_counts['high']}, Medium: {severity_counts['medium']}, "
887
+ f"Low: {severity_counts['low']}")
888
+ lines.append("")
889
+
890
+ for issue in const_analysis["issues"][:5]: # Show first 5
891
+ lines.append(f"• {issue['table']}: {issue['description']}")
892
+ lines.append(f" Suggestion: {issue['suggestion']}")
893
+ lines.append("")
894
+
895
+ # Missing Indexes
896
+ missing_idx = analysis["missing_indexes"]
897
+ if missing_idx:
898
+ lines.append(f"MISSING INDEXES ({len(missing_idx)} total)")
899
+ lines.append("-" * 17)
900
+ for idx in missing_idx[:5]: # Show first 5
901
+ lines.append(f"• {idx['table']}.{idx['column']} ({idx['type']})")
902
+ lines.append(f" SQL: {idx['suggestion']}")
903
+ lines.append("")
904
+
905
+ return "\n".join(lines)
906
+
907
+
908
+ def main():
909
+ parser = argparse.ArgumentParser(description="Analyze database schema for design issues and generate ERD")
910
+ parser.add_argument("--input", "-i", required=True, help="Input file (SQL DDL or JSON schema)")
911
+ parser.add_argument("--output", "-o", help="Output file (default: stdout)")
912
+ parser.add_argument("--output-format", "-f", choices=["json", "text"], default="text",
913
+ help="Output format")
914
+ parser.add_argument("--generate-erd", "-e", action="store_true", help="Include Mermaid ERD in output")
915
+ parser.add_argument("--erd-only", action="store_true", help="Output only the Mermaid ERD")
916
+
917
+ args = parser.parse_args()
918
+
919
+ try:
920
+ # Read input file
921
+ with open(args.input, 'r') as f:
922
+ content = f.read()
923
+
924
+ # Initialize analyzer
925
+ analyzer = SchemaAnalyzer()
926
+
927
+ # Parse input based on file extension
928
+ if args.input.lower().endswith('.json'):
929
+ analyzer.parse_json_schema(content)
930
+ else:
931
+ analyzer.parse_sql_ddl(content)
932
+
933
+ if not analyzer.tables:
934
+ print("Error: No tables found in input file", file=sys.stderr)
935
+ return 1
936
+
937
+ if args.erd_only:
938
+ # Output only ERD
939
+ erd = analyzer.generate_mermaid_erd()
940
+ if args.output:
941
+ with open(args.output, 'w') as f:
942
+ f.write(erd)
943
+ else:
944
+ print(erd)
945
+ return 0
946
+
947
+ # Perform analysis
948
+ analyzer.analyze_normalization()
949
+ analyzer.analyze_data_types()
950
+ analyzer.analyze_constraints()
951
+ analyzer.analyze_naming_conventions()
952
+
953
+ # Generate report
954
+ analysis = analyzer.get_analysis_summary()
955
+
956
+ if args.generate_erd:
957
+ analysis["mermaid_erd"] = analyzer.generate_mermaid_erd()
958
+
959
+ # Output results
960
+ if args.output_format == "json":
961
+ output = json.dumps(analysis, indent=2)
962
+ else:
963
+ output = analyzer.format_text_report(analysis)
964
+ if args.generate_erd:
965
+ output += "\n\nMERMAID ERD\n" + "=" * 11 + "\n"
966
+ output += analysis["mermaid_erd"]
967
+
968
+ if args.output:
969
+ with open(args.output, 'w') as f:
970
+ f.write(output)
971
+ else:
972
+ print(output)
973
+
974
+ return 0
975
+
976
+ except Exception as e:
977
+ print(f"Error: {e}", file=sys.stderr)
978
+ return 1
979
+
980
+
981
+ if __name__ == "__main__":
982
+ sys.exit(main())