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,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Deduplicator
|
|
3
|
+
*
|
|
4
|
+
* Provides SHA256-based message fingerprinting and deduplication for Redis queues.
|
|
5
|
+
* Part of Task 3.4: Redis Queue Consistency & Recovery (Integration Standardization Sprint 3)
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - SHA256-based message fingerprinting
|
|
9
|
+
* - Deduplication window (default 1 hour)
|
|
10
|
+
* - Idempotency key tracking in Redis
|
|
11
|
+
* - Automatic cleanup of expired keys
|
|
12
|
+
* - Batch deduplication support
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const deduplicator = new MessageDeduplicator(redisClient);
|
|
16
|
+
* const isDuplicate = await deduplicator.isDuplicate(message);
|
|
17
|
+
* if (!isDuplicate) {
|
|
18
|
+
* await deduplicator.markProcessed(message);
|
|
19
|
+
* }
|
|
20
|
+
*/ import * as crypto from 'crypto';
|
|
21
|
+
import { createLogger } from './logging.js';
|
|
22
|
+
import { createError, ErrorCode, isRetryableError } from './errors.js';
|
|
23
|
+
import { withRetry } from './retry.js';
|
|
24
|
+
const logger = createLogger('message-deduplicator');
|
|
25
|
+
/**
|
|
26
|
+
* Default deduplication options
|
|
27
|
+
*/ const DEFAULT_OPTIONS = {
|
|
28
|
+
windowMs: 60 * 60 * 1000,
|
|
29
|
+
keyPrefix: 'dedup:',
|
|
30
|
+
autoCleanup: true,
|
|
31
|
+
cleanupIntervalMs: 5 * 60 * 1000,
|
|
32
|
+
maxRetries: 3
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Message Deduplicator
|
|
36
|
+
*
|
|
37
|
+
* Provides idempotent message processing using SHA256-based fingerprinting.
|
|
38
|
+
*/ export class MessageDeduplicator {
|
|
39
|
+
redis;
|
|
40
|
+
options;
|
|
41
|
+
cleanupTimer = null;
|
|
42
|
+
stats = {
|
|
43
|
+
processed: 0,
|
|
44
|
+
duplicates: 0,
|
|
45
|
+
unique: 0
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Create a new MessageDeduplicator instance
|
|
49
|
+
*
|
|
50
|
+
* @param redis - Redis client instance
|
|
51
|
+
* @param options - Deduplication options
|
|
52
|
+
*/ constructor(redis, options = {}){
|
|
53
|
+
this.redis = redis;
|
|
54
|
+
this.options = {
|
|
55
|
+
...DEFAULT_OPTIONS,
|
|
56
|
+
...options
|
|
57
|
+
};
|
|
58
|
+
// Start automatic cleanup if enabled
|
|
59
|
+
if (this.options.autoCleanup) {
|
|
60
|
+
this.startAutoCleanup();
|
|
61
|
+
}
|
|
62
|
+
logger.info('MessageDeduplicator initialized', {
|
|
63
|
+
windowMs: this.options.windowMs,
|
|
64
|
+
keyPrefix: this.options.keyPrefix,
|
|
65
|
+
autoCleanup: this.options.autoCleanup
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create message fingerprint (SHA256 hash)
|
|
70
|
+
*
|
|
71
|
+
* @param message - Message content to fingerprint
|
|
72
|
+
* @returns SHA256 hash
|
|
73
|
+
*/ createFingerprint(message) {
|
|
74
|
+
// Normalize message to JSON string for consistent hashing
|
|
75
|
+
let content;
|
|
76
|
+
if (typeof message === 'string') {
|
|
77
|
+
content = message;
|
|
78
|
+
} else if (typeof message === 'object') {
|
|
79
|
+
// Sort object keys for deterministic hashing
|
|
80
|
+
content = JSON.stringify(message, Object.keys(message).sort());
|
|
81
|
+
} else {
|
|
82
|
+
content = String(message);
|
|
83
|
+
}
|
|
84
|
+
// Generate SHA256 hash
|
|
85
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
86
|
+
logger.debug('Created message fingerprint', {
|
|
87
|
+
hash: hash.substring(0, 16) + '...',
|
|
88
|
+
contentLength: content.length
|
|
89
|
+
});
|
|
90
|
+
return hash;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if message is a duplicate
|
|
94
|
+
*
|
|
95
|
+
* @param message - Message content to check
|
|
96
|
+
* @returns True if duplicate, false if unique
|
|
97
|
+
*/ async isDuplicate(message) {
|
|
98
|
+
const hash = this.createFingerprint(message);
|
|
99
|
+
const key = this.getRedisKey(hash);
|
|
100
|
+
try {
|
|
101
|
+
const exists = await withRetry(async ()=>{
|
|
102
|
+
const result = await this.redis.exists(key);
|
|
103
|
+
return result === 1;
|
|
104
|
+
}, {
|
|
105
|
+
maxAttempts: this.options.maxRetries,
|
|
106
|
+
shouldRetry: isRetryableError
|
|
107
|
+
});
|
|
108
|
+
this.stats.processed++;
|
|
109
|
+
if (exists) {
|
|
110
|
+
this.stats.duplicates++;
|
|
111
|
+
// Increment seen count
|
|
112
|
+
await this.incrementSeenCount(hash);
|
|
113
|
+
logger.debug('Duplicate message detected', {
|
|
114
|
+
hash: hash.substring(0, 16) + '...',
|
|
115
|
+
duplicateCount: this.stats.duplicates
|
|
116
|
+
});
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
this.stats.unique++;
|
|
120
|
+
return false;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
logger.error('Failed to check duplicate', error instanceof Error ? error : new Error(String(error)), {
|
|
123
|
+
hash: hash.substring(0, 16) + '...'
|
|
124
|
+
});
|
|
125
|
+
// On error, assume not duplicate to allow processing
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Mark message as processed
|
|
131
|
+
*
|
|
132
|
+
* @param message - Message content to mark
|
|
133
|
+
* @param metadata - Optional metadata to store
|
|
134
|
+
*/ async markProcessed(message, metadata) {
|
|
135
|
+
const hash = this.createFingerprint(message);
|
|
136
|
+
const key = this.getRedisKey(hash);
|
|
137
|
+
try {
|
|
138
|
+
const now = new Date();
|
|
139
|
+
const expiresAt = new Date(now.getTime() + this.options.windowMs);
|
|
140
|
+
const fingerprint = {
|
|
141
|
+
hash,
|
|
142
|
+
content: message,
|
|
143
|
+
firstSeenAt: now,
|
|
144
|
+
expiresAt,
|
|
145
|
+
seenCount: 1,
|
|
146
|
+
...metadata
|
|
147
|
+
};
|
|
148
|
+
await withRetry(async ()=>{
|
|
149
|
+
// Store fingerprint with TTL
|
|
150
|
+
await this.redis.set(key, JSON.stringify(fingerprint), {
|
|
151
|
+
PX: this.options.windowMs
|
|
152
|
+
});
|
|
153
|
+
}, {
|
|
154
|
+
maxAttempts: this.options.maxRetries,
|
|
155
|
+
shouldRetry: isRetryableError
|
|
156
|
+
});
|
|
157
|
+
logger.debug('Marked message as processed', {
|
|
158
|
+
hash: hash.substring(0, 16) + '...',
|
|
159
|
+
expiresAt: expiresAt.toISOString()
|
|
160
|
+
});
|
|
161
|
+
} catch (error) {
|
|
162
|
+
logger.error('Failed to mark message as processed', error instanceof Error ? error : new Error(String(error)), {
|
|
163
|
+
hash: hash.substring(0, 16) + '...'
|
|
164
|
+
});
|
|
165
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to mark message as processed', {
|
|
166
|
+
hash
|
|
167
|
+
}, error instanceof Error ? error : undefined);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Batch check for duplicates
|
|
172
|
+
*
|
|
173
|
+
* @param messages - Array of messages to check
|
|
174
|
+
* @returns Map of message hash to duplicate status
|
|
175
|
+
*/ async batchIsDuplicate(messages) {
|
|
176
|
+
const results = new Map();
|
|
177
|
+
try {
|
|
178
|
+
// Create fingerprints for all messages
|
|
179
|
+
const fingerprints = messages.map((msg)=>({
|
|
180
|
+
message: msg,
|
|
181
|
+
hash: this.createFingerprint(msg)
|
|
182
|
+
}));
|
|
183
|
+
// Check existence in batch using MGET
|
|
184
|
+
const keys = fingerprints.map((fp)=>this.getRedisKey(fp.hash));
|
|
185
|
+
const existsResults = await withRetry(async ()=>{
|
|
186
|
+
// Use pipeline for efficient batch operations
|
|
187
|
+
const pipeline = this.redis.multi();
|
|
188
|
+
keys.forEach((key)=>pipeline.exists(key));
|
|
189
|
+
return await pipeline.exec();
|
|
190
|
+
}, {
|
|
191
|
+
maxAttempts: this.options.maxRetries,
|
|
192
|
+
shouldRetry: isRetryableError
|
|
193
|
+
});
|
|
194
|
+
// Build results map
|
|
195
|
+
fingerprints.forEach((fp, index)=>{
|
|
196
|
+
const exists = existsResults && existsResults[index] === 1;
|
|
197
|
+
results.set(fp.hash, !!exists);
|
|
198
|
+
this.stats.processed++;
|
|
199
|
+
if (exists) {
|
|
200
|
+
this.stats.duplicates++;
|
|
201
|
+
} else {
|
|
202
|
+
this.stats.unique++;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
logger.debug('Batch duplicate check complete', {
|
|
206
|
+
totalMessages: messages.length,
|
|
207
|
+
duplicates: Array.from(results.values()).filter((v)=>v).length
|
|
208
|
+
});
|
|
209
|
+
return results;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
logger.error('Failed to batch check duplicates', error instanceof Error ? error : new Error(String(error)), {
|
|
212
|
+
messageCount: messages.length
|
|
213
|
+
});
|
|
214
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to batch check duplicates', {
|
|
215
|
+
messageCount: messages.length
|
|
216
|
+
}, error instanceof Error ? error : undefined);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Batch mark messages as processed
|
|
221
|
+
*
|
|
222
|
+
* @param messages - Array of messages to mark
|
|
223
|
+
*/ async batchMarkProcessed(messages) {
|
|
224
|
+
try {
|
|
225
|
+
const now = new Date();
|
|
226
|
+
const expiresAt = new Date(now.getTime() + this.options.windowMs);
|
|
227
|
+
// Use pipeline for efficient batch operations
|
|
228
|
+
const pipeline = this.redis.multi();
|
|
229
|
+
messages.forEach((message)=>{
|
|
230
|
+
const hash = this.createFingerprint(message);
|
|
231
|
+
const key = this.getRedisKey(hash);
|
|
232
|
+
const fingerprint = {
|
|
233
|
+
hash,
|
|
234
|
+
content: message,
|
|
235
|
+
firstSeenAt: now,
|
|
236
|
+
expiresAt,
|
|
237
|
+
seenCount: 1
|
|
238
|
+
};
|
|
239
|
+
pipeline.set(key, JSON.stringify(fingerprint), {
|
|
240
|
+
PX: this.options.windowMs
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
await withRetry(async ()=>await pipeline.exec(), {
|
|
244
|
+
maxAttempts: this.options.maxRetries,
|
|
245
|
+
shouldRetry: isRetryableError
|
|
246
|
+
});
|
|
247
|
+
logger.debug('Batch marked messages as processed', {
|
|
248
|
+
count: messages.length,
|
|
249
|
+
expiresAt: expiresAt.toISOString()
|
|
250
|
+
});
|
|
251
|
+
} catch (error) {
|
|
252
|
+
logger.error('Failed to batch mark messages', error instanceof Error ? error : new Error(String(error)), {
|
|
253
|
+
messageCount: messages.length
|
|
254
|
+
});
|
|
255
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to batch mark messages', {
|
|
256
|
+
messageCount: messages.length
|
|
257
|
+
}, error instanceof Error ? error : undefined);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get message fingerprint details
|
|
262
|
+
*
|
|
263
|
+
* @param message - Message to get fingerprint for
|
|
264
|
+
* @returns Fingerprint metadata or null if not found
|
|
265
|
+
*/ async getFingerprint(message) {
|
|
266
|
+
const hash = this.createFingerprint(message);
|
|
267
|
+
const key = this.getRedisKey(hash);
|
|
268
|
+
try {
|
|
269
|
+
const data = await withRetry(async ()=>await this.redis.get(key), {
|
|
270
|
+
maxAttempts: this.options.maxRetries,
|
|
271
|
+
shouldRetry: isRetryableError
|
|
272
|
+
});
|
|
273
|
+
if (!data) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
const fingerprint = JSON.parse(data);
|
|
277
|
+
// Convert date strings back to Date objects
|
|
278
|
+
fingerprint.firstSeenAt = new Date(fingerprint.firstSeenAt);
|
|
279
|
+
fingerprint.expiresAt = new Date(fingerprint.expiresAt);
|
|
280
|
+
return fingerprint;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
logger.error('Failed to get fingerprint', error instanceof Error ? error : new Error(String(error)), {
|
|
283
|
+
hash: hash.substring(0, 16) + '...'
|
|
284
|
+
});
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Remove message fingerprint
|
|
290
|
+
*
|
|
291
|
+
* @param message - Message to remove fingerprint for
|
|
292
|
+
*/ async removeFingerprint(message) {
|
|
293
|
+
const hash = this.createFingerprint(message);
|
|
294
|
+
const key = this.getRedisKey(hash);
|
|
295
|
+
try {
|
|
296
|
+
await withRetry(async ()=>await this.redis.del(key), {
|
|
297
|
+
maxAttempts: this.options.maxRetries,
|
|
298
|
+
shouldRetry: isRetryableError
|
|
299
|
+
});
|
|
300
|
+
logger.debug('Removed fingerprint', {
|
|
301
|
+
hash: hash.substring(0, 16) + '...'
|
|
302
|
+
});
|
|
303
|
+
} catch (error) {
|
|
304
|
+
logger.error('Failed to remove fingerprint', error instanceof Error ? error : new Error(String(error)), {
|
|
305
|
+
hash: hash.substring(0, 16) + '...'
|
|
306
|
+
});
|
|
307
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to remove fingerprint', {
|
|
308
|
+
hash
|
|
309
|
+
}, error instanceof Error ? error : undefined);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Cleanup expired fingerprints
|
|
314
|
+
*
|
|
315
|
+
* Note: Redis automatically removes expired keys, but this can be used for manual cleanup
|
|
316
|
+
*
|
|
317
|
+
* @returns Number of fingerprints cleaned up
|
|
318
|
+
*/ async cleanupExpired() {
|
|
319
|
+
try {
|
|
320
|
+
const pattern = `${this.options.keyPrefix}*`;
|
|
321
|
+
const keys = await this.redis.keys(pattern);
|
|
322
|
+
let cleanedCount = 0;
|
|
323
|
+
for (const key of keys){
|
|
324
|
+
const ttl = await this.redis.ttl(key);
|
|
325
|
+
// If TTL is -1 (no expiration) or -2 (key doesn't exist), skip
|
|
326
|
+
if (ttl === -1 || ttl === -2) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
// If TTL is 0 or negative (expired but not yet removed), delete
|
|
330
|
+
if (ttl <= 0) {
|
|
331
|
+
await this.redis.del(key);
|
|
332
|
+
cleanedCount++;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
logger.info('Cleaned up expired fingerprints', {
|
|
336
|
+
cleanedCount,
|
|
337
|
+
totalKeys: keys.length
|
|
338
|
+
});
|
|
339
|
+
return cleanedCount;
|
|
340
|
+
} catch (error) {
|
|
341
|
+
logger.error('Failed to cleanup expired fingerprints', error instanceof Error ? error : new Error(String(error)));
|
|
342
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to cleanup expired fingerprints', {}, error instanceof Error ? error : undefined);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Get deduplication statistics
|
|
347
|
+
*
|
|
348
|
+
* @returns Current statistics
|
|
349
|
+
*/ async getStats() {
|
|
350
|
+
try {
|
|
351
|
+
const pattern = `${this.options.keyPrefix}*`;
|
|
352
|
+
const keys = await this.redis.keys(pattern);
|
|
353
|
+
return {
|
|
354
|
+
totalProcessed: this.stats.processed,
|
|
355
|
+
duplicatesDetected: this.stats.duplicates,
|
|
356
|
+
uniqueMessages: this.stats.unique,
|
|
357
|
+
deduplicationRate: this.stats.processed > 0 ? this.stats.duplicates / this.stats.processed : 0,
|
|
358
|
+
activeFingerprints: keys.length
|
|
359
|
+
};
|
|
360
|
+
} catch (error) {
|
|
361
|
+
logger.error('Failed to get stats', error instanceof Error ? error : new Error(String(error)));
|
|
362
|
+
return {
|
|
363
|
+
totalProcessed: this.stats.processed,
|
|
364
|
+
duplicatesDetected: this.stats.duplicates,
|
|
365
|
+
uniqueMessages: this.stats.unique,
|
|
366
|
+
deduplicationRate: this.stats.processed > 0 ? this.stats.duplicates / this.stats.processed : 0,
|
|
367
|
+
activeFingerprints: 0
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Reset statistics
|
|
373
|
+
*/ resetStats() {
|
|
374
|
+
this.stats = {
|
|
375
|
+
processed: 0,
|
|
376
|
+
duplicates: 0,
|
|
377
|
+
unique: 0
|
|
378
|
+
};
|
|
379
|
+
logger.debug('Statistics reset');
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Stop automatic cleanup
|
|
383
|
+
*/ stopAutoCleanup() {
|
|
384
|
+
if (this.cleanupTimer) {
|
|
385
|
+
clearInterval(this.cleanupTimer);
|
|
386
|
+
this.cleanupTimer = null;
|
|
387
|
+
logger.debug('Auto cleanup stopped');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Shutdown deduplicator (stop auto cleanup)
|
|
392
|
+
*/ shutdown() {
|
|
393
|
+
this.stopAutoCleanup();
|
|
394
|
+
logger.info('MessageDeduplicator shutdown');
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get Redis key for fingerprint hash
|
|
398
|
+
*/ getRedisKey(hash) {
|
|
399
|
+
return `${this.options.keyPrefix}${hash}`;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Increment seen count for fingerprint
|
|
403
|
+
*/ async incrementSeenCount(hash) {
|
|
404
|
+
const key = this.getRedisKey(hash);
|
|
405
|
+
try {
|
|
406
|
+
const data = await this.redis.get(key);
|
|
407
|
+
if (data) {
|
|
408
|
+
const fingerprint = JSON.parse(data);
|
|
409
|
+
fingerprint.seenCount++;
|
|
410
|
+
await this.redis.set(key, JSON.stringify(fingerprint), {
|
|
411
|
+
KEEPTTL: true
|
|
412
|
+
} // Preserve existing TTL
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
} catch (error) {
|
|
416
|
+
// Log but don't throw - incrementing seen count is not critical
|
|
417
|
+
logger.debug('Failed to increment seen count', {
|
|
418
|
+
hash: hash.substring(0, 16) + '...',
|
|
419
|
+
error: error instanceof Error ? error.message : String(error)
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Start automatic cleanup timer
|
|
425
|
+
*/ startAutoCleanup() {
|
|
426
|
+
this.cleanupTimer = setInterval(async ()=>{
|
|
427
|
+
try {
|
|
428
|
+
await this.cleanupExpired();
|
|
429
|
+
} catch (error) {
|
|
430
|
+
logger.error('Auto cleanup failed', error instanceof Error ? error : new Error(String(error)));
|
|
431
|
+
}
|
|
432
|
+
}, this.options.cleanupIntervalMs);
|
|
433
|
+
logger.debug('Auto cleanup started', {
|
|
434
|
+
intervalMs: this.options.cleanupIntervalMs
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
//# sourceMappingURL=message-deduplicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/message-deduplicator.ts"],"sourcesContent":["/**\r\n * Message Deduplicator\r\n *\r\n * Provides SHA256-based message fingerprinting and deduplication for Redis queues.\r\n * Part of Task 3.4: Redis Queue Consistency & Recovery (Integration Standardization Sprint 3)\r\n *\r\n * Features:\r\n * - SHA256-based message fingerprinting\r\n * - Deduplication window (default 1 hour)\r\n * - Idempotency key tracking in Redis\r\n * - Automatic cleanup of expired keys\r\n * - Batch deduplication support\r\n *\r\n * Usage:\r\n * const deduplicator = new MessageDeduplicator(redisClient);\r\n * const isDuplicate = await deduplicator.isDuplicate(message);\r\n * if (!isDuplicate) {\r\n * await deduplicator.markProcessed(message);\r\n * }\r\n */\r\n\r\nimport * as crypto from 'crypto';\r\nimport { RedisClientType } from 'redis';\r\nimport { createLogger } from './logging.js';\r\nimport { createError, ErrorCode, isRetryableError } from './errors.js';\r\nimport { withRetry } from './retry.js';\r\n\r\nconst logger = createLogger('message-deduplicator');\r\n\r\n/**\r\n * Message deduplication options\r\n */\r\nexport interface DeduplicationOptions {\r\n /** Deduplication window in milliseconds (default: 1 hour) */\r\n windowMs?: number;\r\n /** Key prefix for Redis storage (default: 'dedup:') */\r\n keyPrefix?: string;\r\n /** Enable automatic cleanup of expired keys (default: true) */\r\n autoCleanup?: boolean;\r\n /** Cleanup interval in milliseconds (default: 5 minutes) */\r\n cleanupIntervalMs?: number;\r\n /** Maximum retry attempts for Redis operations (default: 3) */\r\n maxRetries?: number;\r\n}\r\n\r\n/**\r\n * Message fingerprint metadata\r\n */\r\nexport interface MessageFingerprint {\r\n /** SHA256 hash of message content */\r\n hash: string;\r\n /** Message content (for debugging) */\r\n content: any;\r\n /** First seen timestamp */\r\n firstSeenAt: Date;\r\n /** Expiration timestamp */\r\n expiresAt: Date;\r\n /** Number of times this message was seen */\r\n seenCount: number;\r\n}\r\n\r\n/**\r\n * Deduplication statistics\r\n */\r\nexport interface DeduplicationStats {\r\n /** Total messages processed */\r\n totalProcessed: number;\r\n /** Number of duplicates detected */\r\n duplicatesDetected: number;\r\n /** Number of unique messages */\r\n uniqueMessages: number;\r\n /** Deduplication rate (duplicates / total) */\r\n deduplicationRate: number;\r\n /** Active fingerprints in window */\r\n activeFingerprints: number;\r\n}\r\n\r\n/**\r\n * Default deduplication options\r\n */\r\nconst DEFAULT_OPTIONS: Required<DeduplicationOptions> = {\r\n windowMs: 60 * 60 * 1000, // 1 hour\r\n keyPrefix: 'dedup:',\r\n autoCleanup: true,\r\n cleanupIntervalMs: 5 * 60 * 1000, // 5 minutes\r\n maxRetries: 3,\r\n};\r\n\r\n/**\r\n * Message Deduplicator\r\n *\r\n * Provides idempotent message processing using SHA256-based fingerprinting.\r\n */\r\nexport class MessageDeduplicator {\r\n private redis: RedisClientType;\r\n private options: Required<DeduplicationOptions>;\r\n private cleanupTimer: NodeJS.Timeout | null = null;\r\n private stats: {\r\n processed: number;\r\n duplicates: number;\r\n unique: number;\r\n } = {\r\n processed: 0,\r\n duplicates: 0,\r\n unique: 0,\r\n };\r\n\r\n /**\r\n * Create a new MessageDeduplicator instance\r\n *\r\n * @param redis - Redis client instance\r\n * @param options - Deduplication options\r\n */\r\n constructor(redis: RedisClientType, options: DeduplicationOptions = {}) {\r\n this.redis = redis;\r\n this.options = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n // Start automatic cleanup if enabled\r\n if (this.options.autoCleanup) {\r\n this.startAutoCleanup();\r\n }\r\n\r\n logger.info('MessageDeduplicator initialized', {\r\n windowMs: this.options.windowMs,\r\n keyPrefix: this.options.keyPrefix,\r\n autoCleanup: this.options.autoCleanup,\r\n });\r\n }\r\n\r\n /**\r\n * Create message fingerprint (SHA256 hash)\r\n *\r\n * @param message - Message content to fingerprint\r\n * @returns SHA256 hash\r\n */\r\n public createFingerprint(message: any): string {\r\n // Normalize message to JSON string for consistent hashing\r\n let content: string;\r\n\r\n if (typeof message === 'string') {\r\n content = message;\r\n } else if (typeof message === 'object') {\r\n // Sort object keys for deterministic hashing\r\n content = JSON.stringify(message, Object.keys(message).sort());\r\n } else {\r\n content = String(message);\r\n }\r\n\r\n // Generate SHA256 hash\r\n const hash = crypto.createHash('sha256').update(content).digest('hex');\r\n\r\n logger.debug('Created message fingerprint', {\r\n hash: hash.substring(0, 16) + '...',\r\n contentLength: content.length,\r\n });\r\n\r\n return hash;\r\n }\r\n\r\n /**\r\n * Check if message is a duplicate\r\n *\r\n * @param message - Message content to check\r\n * @returns True if duplicate, false if unique\r\n */\r\n public async isDuplicate(message: any): Promise<boolean> {\r\n const hash = this.createFingerprint(message);\r\n const key = this.getRedisKey(hash);\r\n\r\n try {\r\n const exists = await withRetry(\r\n async () => {\r\n const result = await this.redis.exists(key);\r\n return result === 1;\r\n },\r\n { maxAttempts: this.options.maxRetries, shouldRetry: isRetryableError }\r\n );\r\n\r\n this.stats.processed++;\r\n\r\n if (exists) {\r\n this.stats.duplicates++;\r\n\r\n // Increment seen count\r\n await this.incrementSeenCount(hash);\r\n\r\n logger.debug('Duplicate message detected', {\r\n hash: hash.substring(0, 16) + '...',\r\n duplicateCount: this.stats.duplicates,\r\n });\r\n\r\n return true;\r\n }\r\n\r\n this.stats.unique++;\r\n return false;\r\n } catch (error) {\r\n logger.error('Failed to check duplicate', error instanceof Error ? error : new Error(String(error)), {\r\n hash: hash.substring(0, 16) + '...',\r\n });\r\n\r\n // On error, assume not duplicate to allow processing\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Mark message as processed\r\n *\r\n * @param message - Message content to mark\r\n * @param metadata - Optional metadata to store\r\n */\r\n public async markProcessed(message: any, metadata?: Record<string, any>): Promise<void> {\r\n const hash = this.createFingerprint(message);\r\n const key = this.getRedisKey(hash);\r\n\r\n try {\r\n const now = new Date();\r\n const expiresAt = new Date(now.getTime() + this.options.windowMs);\r\n\r\n const fingerprint: MessageFingerprint = {\r\n hash,\r\n content: message,\r\n firstSeenAt: now,\r\n expiresAt,\r\n seenCount: 1,\r\n ...metadata,\r\n };\r\n\r\n await withRetry(\r\n async () => {\r\n // Store fingerprint with TTL\r\n await this.redis.set(\r\n key,\r\n JSON.stringify(fingerprint),\r\n {\r\n PX: this.options.windowMs, // Expiration in milliseconds\r\n }\r\n );\r\n },\r\n { maxAttempts: this.options.maxRetries, shouldRetry: isRetryableError }\r\n );\r\n\r\n logger.debug('Marked message as processed', {\r\n hash: hash.substring(0, 16) + '...',\r\n expiresAt: expiresAt.toISOString(),\r\n });\r\n } catch (error) {\r\n logger.error('Failed to mark message as processed', error instanceof Error ? error : new Error(String(error)), {\r\n hash: hash.substring(0, 16) + '...',\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to mark message as processed',\r\n { hash },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Batch check for duplicates\r\n *\r\n * @param messages - Array of messages to check\r\n * @returns Map of message hash to duplicate status\r\n */\r\n public async batchIsDuplicate(messages: any[]): Promise<Map<string, boolean>> {\r\n const results = new Map<string, boolean>();\r\n\r\n try {\r\n // Create fingerprints for all messages\r\n const fingerprints = messages.map(msg => ({\r\n message: msg,\r\n hash: this.createFingerprint(msg),\r\n }));\r\n\r\n // Check existence in batch using MGET\r\n const keys = fingerprints.map(fp => this.getRedisKey(fp.hash));\r\n\r\n const existsResults = await withRetry(\r\n async () => {\r\n // Use pipeline for efficient batch operations\r\n const pipeline = this.redis.multi();\r\n keys.forEach(key => pipeline.exists(key));\r\n return await pipeline.exec();\r\n },\r\n { maxAttempts: this.options.maxRetries, shouldRetry: isRetryableError }\r\n );\r\n\r\n // Build results map\r\n fingerprints.forEach((fp, index) => {\r\n const exists = existsResults && existsResults[index] === 1;\r\n results.set(fp.hash, !!exists);\r\n\r\n this.stats.processed++;\r\n if (exists) {\r\n this.stats.duplicates++;\r\n } else {\r\n this.stats.unique++;\r\n }\r\n });\r\n\r\n logger.debug('Batch duplicate check complete', {\r\n totalMessages: messages.length,\r\n duplicates: Array.from(results.values()).filter(v => v).length,\r\n });\r\n\r\n return results;\r\n } catch (error) {\r\n logger.error('Failed to batch check duplicates', error instanceof Error ? error : new Error(String(error)), {\r\n messageCount: messages.length,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to batch check duplicates',\r\n { messageCount: messages.length },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Batch mark messages as processed\r\n *\r\n * @param messages - Array of messages to mark\r\n */\r\n public async batchMarkProcessed(messages: any[]): Promise<void> {\r\n try {\r\n const now = new Date();\r\n const expiresAt = new Date(now.getTime() + this.options.windowMs);\r\n\r\n // Use pipeline for efficient batch operations\r\n const pipeline = this.redis.multi();\r\n\r\n messages.forEach(message => {\r\n const hash = this.createFingerprint(message);\r\n const key = this.getRedisKey(hash);\r\n\r\n const fingerprint: MessageFingerprint = {\r\n hash,\r\n content: message,\r\n firstSeenAt: now,\r\n expiresAt,\r\n seenCount: 1,\r\n };\r\n\r\n pipeline.set(\r\n key,\r\n JSON.stringify(fingerprint),\r\n { PX: this.options.windowMs }\r\n );\r\n });\r\n\r\n await withRetry(\r\n async () => await pipeline.exec(),\r\n { maxAttempts: this.options.maxRetries, shouldRetry: isRetryableError }\r\n );\r\n\r\n logger.debug('Batch marked messages as processed', {\r\n count: messages.length,\r\n expiresAt: expiresAt.toISOString(),\r\n });\r\n } catch (error) {\r\n logger.error('Failed to batch mark messages', error instanceof Error ? error : new Error(String(error)), {\r\n messageCount: messages.length,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to batch mark messages',\r\n { messageCount: messages.length },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Get message fingerprint details\r\n *\r\n * @param message - Message to get fingerprint for\r\n * @returns Fingerprint metadata or null if not found\r\n */\r\n public async getFingerprint(message: any): Promise<MessageFingerprint | null> {\r\n const hash = this.createFingerprint(message);\r\n const key = this.getRedisKey(hash);\r\n\r\n try {\r\n const data = await withRetry(\r\n async () => await this.redis.get(key),\r\n { maxAttempts: this.options.maxRetries, shouldRetry: isRetryableError }\r\n );\r\n\r\n if (!data) {\r\n return null;\r\n }\r\n\r\n const fingerprint = JSON.parse(data) as MessageFingerprint;\r\n\r\n // Convert date strings back to Date objects\r\n fingerprint.firstSeenAt = new Date(fingerprint.firstSeenAt);\r\n fingerprint.expiresAt = new Date(fingerprint.expiresAt);\r\n\r\n return fingerprint;\r\n } catch (error) {\r\n logger.error('Failed to get fingerprint', error instanceof Error ? error : new Error(String(error)), {\r\n hash: hash.substring(0, 16) + '...',\r\n });\r\n\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Remove message fingerprint\r\n *\r\n * @param message - Message to remove fingerprint for\r\n */\r\n public async removeFingerprint(message: any): Promise<void> {\r\n const hash = this.createFingerprint(message);\r\n const key = this.getRedisKey(hash);\r\n\r\n try {\r\n await withRetry(\r\n async () => await this.redis.del(key),\r\n { maxAttempts: this.options.maxRetries, shouldRetry: isRetryableError }\r\n );\r\n\r\n logger.debug('Removed fingerprint', {\r\n hash: hash.substring(0, 16) + '...',\r\n });\r\n } catch (error) {\r\n logger.error('Failed to remove fingerprint', error instanceof Error ? error : new Error(String(error)), {\r\n hash: hash.substring(0, 16) + '...',\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to remove fingerprint',\r\n { hash },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Cleanup expired fingerprints\r\n *\r\n * Note: Redis automatically removes expired keys, but this can be used for manual cleanup\r\n *\r\n * @returns Number of fingerprints cleaned up\r\n */\r\n public async cleanupExpired(): Promise<number> {\r\n try {\r\n const pattern = `${this.options.keyPrefix}*`;\r\n const keys = await this.redis.keys(pattern);\r\n\r\n let cleanedCount = 0;\r\n\r\n for (const key of keys) {\r\n const ttl = await this.redis.ttl(key);\r\n\r\n // If TTL is -1 (no expiration) or -2 (key doesn't exist), skip\r\n if (ttl === -1 || ttl === -2) {\r\n continue;\r\n }\r\n\r\n // If TTL is 0 or negative (expired but not yet removed), delete\r\n if (ttl <= 0) {\r\n await this.redis.del(key);\r\n cleanedCount++;\r\n }\r\n }\r\n\r\n logger.info('Cleaned up expired fingerprints', {\r\n cleanedCount,\r\n totalKeys: keys.length,\r\n });\r\n\r\n return cleanedCount;\r\n } catch (error) {\r\n logger.error('Failed to cleanup expired fingerprints', error instanceof Error ? error : new Error(String(error)));\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to cleanup expired fingerprints',\r\n {},\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Get deduplication statistics\r\n *\r\n * @returns Current statistics\r\n */\r\n public async getStats(): Promise<DeduplicationStats> {\r\n try {\r\n const pattern = `${this.options.keyPrefix}*`;\r\n const keys = await this.redis.keys(pattern);\r\n\r\n return {\r\n totalProcessed: this.stats.processed,\r\n duplicatesDetected: this.stats.duplicates,\r\n uniqueMessages: this.stats.unique,\r\n deduplicationRate: this.stats.processed > 0\r\n ? this.stats.duplicates / this.stats.processed\r\n : 0,\r\n activeFingerprints: keys.length,\r\n };\r\n } catch (error) {\r\n logger.error('Failed to get stats', error instanceof Error ? error : new Error(String(error)));\r\n\r\n return {\r\n totalProcessed: this.stats.processed,\r\n duplicatesDetected: this.stats.duplicates,\r\n uniqueMessages: this.stats.unique,\r\n deduplicationRate: this.stats.processed > 0\r\n ? this.stats.duplicates / this.stats.processed\r\n : 0,\r\n activeFingerprints: 0,\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Reset statistics\r\n */\r\n public resetStats(): void {\r\n this.stats = {\r\n processed: 0,\r\n duplicates: 0,\r\n unique: 0,\r\n };\r\n\r\n logger.debug('Statistics reset');\r\n }\r\n\r\n /**\r\n * Stop automatic cleanup\r\n */\r\n public stopAutoCleanup(): void {\r\n if (this.cleanupTimer) {\r\n clearInterval(this.cleanupTimer);\r\n this.cleanupTimer = null;\r\n logger.debug('Auto cleanup stopped');\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown deduplicator (stop auto cleanup)\r\n */\r\n public shutdown(): void {\r\n this.stopAutoCleanup();\r\n logger.info('MessageDeduplicator shutdown');\r\n }\r\n\r\n /**\r\n * Get Redis key for fingerprint hash\r\n */\r\n private getRedisKey(hash: string): string {\r\n return `${this.options.keyPrefix}${hash}`;\r\n }\r\n\r\n /**\r\n * Increment seen count for fingerprint\r\n */\r\n private async incrementSeenCount(hash: string): Promise<void> {\r\n const key = this.getRedisKey(hash);\r\n\r\n try {\r\n const data = await this.redis.get(key);\r\n\r\n if (data) {\r\n const fingerprint = JSON.parse(data) as MessageFingerprint;\r\n fingerprint.seenCount++;\r\n\r\n await this.redis.set(\r\n key,\r\n JSON.stringify(fingerprint),\r\n { KEEPTTL: true } // Preserve existing TTL\r\n );\r\n }\r\n } catch (error) {\r\n // Log but don't throw - incrementing seen count is not critical\r\n logger.debug('Failed to increment seen count', {\r\n hash: hash.substring(0, 16) + '...',\r\n error: error instanceof Error ? error.message : String(error),\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Start automatic cleanup timer\r\n */\r\n private startAutoCleanup(): void {\r\n this.cleanupTimer = setInterval(\r\n async () => {\r\n try {\r\n await this.cleanupExpired();\r\n } catch (error) {\r\n logger.error('Auto cleanup failed', error instanceof Error ? error : new Error(String(error)));\r\n }\r\n },\r\n this.options.cleanupIntervalMs\r\n );\r\n\r\n logger.debug('Auto cleanup started', {\r\n intervalMs: this.options.cleanupIntervalMs,\r\n });\r\n }\r\n}\r\n"],"names":["crypto","createLogger","createError","ErrorCode","isRetryableError","withRetry","logger","DEFAULT_OPTIONS","windowMs","keyPrefix","autoCleanup","cleanupIntervalMs","maxRetries","MessageDeduplicator","redis","options","cleanupTimer","stats","processed","duplicates","unique","startAutoCleanup","info","createFingerprint","message","content","JSON","stringify","Object","keys","sort","String","hash","createHash","update","digest","debug","substring","contentLength","length","isDuplicate","key","getRedisKey","exists","result","maxAttempts","shouldRetry","incrementSeenCount","duplicateCount","error","Error","markProcessed","metadata","now","Date","expiresAt","getTime","fingerprint","firstSeenAt","seenCount","set","PX","toISOString","DB_QUERY_FAILED","undefined","batchIsDuplicate","messages","results","Map","fingerprints","map","msg","fp","existsResults","pipeline","multi","forEach","exec","index","totalMessages","Array","from","values","filter","v","messageCount","batchMarkProcessed","count","getFingerprint","data","get","parse","removeFingerprint","del","cleanupExpired","pattern","cleanedCount","ttl","totalKeys","getStats","totalProcessed","duplicatesDetected","uniqueMessages","deduplicationRate","activeFingerprints","resetStats","stopAutoCleanup","clearInterval","shutdown","KEEPTTL","setInterval","intervalMs"],"mappings":"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"}
|