claude-flow-novice 2.15.3 → 2.15.4
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/.claude/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
- package/.claude/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
- package/.claude/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
- package/.claude/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
- package/.claude/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
- package/.claude/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
- package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
- package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
- package/.claude/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
- package/.claude/commands/cfn-loop-cli.md +16 -2
- package/.claude/commands/switch-api.md +31 -10
- package/.claude/hooks/cfn-lint-sql-injection.sh +61 -0
- package/.claude/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
- package/.claude/hooks/cfn-pre-edit-security-warning.sh +40 -0
- package/.claude/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
- package/.claude/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
- package/.claude/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
- package/.claude/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
- package/.claude/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
- package/.claude/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
- package/.claude/skills/cfn-loop-orchestration/orchestrate.sh +184 -23
- package/.claude/skills/cfn-loop-orchestration/security_utils.sh +24 -0
- package/.claude/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
- package/.claude/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
- package/.claude/skills/cfn-redis-coordination/agent-log.sh +4 -0
- package/.claude/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
- package/.claude/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
- package/.claude/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
- package/.claude/skills/cfn-redis-coordination/get-context.sh +33 -0
- package/.claude/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
- package/.claude/skills/cfn-redis-coordination/invoke-waiting-mode.sh +3 -0
- package/.claude/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
- package/.claude/skills/cfn-redis-coordination/redis-functions.sh +33 -0
- package/.claude/skills/cfn-redis-coordination/report-completion.sh +24 -31
- package/.claude/skills/cfn-redis-coordination/store-context.sh +4 -0
- package/.claude/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
- package/.claude/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
- package/.claude/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
- package/.claude/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
- package/.claude/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
- package/README.md +116 -475
- package/claude-assets/agents/cfn-dev-team/README.md +103 -0
- package/claude-assets/agents/cfn-dev-team/architecture/goal-planner.md +1 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +77 -15
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +355 -6
- package/claude-assets/agents/cfn-dev-team/coordinators/consensus-builder.md +82 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +82 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/multi-sprint-coordinator.md +77 -15
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +99 -12
- package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +1 -1
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +97 -0
- package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +20 -1
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +97 -0
- package/claude-assets/agents/cfn-dev-team/developers/backend-developer.md +110 -13
- package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +106 -15
- package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +115 -11
- package/claude-assets/agents/cfn-dev-team/developers/frontend/mobile-dev.md +94 -7
- package/claude-assets/agents/cfn-dev-team/developers/frontend/react-frontend-engineer.md +87 -9
- package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +85 -7
- package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +160 -28
- package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +101 -19
- package/claude-assets/agents/cfn-dev-team/developers/rust-developer.md +108 -14
- package/claude-assets/agents/cfn-dev-team/reviewers/{reviewer.md → code-reviewer.md} +95 -8
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/code-quality-validator.md +107 -7
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/perf-analyzer.md +98 -7
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/performance-benchmarker.md +95 -7
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +136 -9
- package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +108 -1
- package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +107 -13
- package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +737 -0
- package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +828 -0
- package/claude-assets/agents/cfn-dev-team/testers/interaction-tester.md +106 -7
- package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +77 -0
- package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +684 -0
- package/claude-assets/agents/cfn-dev-team/testers/playwright-tester.md +110 -1
- package/claude-assets/agents/cfn-dev-team/testers/tester.md +94 -7
- package/claude-assets/agents/cfn-dev-team/utility/code-booster.md +1 -3
- package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +87 -13
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +103 -7
- package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -3
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +94 -7
- package/claude-assets/agents/docker-coordinators/cfn-docker-v3-coordinator.md +46 -0
- package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
- package/claude-assets/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
- package/claude-assets/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
- package/claude-assets/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
- package/claude-assets/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
- package/claude-assets/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
- package/claude-assets/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
- package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
- package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
- package/claude-assets/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
- package/claude-assets/commands/cfn-loop-cli.md +16 -2
- package/claude-assets/commands/switch-api.md +31 -10
- package/claude-assets/hooks/cfn-lint-sql-injection.sh +61 -0
- package/claude-assets/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
- package/claude-assets/hooks/cfn-pre-edit-security-warning.sh +40 -0
- package/claude-assets/hooks/detect-hardcoded-credentials.sh +212 -0
- package/claude-assets/skills/SKILL_TEMPLATE.md +774 -0
- package/claude-assets/skills/agent-lifecycle/execute-lifecycle-hook.sh +84 -113
- package/claude-assets/skills/agent-lifecycle/simple-audit.sh +33 -6
- package/claude-assets/skills/agent-template-generator/SKILL.md +440 -0
- package/claude-assets/skills/agent-template-generator/generate-agent.sh +405 -0
- package/claude-assets/skills/agent-validation-linter/SKILL.md +589 -0
- package/claude-assets/skills/agent-validation-linter/lint-agents.sh +271 -0
- package/claude-assets/skills/bootstrap/bash-fundamentals.md +786 -0
- package/claude-assets/skills/bootstrap/database-connection.md +464 -0
- package/claude-assets/skills/bootstrap/error-handling.md +580 -0
- package/claude-assets/skills/bootstrap/file-operations.md +699 -0
- package/claude-assets/skills/bootstrap/skill-loader.md +616 -0
- package/claude-assets/skills/bootstrap/sqlite-params.sh +287 -0
- package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
- package/claude-assets/skills/cfn-automatic-memory-persistence/test-memory-persistence.sh +17 -16
- package/claude-assets/skills/cfn-deployment/SKILL.md +293 -0
- package/claude-assets/skills/cfn-deployment/execute.sh +21 -0
- package/claude-assets/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
- package/claude-assets/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
- package/claude-assets/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
- package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +38 -0
- package/claude-assets/skills/cfn-error-batching-strategy/lib/core-functions.sh +47 -47
- package/claude-assets/skills/cfn-file-operations/SKILL.md +290 -0
- package/claude-assets/skills/cfn-file-operations/execute.sh +129 -0
- package/claude-assets/skills/cfn-file-operations/lib/atomic-write.sh +294 -0
- package/claude-assets/skills/cfn-file-operations/lib/lock.sh +361 -0
- package/claude-assets/skills/cfn-file-operations/test.sh +369 -0
- package/claude-assets/skills/cfn-log-operations/SKILL.md +308 -0
- package/claude-assets/skills/cfn-log-operations/execute.sh +420 -0
- package/claude-assets/skills/cfn-log-operations/lib/rotate.sh +406 -0
- package/claude-assets/skills/cfn-log-operations/lib/search.sh +448 -0
- package/claude-assets/skills/cfn-log-operations/test.sh +394 -0
- package/claude-assets/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
- package/claude-assets/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh +184 -23
- package/claude-assets/skills/cfn-loop-orchestration/security_utils.sh +24 -0
- package/claude-assets/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
- package/claude-assets/skills/cfn-parameterized-queries/SKILL.md +339 -0
- package/claude-assets/skills/cfn-playbook/query-playbook.sh +19 -15
- package/claude-assets/skills/cfn-playbook/update-playbook.sh +25 -14
- package/claude-assets/skills/cfn-process-instrumentation/instrument-process.sh +44 -0
- package/claude-assets/skills/cfn-promotion/SKILL.md +305 -0
- package/claude-assets/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
- package/claude-assets/skills/cfn-redis-coordination/agent-log.sh +4 -0
- package/claude-assets/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
- package/claude-assets/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
- package/claude-assets/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
- package/claude-assets/skills/cfn-redis-coordination/get-context.sh +33 -0
- package/claude-assets/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
- package/claude-assets/skills/cfn-redis-coordination/invoke-waiting-mode.sh +3 -0
- package/claude-assets/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
- package/claude-assets/skills/cfn-redis-coordination/redis-functions.sh +33 -0
- package/claude-assets/skills/cfn-redis-coordination/report-completion.sh +24 -31
- package/claude-assets/skills/cfn-redis-coordination/store-context.sh +4 -0
- package/claude-assets/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
- package/claude-assets/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
- package/claude-assets/skills/cfn-skill-loader/SKILL.md +466 -0
- package/claude-assets/skills/cfn-skill-loader/execute.sh +344 -0
- package/claude-assets/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
- package/claude-assets/skills/cfn-task-audit/get-audit-data.sh +42 -21
- package/claude-assets/skills/cfn-task-audit/store-task-audit.sh +17 -10
- package/claude-assets/skills/cfn-test-runner/detect-regressions.sh +17 -14
- package/claude-assets/skills/cfn-test-runner/detect-regressions.sh.backup-1763392821 +55 -0
- package/claude-assets/skills/cfn-test-runner/store-benchmarks.sh +17 -19
- package/claude-assets/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
- package/claude-assets/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
- package/claude-assets/skills/cfn-utilities/SKILL.md +237 -0
- package/claude-assets/skills/cfn-utilities/execute.sh +32 -0
- package/claude-assets/skills/cfn-utilities/lib/errors.sh +56 -0
- package/claude-assets/skills/cfn-utilities/lib/file-ops.sh +164 -0
- package/claude-assets/skills/cfn-utilities/lib/logging.sh +77 -0
- package/claude-assets/skills/cfn-utilities/lib/retry.sh +127 -0
- package/claude-assets/skills/cfn-utilities/test.sh +317 -0
- package/claude-assets/skills/integration/agent-handoff.sh +62 -64
- package/claude-assets/skills/json-validation/SKILL.md +431 -0
- package/claude-assets/skills/json-validation/test-validate-success-criteria.sh +421 -0
- package/claude-assets/skills/json-validation/validate-success-criteria.sh +197 -0
- package/claude-assets/skills/redis-coordination/validate-parameters.sh +34 -0
- package/claude-assets/skills/workflow-codification/DEPLOY_QUICK_REFERENCE.md +106 -0
- package/claude-assets/skills/workflow-codification/PROPAGATE_UPDATE_QUICK_REFERENCE.md +366 -0
- package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh +481 -0
- package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh.backup-1763392820 +512 -0
- package/claude-assets/skills/workflow-codification/lib/security-utils.sh +204 -0
- package/claude-assets/skills/workflow-codification/propagate-skill-update.sh +648 -0
- package/claude-assets/skills/workflow-codification/propagate-skill-update.sh.backup-1763392820 +664 -0
- package/claude-assets/skills/workflow-codification/test-integration.sh +15 -0
- package/claude-assets/skills/workflow-codification/test-metadata-update.sh +350 -0
- package/claude-assets/skills/workflow-codification/track-cost-savings.sh +55 -14
- package/claude-assets/skills/workflow-codification/track-cost-savings.sh.backup-1763392821 +445 -0
- package/claude-assets/skills/workflow-codification/track-edge-case.sh +27 -60
- package/claude-assets/skills/workflow-codification/workflow-codification.db +0 -0
- package/dist/ace/ace-curator.js +10 -2
- package/dist/ace/ace-curator.js.map +1 -1
- package/dist/ace/ace-generator.js +4 -0
- package/dist/ace/ace-generator.js.map +1 -1
- package/dist/ace/ace-reflector.js +1 -1
- package/dist/ace/ace-reflector.js.map +1 -1
- package/dist/ace/context-injection.js +24 -2
- package/dist/ace/context-injection.js.map +1 -1
- package/dist/agents/agent-loader.js +146 -165
- package/dist/agents/agent-loader.js.map +1 -1
- package/dist/agents/task-agent-integration.js +1 -1
- package/dist/agents/task-agent-integration.js.map +1 -1
- package/dist/api/health-endpoints.js +390 -0
- package/dist/api/health-endpoints.js.map +1 -0
- package/dist/cli/agent-executor.js +4 -1
- package/dist/cli/agent-executor.js.map +1 -1
- package/dist/cli/agent-prompt-builder.js +89 -1
- package/dist/cli/agent-prompt-builder.js.map +1 -1
- package/dist/cli/agent-spawn.js +130 -37
- package/dist/cli/agent-spawn.js.map +1 -1
- package/dist/cli/skill-cache-validator.js +412 -0
- package/dist/cli/skill-cache-validator.js.map +1 -0
- package/dist/cli/skill-cli.js +991 -0
- package/dist/cli/skill-cli.js.map +1 -0
- package/dist/cli/skill-execution-logger.js +284 -0
- package/dist/cli/skill-execution-logger.js.map +1 -0
- package/dist/cli/skill-loader.js +457 -0
- package/dist/cli/skill-loader.js.map +1 -0
- package/dist/coordination/event-bus.js +2 -2
- package/dist/coordination/event-bus.js.map +1 -1
- package/dist/coordination/fleet-manager.js +1 -1
- package/dist/coordination/fleet-manager.js.map +1 -1
- package/dist/coordination/index.js +23 -9
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/types/fleet-manager.types.js.map +1 -1
- package/dist/db/migration-manager.js +483 -0
- package/dist/db/migration-manager.js.map +1 -0
- package/dist/db/skills-query.js +535 -0
- package/dist/db/skills-query.js.map +1 -0
- package/dist/integration/DatabaseHandoff.js +1 -1
- package/dist/integration/DatabaseHandoff.js.map +1 -1
- package/dist/jobs/edge-case-analyzer.js +367 -0
- package/dist/jobs/edge-case-analyzer.js.map +1 -0
- package/dist/jobs/promotion-sla-enforcer.js +288 -0
- package/dist/jobs/promotion-sla-enforcer.js.map +1 -0
- package/dist/lib/agent-output-parser.js.map +1 -1
- package/dist/lib/agent-output-validator.js.map +1 -1
- package/dist/lib/agent-workspace.js +281 -0
- package/dist/lib/agent-workspace.js.map +1 -0
- package/dist/lib/atomic-file-writer.js +377 -0
- package/dist/lib/atomic-file-writer.js.map +1 -0
- package/dist/lib/backup-manager.js +779 -0
- package/dist/lib/backup-manager.js.map +1 -0
- package/dist/lib/checkpoint-manager.js +837 -0
- package/dist/lib/checkpoint-manager.js.map +1 -0
- package/dist/lib/circuit-breaker.js +340 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/completion-signal-handler.js +243 -0
- package/dist/lib/completion-signal-handler.js.map +1 -0
- package/dist/lib/config-manager.js +312 -0
- package/dist/lib/config-manager.js.map +1 -0
- package/dist/lib/config-migrator.js +386 -0
- package/dist/lib/config-migrator.js.map +1 -0
- package/dist/lib/config-validator.js.map +1 -1
- package/dist/lib/correlation-cache.js +311 -0
- package/dist/lib/correlation-cache.js.map +1 -0
- package/dist/lib/correlation.js +263 -0
- package/dist/lib/correlation.js.map +1 -0
- package/dist/lib/database-service/connection-pool-manager.js +520 -0
- package/dist/lib/database-service/connection-pool-manager.js.map +1 -0
- package/dist/lib/database-service/correlation.js +329 -0
- package/dist/lib/database-service/correlation.js.map +1 -0
- package/dist/lib/database-service/errors.js +120 -0
- package/dist/lib/database-service/errors.js.map +1 -0
- package/dist/lib/database-service/index.js +168 -0
- package/dist/lib/database-service/index.js.map +1 -0
- package/dist/lib/database-service/postgres-adapter.js +526 -0
- package/dist/lib/database-service/postgres-adapter.js.map +1 -0
- package/dist/lib/database-service/redis-adapter.js +360 -0
- package/dist/lib/database-service/redis-adapter.js.map +1 -0
- package/dist/lib/database-service/sqlite-adapter.js +544 -0
- package/dist/lib/database-service/sqlite-adapter.js.map +1 -0
- package/dist/lib/database-service/transaction-manager.js +773 -0
- package/dist/lib/database-service/transaction-manager.js.map +1 -0
- package/dist/lib/database-service/types.js +23 -0
- package/dist/lib/database-service/types.js.map +1 -0
- package/dist/lib/deadlock-resolver.js +292 -0
- package/dist/lib/deadlock-resolver.js.map +1 -0
- package/dist/lib/distributed-lock.js +451 -0
- package/dist/lib/distributed-lock.js.map +1 -0
- package/dist/lib/edge-case-deduplicator.js +227 -0
- package/dist/lib/edge-case-deduplicator.js.map +1 -0
- package/dist/lib/encryption-manager.js +322 -0
- package/dist/lib/encryption-manager.js.map +1 -0
- package/dist/lib/error-aggregator.js +234 -0
- package/dist/lib/error-aggregator.js.map +1 -0
- package/dist/lib/errors.js +287 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/file-lock-manager.js +578 -0
- package/dist/lib/file-lock-manager.js.map +1 -0
- package/dist/lib/file-operations.js +367 -0
- package/dist/lib/file-operations.js.map +1 -0
- package/dist/lib/idempotent-write.js +237 -0
- package/dist/lib/idempotent-write.js.map +1 -0
- package/dist/lib/integration-schema-validator.js +522 -0
- package/dist/lib/integration-schema-validator.js.map +1 -0
- package/dist/lib/lock-health-monitor.js +298 -0
- package/dist/lib/lock-health-monitor.js.map +1 -0
- package/dist/lib/log-shipper.js +422 -0
- package/dist/lib/log-shipper.js.map +1 -0
- package/dist/lib/logging.js +146 -0
- package/dist/lib/logging.js.map +1 -0
- package/dist/lib/message-deduplicator.js +439 -0
- package/dist/lib/message-deduplicator.js.map +1 -0
- package/dist/lib/multi-system-query.js +604 -0
- package/dist/lib/multi-system-query.js.map +1 -0
- package/dist/lib/orphan-detector.js +332 -0
- package/dist/lib/orphan-detector.js.map +1 -0
- package/dist/lib/password-generator.js +166 -0
- package/dist/lib/password-generator.js.map +1 -0
- package/dist/lib/path-validator.js +429 -0
- package/dist/lib/path-validator.js.map +1 -0
- package/dist/lib/query-translator.js +905 -0
- package/dist/lib/query-translator.js.map +1 -0
- package/dist/lib/queue-recovery.js +469 -0
- package/dist/lib/queue-recovery.js.map +1 -0
- package/dist/lib/redis-queue-manager.js +512 -0
- package/dist/lib/redis-queue-manager.js.map +1 -0
- package/dist/lib/reflection-archiver.js +272 -0
- package/dist/lib/reflection-archiver.js.map +1 -0
- package/dist/lib/retry-manager.js +453 -0
- package/dist/lib/retry-manager.js.map +1 -0
- package/dist/lib/retry.js +262 -0
- package/dist/lib/retry.js.map +1 -0
- package/dist/lib/schema-transform.js +695 -0
- package/dist/lib/schema-transform.js.map +1 -0
- package/dist/lib/schema-validator.js +491 -0
- package/dist/lib/schema-validator.js.map +1 -0
- package/dist/lib/skill-cache.js +297 -0
- package/dist/lib/skill-cache.js.map +1 -0
- package/dist/lib/skill-content-manager.js +337 -0
- package/dist/lib/skill-content-manager.js.map +1 -0
- package/dist/lib/skill-frontmatter-parser.js +237 -0
- package/dist/lib/skill-frontmatter-parser.js.map +1 -0
- package/dist/lib/skill-git-integration.js +275 -0
- package/dist/lib/skill-git-integration.js.map +1 -0
- package/dist/lib/skill-markdown-validator.js +396 -0
- package/dist/lib/skill-markdown-validator.js.map +1 -0
- package/dist/lib/skill-output-parser.js +312 -0
- package/dist/lib/skill-output-parser.js.map +1 -0
- package/dist/lib/unified-query-api.js +467 -0
- package/dist/lib/unified-query-api.js.map +1 -0
- package/dist/middleware/auth-middleware.js +350 -0
- package/dist/middleware/auth-middleware.js.map +1 -0
- package/dist/middleware/schema-validation.js +347 -0
- package/dist/middleware/schema-validation.js.map +1 -0
- package/dist/providers/anthropic-provider.js +1 -1
- package/dist/providers/anthropic-provider.js.map +1 -1
- package/dist/providers/provider-factory.js +2 -2
- package/dist/providers/provider-factory.js.map +1 -1
- package/dist/services/edge-case-analyzer.js +321 -0
- package/dist/services/edge-case-analyzer.js.map +1 -0
- package/dist/services/edge-case-deduplicator.js +266 -0
- package/dist/services/edge-case-deduplicator.js.map +1 -0
- package/dist/services/edge-case-detector.js +337 -0
- package/dist/services/edge-case-detector.js.map +1 -0
- package/dist/services/edge-case-tracker.js +547 -0
- package/dist/services/edge-case-tracker.js.map +1 -0
- package/dist/services/health-check-system.js +586 -0
- package/dist/services/health-check-system.js.map +1 -0
- package/dist/services/metrics-logger.js +412 -0
- package/dist/services/metrics-logger.js.map +1 -0
- package/dist/services/patch-generator.js +378 -0
- package/dist/services/patch-generator.js.map +1 -0
- package/dist/services/patch-validator.js +337 -0
- package/dist/services/patch-validator.js.map +1 -0
- package/dist/services/performance-monitor.js +811 -0
- package/dist/services/performance-monitor.js.map +1 -0
- package/dist/services/promotion-pipeline.js +918 -0
- package/dist/services/promotion-pipeline.js.map +1 -0
- package/dist/services/promotion-validator.js +394 -0
- package/dist/services/promotion-validator.js.map +1 -0
- package/dist/services/reflection-logger.js +388 -0
- package/dist/services/reflection-logger.js.map +1 -0
- package/dist/services/skill-deployment.js +472 -0
- package/dist/services/skill-deployment.js.map +1 -0
- package/dist/services/skill-loader.js +427 -0
- package/dist/services/skill-loader.js.map +1 -0
- package/dist/services/skill-promotion.js +372 -0
- package/dist/services/skill-promotion.js.map +1 -0
- package/dist/services/skill-validator.js +454 -0
- package/dist/services/skill-validator.js.map +1 -0
- package/dist/services/skill-versioning.js +244 -0
- package/dist/services/skill-versioning.js.map +1 -0
- package/dist/services/workspace-supervisor.js +597 -0
- package/dist/services/workspace-supervisor.js.map +1 -0
- package/dist/types/edge-case.js +45 -0
- package/dist/types/edge-case.js.map +1 -0
- package/package.json +201 -177
- package/readme/README.md +19 -4
- package/scripts/backup-cleanup.sh +627 -0
- package/scripts/cleanup-workspaces.sh +412 -0
- package/scripts/cleanup-yaml-configs.sh +141 -0
- package/scripts/deploy-approved-skills.sh +263 -0
- package/scripts/health-check.sh +447 -0
- package/scripts/log-aggregator.sh +554 -0
- package/scripts/log-monitor.sh +629 -0
- package/scripts/manage-agent-workspaces.sh +434 -0
- package/scripts/migrate-schema.sh +533 -0
- package/scripts/promote-staged-skills.sh +423 -0
- package/scripts/verify-no-secrets.sh +88 -35
- package/.claude/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
- package/.claude/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
- package/.claude/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
- package/.claude/skills/agent-lifecycle/SKILL.md +0 -60
- package/.claude/skills/agent-lifecycle/execute-lifecycle-hook.sh +0 -573
- package/.claude/skills/agent-lifecycle/simple-audit.sh +0 -31
- package/.claude/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
- package/.claude/skills/cfn-loop-orchestration/orchestrate.sh.backup +0 -949
- package/README.md.backup_before_replace +0 -781
- package/claude-assets/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
- package/claude-assets/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
- package/claude-assets/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
- package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh.backup +0 -949
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Translator - SECURITY HARDENED
|
|
3
|
+
*
|
|
4
|
+
* Translates between SQL queries and Redis commands for cross-backend compatibility.
|
|
5
|
+
* Includes query optimization and backend recommendation logic.
|
|
6
|
+
*
|
|
7
|
+
* SECURITY ENHANCEMENTS (CVE-2024-SQL-INJECTION):
|
|
8
|
+
* - Input validation with whitelisting for identifiers (table/column names)
|
|
9
|
+
* - Parameterized queries for ALL database values
|
|
10
|
+
* - Comprehensive SQL injection prevention
|
|
11
|
+
* - Strict identifier validation against allowed patterns
|
|
12
|
+
* - Error handling using StandardError
|
|
13
|
+
*
|
|
14
|
+
* Part of Phase 2, Task P2-3.1: Unified Query API
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - SQL to Redis translation with security validation
|
|
18
|
+
* - Redis to SQL translation with parameterization
|
|
19
|
+
* - Query optimization
|
|
20
|
+
* - Backend recommendation based on query patterns
|
|
21
|
+
* - Performance monitoring (<50ms translation time)
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const translator = new QueryTranslator({
|
|
26
|
+
* allowedTables: ['tasks', 'users', 'projects'],
|
|
27
|
+
* allowedFields: { tasks: ['id', 'name', 'status', 'description'] }
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Translate SQL to Redis (fully parameterized)
|
|
31
|
+
* const redisCmd = translator.translateSQLToRedis(
|
|
32
|
+
* 'SELECT * FROM tasks WHERE id = ?',
|
|
33
|
+
* ['task-123']
|
|
34
|
+
* );
|
|
35
|
+
*
|
|
36
|
+
* // Translate Redis to SQL (fully parameterized)
|
|
37
|
+
* const sqlQuery = translator.translateRedisToSQL({
|
|
38
|
+
* command: 'HGETALL',
|
|
39
|
+
* key: 'task:123'
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/ import { BackendType } from './unified-query-api.js';
|
|
43
|
+
import { StandardError, ErrorCode } from './errors.js';
|
|
44
|
+
/**
|
|
45
|
+
* SQL query parser - SECURITY HARDENED
|
|
46
|
+
*/ let SQLParser = class SQLParser {
|
|
47
|
+
allowedTables;
|
|
48
|
+
allowedFields;
|
|
49
|
+
strictMode;
|
|
50
|
+
constructor(allowedTables, allowedFields, strictMode = true){
|
|
51
|
+
this.allowedTables = new Set(allowedTables || []);
|
|
52
|
+
this.allowedFields = new Map();
|
|
53
|
+
if (allowedFields) {
|
|
54
|
+
for (const [table, fields] of Object.entries(allowedFields)){
|
|
55
|
+
this.allowedFields.set(table.toLowerCase(), new Set(fields.map((f)=>f.toLowerCase())));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
this.strictMode = strictMode;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate SQL identifier (table/column name)
|
|
62
|
+
* Pattern: alphanumeric, underscore, starts with letter or underscore
|
|
63
|
+
*/ validateIdentifier(identifier, type) {
|
|
64
|
+
if (!identifier || typeof identifier !== 'string') {
|
|
65
|
+
return {
|
|
66
|
+
valid: false,
|
|
67
|
+
error: `Invalid ${type} name: must be a non-empty string`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Remove surrounding whitespace
|
|
71
|
+
const trimmed = identifier.trim();
|
|
72
|
+
// Check pattern: must start with letter or underscore, contain only alphanumeric and underscore
|
|
73
|
+
const identifierPattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
74
|
+
if (!identifierPattern.test(trimmed)) {
|
|
75
|
+
return {
|
|
76
|
+
valid: false,
|
|
77
|
+
error: `Invalid ${type} name: must match pattern /^[a-zA-Z_][a-zA-Z0-9_]*$/. Got: "${identifier}"`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Check length (reasonable limit to prevent DoS)
|
|
81
|
+
if (trimmed.length > 128) {
|
|
82
|
+
return {
|
|
83
|
+
valid: false,
|
|
84
|
+
error: `Invalid ${type} name: exceeds maximum length of 128 characters`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
valid: true,
|
|
89
|
+
sanitized: trimmed
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Validate table name against whitelist
|
|
94
|
+
*/ validateTableName(table) {
|
|
95
|
+
const validation = this.validateIdentifier(table, 'table');
|
|
96
|
+
if (!validation.valid) {
|
|
97
|
+
return validation;
|
|
98
|
+
}
|
|
99
|
+
const sanitized = validation.sanitized.toLowerCase();
|
|
100
|
+
// In strict mode, check against whitelist
|
|
101
|
+
if (this.strictMode && this.allowedTables.size > 0) {
|
|
102
|
+
if (!this.allowedTables.has(sanitized)) {
|
|
103
|
+
return {
|
|
104
|
+
valid: false,
|
|
105
|
+
error: `Table "${table}" is not in the whitelist of allowed tables`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
valid: true,
|
|
111
|
+
sanitized: validation.sanitized
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Validate field name against whitelist for specific table
|
|
116
|
+
*/ validateFieldName(field, table) {
|
|
117
|
+
const validation = this.validateIdentifier(field, 'field');
|
|
118
|
+
if (!validation.valid) {
|
|
119
|
+
return validation;
|
|
120
|
+
}
|
|
121
|
+
const sanitized = validation.sanitized.toLowerCase();
|
|
122
|
+
// In strict mode, check against whitelist if available
|
|
123
|
+
if (this.strictMode && table) {
|
|
124
|
+
const tableLower = table.toLowerCase();
|
|
125
|
+
const allowedFieldsForTable = this.allowedFields.get(tableLower);
|
|
126
|
+
if (allowedFieldsForTable && !allowedFieldsForTable.has(sanitized)) {
|
|
127
|
+
return {
|
|
128
|
+
valid: false,
|
|
129
|
+
error: `Field "${field}" is not in the whitelist for table "${table}"`
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
valid: true,
|
|
135
|
+
sanitized: validation.sanitized
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Parse SQL SELECT statement
|
|
140
|
+
*/ parseSelect(sql) {
|
|
141
|
+
const result = {};
|
|
142
|
+
try {
|
|
143
|
+
// Extract table name
|
|
144
|
+
const tableMatch = sql.match(/FROM\s+(\w+)/i);
|
|
145
|
+
if (tableMatch) {
|
|
146
|
+
const tableValidation = this.validateTableName(tableMatch[1]);
|
|
147
|
+
if (!tableValidation.valid) {
|
|
148
|
+
return {
|
|
149
|
+
error: tableValidation.error
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
result.table = tableValidation.sanitized;
|
|
153
|
+
}
|
|
154
|
+
// Extract fields
|
|
155
|
+
const fieldsMatch = sql.match(/SELECT\s+(.*?)\s+FROM/i);
|
|
156
|
+
if (fieldsMatch) {
|
|
157
|
+
const fields = fieldsMatch[1].trim();
|
|
158
|
+
if (fields === '*') {
|
|
159
|
+
result.fields = [
|
|
160
|
+
'*'
|
|
161
|
+
];
|
|
162
|
+
} else {
|
|
163
|
+
const fieldList = fields.split(',').map((f)=>f.trim());
|
|
164
|
+
const validatedFields = [];
|
|
165
|
+
for (const field of fieldList){
|
|
166
|
+
const fieldValidation = this.validateFieldName(field, result.table);
|
|
167
|
+
if (!fieldValidation.valid) {
|
|
168
|
+
return {
|
|
169
|
+
error: fieldValidation.error
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
validatedFields.push(fieldValidation.sanitized);
|
|
173
|
+
}
|
|
174
|
+
result.fields = validatedFields;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Extract WHERE clause
|
|
178
|
+
const whereMatch = sql.match(/WHERE\s+(.*?)(?:ORDER BY|GROUP BY|LIMIT|$)/i);
|
|
179
|
+
if (whereMatch) {
|
|
180
|
+
const whereClause = whereMatch[1].trim();
|
|
181
|
+
const whereResult = this.parseWhereClause(whereClause, result.table);
|
|
182
|
+
if (whereResult.error) {
|
|
183
|
+
return {
|
|
184
|
+
error: whereResult.error
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
result.where = whereResult.conditions;
|
|
188
|
+
}
|
|
189
|
+
// Extract JOINs
|
|
190
|
+
const joinMatches = sql.matchAll(/(?:INNER |LEFT |RIGHT |)?JOIN\s+(\w+)\s+ON\s+(.*?)(?:WHERE|ORDER BY|GROUP BY|LIMIT|JOIN|$)/gi);
|
|
191
|
+
result.joins = [];
|
|
192
|
+
for (const match of joinMatches){
|
|
193
|
+
const joinTableValidation = this.validateTableName(match[1]);
|
|
194
|
+
if (!joinTableValidation.valid) {
|
|
195
|
+
return {
|
|
196
|
+
error: joinTableValidation.error
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
result.joins.push({
|
|
200
|
+
table: joinTableValidation.sanitized,
|
|
201
|
+
on: match[2].trim()
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return {
|
|
207
|
+
error: `Failed to parse SELECT statement: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Parse WHERE clause with validation
|
|
213
|
+
*/ parseWhereClause(whereClause, table) {
|
|
214
|
+
const conditions = [];
|
|
215
|
+
try {
|
|
216
|
+
// Simple parser for basic conditions
|
|
217
|
+
// Format: field = ? OR field LIKE ? etc.
|
|
218
|
+
const parts = whereClause.split(/\s+AND\s+/i);
|
|
219
|
+
for (const part of parts){
|
|
220
|
+
const match = part.match(/(\w+)\s*(=|!=|>|>=|<|<=|LIKE|IN|NOT IN)\s*(.+)/i);
|
|
221
|
+
if (match) {
|
|
222
|
+
const fieldValidation = this.validateFieldName(match[1], table);
|
|
223
|
+
if (!fieldValidation.valid) {
|
|
224
|
+
return {
|
|
225
|
+
error: fieldValidation.error
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
conditions.push({
|
|
229
|
+
field: fieldValidation.sanitized,
|
|
230
|
+
operator: match[2].toLowerCase(),
|
|
231
|
+
value: match[3] === '?' ? undefined : match[3]
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
conditions
|
|
237
|
+
};
|
|
238
|
+
} catch (error) {
|
|
239
|
+
return {
|
|
240
|
+
error: `Failed to parse WHERE clause: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Parse SQL INSERT statement with validation
|
|
246
|
+
*/ parseInsert(sql) {
|
|
247
|
+
const result = {};
|
|
248
|
+
try {
|
|
249
|
+
// Extract table name
|
|
250
|
+
const tableMatch = sql.match(/INSERT INTO\s+(\w+)/i);
|
|
251
|
+
if (tableMatch) {
|
|
252
|
+
const tableValidation = this.validateTableName(tableMatch[1]);
|
|
253
|
+
if (!tableValidation.valid) {
|
|
254
|
+
return {
|
|
255
|
+
error: tableValidation.error
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
result.table = tableValidation.sanitized;
|
|
259
|
+
}
|
|
260
|
+
// Extract fields
|
|
261
|
+
const fieldsMatch = sql.match(/\(([^)]+)\)\s+VALUES/i);
|
|
262
|
+
if (fieldsMatch) {
|
|
263
|
+
const fieldList = fieldsMatch[1].split(',').map((f)=>f.trim());
|
|
264
|
+
const validatedFields = [];
|
|
265
|
+
for (const field of fieldList){
|
|
266
|
+
const fieldValidation = this.validateFieldName(field, result.table);
|
|
267
|
+
if (!fieldValidation.valid) {
|
|
268
|
+
return {
|
|
269
|
+
error: fieldValidation.error
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
validatedFields.push(fieldValidation.sanitized);
|
|
273
|
+
}
|
|
274
|
+
result.fields = validatedFields;
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
return {
|
|
279
|
+
error: `Failed to parse INSERT statement: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Parse SQL UPDATE statement with validation
|
|
285
|
+
*/ parseUpdate(sql) {
|
|
286
|
+
const result = {};
|
|
287
|
+
try {
|
|
288
|
+
// Extract table name
|
|
289
|
+
const tableMatch = sql.match(/UPDATE\s+(\w+)/i);
|
|
290
|
+
if (tableMatch) {
|
|
291
|
+
const tableValidation = this.validateTableName(tableMatch[1]);
|
|
292
|
+
if (!tableValidation.valid) {
|
|
293
|
+
return {
|
|
294
|
+
error: tableValidation.error
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
result.table = tableValidation.sanitized;
|
|
298
|
+
}
|
|
299
|
+
// Extract SET clause
|
|
300
|
+
const setMatch = sql.match(/SET\s+(.*?)\s+WHERE/i);
|
|
301
|
+
if (setMatch) {
|
|
302
|
+
const setParts = setMatch[1].split(',');
|
|
303
|
+
const validatedFields = [];
|
|
304
|
+
for (const part of setParts){
|
|
305
|
+
const field = part.split('=')[0].trim();
|
|
306
|
+
const fieldValidation = this.validateFieldName(field, result.table);
|
|
307
|
+
if (!fieldValidation.valid) {
|
|
308
|
+
return {
|
|
309
|
+
error: fieldValidation.error
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
validatedFields.push(fieldValidation.sanitized);
|
|
313
|
+
}
|
|
314
|
+
result.fields = validatedFields;
|
|
315
|
+
}
|
|
316
|
+
// Extract WHERE clause
|
|
317
|
+
const whereMatch = sql.match(/WHERE\s+(.*?)$/i);
|
|
318
|
+
if (whereMatch) {
|
|
319
|
+
const whereResult = this.parseWhereClause(whereMatch[1].trim(), result.table);
|
|
320
|
+
if (whereResult.error) {
|
|
321
|
+
return {
|
|
322
|
+
error: whereResult.error
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
result.where = whereResult.conditions;
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return {
|
|
330
|
+
error: `Failed to parse UPDATE statement: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Parse SQL DELETE statement with validation
|
|
336
|
+
*/ parseDelete(sql) {
|
|
337
|
+
const result = {};
|
|
338
|
+
try {
|
|
339
|
+
// Extract table name
|
|
340
|
+
const tableMatch = sql.match(/DELETE FROM\s+(\w+)/i);
|
|
341
|
+
if (tableMatch) {
|
|
342
|
+
const tableValidation = this.validateTableName(tableMatch[1]);
|
|
343
|
+
if (!tableValidation.valid) {
|
|
344
|
+
return {
|
|
345
|
+
error: tableValidation.error
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
result.table = tableValidation.sanitized;
|
|
349
|
+
}
|
|
350
|
+
// Extract WHERE clause
|
|
351
|
+
const whereMatch = sql.match(/WHERE\s+(.*?)$/i);
|
|
352
|
+
if (whereMatch) {
|
|
353
|
+
const whereResult = this.parseWhereClause(whereMatch[1].trim(), result.table);
|
|
354
|
+
if (whereResult.error) {
|
|
355
|
+
return {
|
|
356
|
+
error: whereResult.error
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
result.where = whereResult.conditions;
|
|
360
|
+
}
|
|
361
|
+
return result;
|
|
362
|
+
} catch (error) {
|
|
363
|
+
return {
|
|
364
|
+
error: `Failed to parse DELETE statement: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
/**
|
|
370
|
+
* Query Translator - SECURITY HARDENED
|
|
371
|
+
*
|
|
372
|
+
* Provides bidirectional translation between SQL and Redis commands
|
|
373
|
+
* with comprehensive input validation and SQL injection prevention
|
|
374
|
+
*/ export class QueryTranslator {
|
|
375
|
+
parser;
|
|
376
|
+
config;
|
|
377
|
+
constructor(config){
|
|
378
|
+
this.config = {
|
|
379
|
+
allowedTables: config?.allowedTables || [],
|
|
380
|
+
allowedFields: config?.allowedFields || {},
|
|
381
|
+
maxQueryLength: config?.maxQueryLength || 10000,
|
|
382
|
+
maxParams: config?.maxParams || 100,
|
|
383
|
+
strictMode: config?.strictMode !== false
|
|
384
|
+
};
|
|
385
|
+
this.parser = new SQLParser(this.config.allowedTables, this.config.allowedFields, this.config.strictMode);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Validate input parameters
|
|
389
|
+
*/ validateInput(sql, params = []) {
|
|
390
|
+
if (!sql || typeof sql !== 'string') {
|
|
391
|
+
return {
|
|
392
|
+
valid: false,
|
|
393
|
+
error: 'SQL query must be a non-empty string'
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (sql.length > this.config.maxQueryLength) {
|
|
397
|
+
return {
|
|
398
|
+
valid: false,
|
|
399
|
+
error: `SQL query exceeds maximum length of ${this.config.maxQueryLength} characters`
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (!Array.isArray(params)) {
|
|
403
|
+
return {
|
|
404
|
+
valid: false,
|
|
405
|
+
error: 'Parameters must be an array'
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
if (params.length > this.config.maxParams) {
|
|
409
|
+
return {
|
|
410
|
+
valid: false,
|
|
411
|
+
error: `Too many parameters. Maximum: ${this.config.maxParams}`
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
// Detect SQL injection patterns in the query
|
|
415
|
+
// These patterns should ONLY appear in specific contexts (after ?)
|
|
416
|
+
const injectionPatterns = [
|
|
417
|
+
/\bOR\b[\s]*(\d+\s*=\s*\d+|'[^']*'\s*=\s*'[^']*'|true|1)/i,
|
|
418
|
+
/\bUNION\b[\s]+(SELECT|ALL)/i,
|
|
419
|
+
/--\s*$/i,
|
|
420
|
+
/\/\*[\s\S]*?\*\//i,
|
|
421
|
+
/;\s*(SELECT|INSERT|UPDATE|DELETE|DROP|TRUNCATE|ALTER)/i,
|
|
422
|
+
/\bDROP\b|\bTRUNCATE\b|\bALTER\b|\bCREATE\b/i,
|
|
423
|
+
/\bEVAL\b|\bEXEC\b|\bSCRIPT\b/i,
|
|
424
|
+
/\x00/
|
|
425
|
+
];
|
|
426
|
+
// Split query into parts based on ? placeholders
|
|
427
|
+
const parts = sql.split('?');
|
|
428
|
+
// Check structure: should be [prefix, suffix] or [prefix1, middle1, middle2, suffix]
|
|
429
|
+
// Injection attempts will have SQL keywords AFTER the ?
|
|
430
|
+
for(let i = 0; i < parts.length; i++){
|
|
431
|
+
const part = parts[i];
|
|
432
|
+
// First part should contain SELECT/INSERT/UPDATE/DELETE/FROM
|
|
433
|
+
if (i === 0) {
|
|
434
|
+
// Validate it's a valid SQL statement start
|
|
435
|
+
if (!/(SELECT|INSERT|UPDATE|DELETE|FROM)\b/i.test(part)) {
|
|
436
|
+
// Only valid if it's completely empty (error caught elsewhere)
|
|
437
|
+
if (part.trim()) {
|
|
438
|
+
return {
|
|
439
|
+
valid: false,
|
|
440
|
+
error: 'Invalid SQL query structure. Must start with SELECT, INSERT, UPDATE, or DELETE'
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} else if (i === parts.length - 1) {
|
|
445
|
+
// Last part (after last ?) should not have SQL keywords that indicate injection
|
|
446
|
+
if (/\b(OR|UNION|SELECT|INSERT|UPDATE|DELETE|DROP|TRUNCATE)\b/i.test(part)) {
|
|
447
|
+
return {
|
|
448
|
+
valid: false,
|
|
449
|
+
error: 'SQL query contains suspicious injection patterns'
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
} else {
|
|
453
|
+
// Middle parts (between ? and ?) should be minimal
|
|
454
|
+
// Should only contain WHERE, AND, OR (as operators), commas, etc.
|
|
455
|
+
// But NOT SELECT, UNION, etc.
|
|
456
|
+
if (/\b(UNION|SELECT|INSERT|UPDATE|DELETE|DROP)\b/i.test(part)) {
|
|
457
|
+
return {
|
|
458
|
+
valid: false,
|
|
459
|
+
error: 'SQL query contains suspicious injection patterns'
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Check for injection patterns in the entire query
|
|
465
|
+
for (const pattern of injectionPatterns){
|
|
466
|
+
// Skip OR if it's part of valid syntax (after WHERE)
|
|
467
|
+
if (pattern.source.includes('OR') && /WHERE[\s\S]*\?[\s\S]*OR/i.test(sql)) {
|
|
468
|
+
// This could be valid OR in WHERE clause
|
|
469
|
+
const afterQuestion = sql.substring(sql.indexOf('?') + 1);
|
|
470
|
+
if (/\bOR\b[\s]*[\'\"]?\d+['\"]*\s*=\s*[\'\"]?\d+['\"]*|OR\s*'[^']*'\s*=\s*'[^']*'|OR\s*TRUE/i.test(afterQuestion)) {
|
|
471
|
+
return {
|
|
472
|
+
valid: false,
|
|
473
|
+
error: 'SQL query contains SQL injection pattern: OR-based bypass'
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
} else if (pattern.test(sql) && !sql.includes('?')) {
|
|
477
|
+
// Pattern found but no parameterization
|
|
478
|
+
return {
|
|
479
|
+
valid: false,
|
|
480
|
+
error: 'SQL query contains suspicious patterns. Use parameterized queries.'
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
valid: true
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Validate Redis command structure
|
|
490
|
+
*/ validateRedisCommand(command) {
|
|
491
|
+
if (!command || typeof command !== 'object') {
|
|
492
|
+
return {
|
|
493
|
+
valid: false,
|
|
494
|
+
error: 'Redis command must be an object'
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
if (!command.command || typeof command.command !== 'string') {
|
|
498
|
+
return {
|
|
499
|
+
valid: false,
|
|
500
|
+
error: 'Redis command name must be a non-empty string'
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
const allowedCommands = [
|
|
504
|
+
'GET',
|
|
505
|
+
'SET',
|
|
506
|
+
'HGET',
|
|
507
|
+
'HGETALL',
|
|
508
|
+
'HMSET',
|
|
509
|
+
'HSET',
|
|
510
|
+
'DEL',
|
|
511
|
+
'MGET',
|
|
512
|
+
'MSET'
|
|
513
|
+
];
|
|
514
|
+
if (!allowedCommands.includes(command.command.toUpperCase())) {
|
|
515
|
+
return {
|
|
516
|
+
valid: false,
|
|
517
|
+
error: `Redis command "${command.command}" is not allowed. Allowed: ${allowedCommands.join(', ')}`
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
if (command.key && typeof command.key !== 'string') {
|
|
521
|
+
return {
|
|
522
|
+
valid: false,
|
|
523
|
+
error: 'Redis key must be a string'
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (command.fields && typeof command.fields !== 'object') {
|
|
527
|
+
return {
|
|
528
|
+
valid: false,
|
|
529
|
+
error: 'Redis fields must be an object'
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (command.args && !Array.isArray(command.args)) {
|
|
533
|
+
return {
|
|
534
|
+
valid: false,
|
|
535
|
+
error: 'Redis args must be an array'
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
valid: true
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Translate SQL query to Redis commands
|
|
544
|
+
*/ translateSQLToRedis(sql, params = []) {
|
|
545
|
+
const startTime = Date.now();
|
|
546
|
+
const warnings = [];
|
|
547
|
+
try {
|
|
548
|
+
// Validate inputs
|
|
549
|
+
const inputValidation = this.validateInput(sql, params);
|
|
550
|
+
if (!inputValidation.valid) {
|
|
551
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, inputValidation.error || 'Invalid input', {
|
|
552
|
+
sql,
|
|
553
|
+
paramCount: params.length
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
// Determine query type
|
|
557
|
+
const queryType = this.getQueryType(sql);
|
|
558
|
+
let redisCommand;
|
|
559
|
+
let recommendedBackend = BackendType.REDIS;
|
|
560
|
+
switch(queryType){
|
|
561
|
+
case 'SELECT':
|
|
562
|
+
{
|
|
563
|
+
const selectParsed = this.parser.parseSelect(sql);
|
|
564
|
+
if (selectParsed.error) {
|
|
565
|
+
throw new StandardError(ErrorCode.PARSE_ERROR, `Failed to parse SELECT statement: ${selectParsed.error}`, {
|
|
566
|
+
sql
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
// Check if query is complex (has joins)
|
|
570
|
+
if (selectParsed.joins && selectParsed.joins.length > 0) {
|
|
571
|
+
warnings.push('Complex queries with JOINs are better suited for PostgreSQL');
|
|
572
|
+
recommendedBackend = BackendType.POSTGRES;
|
|
573
|
+
}
|
|
574
|
+
// Translate to Redis HGETALL or GET
|
|
575
|
+
if (selectParsed.where && selectParsed.where.length > 0) {
|
|
576
|
+
const idCondition = selectParsed.where.find((w)=>w.field === 'id');
|
|
577
|
+
if (idCondition) {
|
|
578
|
+
const keyValue = params[0];
|
|
579
|
+
// Validate key value
|
|
580
|
+
if (typeof keyValue !== 'string' && typeof keyValue !== 'number') {
|
|
581
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Redis key value must be a string or number', {
|
|
582
|
+
paramValue: typeof keyValue
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
const redisKey = `${selectParsed.table}:${keyValue}`;
|
|
586
|
+
redisCommand = {
|
|
587
|
+
command: selectParsed.fields?.[0] === '*' ? 'HGETALL' : 'HGET',
|
|
588
|
+
key: redisKey
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
case 'INSERT':
|
|
595
|
+
{
|
|
596
|
+
const insertParsed = this.parser.parseInsert(sql);
|
|
597
|
+
if (insertParsed.error) {
|
|
598
|
+
throw new StandardError(ErrorCode.PARSE_ERROR, `Failed to parse INSERT statement: ${insertParsed.error}`, {
|
|
599
|
+
sql
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
if (insertParsed.table && insertParsed.fields) {
|
|
603
|
+
const idValue = params[0];
|
|
604
|
+
// Validate key value
|
|
605
|
+
if (typeof idValue !== 'string' && typeof idValue !== 'number') {
|
|
606
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Redis key value must be a string or number', {
|
|
607
|
+
paramValue: typeof idValue
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
const redisKey = `${insertParsed.table}:${idValue}`;
|
|
611
|
+
// Build field-value pairs with validation
|
|
612
|
+
const fields = {};
|
|
613
|
+
for(let i = 0; i < insertParsed.fields.length && i < params.length; i++){
|
|
614
|
+
// Ensure params are not objects/arrays (prevent injection)
|
|
615
|
+
const paramValue = params[i];
|
|
616
|
+
if (typeof paramValue === 'object' && paramValue !== null) {
|
|
617
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Parameter values must be primitives (string, number, boolean, null)', {
|
|
618
|
+
paramIndex: i,
|
|
619
|
+
paramType: typeof paramValue
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
fields[insertParsed.fields[i]] = paramValue;
|
|
623
|
+
}
|
|
624
|
+
redisCommand = {
|
|
625
|
+
command: 'HMSET',
|
|
626
|
+
key: redisKey,
|
|
627
|
+
fields
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
case 'UPDATE':
|
|
633
|
+
{
|
|
634
|
+
const updateParsed = this.parser.parseUpdate(sql);
|
|
635
|
+
if (updateParsed.error) {
|
|
636
|
+
throw new StandardError(ErrorCode.PARSE_ERROR, `Failed to parse UPDATE statement: ${updateParsed.error}`, {
|
|
637
|
+
sql
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if (updateParsed.table && updateParsed.where) {
|
|
641
|
+
const idCondition = updateParsed.where.find((w)=>w.field === 'id');
|
|
642
|
+
if (idCondition) {
|
|
643
|
+
const keyValue = params[params.length - 1];
|
|
644
|
+
// Validate key value
|
|
645
|
+
if (typeof keyValue !== 'string' && typeof keyValue !== 'number') {
|
|
646
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Redis key value must be a string or number', {
|
|
647
|
+
paramValue: typeof keyValue
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
const redisKey = `${updateParsed.table}:${keyValue}`;
|
|
651
|
+
redisCommand = {
|
|
652
|
+
command: 'HSET',
|
|
653
|
+
key: redisKey,
|
|
654
|
+
args: params.slice(0, -1)
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
case 'DELETE':
|
|
661
|
+
{
|
|
662
|
+
const deleteParsed = this.parser.parseDelete(sql);
|
|
663
|
+
if (deleteParsed.error) {
|
|
664
|
+
throw new StandardError(ErrorCode.PARSE_ERROR, `Failed to parse DELETE statement: ${deleteParsed.error}`, {
|
|
665
|
+
sql
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
if (deleteParsed.table && deleteParsed.where) {
|
|
669
|
+
const idCondition = deleteParsed.where.find((w)=>w.field === 'id');
|
|
670
|
+
if (idCondition) {
|
|
671
|
+
const keyValue = params[0];
|
|
672
|
+
// Validate key value
|
|
673
|
+
if (typeof keyValue !== 'string' && typeof keyValue !== 'number') {
|
|
674
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Redis key value must be a string or number', {
|
|
675
|
+
paramValue: typeof keyValue
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
const redisKey = `${deleteParsed.table}:${keyValue}`;
|
|
679
|
+
redisCommand = {
|
|
680
|
+
command: 'DEL',
|
|
681
|
+
key: redisKey
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
default:
|
|
688
|
+
warnings.push(`Unsupported SQL query type: ${queryType}`);
|
|
689
|
+
recommendedBackend = BackendType.POSTGRES;
|
|
690
|
+
}
|
|
691
|
+
const executionTime = Date.now() - startTime;
|
|
692
|
+
if (executionTime > 50) {
|
|
693
|
+
warnings.push(`Translation took ${executionTime}ms (target: <50ms)`);
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
success: !!redisCommand,
|
|
697
|
+
redisCommand,
|
|
698
|
+
executionTime,
|
|
699
|
+
recommendedBackend,
|
|
700
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
701
|
+
};
|
|
702
|
+
} catch (error) {
|
|
703
|
+
const executionTime = Date.now() - startTime;
|
|
704
|
+
const message = error instanceof StandardError ? error.message : error instanceof Error ? error.message : 'Unknown error';
|
|
705
|
+
return {
|
|
706
|
+
success: false,
|
|
707
|
+
executionTime,
|
|
708
|
+
warnings: [
|
|
709
|
+
`Translation failed: ${message}`
|
|
710
|
+
]
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Translate Redis command to SQL query (with parameterization)
|
|
716
|
+
*/ translateRedisToSQL(command) {
|
|
717
|
+
const startTime = Date.now();
|
|
718
|
+
const warnings = [];
|
|
719
|
+
try {
|
|
720
|
+
// Validate Redis command
|
|
721
|
+
const commandValidation = this.validateRedisCommand(command);
|
|
722
|
+
if (!commandValidation.valid) {
|
|
723
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, commandValidation.error || 'Invalid Redis command', {
|
|
724
|
+
command: command.command
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
let sqlQuery;
|
|
728
|
+
let sqlParams = [];
|
|
729
|
+
// Parse Redis key to extract table and ID
|
|
730
|
+
const keyParts = command.key?.split(':') || [];
|
|
731
|
+
const table = keyParts[0] || 'unknown';
|
|
732
|
+
const id = keyParts[1];
|
|
733
|
+
// Validate table name
|
|
734
|
+
const tableValidation = this.parser['validateTableName'] || ((t)=>({
|
|
735
|
+
valid: true,
|
|
736
|
+
sanitized: t
|
|
737
|
+
}));
|
|
738
|
+
// Since validateTableName is private, we do basic validation
|
|
739
|
+
const tablePattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
740
|
+
if (!tablePattern.test(table)) {
|
|
741
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Invalid table name in Redis key', {
|
|
742
|
+
key: command.key
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
switch(command.command.toUpperCase()){
|
|
746
|
+
case 'GET':
|
|
747
|
+
case 'HGET':
|
|
748
|
+
case 'HGETALL':
|
|
749
|
+
{
|
|
750
|
+
// SELECT * FROM table WHERE id = ?
|
|
751
|
+
sqlQuery = `SELECT * FROM ${table} WHERE id = ?`;
|
|
752
|
+
sqlParams = [
|
|
753
|
+
id
|
|
754
|
+
];
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
case 'SET':
|
|
758
|
+
case 'HMSET':
|
|
759
|
+
{
|
|
760
|
+
// INSERT INTO table (field1, field2, ...) VALUES (?, ?, ...)
|
|
761
|
+
if (command.fields) {
|
|
762
|
+
const fields = Object.keys(command.fields);
|
|
763
|
+
const placeholders = fields.map(()=>'?').join(', ');
|
|
764
|
+
sqlQuery = `INSERT INTO ${table} (${fields.join(', ')}) VALUES (${placeholders})`;
|
|
765
|
+
sqlParams = Object.values(command.fields);
|
|
766
|
+
// Validate all params are primitives
|
|
767
|
+
for(let i = 0; i < sqlParams.length; i++){
|
|
768
|
+
if (typeof sqlParams[i] === 'object' && sqlParams[i] !== null) {
|
|
769
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Parameter values must be primitives', {
|
|
770
|
+
paramIndex: i
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
case 'HSET':
|
|
778
|
+
{
|
|
779
|
+
// UPDATE table SET field = ? WHERE id = ?
|
|
780
|
+
if (command.args && command.args.length > 0) {
|
|
781
|
+
const field = command.args[0];
|
|
782
|
+
// Validate field name
|
|
783
|
+
if (typeof field !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
|
|
784
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Invalid field name in HSET command', {
|
|
785
|
+
field
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
sqlQuery = `UPDATE ${table} SET ${field} = ? WHERE id = ?`;
|
|
789
|
+
sqlParams = [
|
|
790
|
+
command.args[1],
|
|
791
|
+
id
|
|
792
|
+
];
|
|
793
|
+
// Validate params
|
|
794
|
+
for(let i = 0; i < sqlParams.length; i++){
|
|
795
|
+
if (typeof sqlParams[i] === 'object' && sqlParams[i] !== null) {
|
|
796
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Parameter values must be primitives', {
|
|
797
|
+
paramIndex: i
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
case 'DEL':
|
|
805
|
+
{
|
|
806
|
+
// DELETE FROM table WHERE id = ?
|
|
807
|
+
sqlQuery = `DELETE FROM ${table} WHERE id = ?`;
|
|
808
|
+
sqlParams = [
|
|
809
|
+
id
|
|
810
|
+
];
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
default:
|
|
814
|
+
warnings.push(`Unsupported Redis command: ${command.command}`);
|
|
815
|
+
}
|
|
816
|
+
const executionTime = Date.now() - startTime;
|
|
817
|
+
if (executionTime > 50) {
|
|
818
|
+
warnings.push(`Translation took ${executionTime}ms (target: <50ms)`);
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
success: !!sqlQuery,
|
|
822
|
+
sqlQuery,
|
|
823
|
+
sqlParams,
|
|
824
|
+
executionTime,
|
|
825
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
826
|
+
};
|
|
827
|
+
} catch (error) {
|
|
828
|
+
const executionTime = Date.now() - startTime;
|
|
829
|
+
const message = error instanceof StandardError ? error.message : error instanceof Error ? error.message : 'Unknown error';
|
|
830
|
+
return {
|
|
831
|
+
success: false,
|
|
832
|
+
executionTime,
|
|
833
|
+
warnings: [
|
|
834
|
+
`Translation failed: ${message}`
|
|
835
|
+
]
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Optimize query and provide recommendations
|
|
841
|
+
*/ optimizeQuery(request) {
|
|
842
|
+
const result = {
|
|
843
|
+
indexed: [],
|
|
844
|
+
recommendations: []
|
|
845
|
+
};
|
|
846
|
+
// Recommend indexes for filtered fields
|
|
847
|
+
if (request.filters) {
|
|
848
|
+
const indexFields = request.filters.map((f)=>String(f.field));
|
|
849
|
+
result.indexed = indexFields;
|
|
850
|
+
result.indexes = indexFields;
|
|
851
|
+
result.recommendations?.push(`Consider adding indexes on: ${indexFields.join(', ')}`);
|
|
852
|
+
}
|
|
853
|
+
// Estimate query cost
|
|
854
|
+
let cost = 1;
|
|
855
|
+
if (request.joins) {
|
|
856
|
+
cost += request.joins.length * 10; // JOINs are expensive
|
|
857
|
+
}
|
|
858
|
+
if (request.filters) {
|
|
859
|
+
cost += request.filters.length * 2;
|
|
860
|
+
}
|
|
861
|
+
result.estimatedCost = cost;
|
|
862
|
+
// Provide optimization recommendations
|
|
863
|
+
if (request.joins && request.joins.length > 2) {
|
|
864
|
+
result.recommendations?.push('Consider denormalizing data or using materialized views for complex joins');
|
|
865
|
+
}
|
|
866
|
+
if (request.filters && request.filters.length > 5) {
|
|
867
|
+
result.recommendations?.push('Consider composite indexes for multiple filter conditions');
|
|
868
|
+
}
|
|
869
|
+
return result;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Recommend backend based on query characteristics
|
|
873
|
+
*/ recommendBackend(request) {
|
|
874
|
+
// Simple key-value access → Redis
|
|
875
|
+
if (request.key && !request.joins) {
|
|
876
|
+
return BackendType.REDIS;
|
|
877
|
+
}
|
|
878
|
+
// Complex queries with JOINs → PostgreSQL
|
|
879
|
+
if (request.joins && request.joins.length > 0) {
|
|
880
|
+
return BackendType.POSTGRES;
|
|
881
|
+
}
|
|
882
|
+
// Session/cache data → Redis
|
|
883
|
+
if (request.dataType === 'cache' || request.dataType === 'session') {
|
|
884
|
+
return BackendType.REDIS;
|
|
885
|
+
}
|
|
886
|
+
// Embedded/local data → SQLite
|
|
887
|
+
if (request.dataType === 'embedded') {
|
|
888
|
+
return BackendType.SQLITE;
|
|
889
|
+
}
|
|
890
|
+
// Default to PostgreSQL for structured data
|
|
891
|
+
return BackendType.POSTGRES;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Get query type from SQL string
|
|
895
|
+
*/ getQueryType(sql) {
|
|
896
|
+
const trimmed = sql.trim().toUpperCase();
|
|
897
|
+
if (trimmed.startsWith('SELECT')) return 'SELECT';
|
|
898
|
+
if (trimmed.startsWith('INSERT')) return 'INSERT';
|
|
899
|
+
if (trimmed.startsWith('UPDATE')) return 'UPDATE';
|
|
900
|
+
if (trimmed.startsWith('DELETE')) return 'DELETE';
|
|
901
|
+
return 'UNKNOWN';
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
//# sourceMappingURL=query-translator.js.map
|