claude-flow-novice 2.15.3 → 2.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (473) hide show
  1. package/.claude/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
  2. package/.claude/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
  3. package/.claude/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
  4. package/.claude/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
  5. package/.claude/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
  6. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
  7. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
  8. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
  9. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
  10. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
  11. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
  12. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
  13. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
  14. package/.claude/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
  15. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
  16. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
  17. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
  18. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
  19. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
  20. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
  21. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
  22. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
  23. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
  24. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
  25. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
  26. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
  27. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
  28. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
  29. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
  30. package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
  31. package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
  32. package/.claude/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
  33. package/.claude/commands/cfn-loop-cli.md +29 -6
  34. package/.claude/commands/switch-api.md +31 -10
  35. package/.claude/hooks/cfn-lint-sql-injection.sh +61 -0
  36. package/.claude/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
  37. package/.claude/hooks/cfn-pre-edit-security-warning.sh +40 -0
  38. package/.claude/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
  39. package/.claude/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
  40. package/.claude/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
  41. package/.claude/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
  42. package/.claude/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
  43. package/.claude/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
  44. package/.claude/skills/cfn-loop-orchestration/orchestrate.sh +238 -29
  45. package/.claude/skills/cfn-loop-orchestration/security_utils.sh +24 -0
  46. package/.claude/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
  47. package/.claude/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
  48. package/.claude/skills/cfn-redis-coordination/agent-log.sh +4 -0
  49. package/.claude/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
  50. package/.claude/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
  51. package/.claude/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
  52. package/.claude/skills/cfn-redis-coordination/get-context.sh +33 -0
  53. package/.claude/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
  54. package/.claude/skills/cfn-redis-coordination/invoke-waiting-mode.sh +6 -2
  55. package/.claude/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
  56. package/.claude/skills/cfn-redis-coordination/redis-functions.sh +34 -0
  57. package/.claude/skills/cfn-redis-coordination/report-completion.sh +24 -31
  58. package/.claude/skills/cfn-redis-coordination/store-context.sh +4 -0
  59. package/.claude/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
  60. package/.claude/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
  61. package/.claude/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
  62. package/.claude/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
  63. package/.claude/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
  64. package/README.md +116 -475
  65. package/claude-assets/agents/cfn-dev-team/README.md +103 -0
  66. package/claude-assets/agents/cfn-dev-team/architecture/goal-planner.md +1 -1
  67. package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +77 -15
  68. package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +355 -6
  69. package/claude-assets/agents/cfn-dev-team/coordinators/consensus-builder.md +82 -1
  70. package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +82 -1
  71. package/claude-assets/agents/cfn-dev-team/coordinators/multi-sprint-coordinator.md +77 -15
  72. package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +99 -12
  73. package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +1 -1
  74. package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +97 -0
  75. package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +20 -1
  76. package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +97 -0
  77. package/claude-assets/agents/cfn-dev-team/developers/backend-developer.md +110 -13
  78. package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +106 -15
  79. package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +115 -11
  80. package/claude-assets/agents/cfn-dev-team/developers/frontend/mobile-dev.md +94 -7
  81. package/claude-assets/agents/cfn-dev-team/developers/frontend/react-frontend-engineer.md +87 -9
  82. package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +85 -7
  83. package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +160 -28
  84. package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +101 -19
  85. package/claude-assets/agents/cfn-dev-team/developers/rust-developer.md +108 -14
  86. package/claude-assets/agents/cfn-dev-team/reviewers/{reviewer.md → code-reviewer.md} +95 -8
  87. package/claude-assets/agents/cfn-dev-team/reviewers/quality/code-quality-validator.md +107 -7
  88. package/claude-assets/agents/cfn-dev-team/reviewers/quality/perf-analyzer.md +98 -7
  89. package/claude-assets/agents/cfn-dev-team/reviewers/quality/performance-benchmarker.md +95 -7
  90. package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +136 -9
  91. package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +108 -1
  92. package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +107 -13
  93. package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +737 -0
  94. package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
  95. package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +828 -0
  96. package/claude-assets/agents/cfn-dev-team/testers/interaction-tester.md +106 -7
  97. package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +77 -0
  98. package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +684 -0
  99. package/claude-assets/agents/cfn-dev-team/testers/playwright-tester.md +110 -1
  100. package/claude-assets/agents/cfn-dev-team/testers/tester.md +94 -7
  101. package/claude-assets/agents/cfn-dev-team/utility/code-booster.md +1 -3
  102. package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +87 -13
  103. package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +103 -7
  104. package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -3
  105. package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +94 -7
  106. package/claude-assets/agents/docker-coordinators/cfn-docker-v3-coordinator.md +46 -0
  107. package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
  108. package/claude-assets/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
  109. package/claude-assets/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
  110. package/claude-assets/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
  111. package/claude-assets/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
  112. package/claude-assets/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
  113. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
  114. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
  115. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
  116. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
  117. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
  118. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
  119. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
  120. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
  121. package/claude-assets/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
  122. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
  123. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
  124. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
  125. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
  126. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
  127. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
  128. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
  129. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
  130. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
  131. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
  132. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
  133. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
  134. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
  135. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
  136. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
  137. package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
  138. package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
  139. package/claude-assets/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
  140. package/claude-assets/commands/cfn-loop-cli.md +29 -6
  141. package/claude-assets/commands/switch-api.md +31 -10
  142. package/claude-assets/hooks/cfn-lint-sql-injection.sh +61 -0
  143. package/claude-assets/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
  144. package/claude-assets/hooks/cfn-pre-edit-security-warning.sh +40 -0
  145. package/claude-assets/hooks/detect-hardcoded-credentials.sh +212 -0
  146. package/claude-assets/skills/SKILL_TEMPLATE.md +774 -0
  147. package/claude-assets/skills/agent-lifecycle/execute-lifecycle-hook.sh +84 -113
  148. package/claude-assets/skills/agent-lifecycle/simple-audit.sh +33 -6
  149. package/claude-assets/skills/agent-template-generator/SKILL.md +440 -0
  150. package/claude-assets/skills/agent-template-generator/generate-agent.sh +405 -0
  151. package/claude-assets/skills/agent-validation-linter/SKILL.md +589 -0
  152. package/claude-assets/skills/agent-validation-linter/lint-agents.sh +271 -0
  153. package/claude-assets/skills/bootstrap/bash-fundamentals.md +786 -0
  154. package/claude-assets/skills/bootstrap/database-connection.md +464 -0
  155. package/claude-assets/skills/bootstrap/error-handling.md +580 -0
  156. package/claude-assets/skills/bootstrap/file-operations.md +699 -0
  157. package/claude-assets/skills/bootstrap/skill-loader.md +616 -0
  158. package/claude-assets/skills/bootstrap/sqlite-params.sh +287 -0
  159. package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
  160. package/claude-assets/skills/cfn-automatic-memory-persistence/test-memory-persistence.sh +17 -16
  161. package/claude-assets/skills/cfn-deployment/SKILL.md +293 -0
  162. package/claude-assets/skills/cfn-deployment/execute.sh +21 -0
  163. package/claude-assets/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
  164. package/claude-assets/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
  165. package/claude-assets/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
  166. package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +38 -0
  167. package/claude-assets/skills/cfn-error-batching-strategy/lib/core-functions.sh +47 -47
  168. package/claude-assets/skills/cfn-file-operations/SKILL.md +290 -0
  169. package/claude-assets/skills/cfn-file-operations/execute.sh +129 -0
  170. package/claude-assets/skills/cfn-file-operations/lib/atomic-write.sh +294 -0
  171. package/claude-assets/skills/cfn-file-operations/lib/lock.sh +361 -0
  172. package/claude-assets/skills/cfn-file-operations/test.sh +369 -0
  173. package/claude-assets/skills/cfn-log-operations/SKILL.md +308 -0
  174. package/claude-assets/skills/cfn-log-operations/execute.sh +420 -0
  175. package/claude-assets/skills/cfn-log-operations/lib/rotate.sh +406 -0
  176. package/claude-assets/skills/cfn-log-operations/lib/search.sh +448 -0
  177. package/claude-assets/skills/cfn-log-operations/test.sh +394 -0
  178. package/claude-assets/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
  179. package/claude-assets/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
  180. package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh +238 -29
  181. package/claude-assets/skills/cfn-loop-orchestration/security_utils.sh +24 -0
  182. package/claude-assets/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
  183. package/claude-assets/skills/cfn-parameterized-queries/SKILL.md +339 -0
  184. package/claude-assets/skills/cfn-playbook/query-playbook.sh +19 -15
  185. package/claude-assets/skills/cfn-playbook/update-playbook.sh +25 -14
  186. package/claude-assets/skills/cfn-process-instrumentation/instrument-process.sh +44 -0
  187. package/claude-assets/skills/cfn-promotion/SKILL.md +305 -0
  188. package/claude-assets/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
  189. package/claude-assets/skills/cfn-redis-coordination/agent-log.sh +4 -0
  190. package/claude-assets/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
  191. package/claude-assets/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
  192. package/claude-assets/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
  193. package/claude-assets/skills/cfn-redis-coordination/get-context.sh +33 -0
  194. package/claude-assets/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
  195. package/claude-assets/skills/cfn-redis-coordination/invoke-waiting-mode.sh +6 -2
  196. package/claude-assets/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
  197. package/claude-assets/skills/cfn-redis-coordination/redis-functions.sh +34 -0
  198. package/claude-assets/skills/cfn-redis-coordination/report-completion.sh +24 -31
  199. package/claude-assets/skills/cfn-redis-coordination/store-context.sh +4 -0
  200. package/claude-assets/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
  201. package/claude-assets/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
  202. package/claude-assets/skills/cfn-skill-loader/SKILL.md +466 -0
  203. package/claude-assets/skills/cfn-skill-loader/execute.sh +344 -0
  204. package/claude-assets/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
  205. package/claude-assets/skills/cfn-task-audit/get-audit-data.sh +42 -21
  206. package/claude-assets/skills/cfn-task-audit/store-task-audit.sh +17 -10
  207. package/claude-assets/skills/cfn-test-runner/detect-regressions.sh +17 -14
  208. package/claude-assets/skills/cfn-test-runner/detect-regressions.sh.backup-1763392821 +55 -0
  209. package/claude-assets/skills/cfn-test-runner/store-benchmarks.sh +17 -19
  210. package/claude-assets/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
  211. package/claude-assets/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
  212. package/claude-assets/skills/cfn-utilities/SKILL.md +237 -0
  213. package/claude-assets/skills/cfn-utilities/execute.sh +32 -0
  214. package/claude-assets/skills/cfn-utilities/lib/errors.sh +56 -0
  215. package/claude-assets/skills/cfn-utilities/lib/file-ops.sh +164 -0
  216. package/claude-assets/skills/cfn-utilities/lib/logging.sh +77 -0
  217. package/claude-assets/skills/cfn-utilities/lib/retry.sh +127 -0
  218. package/claude-assets/skills/cfn-utilities/test.sh +317 -0
  219. package/claude-assets/skills/integration/agent-handoff.sh +62 -64
  220. package/claude-assets/skills/json-validation/SKILL.md +431 -0
  221. package/claude-assets/skills/json-validation/test-validate-success-criteria.sh +421 -0
  222. package/claude-assets/skills/json-validation/validate-success-criteria.sh +197 -0
  223. package/claude-assets/skills/redis-coordination/validate-parameters.sh +34 -0
  224. package/claude-assets/skills/workflow-codification/DEPLOY_QUICK_REFERENCE.md +106 -0
  225. package/claude-assets/skills/workflow-codification/PROPAGATE_UPDATE_QUICK_REFERENCE.md +366 -0
  226. package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh +481 -0
  227. package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh.backup-1763392820 +512 -0
  228. package/claude-assets/skills/workflow-codification/lib/security-utils.sh +204 -0
  229. package/claude-assets/skills/workflow-codification/propagate-skill-update.sh +648 -0
  230. package/claude-assets/skills/workflow-codification/propagate-skill-update.sh.backup-1763392820 +664 -0
  231. package/claude-assets/skills/workflow-codification/test-integration.sh +15 -0
  232. package/claude-assets/skills/workflow-codification/test-metadata-update.sh +350 -0
  233. package/claude-assets/skills/workflow-codification/track-cost-savings.sh +55 -14
  234. package/claude-assets/skills/workflow-codification/track-cost-savings.sh.backup-1763392821 +445 -0
  235. package/claude-assets/skills/workflow-codification/track-edge-case.sh +27 -60
  236. package/claude-assets/skills/workflow-codification/workflow-codification.db +0 -0
  237. package/dist/ace/ace-curator.js +10 -2
  238. package/dist/ace/ace-curator.js.map +1 -1
  239. package/dist/ace/ace-generator.js +4 -0
  240. package/dist/ace/ace-generator.js.map +1 -1
  241. package/dist/ace/ace-reflector.js +1 -1
  242. package/dist/ace/ace-reflector.js.map +1 -1
  243. package/dist/ace/context-injection.js +24 -2
  244. package/dist/ace/context-injection.js.map +1 -1
  245. package/dist/agents/task-agent-integration.js +1 -1
  246. package/dist/agents/task-agent-integration.js.map +1 -1
  247. package/dist/api/health-endpoints.js +390 -0
  248. package/dist/api/health-endpoints.js.map +1 -0
  249. package/dist/cli/agent-executor.js +4 -1
  250. package/dist/cli/agent-executor.js.map +1 -1
  251. package/dist/cli/agent-prompt-builder.js +89 -1
  252. package/dist/cli/agent-prompt-builder.js.map +1 -1
  253. package/dist/cli/agent-spawn.js +130 -37
  254. package/dist/cli/agent-spawn.js.map +1 -1
  255. package/dist/cli/config-manager.js +109 -91
  256. package/dist/cli/config-manager.js.map +1 -1
  257. package/dist/cli/conversation-fork-cleanup.js +201 -0
  258. package/dist/cli/conversation-fork-cleanup.js.map +1 -0
  259. package/dist/cli/conversation-fork.js +16 -3
  260. package/dist/cli/conversation-fork.js.map +1 -1
  261. package/dist/cli/skill-cache-validator.js +412 -0
  262. package/dist/cli/skill-cache-validator.js.map +1 -0
  263. package/dist/cli/skill-cli.js +991 -0
  264. package/dist/cli/skill-cli.js.map +1 -0
  265. package/dist/cli/skill-execution-logger.js +284 -0
  266. package/dist/cli/skill-execution-logger.js.map +1 -0
  267. package/dist/cli/skill-loader.js +457 -0
  268. package/dist/cli/skill-loader.js.map +1 -0
  269. package/dist/coordination/event-bus.js +2 -2
  270. package/dist/coordination/event-bus.js.map +1 -1
  271. package/dist/coordination/fleet-manager.js +1 -1
  272. package/dist/coordination/fleet-manager.js.map +1 -1
  273. package/dist/coordination/index.js +23 -9
  274. package/dist/coordination/index.js.map +1 -1
  275. package/dist/coordination/types/fleet-manager.types.js.map +1 -1
  276. package/dist/db/migration-manager.js +483 -0
  277. package/dist/db/migration-manager.js.map +1 -0
  278. package/dist/db/skills-query.js +535 -0
  279. package/dist/db/skills-query.js.map +1 -0
  280. package/dist/integration/DatabaseHandoff.js +1 -1
  281. package/dist/integration/DatabaseHandoff.js.map +1 -1
  282. package/dist/jobs/edge-case-analyzer.js +367 -0
  283. package/dist/jobs/edge-case-analyzer.js.map +1 -0
  284. package/dist/jobs/promotion-sla-enforcer.js +288 -0
  285. package/dist/jobs/promotion-sla-enforcer.js.map +1 -0
  286. package/dist/lib/agent-output-parser.js.map +1 -1
  287. package/dist/lib/agent-output-validator.js.map +1 -1
  288. package/dist/lib/agent-workspace.js +281 -0
  289. package/dist/lib/agent-workspace.js.map +1 -0
  290. package/dist/lib/atomic-file-writer.js +377 -0
  291. package/dist/lib/atomic-file-writer.js.map +1 -0
  292. package/dist/lib/backup-manager.js +779 -0
  293. package/dist/lib/backup-manager.js.map +1 -0
  294. package/dist/lib/checkpoint-manager.js +837 -0
  295. package/dist/lib/checkpoint-manager.js.map +1 -0
  296. package/dist/lib/circuit-breaker.js +340 -0
  297. package/dist/lib/circuit-breaker.js.map +1 -0
  298. package/dist/lib/completion-signal-handler.js +243 -0
  299. package/dist/lib/completion-signal-handler.js.map +1 -0
  300. package/dist/lib/config-manager.js +312 -0
  301. package/dist/lib/config-manager.js.map +1 -0
  302. package/dist/lib/config-migrator.js +386 -0
  303. package/dist/lib/config-migrator.js.map +1 -0
  304. package/dist/lib/config-validator.js.map +1 -1
  305. package/dist/lib/correlation-cache.js +311 -0
  306. package/dist/lib/correlation-cache.js.map +1 -0
  307. package/dist/lib/correlation.js +263 -0
  308. package/dist/lib/correlation.js.map +1 -0
  309. package/dist/lib/database-service/connection-pool-manager.js +520 -0
  310. package/dist/lib/database-service/connection-pool-manager.js.map +1 -0
  311. package/dist/lib/database-service/correlation.js +329 -0
  312. package/dist/lib/database-service/correlation.js.map +1 -0
  313. package/dist/lib/database-service/errors.js +120 -0
  314. package/dist/lib/database-service/errors.js.map +1 -0
  315. package/dist/lib/database-service/index.js +168 -0
  316. package/dist/lib/database-service/index.js.map +1 -0
  317. package/dist/lib/database-service/postgres-adapter.js +526 -0
  318. package/dist/lib/database-service/postgres-adapter.js.map +1 -0
  319. package/dist/lib/database-service/redis-adapter.js +360 -0
  320. package/dist/lib/database-service/redis-adapter.js.map +1 -0
  321. package/dist/lib/database-service/sqlite-adapter.js +544 -0
  322. package/dist/lib/database-service/sqlite-adapter.js.map +1 -0
  323. package/dist/lib/database-service/transaction-manager.js +773 -0
  324. package/dist/lib/database-service/transaction-manager.js.map +1 -0
  325. package/dist/lib/database-service/types.js +23 -0
  326. package/dist/lib/database-service/types.js.map +1 -0
  327. package/dist/lib/deadlock-resolver.js +292 -0
  328. package/dist/lib/deadlock-resolver.js.map +1 -0
  329. package/dist/lib/distributed-lock.js +451 -0
  330. package/dist/lib/distributed-lock.js.map +1 -0
  331. package/dist/lib/edge-case-deduplicator.js +227 -0
  332. package/dist/lib/edge-case-deduplicator.js.map +1 -0
  333. package/dist/lib/encryption-manager.js +322 -0
  334. package/dist/lib/encryption-manager.js.map +1 -0
  335. package/dist/lib/error-aggregator.js +234 -0
  336. package/dist/lib/error-aggregator.js.map +1 -0
  337. package/dist/lib/errors.js +287 -0
  338. package/dist/lib/errors.js.map +1 -0
  339. package/dist/lib/file-lock-manager.js +578 -0
  340. package/dist/lib/file-lock-manager.js.map +1 -0
  341. package/dist/lib/file-operations.js +367 -0
  342. package/dist/lib/file-operations.js.map +1 -0
  343. package/dist/lib/idempotent-write.js +237 -0
  344. package/dist/lib/idempotent-write.js.map +1 -0
  345. package/dist/lib/integration-schema-validator.js +522 -0
  346. package/dist/lib/integration-schema-validator.js.map +1 -0
  347. package/dist/lib/lock-health-monitor.js +298 -0
  348. package/dist/lib/lock-health-monitor.js.map +1 -0
  349. package/dist/lib/log-shipper.js +422 -0
  350. package/dist/lib/log-shipper.js.map +1 -0
  351. package/dist/lib/logging.js +146 -0
  352. package/dist/lib/logging.js.map +1 -0
  353. package/dist/lib/message-deduplicator.js +439 -0
  354. package/dist/lib/message-deduplicator.js.map +1 -0
  355. package/dist/lib/multi-system-query.js +604 -0
  356. package/dist/lib/multi-system-query.js.map +1 -0
  357. package/dist/lib/orphan-detector.js +332 -0
  358. package/dist/lib/orphan-detector.js.map +1 -0
  359. package/dist/lib/password-generator.js +166 -0
  360. package/dist/lib/password-generator.js.map +1 -0
  361. package/dist/lib/path-validator.js +429 -0
  362. package/dist/lib/path-validator.js.map +1 -0
  363. package/dist/lib/query-translator.js +905 -0
  364. package/dist/lib/query-translator.js.map +1 -0
  365. package/dist/lib/queue-recovery.js +469 -0
  366. package/dist/lib/queue-recovery.js.map +1 -0
  367. package/dist/lib/redis-queue-manager.js +512 -0
  368. package/dist/lib/redis-queue-manager.js.map +1 -0
  369. package/dist/lib/reflection-archiver.js +272 -0
  370. package/dist/lib/reflection-archiver.js.map +1 -0
  371. package/dist/lib/retry-manager.js +453 -0
  372. package/dist/lib/retry-manager.js.map +1 -0
  373. package/dist/lib/retry.js +262 -0
  374. package/dist/lib/retry.js.map +1 -0
  375. package/dist/lib/schema-transform.js +695 -0
  376. package/dist/lib/schema-transform.js.map +1 -0
  377. package/dist/lib/schema-validator.js +491 -0
  378. package/dist/lib/schema-validator.js.map +1 -0
  379. package/dist/lib/skill-cache.js +297 -0
  380. package/dist/lib/skill-cache.js.map +1 -0
  381. package/dist/lib/skill-content-manager.js +337 -0
  382. package/dist/lib/skill-content-manager.js.map +1 -0
  383. package/dist/lib/skill-frontmatter-parser.js +237 -0
  384. package/dist/lib/skill-frontmatter-parser.js.map +1 -0
  385. package/dist/lib/skill-git-integration.js +275 -0
  386. package/dist/lib/skill-git-integration.js.map +1 -0
  387. package/dist/lib/skill-markdown-validator.js +396 -0
  388. package/dist/lib/skill-markdown-validator.js.map +1 -0
  389. package/dist/lib/skill-output-parser.js +312 -0
  390. package/dist/lib/skill-output-parser.js.map +1 -0
  391. package/dist/lib/unified-query-api.js +467 -0
  392. package/dist/lib/unified-query-api.js.map +1 -0
  393. package/dist/middleware/auth-middleware.js +350 -0
  394. package/dist/middleware/auth-middleware.js.map +1 -0
  395. package/dist/middleware/schema-validation.js +347 -0
  396. package/dist/middleware/schema-validation.js.map +1 -0
  397. package/dist/providers/anthropic-provider.js +1 -1
  398. package/dist/providers/anthropic-provider.js.map +1 -1
  399. package/dist/providers/provider-factory.js +2 -2
  400. package/dist/providers/provider-factory.js.map +1 -1
  401. package/dist/services/edge-case-analyzer.js +321 -0
  402. package/dist/services/edge-case-analyzer.js.map +1 -0
  403. package/dist/services/edge-case-deduplicator.js +266 -0
  404. package/dist/services/edge-case-deduplicator.js.map +1 -0
  405. package/dist/services/edge-case-detector.js +337 -0
  406. package/dist/services/edge-case-detector.js.map +1 -0
  407. package/dist/services/edge-case-tracker.js +547 -0
  408. package/dist/services/edge-case-tracker.js.map +1 -0
  409. package/dist/services/health-check-system.js +586 -0
  410. package/dist/services/health-check-system.js.map +1 -0
  411. package/dist/services/metrics-logger.js +412 -0
  412. package/dist/services/metrics-logger.js.map +1 -0
  413. package/dist/services/patch-generator.js +378 -0
  414. package/dist/services/patch-generator.js.map +1 -0
  415. package/dist/services/patch-validator.js +337 -0
  416. package/dist/services/patch-validator.js.map +1 -0
  417. package/dist/services/performance-monitor.js +811 -0
  418. package/dist/services/performance-monitor.js.map +1 -0
  419. package/dist/services/promotion-pipeline.js +918 -0
  420. package/dist/services/promotion-pipeline.js.map +1 -0
  421. package/dist/services/promotion-validator.js +394 -0
  422. package/dist/services/promotion-validator.js.map +1 -0
  423. package/dist/services/reflection-logger.js +388 -0
  424. package/dist/services/reflection-logger.js.map +1 -0
  425. package/dist/services/skill-deployment.js +472 -0
  426. package/dist/services/skill-deployment.js.map +1 -0
  427. package/dist/services/skill-loader.js +427 -0
  428. package/dist/services/skill-loader.js.map +1 -0
  429. package/dist/services/skill-promotion.js +372 -0
  430. package/dist/services/skill-promotion.js.map +1 -0
  431. package/dist/services/skill-validator.js +454 -0
  432. package/dist/services/skill-validator.js.map +1 -0
  433. package/dist/services/skill-versioning.js +244 -0
  434. package/dist/services/skill-versioning.js.map +1 -0
  435. package/dist/services/workspace-supervisor.js +597 -0
  436. package/dist/services/workspace-supervisor.js.map +1 -0
  437. package/dist/types/edge-case.js +45 -0
  438. package/dist/types/edge-case.js.map +1 -0
  439. package/docs/BUG_19_MEMORY_LEAK_TASK_MODE.md +405 -0
  440. package/docs/MEMORY_CLEANUP_GUIDE.md +358 -0
  441. package/docs/MEMORY_LEAK_FIX_SUMMARY.md +322 -0
  442. package/docs/REDIS_CLEANUP_EXECUTIVE_SUMMARY.md +319 -0
  443. package/docs/REDIS_CLEANUP_VERIFICATION_REPORT.md +574 -0
  444. package/package.json +35 -4
  445. package/readme/README.md +53 -5
  446. package/scripts/backup-cleanup.sh +627 -0
  447. package/scripts/cleanup-workspaces.sh +412 -0
  448. package/scripts/cleanup-yaml-configs.sh +141 -0
  449. package/scripts/deploy-approved-skills.sh +263 -0
  450. package/scripts/health-check.sh +447 -0
  451. package/scripts/log-aggregator.sh +554 -0
  452. package/scripts/log-monitor.sh +629 -0
  453. package/scripts/manage-agent-workspaces.sh +434 -0
  454. package/scripts/migrate-schema.sh +533 -0
  455. package/scripts/promote-staged-skills.sh +423 -0
  456. package/scripts/verify-no-secrets.sh +88 -35
  457. package/scripts/verify-redis-cleanup.sh +173 -0
  458. package/tests/README.md +84 -0
  459. package/tests/test-memory-leak-task-mode.sh +435 -0
  460. package/.claude/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
  461. package/.claude/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
  462. package/.claude/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
  463. package/.claude/skills/agent-lifecycle/SKILL.md +0 -60
  464. package/.claude/skills/agent-lifecycle/execute-lifecycle-hook.sh +0 -573
  465. package/.claude/skills/agent-lifecycle/simple-audit.sh +0 -31
  466. package/.claude/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
  467. package/.claude/skills/cfn-loop-orchestration/orchestrate.sh.backup +0 -949
  468. package/README.md.backup_before_replace +0 -781
  469. package/claude-assets/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
  470. package/claude-assets/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
  471. package/claude-assets/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
  472. package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
  473. 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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"}