@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.
- package/README.md +152 -129
- package/bin/alice-install.mjs +27 -35
- package/lib/hermes-agent.mjs +449 -0
- package/lib/hermes-installer.mjs +338 -0
- package/lib/installer.mjs +254 -19
- package/lib/skills.mjs +128 -4
- package/package.json +3 -3
- package/templates/skills/acculynx/SKILL.md +183 -0
- package/templates/skills/acculynx/references/analysis_template.py +116 -0
- package/templates/skills/acculynx/references/dashboard_page.tsx +641 -0
- package/templates/skills/claude-code/SKILL.md +2 -2
- package/templates/skills/coding-agent/SKILL.md +68 -0
- package/templates/skills/crawl4ai/SKILL.md +119 -0
- package/templates/skills/crawl4ai/scripts/crwl +3 -0
- package/templates/workspaces/_shared/AGENTS-hermes.md +54 -0
- package/templates/workspaces/_shared/AGENTS.md +25 -0
- package/templates/workspaces/_shared/SOUL-hermes.md +35 -0
- package/templates/workspaces/_shared/hermes-agent-skill.md +40 -0
- package/templates/workspaces/_shared/hermes-orchestrator-skill.md +150 -0
- package/templates/workspaces/_shared/hermes-specialist-skill.md +109 -0
- package/templates/workspaces/accuscope/AGENTS.md +38 -0
- package/templates/workspaces/accuscope/FEEDBACK.md +27 -0
- package/templates/workspaces/accuscope/HEARTBEAT.md +26 -0
- package/templates/workspaces/accuscope/IDENTITY.md +48 -0
- package/templates/workspaces/accuscope/LEARNINGS.md +46 -0
- package/templates/workspaces/accuscope/MEMORY.md +47 -0
- package/templates/workspaces/accuscope/PLAYBOOK.md +65 -0
- package/templates/workspaces/accuscope/SOUL.md +40 -0
- package/templates/workspaces/accuscope/TOOLS.md +63 -0
- package/templates/workspaces/accuscope/USER.md +39 -0
- package/templates/workspaces/aiden/AGENTS.md +52 -0
- package/templates/workspaces/aiden/FEEDBACK.md +12 -0
- package/templates/workspaces/aiden/HEARTBEAT.md +9 -0
- package/templates/workspaces/aiden/IDENTITY.md +6 -0
- package/templates/workspaces/aiden/LEARNINGS.md +6 -0
- package/templates/workspaces/aiden/MEMORY.md +22 -0
- package/templates/workspaces/aiden/PLAYBOOK.md +16 -0
- package/templates/workspaces/aiden/SOUL.md +1 -1
- package/templates/workspaces/aiden/USER.md +17 -0
- package/templates/workspaces/alex/AGENTS.md +52 -0
- package/templates/workspaces/alex/FEEDBACK.md +11 -0
- package/templates/workspaces/alex/HEARTBEAT.md +9 -0
- package/templates/workspaces/alex/IDENTITY.md +6 -0
- package/templates/workspaces/alex/LEARNINGS.md +5 -0
- package/templates/workspaces/alex/MEMORY.md +22 -0
- package/templates/workspaces/alex/PLAYBOOK.md +16 -0
- package/templates/workspaces/alex/SOUL.md +1 -1
- package/templates/workspaces/alex/USER.md +13 -0
- package/templates/workspaces/aria/AGENTS.md +18 -0
- package/templates/workspaces/aria/FEEDBACK.md +12 -0
- package/templates/workspaces/aria/HEARTBEAT.md +32 -0
- package/templates/workspaces/aria/IDENTITY.md +12 -0
- package/templates/workspaces/aria/LEARNINGS.md +31 -0
- package/templates/workspaces/aria/MEMORY.md +29 -0
- package/templates/workspaces/aria/PLAYBOOK.md +71 -0
- package/templates/workspaces/aria/SOUL.md +57 -0
- package/templates/workspaces/aria/TOOLS.md +47 -0
- package/templates/workspaces/aria/USER.md +18 -0
- package/templates/workspaces/audrey/AGENTS.md +59 -0
- package/templates/workspaces/audrey/FEEDBACK.md +11 -0
- package/templates/workspaces/audrey/HEARTBEAT.md +9 -0
- package/templates/workspaces/audrey/IDENTITY.md +6 -0
- package/templates/workspaces/audrey/LEARNINGS.md +5 -0
- package/templates/workspaces/audrey/MEMORY.md +22 -0
- package/templates/workspaces/audrey/PLAYBOOK.md +16 -0
- package/templates/workspaces/audrey/SOUL.md +1 -1
- package/templates/workspaces/audrey/TOOLS.md +15 -0
- package/templates/workspaces/audrey/USER.md +13 -0
- package/templates/workspaces/avery/AGENTS.md +52 -0
- package/templates/workspaces/avery/FEEDBACK.md +12 -0
- package/templates/workspaces/avery/HEARTBEAT.md +5 -0
- package/templates/workspaces/avery/IDENTITY.md +6 -0
- package/templates/workspaces/avery/LEARNINGS.md +6 -0
- package/templates/workspaces/avery/MEMORY.md +22 -0
- package/templates/workspaces/avery/PLAYBOOK.md +16 -0
- package/templates/workspaces/avery/SOUL.md +1 -1
- package/templates/workspaces/avery/USER.md +17 -0
- package/templates/workspaces/avery/skills/claude-code/SKILL.md +38 -0
- package/templates/workspaces/avery/skills/claude-code/claude_code +55 -0
- package/templates/workspaces/caleb/AGENTS.md +52 -0
- package/templates/workspaces/caleb/FEEDBACK.md +11 -0
- package/templates/workspaces/caleb/HEARTBEAT.md +9 -0
- package/templates/workspaces/caleb/IDENTITY.md +6 -0
- package/templates/workspaces/caleb/LEARNINGS.md +5 -0
- package/templates/workspaces/caleb/MEMORY.md +22 -0
- package/templates/workspaces/caleb/PLAYBOOK.md +16 -0
- package/templates/workspaces/caleb/SOUL.md +1 -1
- package/templates/workspaces/caleb/TOOLS.md +30 -0
- package/templates/workspaces/caleb/USER.md +13 -0
- package/templates/workspaces/clara/AGENTS.md +59 -0
- package/templates/workspaces/clara/FEEDBACK.md +12 -0
- package/templates/workspaces/clara/HEARTBEAT.md +5 -0
- package/templates/workspaces/clara/IDENTITY.md +6 -0
- package/templates/workspaces/clara/LEARNINGS.md +6 -0
- package/templates/workspaces/clara/MEMORY.md +22 -0
- package/templates/workspaces/clara/PLAYBOOK.md +16 -0
- package/templates/workspaces/clara/SOUL.md +1 -1
- package/templates/workspaces/clara/TOOLS.md +15 -0
- package/templates/workspaces/clara/USER.md +17 -0
- package/templates/workspaces/daphne/AGENTS.md +59 -0
- package/templates/workspaces/daphne/FEEDBACK.md +18 -0
- package/templates/workspaces/daphne/HEARTBEAT.md +5 -0
- package/templates/workspaces/daphne/IDENTITY.md +6 -0
- package/templates/workspaces/daphne/LEARNINGS.md +6 -0
- package/templates/workspaces/daphne/MEMORY.md +22 -0
- package/templates/workspaces/daphne/PLAYBOOK.md +48 -0
- package/templates/workspaces/daphne/SOUL.md +1 -1
- package/templates/workspaces/daphne/TOOLS.md +15 -0
- package/templates/workspaces/daphne/USER.md +17 -0
- package/templates/workspaces/darius/AGENTS.md +52 -0
- package/templates/workspaces/darius/FEEDBACK.md +12 -0
- package/templates/workspaces/darius/HEARTBEAT.md +5 -0
- package/templates/workspaces/darius/IDENTITY.md +6 -0
- package/templates/workspaces/darius/LEARNINGS.md +6 -0
- package/templates/workspaces/darius/MEMORY.md +22 -0
- package/templates/workspaces/darius/PLAYBOOK.md +16 -0
- package/templates/workspaces/darius/SOUL.md +1 -1
- package/templates/workspaces/darius/USER.md +17 -0
- package/templates/workspaces/darius/skills/claude-code/SKILL.md +38 -0
- package/templates/workspaces/darius/skills/claude-code/claude_code +55 -0
- package/templates/workspaces/devon/AGENTS.md +52 -0
- package/templates/workspaces/devon/FEEDBACK.md +11 -0
- package/templates/workspaces/devon/HEARTBEAT.md +5 -0
- package/templates/workspaces/devon/IDENTITY.md +6 -0
- package/templates/workspaces/devon/LEARNINGS.md +11 -0
- package/templates/workspaces/devon/MEMORY.md +22 -0
- package/templates/workspaces/devon/PLAYBOOK.md +16 -0
- package/templates/workspaces/devon/SOUL.md +1 -1
- package/templates/workspaces/devon/USER.md +13 -0
- package/templates/workspaces/devon/check_github.py +12 -0
- package/templates/workspaces/devon/check_mc_env.py +30 -0
- package/templates/workspaces/devon/check_sb.py +34 -0
- package/templates/workspaces/devon/check_vercel.py +12 -0
- package/templates/workspaces/devon/get_mc_files.py +17 -0
- package/templates/workspaces/devon/write_heartbeat.py +67 -0
- package/templates/workspaces/dylan/.env.example +33 -0
- package/templates/workspaces/dylan/00007_verify_licenses_table.sql +100 -0
- package/templates/workspaces/dylan/AGENTS.md +52 -0
- package/templates/workspaces/dylan/FEEDBACK.md +28 -0
- package/templates/workspaces/dylan/HEARTBEAT.md +5 -0
- package/templates/workspaces/dylan/IDENTITY.md +6 -0
- package/templates/workspaces/dylan/LEARNINGS.md +70 -0
- package/templates/workspaces/dylan/MEMORY.md +22 -0
- package/templates/workspaces/dylan/PLAYBOOK.md +16 -0
- package/templates/workspaces/dylan/SOUL.md +1 -1
- package/templates/workspaces/dylan/STRIPE_PIPELINE.md +185 -0
- package/templates/workspaces/dylan/USER.md +17 -0
- package/templates/workspaces/dylan/n8n-stripe-welcome-workflow.json +123 -0
- package/templates/workspaces/dylan/skills/claude-code/SKILL.md +38 -0
- package/templates/workspaces/dylan/skills/claude-code/claude_code +55 -0
- package/templates/workspaces/dylan/stripe-webhook-handler.py +433 -0
- package/templates/workspaces/dylan/test_mock_webhook.py +103 -0
- package/templates/workspaces/elena/AGENTS.md +59 -0
- package/templates/workspaces/elena/FEEDBACK.md +11 -0
- package/templates/workspaces/elena/HEARTBEAT.md +9 -0
- package/templates/workspaces/elena/IDENTITY.md +6 -0
- package/templates/workspaces/elena/LEARNINGS.md +5 -0
- package/templates/workspaces/elena/MEMORY.md +22 -0
- package/templates/workspaces/elena/PLAYBOOK.md +16 -0
- package/templates/workspaces/elena/SOUL.md +1 -1
- package/templates/workspaces/elena/TOOLS.md +15 -0
- package/templates/workspaces/elena/USER.md +13 -0
- package/templates/workspaces/eva/AGENTS.md +59 -0
- package/templates/workspaces/eva/FEEDBACK.md +11 -0
- package/templates/workspaces/eva/HEARTBEAT.md +9 -0
- package/templates/workspaces/eva/IDENTITY.md +6 -0
- package/templates/workspaces/eva/LEARNINGS.md +5 -0
- package/templates/workspaces/eva/MEMORY.md +22 -0
- package/templates/workspaces/eva/PLAYBOOK.md +16 -0
- package/templates/workspaces/eva/SOUL.md +1 -1
- package/templates/workspaces/eva/TOOLS.md +15 -0
- package/templates/workspaces/eva/USER.md +13 -0
- package/templates/workspaces/felix/AGENTS.md +52 -0
- package/templates/workspaces/felix/FEEDBACK.md +11 -0
- package/templates/workspaces/felix/HEARTBEAT.md +5 -0
- package/templates/workspaces/felix/IDENTITY.md +6 -0
- package/templates/workspaces/felix/LEARNINGS.md +17 -0
- package/templates/workspaces/felix/MEMORY.md +22 -0
- package/templates/workspaces/felix/PLAYBOOK.md +16 -0
- package/templates/workspaces/felix/SOUL.md +1 -1
- package/templates/workspaces/felix/USER.md +13 -0
- package/templates/workspaces/felix/fidelia-psychology.html +1594 -0
- package/templates/workspaces/felix/task.txt +164 -0
- package/templates/workspaces/hannah/AGENTS.md +59 -0
- package/templates/workspaces/hannah/FEEDBACK.md +12 -0
- package/templates/workspaces/hannah/HEARTBEAT.md +5 -0
- package/templates/workspaces/hannah/IDENTITY.md +6 -0
- package/templates/workspaces/hannah/LEARNINGS.md +6 -0
- package/templates/workspaces/hannah/MEMORY.md +22 -0
- package/templates/workspaces/hannah/PLAYBOOK.md +16 -0
- package/templates/workspaces/hannah/SOUL.md +1 -1
- package/templates/workspaces/hannah/TOOLS.md +15 -0
- package/templates/workspaces/hannah/USER.md +17 -0
- package/templates/workspaces/isaac/AGENTS.md +52 -0
- package/templates/workspaces/isaac/FEEDBACK.md +12 -0
- package/templates/workspaces/isaac/HEARTBEAT.md +9 -0
- package/templates/workspaces/isaac/IDENTITY.md +6 -0
- package/templates/workspaces/isaac/LEARNINGS.md +6 -0
- package/templates/workspaces/isaac/MEMORY.md +22 -0
- package/templates/workspaces/isaac/PLAYBOOK.md +16 -0
- package/templates/workspaces/isaac/SOUL.md +1 -1
- package/templates/workspaces/isaac/USER.md +17 -0
- package/templates/workspaces/isaac/skills/claude-code/SKILL.md +38 -0
- package/templates/workspaces/isaac/skills/claude-code/claude_code +55 -0
- package/templates/workspaces/logan/AGENTS.md +59 -0
- package/templates/workspaces/logan/FEEDBACK.md +11 -0
- package/templates/workspaces/logan/HEARTBEAT.md +9 -0
- package/templates/workspaces/logan/IDENTITY.md +6 -0
- package/templates/workspaces/logan/LEARNINGS.md +5 -0
- package/templates/workspaces/logan/MEMORY.md +22 -0
- package/templates/workspaces/logan/PLAYBOOK.md +16 -0
- package/templates/workspaces/logan/SOUL.md +1 -1
- package/templates/workspaces/logan/TOOLS.md +15 -0
- package/templates/workspaces/logan/USER.md +13 -0
- package/templates/workspaces/maxxipro/AGENTS.md +29 -0
- package/templates/workspaces/maxxipro/FEEDBACK.md +19 -0
- package/templates/workspaces/maxxipro/HEARTBEAT.md +22 -0
- package/templates/workspaces/maxxipro/IDENTITY.md +35 -0
- package/templates/workspaces/maxxipro/KNOWLEDGE.md +335 -0
- package/templates/workspaces/maxxipro/LEARNINGS.md +47 -0
- package/templates/workspaces/maxxipro/MEMORY.md +60 -0
- package/templates/workspaces/maxxipro/OUTREACH_TEMPLATES.md +143 -0
- package/templates/workspaces/maxxipro/PLAYBOOK.md +81 -0
- package/templates/workspaces/maxxipro/SOUL.md +146 -0
- package/templates/workspaces/maxxipro/TOOLS.md +81 -0
- package/templates/workspaces/maxxipro/USER.md +40 -0
- package/templates/workspaces/morgan/AGENTS.md +59 -0
- package/templates/workspaces/morgan/FEEDBACK.md +19 -0
- package/templates/workspaces/morgan/HEARTBEAT.md +5 -0
- package/templates/workspaces/morgan/IDENTITY.md +6 -0
- package/templates/workspaces/morgan/LEARNINGS.md +18 -0
- package/templates/workspaces/morgan/MEMORY.md +22 -0
- package/templates/workspaces/morgan/PLAYBOOK.md +16 -0
- package/templates/workspaces/morgan/SOUL.md +1 -1
- package/templates/workspaces/morgan/TOOLS.md +15 -0
- package/templates/workspaces/morgan/USER.md +13 -0
- package/templates/workspaces/nadia/AGENTS.md +59 -0
- package/templates/workspaces/nadia/FEEDBACK.md +12 -0
- package/templates/workspaces/nadia/HEARTBEAT.md +5 -0
- package/templates/workspaces/nadia/IDENTITY.md +6 -0
- package/templates/workspaces/nadia/LEARNINGS.md +6 -0
- package/templates/workspaces/nadia/MEMORY.md +22 -0
- package/templates/workspaces/nadia/PLAYBOOK.md +16 -0
- package/templates/workspaces/nadia/SOUL.md +1 -1
- package/templates/workspaces/nadia/TOOLS.md +15 -0
- package/templates/workspaces/nadia/USER.md +13 -0
- package/templates/workspaces/nate/AGENTS.md +24 -0
- package/templates/workspaces/nate/FEEDBACK.md +12 -0
- package/templates/workspaces/nate/HEARTBEAT.md +33 -0
- package/templates/workspaces/nate/IDENTITY.md +15 -0
- package/templates/workspaces/nate/LEARNINGS.md +33 -0
- package/templates/workspaces/nate/MEMORY.md +39 -0
- package/templates/workspaces/nate/PLAYBOOK.md +160 -0
- package/templates/workspaces/nate/SOUL.md +50 -0
- package/templates/workspaces/nate/TOOLS.md +111 -0
- package/templates/workspaces/nate/USER.md +32 -0
- package/templates/workspaces/olivia/.last-openclaw-version +1 -0
- package/templates/workspaces/olivia/.npmrc.tmp +0 -0
- package/templates/workspaces/olivia/AGENTS.md +77 -0
- package/templates/workspaces/olivia/ALPHA_CODING_BENCHMARK.txt +148 -0
- package/templates/workspaces/olivia/ALPHA_MODEL_GUIDE.md +393 -0
- package/templates/workspaces/olivia/FEEDBACK.md +13 -0
- package/templates/workspaces/olivia/HEADTOHEAD_BENCHMARK.txt +1289 -0
- package/templates/workspaces/olivia/HEARTBEAT.md +267 -0
- package/templates/workspaces/olivia/IDENTITY.md +6 -0
- package/templates/workspaces/olivia/LEARNINGS.md +708 -0
- package/templates/workspaces/olivia/MEMORY.md +202 -0
- package/templates/workspaces/olivia/MISSION_CONTROL_DESIGN_SPEC_v1.md +1143 -0
- package/templates/workspaces/olivia/MVP-COMPLETION-SUMMARY.md +175 -0
- package/templates/workspaces/olivia/NETWORK_IMPLEMENTATION_PLAN.md +1556 -0
- package/templates/workspaces/olivia/NEW_NODES_BENCHMARK.txt +947 -0
- package/templates/workspaces/olivia/PLAYBOOK.md +42 -0
- package/templates/workspaces/olivia/SELF-HEALING-COMPLETE.md +150 -0
- package/templates/workspaces/olivia/SOUL.md +8 -8
- package/templates/workspaces/olivia/TOOLS.md +15 -0
- package/templates/workspaces/olivia/USER.md +17 -0
- package/templates/workspaces/olivia/alicefleet-supabase-credentials.md +50 -0
- package/templates/workspaces/olivia/dzombo-copy-rewrite.md +115 -0
- package/templates/workspaces/olivia/dzombo-implementation-plan.md +1248 -0
- package/templates/workspaces/olivia/fidelia-psychology.html +1594 -0
- package/templates/workspaces/olivia/lead_debug.png +0 -0
- package/templates/workspaces/olivia/minimatch-10.2.4.tgz +0 -0
- package/templates/workspaces/olivia/operation-bllm-research.md +157 -0
- package/templates/workspaces/olivia/qa-audit-mission-control-v2.md +538 -0
- package/templates/workspaces/olivia/roofmaxx_logo.svg +1 -0
- package/templates/workspaces/olivia/roofmaxx_social.jpg +0 -0
- package/templates/workspaces/olivia/skills/1password/SKILL.md +53 -0
- package/templates/workspaces/olivia/skills/1password/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/README.md +57 -0
- package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/SKILL.md +534 -0
- package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/agent-security/SKILL.md +69 -0
- package/templates/workspaces/olivia/skills/agent-security/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/agentic-security-audit/SKILL.md +855 -0
- package/templates/workspaces/olivia/skills/agentic-security-audit/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-automation-consulting/SKILL.md +67 -0
- package/templates/workspaces/olivia/skills/ai-automation-consulting/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-automation-consulting/skill.json +12 -0
- package/templates/workspaces/olivia/skills/ai-presentation-maker/SKILL.md +1104 -0
- package/templates/workspaces/olivia/skills/ai-presentation-maker/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-productivity-audit/SKILL.md +181 -0
- package/templates/workspaces/olivia/skills/ai-productivity-audit/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-researcher/README.md +31 -0
- package/templates/workspaces/olivia/skills/ai-researcher/SKILL.md +59 -0
- package/templates/workspaces/olivia/skills/ai-researcher/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-seo-writer/README.md +19 -0
- package/templates/workspaces/olivia/skills/ai-seo-writer/SKILL.md +100 -0
- package/templates/workspaces/olivia/skills/ai-seo-writer/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/analytics-tracking-2/SKILL.md +309 -0
- package/templates/workspaces/olivia/skills/analytics-tracking-2/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/api-doc-writer/SKILL.md +232 -0
- package/templates/workspaces/olivia/skills/api-doc-writer/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/api-generator/SKILL.md +49 -0
- package/templates/workspaces/olivia/skills/api-generator/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/api-generator/tips.md +10 -0
- package/templates/workspaces/olivia/skills/apple-notes/SKILL.md +50 -0
- package/templates/workspaces/olivia/skills/apple-notes/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/apple-reminders/SKILL.md +67 -0
- package/templates/workspaces/olivia/skills/apple-reminders/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/automation-workflows/SKILL.md +267 -0
- package/templates/workspaces/olivia/skills/automation-workflows/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/autoresearch/SKILL.md +46 -0
- package/templates/workspaces/olivia/skills/autoresearch/aria_write.py +148 -0
- package/templates/workspaces/olivia/skills/autoresearch/autoresearch.py +75 -0
- package/templates/workspaces/olivia/skills/azure-devops/SKILL.md +115 -0
- package/templates/workspaces/olivia/skills/azure-devops/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/blogwatcher/SKILL.md +46 -0
- package/templates/workspaces/olivia/skills/blogwatcher/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/blucli/SKILL.md +27 -0
- package/templates/workspaces/olivia/skills/blucli/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/check-analytics/SKILL.md +92 -0
- package/templates/workspaces/olivia/skills/check-analytics/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/cloud-architect/SKILL.md +89 -0
- package/templates/workspaces/olivia/skills/cloud-architect/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/cloud-infra-automation/SKILL.md +50 -0
- package/templates/workspaces/olivia/skills/cloud-infra-automation/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/cloud-storage/SKILL.md +61 -0
- package/templates/workspaces/olivia/skills/cloud-storage/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/cloud-storage/auth.md +97 -0
- package/templates/workspaces/olivia/skills/cloud-storage/costs.md +88 -0
- package/templates/workspaces/olivia/skills/cloud-storage/providers.md +55 -0
- package/templates/workspaces/olivia/skills/copywriting-pro/SKILL.md +107 -0
- package/templates/workspaces/olivia/skills/copywriting-pro/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/data-analyst-pro/SKILL.md +21 -0
- package/templates/workspaces/olivia/skills/data-analyst-pro/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/database-designer/README.md +388 -0
- package/templates/workspaces/olivia/skills/database-designer/SKILL.md +66 -0
- package/templates/workspaces/olivia/skills/database-designer/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/database-designer/index_optimizer.py +926 -0
- package/templates/workspaces/olivia/skills/database-designer/migration_generator.py +1199 -0
- package/templates/workspaces/olivia/skills/database-designer/schema_analyzer.py +982 -0
- package/templates/workspaces/olivia/skills/deploy-agent/SKILL.md +255 -0
- package/templates/workspaces/olivia/skills/deploy-agent/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/devops-automation-pack/SKILL.md +72 -0
- package/templates/workspaces/olivia/skills/devops-automation-pack/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/devops-automation-pack/deploy.sh +0 -0
- package/templates/workspaces/olivia/skills/financial-analysis-agent/SKILL.md +489 -0
- package/templates/workspaces/olivia/skills/financial-analysis-agent/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/README.md +72 -0
- package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/SKILL.md +226 -0
- package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/gifgrep/SKILL.md +47 -0
- package/templates/workspaces/olivia/skills/gifgrep/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/github/SKILL.md +47 -0
- package/templates/workspaces/olivia/skills/github/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/gog/SKILL.md +36 -0
- package/templates/workspaces/olivia/skills/gog/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/growth-strategy-hub/SKILL.md +135 -0
- package/templates/workspaces/olivia/skills/growth-strategy-hub/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/growth-strategy-hub/metadata.json +4 -0
- package/templates/workspaces/olivia/skills/hetzner-cloud/SKILL.md +130 -0
- package/templates/workspaces/olivia/skills/hetzner-cloud/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/himalaya/SKILL.md +217 -0
- package/templates/workspaces/olivia/skills/himalaya/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/hotel-recommendation/SKILL.md +117 -0
- package/templates/workspaces/olivia/skills/hotel-recommendation/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/hr-policy-generator/SKILL.md +54 -0
- package/templates/workspaces/olivia/skills/hr-policy-generator/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/human-writing/SKILL.md +41 -0
- package/templates/workspaces/olivia/skills/human-writing/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/imsg/SKILL.md +25 -0
- package/templates/workspaces/olivia/skills/imsg/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/in-depth-research/SKILL.md +124 -0
- package/templates/workspaces/olivia/skills/in-depth-research/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/in-depth-research/methodology.md +75 -0
- package/templates/workspaces/olivia/skills/in-depth-research/output-formats.md +168 -0
- package/templates/workspaces/olivia/skills/in-depth-research/sources.md +80 -0
- package/templates/workspaces/olivia/skills/javascript-skills/README.md +71 -0
- package/templates/workspaces/olivia/skills/javascript-skills/SKILL.md +746 -0
- package/templates/workspaces/olivia/skills/javascript-skills/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/leadership-strategy-playbook/SKILL.md +147 -0
- package/templates/workspaces/olivia/skills/leadership-strategy-playbook/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/market-research-agent/README.md +29 -0
- package/templates/workspaces/olivia/skills/market-research-agent/SKILL.md +52 -0
- package/templates/workspaces/olivia/skills/market-research-agent/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/marketing-analytics/SKILL.md +74 -0
- package/templates/workspaces/olivia/skills/marketing-analytics/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/marketing-master-io/SKILL.md +125 -0
- package/templates/workspaces/olivia/skills/marketing-master-io/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/marketing-strategy-pmm/SKILL.md +398 -0
- package/templates/workspaces/olivia/skills/marketing-strategy-pmm/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/meta-ads-analytics/SKILL.md +53 -0
- package/templates/workspaces/olivia/skills/meta-ads-analytics/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/obsidian/SKILL.md +55 -0
- package/templates/workspaces/olivia/skills/obsidian/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/openclaw-accounting/SKILL.md +125 -0
- package/templates/workspaces/olivia/skills/openclaw-accounting/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANGELOG.md +35 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANNELLOG.md +73 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/README.md +161 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/SKILL.md +130 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/config.json +36 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/metadata.json +19 -0
- package/templates/workspaces/olivia/skills/openhue/SKILL.md +30 -0
- package/templates/workspaces/olivia/skills/openhue/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/orgx-operations-agent/SKILL.md +41 -0
- package/templates/workspaces/olivia/skills/orgx-operations-agent/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/outreach/SKILL.md +84 -0
- package/templates/workspaces/olivia/skills/outreach/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/outreach/by-type.md +166 -0
- package/templates/workspaces/olivia/skills/outreach/templates.md +154 -0
- package/templates/workspaces/olivia/skills/outreach/tracking.md +145 -0
- package/templates/workspaces/olivia/skills/persona-hr-coordinator/SKILL.md +38 -0
- package/templates/workspaces/olivia/skills/persona-hr-coordinator/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/personal-productivity/SKILL.md +161 -0
- package/templates/workspaces/olivia/skills/personal-productivity/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/personal-productivity/index.js +363 -0
- package/templates/workspaces/olivia/skills/personal-productivity/package.json +15 -0
- package/templates/workspaces/olivia/skills/personal-travel/README.md +34 -0
- package/templates/workspaces/olivia/skills/personal-travel/SKILL.md +46 -0
- package/templates/workspaces/olivia/skills/personal-travel/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/presentation-html-generator-skill/SKILL.md +185 -0
- package/templates/workspaces/olivia/skills/presentation-html-generator-skill/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/product-manager/SKILL.md +77 -0
- package/templates/workspaces/olivia/skills/product-manager/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/quant-strategy/SKILL.md +28 -0
- package/templates/workspaces/olivia/skills/quant-strategy/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/sales-pipeline-tracker/README.md +29 -0
- package/templates/workspaces/olivia/skills/sales-pipeline-tracker/SKILL.md +45 -0
- package/templates/workspaces/olivia/skills/sales-pipeline-tracker/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/security-auditor/SKILL.md +399 -0
- package/templates/workspaces/olivia/skills/security-auditor/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/security-hardening/SKILL.md +296 -0
- package/templates/workspaces/olivia/skills/security-hardening/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/security-scanner/SKILL.md +67 -0
- package/templates/workspaces/olivia/skills/security-scanner/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/seo-optimization/SKILL.md +31 -0
- package/templates/workspaces/olivia/skills/seo-optimization/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/service-booking/SKILL.md +193 -0
- package/templates/workspaces/olivia/skills/service-booking/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/sme-hr-automation/SKILL.md +131 -0
- package/templates/workspaces/olivia/skills/sme-hr-automation/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/social-media-scheduler/README.md +29 -0
- package/templates/workspaces/olivia/skills/social-media-scheduler/SKILL.md +49 -0
- package/templates/workspaces/olivia/skills/social-media-scheduler/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/sonoscli/SKILL.md +26 -0
- package/templates/workspaces/olivia/skills/sonoscli/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/strategy-advisor/SKILL.md +33 -0
- package/templates/workspaces/olivia/skills/strategy-advisor/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/summarize/SKILL.md +49 -0
- package/templates/workspaces/olivia/skills/summarize/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/things-mac/SKILL.md +61 -0
- package/templates/workspaces/olivia/skills/things-mac/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/travel-itinerary-planner/SKILL.md +121 -0
- package/templates/workspaces/olivia/skills/travel-itinerary-planner/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/travel-manager/SKILL.md +36 -0
- package/templates/workspaces/olivia/skills/travel-manager/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/travel-planning/SKILL.md +238 -0
- package/templates/workspaces/olivia/skills/travel-planning/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/travel-planning/booking-guide.md +91 -0
- package/templates/workspaces/olivia/skills/travel-planning/memory-template.md +111 -0
- package/templates/workspaces/olivia/skills/travel-planning/multi-city.md +131 -0
- package/templates/workspaces/olivia/skills/travel-planning/packing-templates.md +155 -0
- package/templates/workspaces/olivia/skills/travel-planning/setup.md +66 -0
- package/templates/workspaces/olivia/skills/update-it-all/SKILL.md +143 -0
- package/templates/workspaces/olivia/skills/update-it-all/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/voice/SKILL.md +62 -0
- package/templates/workspaces/olivia/skills/weather/SKILL.md +49 -0
- package/templates/workspaces/olivia/skills/weather/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/web-researcher/SKILL.md +21 -0
- package/templates/workspaces/olivia/skills/web-researcher/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/website-seo/SKILL.md +284 -0
- package/templates/workspaces/olivia/skills/website-seo/_meta.json +6 -0
- package/templates/workspaces/olivia/stripe-welcome-n8n.json +103 -0
- package/templates/workspaces/olivia/test2.wav.wav +0 -0
- package/templates/workspaces/olivia/test_speech.json +1 -0
- package/templates/workspaces/olivia/test_speech.srt +0 -0
- package/templates/workspaces/olivia/test_speech.tsv +1 -0
- package/templates/workspaces/olivia/test_speech.txt +0 -0
- package/templates/workspaces/olivia/test_speech.vtt +2 -0
- package/templates/workspaces/owen/AGENTS.md +59 -0
- package/templates/workspaces/owen/FEEDBACK.md +12 -0
- package/templates/workspaces/owen/HEARTBEAT.md +5 -0
- package/templates/workspaces/owen/IDENTITY.md +6 -0
- package/templates/workspaces/owen/LEARNINGS.md +46 -0
- package/templates/workspaces/owen/MEMORY.md +22 -0
- package/templates/workspaces/owen/PLAYBOOK.md +16 -0
- package/templates/workspaces/owen/SOUL.md +1 -1
- package/templates/workspaces/owen/TOOLS.md +15 -0
- package/templates/workspaces/owen/USER.md +17 -0
- package/templates/workspaces/parker/AGENTS.md +59 -0
- package/templates/workspaces/parker/FEEDBACK.md +11 -0
- package/templates/workspaces/parker/HEARTBEAT.md +5 -0
- package/templates/workspaces/parker/IDENTITY.md +6 -0
- package/templates/workspaces/parker/LEARNINGS.md +17 -0
- package/templates/workspaces/parker/MEMORY.md +22 -0
- package/templates/workspaces/parker/PLAYBOOK.md +16 -0
- package/templates/workspaces/parker/SOUL.md +1 -1
- package/templates/workspaces/parker/TOOLS.md +15 -0
- package/templates/workspaces/parker/USER.md +13 -0
- package/templates/workspaces/quinn/AGENTS.md +52 -0
- package/templates/workspaces/quinn/FEEDBACK.md +11 -0
- package/templates/workspaces/quinn/HEARTBEAT.md +5 -0
- package/templates/workspaces/quinn/IDENTITY.md +6 -0
- package/templates/workspaces/quinn/LEARNINGS.md +35 -0
- package/templates/workspaces/quinn/MEMORY.md +22 -0
- package/templates/workspaces/quinn/PLAYBOOK.md +16 -0
- package/templates/workspaces/quinn/SOUL.md +1 -1
- package/templates/workspaces/quinn/USER.md +17 -0
- package/templates/workspaces/quinn/alice-login-page.png +0 -0
- package/templates/workspaces/rowan/AGENTS.md +59 -0
- package/templates/workspaces/rowan/FEEDBACK.md +12 -0
- package/templates/workspaces/rowan/HEARTBEAT.md +5 -0
- package/templates/workspaces/rowan/IDENTITY.md +6 -0
- package/templates/workspaces/rowan/LEARNINGS.md +12 -0
- package/templates/workspaces/rowan/MEMORY.md +22 -0
- package/templates/workspaces/rowan/PLAYBOOK.md +16 -0
- package/templates/workspaces/rowan/SOUL.md +1 -1
- package/templates/workspaces/rowan/USER.md +17 -0
- package/templates/workspaces/selena/AGENTS.md +59 -0
- package/templates/workspaces/selena/FEEDBACK.md +12 -0
- package/templates/workspaces/selena/HEARTBEAT.md +5 -0
- package/templates/workspaces/selena/IDENTITY.md +6 -0
- package/templates/workspaces/selena/LEARNINGS.md +24 -0
- package/templates/workspaces/selena/MEMORY.md +22 -0
- package/templates/workspaces/selena/PLAYBOOK.md +16 -0
- package/templates/workspaces/selena/SOUL.md +1 -1
- package/templates/workspaces/selena/USER.md +17 -0
- package/templates/workspaces/selena/kids-ai-security-compliance-plan.md +791 -0
- package/templates/workspaces/selena/kidspark-coppa-compliance-audit.md +866 -0
- package/templates/workspaces/sloane/AGENTS.md +59 -0
- package/templates/workspaces/sloane/FEEDBACK.md +12 -0
- package/templates/workspaces/sloane/HEARTBEAT.md +9 -0
- package/templates/workspaces/sloane/IDENTITY.md +6 -0
- package/templates/workspaces/sloane/LEARNINGS.md +6 -0
- package/templates/workspaces/sloane/MEMORY.md +22 -0
- package/templates/workspaces/sloane/PLAYBOOK.md +16 -0
- package/templates/workspaces/sloane/SOUL.md +1 -1
- package/templates/workspaces/sloane/TOOLS.md +15 -0
- package/templates/workspaces/sloane/USER.md +13 -0
- package/templates/workspaces/smoketestagent/AGENTS.md +52 -0
- package/templates/workspaces/smoketestagent/FEEDBACK.md +3 -0
- package/templates/workspaces/smoketestagent/HEARTBEAT.md +14 -0
- package/templates/workspaces/smoketestagent/IDENTITY.md +6 -0
- package/templates/workspaces/smoketestagent/LEARNINGS.md +3 -0
- package/templates/workspaces/smoketestagent/MEMORY.md +24 -0
- package/templates/workspaces/smoketestagent/PLAYBOOK.md +7 -0
- package/templates/workspaces/smoketestagent/SOUL.md +32 -0
- package/templates/workspaces/smoketestagent/TOOLS.md +13 -0
- package/templates/workspaces/smoketestagent/USER.md +5 -0
- package/templates/workspaces/sophie/AGENTS.md +59 -0
- package/templates/workspaces/sophie/FEEDBACK.md +12 -0
- package/templates/workspaces/sophie/HEARTBEAT.md +9 -0
- package/templates/workspaces/sophie/IDENTITY.md +6 -0
- package/templates/workspaces/sophie/LEARNINGS.md +6 -0
- package/templates/workspaces/sophie/MEMORY.md +22 -0
- package/templates/workspaces/sophie/PLAYBOOK.md +16 -0
- package/templates/workspaces/sophie/SOUL.md +1 -1
- package/templates/workspaces/sophie/TOOLS.md +15 -0
- package/templates/workspaces/sophie/USER.md +17 -0
- package/templates/workspaces/tommy/AGENTS.md +59 -0
- package/templates/workspaces/tommy/FEEDBACK.md +12 -0
- package/templates/workspaces/tommy/HEARTBEAT.md +9 -0
- package/templates/workspaces/tommy/IDENTITY.md +6 -0
- package/templates/workspaces/tommy/LEARNINGS.md +6 -0
- package/templates/workspaces/tommy/MEMORY.md +22 -0
- package/templates/workspaces/tommy/PLAYBOOK.md +16 -0
- package/templates/workspaces/tommy/SOUL.md +1 -1
- package/templates/workspaces/tommy/TOOLS.md +15 -0
- package/templates/workspaces/tommy/USER.md +17 -0
- package/templates/workspaces/uma/AGENTS.md +59 -0
- package/templates/workspaces/uma/FEEDBACK.md +11 -0
- package/templates/workspaces/uma/HEARTBEAT.md +5 -0
- package/templates/workspaces/uma/IDENTITY.md +6 -0
- package/templates/workspaces/uma/LEARNINGS.md +11 -0
- package/templates/workspaces/uma/MEMORY.md +22 -0
- package/templates/workspaces/uma/PLAYBOOK.md +16 -0
- package/templates/workspaces/uma/SOUL.md +1 -1
- package/templates/workspaces/uma/TOOLS.md +15 -0
- 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())
|