claude-flow-novice 2.15.10 → 2.16.0
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/agents/cfn-v3-coordinator.md +517 -0
- package/.claude/commands/cfn-loop-cli.md +158 -464
- package/.claude/commands/cfn-loop-trigger.md +114 -0
- package/.claude/hooks/cfn-invoke-post-edit-ts.sh +100 -0
- package/.claude/hooks/cfn-invoke-post-edit-ts.sh.backup +78 -0
- package/.claude/hooks/cfn-invoke-post-edit.sh +22 -0
- package/.claude/hooks/cfn-invoke-post-edit.sh.backup +87 -0
- package/.claude/hooks/cfn-invoke-pre-edit-ts.sh +116 -0
- package/.claude/hooks/cfn-invoke-pre-edit-ts.sh.backup +94 -0
- package/.claude/hooks/cfn-invoke-pre-edit.sh +22 -0
- package/.claude/hooks/cfn-invoke-pre-edit.sh.backup +88 -0
- package/.claude/skills/cfn-agent-spawning/SKILL.md +48 -1
- package/.claude/skills/cfn-agent-spawning/SKILL.md.backup +135 -0
- package/.claude/skills/cfn-agent-spawning/TYPESCRIPT_MIGRATION.md +567 -0
- package/.claude/skills/cfn-agent-spawning/check-dependencies.sh +22 -0
- package/.claude/skills/{cfn-redis-coordination/check-dependencies.sh → cfn-agent-spawning/check-dependencies.sh.backup} +3 -5
- package/.claude/skills/cfn-agent-spawning/get-agent-provider-env.sh +22 -0
- package/.claude/skills/cfn-agent-spawning/get-agent-provider-env.sh.backup +127 -0
- package/.claude/skills/cfn-agent-spawning/parse-agent-provider.sh +22 -0
- package/.claude/skills/cfn-agent-spawning/parse-agent-provider.sh.backup +59 -0
- package/.claude/skills/cfn-agent-spawning/spawn-agent-wrapper.sh +63 -0
- package/.claude/skills/cfn-agent-spawning/spawn-agent-wrapper.sh.backup +41 -0
- package/.claude/skills/cfn-agent-spawning/spawn-agent.sh +26 -1
- package/.claude/skills/cfn-agent-spawning/spawn-templates.sh +22 -0
- package/.claude/skills/cfn-agent-spawning/spawn-templates.sh.backup +613 -0
- package/.claude/skills/cfn-agent-spawning/spawn-worker.sh +22 -0
- package/.claude/skills/cfn-agent-spawning/spawn-worker.sh.backup +176 -0
- package/.claude/skills/cfn-loop-orchestration/.backups/unknown/1763619700_33aff4a69b99159e4e849107ebc4d09f/metadata.json +8 -0
- package/.claude/skills/cfn-loop-orchestration/.backups/unknown/1763619700_33aff4a69b99159e4e849107ebc4d09f/original +271 -0
- package/.claude/skills/cfn-loop-orchestration/.backups/unknown/1763619700_33aff4a69b99159e4e849107ebc4d09f/revert.sh +7 -0
- package/.claude/skills/cfn-loop-orchestration/.backups/unknown/1763671642_06496e8c399a79db08167cc00ed4b31e/metadata.json +8 -0
- package/.claude/skills/cfn-loop-orchestration/.backups/unknown/1763671642_06496e8c399a79db08167cc00ed4b31e/original +325 -0
- package/.claude/skills/cfn-loop-orchestration/.backups/unknown/1763671642_06496e8c399a79db08167cc00ed4b31e/revert.sh +7 -0
- package/.claude/skills/cfn-loop-orchestration/CLI_IMPLEMENTATION_SUMMARY.md +330 -0
- package/.claude/skills/cfn-loop-orchestration/CONFIGURATION_IMPROVEMENTS.md +318 -0
- package/.claude/skills/cfn-loop-orchestration/CONTEXT_LOOKUP_MIGRATION.md +308 -0
- package/.claude/skills/cfn-loop-orchestration/CONTEXT_LOOKUP_QUICK_START.md +378 -0
- package/.claude/skills/cfn-loop-orchestration/E2E_VALIDATION_REPORT.md +262 -0
- package/.claude/skills/cfn-loop-orchestration/IMPLEMENTATION_SUMMARY.md +319 -519
- package/.claude/skills/cfn-loop-orchestration/NORTH_STAR_E2E_REPORT.md +299 -0
- package/.claude/skills/cfn-loop-orchestration/NORTH_STAR_EXECUTION_SUMMARY.md +403 -0
- package/.claude/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +323 -0
- package/.claude/skills/cfn-loop-orchestration/SKILL.md +159 -48
- package/.claude/skills/cfn-loop-orchestration/SPAWN_AGENTS_IMPLEMENTATION.md +188 -0
- package/.claude/skills/cfn-loop-orchestration/TEST_COVERAGE_REPORT.md +335 -0
- package/.claude/skills/cfn-loop-orchestration/TEST_COVERAGE_SUMMARY.md +456 -0
- package/.claude/skills/cfn-loop-orchestration/TYPESCRIPT_INTEGRATION_REPORT.md +709 -0
- package/.claude/skills/cfn-loop-orchestration/TYPESCRIPT_INTEGRATION_SUMMARY.md +257 -0
- package/.claude/skills/cfn-loop-orchestration/VALIDATION_REPORT.md +572 -0
- package/.claude/skills/cfn-loop-orchestration/VALIDATION_SUMMARY.txt +196 -0
- package/.claude/skills/cfn-loop-orchestration/VALIDATOR_MODULE_GUIDE.md +526 -0
- package/.claude/skills/cfn-loop-orchestration/archive/legacy-bash/README.md +167 -0
- package/.claude/skills/cfn-loop-orchestration/archive/legacy-bash/orchestrate-enhanced.sh +548 -0
- package/{claude-assets/skills/cfn-loop-orchestration → .claude/skills/cfn-loop-orchestration/archive/legacy-bash}/orchestrate-wrapper.sh +11 -1
- package/.claude/skills/cfn-loop-orchestration/archive/legacy-bash/orchestrate.sh +182 -0
- package/.claude/skills/cfn-loop-orchestration/e2e-validation-fixed.js +240 -0
- package/.claude/skills/cfn-loop-orchestration/e2e-validation.js +213 -0
- package/.claude/skills/cfn-loop-orchestration/package-lock.json +3 -0
- package/.claude/skills/cfn-loop-orchestration/package.json +4 -0
- package/.claude/skills/cfn-loop-orchestration/run-north-star-e2e.ts +210 -0
- package/.claude/skills/cfn-loop-orchestration/src/cli/orchestrator-cli.ts +396 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/CONFIDENCE_AGGREGATOR.md +564 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/CONFIDENCE_AGGREGATOR_QUICK_REF.md +241 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/CONTEXT_INJECTOR_IMPLEMENTATION.md +375 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/CONTEXT_INJECTOR_QUICK_REFERENCE.md +362 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/CONTEXT_INJECTOR_README.md +307 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/CONTEXT_INJECTOR_USAGE_GUIDE.md +508 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/confidence-aggregator.ts +473 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/consensus.ts +1 -1
- package/.claude/skills/cfn-loop-orchestration/src/helpers/context-injector.ts +349 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/context-lookup.ts +486 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/deliverable-verifier.ts +6 -2
- package/.claude/skills/cfn-loop-orchestration/src/helpers/gate-check.ts +1 -1
- package/.claude/skills/cfn-loop-orchestration/src/helpers/product-owner-decision.ts +316 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/spawn-agents.ts +357 -0
- package/.claude/skills/cfn-loop-orchestration/src/helpers/validator.ts +276 -0
- package/.claude/skills/cfn-loop-orchestration/src/index.ts +2 -0
- package/.claude/skills/cfn-loop-orchestration/src/orchestrate.ts +743 -2
- package/.claude/skills/cfn-loop-orchestration/src/types.ts +56 -0
- package/.claude/skills/cfn-loop-orchestration/test-cli.sh +92 -0
- package/.claude/skills/cfn-loop-orchestration/test-typescript-integration.sh +442 -0
- package/.claude/skills/cfn-loop-orchestration/tests/agent-spawner.test.ts +124 -0
- package/.claude/skills/cfn-loop-orchestration/tests/confidence-aggregator.test.ts +604 -0
- package/.claude/skills/cfn-loop-orchestration/tests/context-injector.test.ts +561 -0
- package/.claude/skills/cfn-loop-orchestration/tests/context-lookup.test.ts +661 -0
- package/.claude/skills/cfn-loop-orchestration/tests/deliverable-verifier.test.ts +2 -2
- package/.claude/skills/cfn-loop-orchestration/tests/gate-check-edge-cases.test.ts +422 -0
- package/.claude/skills/cfn-loop-orchestration/tests/gate-checker.test.ts +276 -0
- package/.claude/skills/cfn-loop-orchestration/tests/logger.test.ts +291 -0
- package/.claude/skills/cfn-loop-orchestration/tests/north-star-e2e.test.ts +334 -0
- package/.claude/skills/cfn-loop-orchestration/tests/redis-coordinator.test.ts +321 -0
- package/.claude/skills/cfn-loop-orchestration/tests/spawn-agents.test.ts +284 -0
- package/.claude/skills/cfn-loop-orchestration/tests/validator.test.ts +643 -0
- package/.claude/skills/cfn-loop-validation/IMPLEMENTATION_SUMMARY.md +672 -0
- package/.claude/skills/cfn-loop-validation/INDEX.md +531 -0
- package/.claude/skills/cfn-loop-validation/README_TYPESCRIPT.md +454 -0
- package/.claude/skills/cfn-loop-validation/SKILL.md +48 -1
- package/.claude/skills/cfn-loop-validation/SKILL.md.backup +353 -0
- package/.claude/skills/cfn-loop-validation/SKILL_TYPESCRIPT.md +782 -0
- package/.claude/skills/cfn-loop-validation/VAPOR_DETECTION_EXAMPLES.md +598 -0
- package/.claude/skills/cfn-loop-validation/check-dependencies.sh +22 -0
- package/{claude-assets/skills/cfn-redis-coordination/check-dependencies.sh → .claude/skills/cfn-loop-validation/check-dependencies.sh.backup} +4 -5
- package/.claude/skills/cfn-loop-validation/detect-vapor.sh +59 -0
- package/.claude/skills/cfn-loop-validation/detect-vapor.sh.backup +37 -0
- package/.claude/skills/cfn-loop-validation/dist/.tsbuildinfo +1 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/detect-vapor.d.ts +14 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/detect-vapor.d.ts.map +1 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/detect-vapor.js +185 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/detect-vapor.js.map +1 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/validate-deliverables.d.ts +14 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/validate-deliverables.d.ts.map +1 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/validate-deliverables.js +176 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/validate-deliverables.js.map +1 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/validate-gate.d.ts +19 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/validate-gate.d.ts.map +1 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/validate-gate.js +123 -0
- package/.claude/skills/cfn-loop-validation/dist/cli/validate-gate.js.map +1 -0
- package/.claude/skills/cfn-loop-validation/dist/types.d.ts +156 -0
- package/.claude/skills/cfn-loop-validation/dist/types.d.ts.map +1 -0
- package/.claude/skills/cfn-loop-validation/dist/types.js +66 -0
- package/.claude/skills/cfn-loop-validation/dist/types.js.map +1 -0
- package/.claude/skills/cfn-loop-validation/dist/validator.d.ts +85 -0
- package/.claude/skills/cfn-loop-validation/dist/validator.d.ts.map +1 -0
- package/.claude/skills/cfn-loop-validation/dist/validator.js +411 -0
- package/.claude/skills/cfn-loop-validation/dist/validator.js.map +1 -0
- package/.claude/skills/cfn-loop-validation/orchestrate-cfn-loop.sh +22 -0
- package/.claude/skills/cfn-loop-validation/orchestrate-cfn-loop.sh.backup +252 -0
- package/.claude/skills/cfn-loop-validation/package.json +93 -0
- package/.claude/skills/cfn-loop-validation/src/cli/detect-vapor.ts +177 -0
- package/.claude/skills/cfn-loop-validation/src/cli/validate-deliverables.ts +161 -0
- package/.claude/skills/cfn-loop-validation/src/cli/validate-gate.ts +139 -0
- package/.claude/skills/cfn-loop-validation/src/types.ts +215 -0
- package/.claude/skills/cfn-loop-validation/src/validator.ts +503 -0
- package/.claude/skills/cfn-loop-validation/tests/validator.test.ts +537 -0
- package/.claude/skills/{cfn-redis-coordination → cfn-loop-validation}/tsconfig.json +34 -31
- package/.claude/skills/cfn-loop-validation/validate-deliverables.sh +59 -0
- package/.claude/skills/cfn-loop-validation/validate-deliverables.sh.backup +37 -0
- package/.claude/skills/cfn-loop-validation/validate-gate.sh +63 -0
- package/.claude/skills/cfn-loop-validation/validate-gate.sh.backup +41 -0
- package/.claude/skills/cfn-loop-validation/validate-iteration.sh +22 -0
- package/.claude/skills/cfn-loop-validation/validate-iteration.sh.backup +134 -0
- package/.claude/skills/cfn-product-owner-decision/SKILL.md +479 -147
- package/.claude/skills/cfn-product-owner-decision/TYPESCRIPT_IMPLEMENTATION.md +653 -0
- package/{claude-assets/skills/cfn-product-owner-decision → .claude/skills/cfn-product-owner-decision/archive/legacy-bash}/execute-decision.sh +24 -2
- package/.claude/skills/pre-edit-backup/SKILL.md +324 -0
- package/.claude/skills/pre-edit-backup/SKILL.md.backup +277 -0
- package/.claude/skills/pre-edit-backup/backup.sh +22 -0
- package/.claude/skills/pre-edit-backup/backup.sh.backup +107 -0
- package/claude-assets/agents/cfn-dev-team/CLAUDE.md +9 -81
- package/claude-assets/agents/cfn-dev-team/architecture/base-template-generator.md +4 -4
- package/claude-assets/agents/cfn-dev-team/architecture/planner.md +4 -4
- package/claude-assets/agents/cfn-dev-team/architecture/system-architect.md +5 -5
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +1 -0
- package/claude-assets/agents/cfn-dev-team/coordinators/consensus-builder.md +1 -0
- package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +1 -0
- package/claude-assets/agents/cfn-dev-team/coordinators/multi-sprint-coordinator.md +1 -0
- package/claude-assets/agents/cfn-dev-team/dev-ops/devops-engineer.md +14 -4
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +63 -70
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +50 -70
- package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +13 -4
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +27 -58
- package/claude-assets/agents/cfn-dev-team/developers/backend-developer.md +40 -58
- package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +18 -20
- package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +19 -28
- package/claude-assets/agents/cfn-dev-team/developers/frontend/mobile-dev.md +15 -19
- package/claude-assets/agents/cfn-dev-team/developers/frontend/react-frontend-engineer.md +15 -10
- package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +15 -10
- package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +15 -25
- package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +17 -21
- package/claude-assets/agents/cfn-dev-team/developers/rust-developer.md +17 -21
- package/claude-assets/agents/cfn-dev-team/product-owners/accessibility-advocate-persona.md +4 -4
- package/claude-assets/agents/cfn-dev-team/product-owners/cto-agent.md +4 -4
- package/claude-assets/agents/cfn-dev-team/product-owners/power-user-persona.md +4 -4
- package/claude-assets/agents/cfn-dev-team/product-owners/product-owner.md +19 -27
- package/claude-assets/agents/cfn-dev-team/reviewers/code-reviewer.md +20 -51
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/code-quality-validator.md +22 -71
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/perf-analyzer.md +21 -64
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/performance-benchmarker.md +22 -67
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +23 -67
- package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +14 -70
- package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +25 -73
- package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +26 -65
- package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +5 -5
- package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +27 -67
- package/claude-assets/agents/cfn-dev-team/testers/interaction-tester.md +16 -73
- package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +27 -67
- package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +27 -60
- package/claude-assets/agents/cfn-dev-team/testers/playwright-tester.md +15 -74
- package/claude-assets/agents/cfn-dev-team/testers/tester.md +14 -60
- package/claude-assets/agents/cfn-dev-team/testers/unit/tdd-london-unit-swarm.md +5 -5
- package/claude-assets/agents/cfn-dev-team/testers/validation/validation-production-validator.md +4 -4
- package/claude-assets/agents/cfn-dev-team/testing/test-validation-agent.md +4 -4
- package/claude-assets/agents/cfn-dev-team/utility/agent-builder.md +16 -16
- package/claude-assets/agents/cfn-dev-team/utility/analyst.md +16 -32
- package/claude-assets/agents/cfn-dev-team/utility/code-booster.md +17 -17
- package/claude-assets/agents/cfn-dev-team/utility/context-curator.md +10 -5
- package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +12 -102
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +127 -814
- package/claude-assets/agents/cfn-dev-team/utility/researcher.md +16 -25
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +157 -667
- package/claude-assets/agents/custom/cfn-docker-expert.md +102 -0
- package/claude-assets/agents/custom/cfn-loops-cli-expert.md +129 -0
- package/claude-assets/cfn-extras/agents/cfn-v3-coordinator.md +517 -0
- package/claude-assets/commands/cfn-loop-cli.md +158 -464
- package/claude-assets/commands/cfn-loop-trigger.md +114 -0
- package/claude-assets/hooks/SKILL.md +518 -0
- package/claude-assets/hooks/SKILL.md.backup +471 -0
- package/claude-assets/hooks/cfn-invoke-post-edit-ts.sh +100 -0
- package/claude-assets/hooks/cfn-invoke-post-edit-ts.sh.backup +78 -0
- package/claude-assets/hooks/cfn-invoke-post-edit.sh +22 -0
- package/claude-assets/hooks/cfn-invoke-post-edit.sh.backup +87 -0
- package/claude-assets/hooks/cfn-invoke-pre-edit-ts.sh +116 -0
- package/claude-assets/hooks/cfn-invoke-pre-edit-ts.sh.backup +94 -0
- package/claude-assets/hooks/cfn-invoke-pre-edit.sh +22 -0
- package/claude-assets/hooks/cfn-invoke-pre-edit.sh.backup +88 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/DELIVERABLES.md +409 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/IMPLEMENTATION_SUMMARY.md +396 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/INTEGRATION_GUIDE.md +308 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/QUICK_REFERENCE.md +239 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/SKILL.md +107 -1
- package/claude-assets/skills/cfn-agent-selection-with-fallback/SKILL.md.backup +302 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/TYPESCRIPT_MIGRATION.md +295 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/dist/agent-selector.cjs +297 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/dist/agent-selector.js +297 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/dist/cli.cjs +96 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/dist/cli.js +96 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/select-agents-ts.sh +45 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/select-agents-ts.sh.backup +23 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/select-agents.sh +22 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/select-agents.sh.backup +173 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/src/agent-selector.test.ts +357 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/src/agent-selector.ts +350 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/src/cli.ts +74 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/task-classifier.sh +22 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/task-classifier.sh.backup +71 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/tsconfig.json +18 -0
- package/claude-assets/skills/cfn-agent-spawning/SKILL.md +48 -1
- package/claude-assets/skills/cfn-agent-spawning/SKILL.md.backup +135 -0
- package/claude-assets/skills/cfn-agent-spawning/TYPESCRIPT_MIGRATION.md +567 -0
- package/claude-assets/skills/cfn-agent-spawning/check-dependencies.sh +22 -0
- package/claude-assets/skills/cfn-agent-spawning/check-dependencies.sh.backup +30 -0
- package/claude-assets/skills/cfn-agent-spawning/get-agent-provider-env.sh +22 -0
- package/claude-assets/skills/cfn-agent-spawning/get-agent-provider-env.sh.backup +127 -0
- package/claude-assets/skills/cfn-agent-spawning/parse-agent-provider.sh +22 -0
- package/claude-assets/skills/cfn-agent-spawning/parse-agent-provider.sh.backup +59 -0
- package/claude-assets/skills/cfn-agent-spawning/spawn-agent-wrapper.sh +63 -0
- package/claude-assets/skills/cfn-agent-spawning/spawn-agent-wrapper.sh.backup +41 -0
- package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh +26 -1
- package/claude-assets/skills/cfn-agent-spawning/spawn-templates.sh +22 -0
- package/claude-assets/skills/cfn-agent-spawning/spawn-templates.sh.backup +613 -0
- package/claude-assets/skills/cfn-agent-spawning/spawn-worker.sh +22 -0
- package/claude-assets/skills/cfn-agent-spawning/spawn-worker.sh.backup +176 -0
- package/claude-assets/skills/cfn-coordination/agent-completion.sh.backup +36 -0
- package/claude-assets/skills/cfn-coordination/coordination-signal.sh.backup +36 -0
- package/claude-assets/skills/cfn-coordination/coordination-wait.sh.backup +36 -0
- package/claude-assets/skills/cfn-dependency-ingestion/README.md +101 -0
- package/claude-assets/skills/cfn-dependency-ingestion/SKILL.md +369 -0
- package/claude-assets/skills/cfn-dependency-ingestion/build.sh +23 -0
- package/claude-assets/skills/cfn-dependency-ingestion/dist/ingest-dependencies.js +478 -0
- package/claude-assets/skills/cfn-dependency-ingestion/ingest-dependencies.sh +295 -0
- package/claude-assets/skills/cfn-dependency-ingestion/src/ingest-dependencies.ts +563 -0
- package/claude-assets/skills/cfn-loop-orchestration/.backups/unknown/1763619700_33aff4a69b99159e4e849107ebc4d09f/metadata.json +8 -0
- package/claude-assets/skills/cfn-loop-orchestration/.backups/unknown/1763619700_33aff4a69b99159e4e849107ebc4d09f/original +271 -0
- package/claude-assets/skills/cfn-loop-orchestration/.backups/unknown/1763619700_33aff4a69b99159e4e849107ebc4d09f/revert.sh +7 -0
- package/claude-assets/skills/cfn-loop-orchestration/.backups/unknown/1763671642_06496e8c399a79db08167cc00ed4b31e/metadata.json +8 -0
- package/claude-assets/skills/cfn-loop-orchestration/.backups/unknown/1763671642_06496e8c399a79db08167cc00ed4b31e/original +325 -0
- package/claude-assets/skills/cfn-loop-orchestration/.backups/unknown/1763671642_06496e8c399a79db08167cc00ed4b31e/revert.sh +7 -0
- package/claude-assets/skills/cfn-loop-orchestration/CLI_IMPLEMENTATION_SUMMARY.md +330 -0
- package/claude-assets/skills/cfn-loop-orchestration/CONFIGURATION_IMPROVEMENTS.md +318 -0
- package/claude-assets/skills/cfn-loop-orchestration/CONTEXT_LOOKUP_MIGRATION.md +308 -0
- package/claude-assets/skills/cfn-loop-orchestration/CONTEXT_LOOKUP_QUICK_START.md +378 -0
- package/claude-assets/skills/cfn-loop-orchestration/E2E_VALIDATION_REPORT.md +262 -0
- package/claude-assets/skills/cfn-loop-orchestration/IMPLEMENTATION_SUMMARY.md +319 -519
- package/claude-assets/skills/cfn-loop-orchestration/NORTH_STAR_E2E_REPORT.md +299 -0
- package/claude-assets/skills/cfn-loop-orchestration/NORTH_STAR_EXECUTION_SUMMARY.md +403 -0
- package/claude-assets/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +323 -0
- package/claude-assets/skills/cfn-loop-orchestration/SKILL.md +159 -48
- package/claude-assets/skills/cfn-loop-orchestration/SPAWN_AGENTS_IMPLEMENTATION.md +188 -0
- package/claude-assets/skills/cfn-loop-orchestration/TEST_COVERAGE_REPORT.md +335 -0
- package/claude-assets/skills/cfn-loop-orchestration/TEST_COVERAGE_SUMMARY.md +456 -0
- package/claude-assets/skills/cfn-loop-orchestration/TYPESCRIPT_INTEGRATION_REPORT.md +709 -0
- package/claude-assets/skills/cfn-loop-orchestration/TYPESCRIPT_INTEGRATION_SUMMARY.md +257 -0
- package/claude-assets/skills/cfn-loop-orchestration/VALIDATION_REPORT.md +572 -0
- package/claude-assets/skills/cfn-loop-orchestration/VALIDATION_SUMMARY.txt +196 -0
- package/claude-assets/skills/cfn-loop-orchestration/VALIDATOR_MODULE_GUIDE.md +526 -0
- package/claude-assets/skills/cfn-loop-orchestration/archive/legacy-bash/README.md +167 -0
- package/claude-assets/skills/cfn-loop-orchestration/archive/legacy-bash/orchestrate-enhanced.sh +548 -0
- package/{.claude/skills/cfn-loop-orchestration → claude-assets/skills/cfn-loop-orchestration/archive/legacy-bash}/orchestrate-wrapper.sh +11 -1
- package/claude-assets/skills/cfn-loop-orchestration/archive/legacy-bash/orchestrate.sh +182 -0
- package/claude-assets/skills/cfn-loop-orchestration/e2e-validation-fixed.js +240 -0
- package/claude-assets/skills/cfn-loop-orchestration/e2e-validation.js +213 -0
- package/claude-assets/skills/cfn-loop-orchestration/package-lock.json +3 -0
- package/claude-assets/skills/cfn-loop-orchestration/package.json +4 -0
- package/claude-assets/skills/cfn-loop-orchestration/run-north-star-e2e.ts +210 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/cli/orchestrator-cli.ts +396 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/CONFIDENCE_AGGREGATOR.md +564 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/CONFIDENCE_AGGREGATOR_QUICK_REF.md +241 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/CONTEXT_INJECTOR_IMPLEMENTATION.md +375 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/CONTEXT_INJECTOR_QUICK_REFERENCE.md +362 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/CONTEXT_INJECTOR_README.md +307 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/CONTEXT_INJECTOR_USAGE_GUIDE.md +508 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/confidence-aggregator.ts +473 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/consensus.ts +1 -1
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/context-injector.ts +349 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/context-lookup.ts +486 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/deliverable-verifier.ts +6 -2
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/gate-check.ts +1 -1
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/product-owner-decision.ts +316 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/spawn-agents.ts +357 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/helpers/validator.ts +276 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/index.ts +2 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/orchestrate.ts +743 -2
- package/claude-assets/skills/cfn-loop-orchestration/src/types.ts +56 -0
- package/claude-assets/skills/cfn-loop-orchestration/test-cli.sh +92 -0
- package/claude-assets/skills/cfn-loop-orchestration/test-typescript-integration.sh +442 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/agent-spawner.test.ts +124 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/confidence-aggregator.test.ts +604 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/context-injector.test.ts +561 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/context-lookup.test.ts +661 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/deliverable-verifier.test.ts +2 -2
- package/claude-assets/skills/cfn-loop-orchestration/tests/gate-check-edge-cases.test.ts +422 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/gate-checker.test.ts +276 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/logger.test.ts +291 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/north-star-e2e.test.ts +334 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/redis-coordinator.test.ts +321 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/spawn-agents.test.ts +284 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/validator.test.ts +643 -0
- package/claude-assets/skills/cfn-loop-output-processing/.eslintrc.json +33 -0
- package/claude-assets/skills/cfn-loop-output-processing/DELIVERY_SUMMARY.txt +462 -0
- package/claude-assets/skills/cfn-loop-output-processing/DEPRECATION_NOTICE.md +183 -0
- package/claude-assets/skills/cfn-loop-output-processing/EXAMPLES.md +609 -0
- package/claude-assets/skills/cfn-loop-output-processing/IMPLEMENTATION_SUMMARY.md +418 -0
- package/claude-assets/skills/cfn-loop-output-processing/INDEX.md +531 -0
- package/claude-assets/skills/cfn-loop-output-processing/MIGRATION.md +362 -0
- package/claude-assets/skills/cfn-loop-output-processing/README.md +114 -0
- package/claude-assets/skills/cfn-loop-output-processing/SKILL.md +633 -0
- package/{.claude/skills/cfn-docker-redis-coordination → claude-assets/skills/cfn-loop-output-processing}/jest.config.js +7 -15
- package/claude-assets/skills/cfn-loop-output-processing/package.json +50 -0
- package/claude-assets/skills/cfn-loop-output-processing/src/cli/process-loop2.ts +195 -0
- package/claude-assets/skills/cfn-loop-output-processing/src/cli/process-loop3.ts +157 -0
- package/claude-assets/skills/cfn-loop-output-processing/src/output-processor.ts +632 -0
- package/claude-assets/skills/cfn-loop-output-processing/tests/output-processor.test.ts +617 -0
- package/claude-assets/skills/{cfn-docker-redis-coordination → cfn-loop-output-processing}/tsconfig.json +16 -7
- package/claude-assets/skills/cfn-loop-validation/IMPLEMENTATION_SUMMARY.md +672 -0
- package/claude-assets/skills/cfn-loop-validation/INDEX.md +531 -0
- package/claude-assets/skills/cfn-loop-validation/README_TYPESCRIPT.md +454 -0
- package/claude-assets/skills/cfn-loop-validation/SKILL.md +48 -1
- package/claude-assets/skills/cfn-loop-validation/SKILL.md.backup +353 -0
- package/claude-assets/skills/cfn-loop-validation/SKILL_TYPESCRIPT.md +782 -0
- package/claude-assets/skills/cfn-loop-validation/VAPOR_DETECTION_EXAMPLES.md +598 -0
- package/claude-assets/skills/cfn-loop-validation/check-dependencies.sh +22 -0
- package/claude-assets/skills/cfn-loop-validation/check-dependencies.sh.backup +31 -0
- package/claude-assets/skills/cfn-loop-validation/detect-vapor.sh +59 -0
- package/claude-assets/skills/cfn-loop-validation/detect-vapor.sh.backup +37 -0
- package/claude-assets/skills/cfn-loop-validation/dist/.tsbuildinfo +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/detect-vapor.d.ts +14 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/detect-vapor.d.ts.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/detect-vapor.js +185 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/detect-vapor.js.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/validate-deliverables.d.ts +14 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/validate-deliverables.d.ts.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/validate-deliverables.js +176 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/validate-deliverables.js.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/validate-gate.d.ts +19 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/validate-gate.d.ts.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/validate-gate.js +123 -0
- package/claude-assets/skills/cfn-loop-validation/dist/cli/validate-gate.js.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/types.d.ts +156 -0
- package/claude-assets/skills/cfn-loop-validation/dist/types.d.ts.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/types.js +66 -0
- package/claude-assets/skills/cfn-loop-validation/dist/types.js.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/validator.d.ts +85 -0
- package/claude-assets/skills/cfn-loop-validation/dist/validator.d.ts.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/dist/validator.js +411 -0
- package/claude-assets/skills/cfn-loop-validation/dist/validator.js.map +1 -0
- package/claude-assets/skills/cfn-loop-validation/orchestrate-cfn-loop.sh +22 -0
- package/claude-assets/skills/cfn-loop-validation/orchestrate-cfn-loop.sh.backup +252 -0
- package/claude-assets/skills/cfn-loop-validation/package.json +93 -0
- package/claude-assets/skills/cfn-loop-validation/src/cli/detect-vapor.ts +177 -0
- package/claude-assets/skills/cfn-loop-validation/src/cli/validate-deliverables.ts +161 -0
- package/claude-assets/skills/cfn-loop-validation/src/cli/validate-gate.ts +139 -0
- package/claude-assets/skills/cfn-loop-validation/src/types.ts +215 -0
- package/claude-assets/skills/cfn-loop-validation/src/validator.ts +503 -0
- package/claude-assets/skills/cfn-loop-validation/tests/validator.test.ts +537 -0
- package/claude-assets/skills/{cfn-redis-coordination → cfn-loop-validation}/tsconfig.json +34 -31
- package/claude-assets/skills/cfn-loop-validation/validate-deliverables.sh +59 -0
- package/claude-assets/skills/cfn-loop-validation/validate-deliverables.sh.backup +37 -0
- package/claude-assets/skills/cfn-loop-validation/validate-gate.sh +63 -0
- package/claude-assets/skills/cfn-loop-validation/validate-gate.sh.backup +41 -0
- package/claude-assets/skills/cfn-loop-validation/validate-iteration.sh +22 -0
- package/claude-assets/skills/cfn-loop-validation/validate-iteration.sh.backup +134 -0
- package/claude-assets/skills/cfn-product-owner-decision/SKILL.md +479 -147
- package/claude-assets/skills/cfn-product-owner-decision/TYPESCRIPT_IMPLEMENTATION.md +653 -0
- package/{.claude/skills/cfn-product-owner-decision → claude-assets/skills/cfn-product-owner-decision/archive/legacy-bash}/execute-decision.sh +24 -2
- package/claude-assets/skills/cfn-provider-routing/README.md +129 -0
- package/claude-assets/skills/cfn-provider-routing/SKILL.md +192 -0
- package/claude-assets/skills/cfn-provider-routing/resolve-provider-model.ts +223 -0
- package/claude-assets/skills/pre-edit-backup/SKILL.md +324 -0
- package/claude-assets/skills/pre-edit-backup/SKILL.md.backup +277 -0
- package/claude-assets/skills/pre-edit-backup/backup.sh +22 -0
- package/claude-assets/skills/pre-edit-backup/backup.sh.backup +107 -0
- package/dist/agents/agent-loader.js +146 -165
- package/dist/agents/agent-loader.js.map +1 -1
- package/dist/api/auth-endpoints.js +415 -0
- package/dist/api/auth-endpoints.js.map +1 -0
- package/dist/api/task-endpoints.js +562 -0
- package/dist/api/task-endpoints.js.map +1 -0
- package/dist/backend/server.js +418 -0
- package/dist/backend/server.js.map +1 -0
- package/dist/cfn-loop/product-owner/decision-parser.js +356 -0
- package/dist/cfn-loop/product-owner/decision-parser.js.map +1 -0
- package/dist/cfn-loop/product-owner/index.js +1 -0
- package/dist/cfn-loop/product-owner/index.js.map +1 -1
- package/dist/cli/agent-command.js +1 -1
- package/dist/cli/agent-command.js.map +1 -1
- package/dist/cli/agent-completion.js +273 -0
- package/dist/cli/agent-completion.js.map +1 -0
- package/dist/cli/agent-definition-parser.js +37 -4
- package/dist/cli/agent-definition-parser.js.map +1 -1
- package/dist/cli/agent-executor.js +32 -2
- package/dist/cli/agent-executor.js.map +1 -1
- package/dist/cli/agent-prompt-builder.js +83 -48
- package/dist/cli/agent-prompt-builder.js.map +1 -1
- package/dist/cli/agent-spawner.js +499 -0
- package/dist/cli/agent-spawner.js.map +1 -0
- package/dist/cli/anthropic-client.js +10 -3
- package/dist/cli/anthropic-client.js.map +1 -1
- package/dist/cli/config-manager.js.map +1 -1
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/parse-decision-cli.js +268 -0
- package/dist/cli/parse-decision-cli.js.map +1 -0
- package/dist/cli/post-edit-hook.js +83 -0
- package/dist/cli/post-edit-hook.js.map +1 -0
- package/dist/cli/pre-edit-hook.js +77 -0
- package/dist/cli/pre-edit-hook.js.map +1 -0
- package/dist/cli/spawn-agent-cli.js +209 -0
- package/dist/cli/spawn-agent-cli.js.map +1 -0
- package/dist/coordination/coordination-wrapper.js +383 -0
- package/dist/coordination/coordination-wrapper.js.map +1 -0
- package/dist/coordination/store-success-criteria.js +68 -0
- package/dist/coordination/store-success-criteria.js.map +1 -0
- package/dist/coordination/store-task-context.js +65 -0
- package/dist/coordination/store-task-context.js.map +1 -0
- package/dist/hooks/backup-manager.js +273 -0
- package/dist/hooks/backup-manager.js.map +1 -0
- package/dist/hooks/post-edit-validator.js +388 -0
- package/dist/hooks/post-edit-validator.js.map +1 -0
- package/dist/integration/index.js +19 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/integration/task-mode-adapter.js +297 -0
- package/dist/integration/task-mode-adapter.js.map +1 -0
- package/dist/integration/trigger-dev-client.js +253 -0
- package/dist/integration/trigger-dev-client.js.map +1 -0
- package/dist/integration/trigger-dev-webhooks.js +362 -0
- package/dist/integration/trigger-dev-webhooks.js.map +1 -0
- package/dist/lib/path-validator.js +14 -5
- package/dist/lib/path-validator.js.map +1 -1
- package/dist/lib/redis-queue-manager.js +5 -1
- package/dist/lib/redis-queue-manager.js.map +1 -1
- package/dist/middleware/authentication.js +317 -0
- package/dist/middleware/authentication.js.map +1 -0
- package/dist/services/authentication.js +669 -0
- package/dist/services/authentication.js.map +1 -0
- package/dist/services/session-management.js +436 -0
- package/dist/services/session-management.js.map +1 -0
- package/dist/services/skill-deployment.js +8 -6
- package/dist/services/skill-deployment.js.map +1 -1
- package/dist/services/user-service.js +710 -0
- package/dist/services/user-service.js.map +1 -0
- package/dist/types/trigger-dev-events.d.js +10 -0
- package/dist/types/trigger-dev-events.d.js.map +1 -0
- package/docs/README.md +240 -0
- package/package.json +13 -4
- package/scripts/compare-workflow-performance.sh +556 -0
- package/scripts/migrate-to-optimized-workflows.sh +438 -0
- package/scripts/organize-docs.sh +338 -0
- package/scripts/trigger-dev-setup.sh +267 -0
- package/.claude/skills/cfn-docker-redis-coordination/MIGRATION_SUMMARY.md +0 -348
- package/.claude/skills/cfn-docker-redis-coordination/README.md +0 -294
- package/.claude/skills/cfn-docker-redis-coordination/SKILL.md +0 -435
- package/.claude/skills/cfn-docker-redis-coordination/coordinate.sh +0 -650
- package/.claude/skills/cfn-docker-redis-coordination/coordinate.sh.backup-1763145142 +0 -641
- package/.claude/skills/cfn-docker-redis-coordination/package-lock.json +0 -5259
- package/.claude/skills/cfn-docker-redis-coordination/package.json +0 -40
- package/.claude/skills/cfn-docker-redis-coordination/src/coordinator.ts +0 -801
- package/.claude/skills/cfn-docker-redis-coordination/src/index.ts +0 -42
- package/.claude/skills/cfn-docker-redis-coordination/src/types.ts +0 -351
- package/.claude/skills/cfn-docker-redis-coordination/tests/coordinator.test.ts +0 -1464
- package/.claude/skills/cfn-docker-redis-coordination/tsconfig.json +0 -30
- package/.claude/skills/cfn-loop-orchestration/helpers/auto-tune-timeouts.sh +0 -228
- package/.claude/skills/cfn-loop-orchestration/helpers/consensus-ts.sh +0 -104
- package/.claude/skills/cfn-loop-orchestration/helpers/consensus.sh +0 -94
- package/.claude/skills/cfn-loop-orchestration/helpers/context-injection.sh +0 -142
- package/.claude/skills/cfn-loop-orchestration/helpers/context-lookup.sh +0 -359
- package/.claude/skills/cfn-loop-orchestration/helpers/deliverable-verifier-ts.sh +0 -123
- package/.claude/skills/cfn-loop-orchestration/helpers/deliverable-verifier.sh +0 -71
- package/.claude/skills/cfn-loop-orchestration/helpers/gate-check.sh +0 -56
- package/.claude/skills/cfn-loop-orchestration/helpers/iteration-manager-ts.sh +0 -89
- package/.claude/skills/cfn-loop-orchestration/helpers/iteration-manager.sh +0 -87
- package/.claude/skills/cfn-loop-orchestration/helpers/orchestrate-ts.sh +0 -104
- package/.claude/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +0 -56
- package/.claude/skills/cfn-loop-orchestration/helpers/spawn-agents.sh +0 -290
- package/.claude/skills/cfn-loop-orchestration/helpers/timeout-calculator-ts.sh +0 -47
- package/.claude/skills/cfn-loop-orchestration/helpers/timeout-calculator.sh +0 -51
- package/.claude/skills/cfn-loop-orchestration/orchestrate.sh +0 -1345
- package/.claude/skills/cfn-redis-coordination/AGENT_LOGGING.md +0 -280
- package/.claude/skills/cfn-redis-coordination/BZPOPMIN_FIX_SUMMARY.md +0 -209
- package/.claude/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +0 -319
- package/.claude/skills/cfn-redis-coordination/agent-log.sh.bak +0 -124
- package/.claude/skills/cfn-redis-coordination/config.json +0 -61
- package/.claude/skills/cfn-redis-coordination/demos/phase4-wake-queue-test-report.md +0 -82
- package/.claude/skills/cfn-redis-coordination/demos/test-bzpopmin-fix.sh +0 -274
- package/.claude/skills/cfn-redis-coordination/demos/test-cancel-swarm.sh +0 -0
- package/.claude/skills/cfn-redis-coordination/docs/migration/PHASE_3_REDIS_COORDINATION_COMPLETION_REPORT.md +0 -553
- package/.claude/skills/cfn-redis-coordination/jest.config.js +0 -23
- package/.claude/skills/cfn-redis-coordination/package-lock.json +0 -5272
- package/.claude/skills/cfn-redis-coordination/package.json +0 -45
- package/.claude/skills/cfn-redis-coordination/src/agent-logger.ts +0 -446
- package/.claude/skills/cfn-redis-coordination/src/agent-recovery.ts +0 -454
- package/.claude/skills/cfn-redis-coordination/src/completion-reporter.ts +0 -396
- package/.claude/skills/cfn-redis-coordination/src/context-manager.ts +0 -327
- package/.claude/skills/cfn-redis-coordination/src/index.ts +0 -82
- package/.claude/skills/cfn-redis-coordination/src/mode-detector.ts +0 -155
- package/.claude/skills/cfn-redis-coordination/src/redis/redis-client.ts +0 -305
- package/.claude/skills/cfn-redis-coordination/src/redis/redis-functions.ts +0 -283
- package/.claude/skills/cfn-redis-coordination/src/redis-client.ts +0 -654
- package/.claude/skills/cfn-redis-coordination/src/result-collector.ts +0 -437
- package/.claude/skills/cfn-redis-coordination/src/swarm-manager.ts +0 -494
- package/.claude/skills/cfn-redis-coordination/src/task-analyzer.ts +0 -404
- package/.claude/skills/cfn-redis-coordination/src/task-executor.ts +0 -423
- package/.claude/skills/cfn-redis-coordination/src/types.ts +0 -235
- package/.claude/skills/cfn-redis-coordination/src/waiting-coordinator.ts +0 -587
- package/.claude/skills/cfn-redis-coordination/store-success-criteria.sh +0 -85
- package/.claude/skills/cfn-redis-coordination/test-connection-attempts.js +0 -70
- package/.claude/skills/cfn-redis-coordination/test-mode-simple.js +0 -121
- package/.claude/skills/cfn-redis-coordination/test-redis-check.js +0 -84
- package/.claude/skills/cfn-redis-coordination/test-task-mode-redis.cjs +0 -391
- package/.claude/skills/cfn-redis-coordination/tests/coordination.test.ts +0 -788
- package/.claude/skills/cfn-redis-coordination/update-all-scripts.sh +0 -67
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +0 -976
- package/claude-assets/agents/typescript-specialist.md +0 -280
- package/claude-assets/skills/cfn-docker-redis-coordination/MIGRATION_SUMMARY.md +0 -348
- package/claude-assets/skills/cfn-docker-redis-coordination/README.md +0 -294
- package/claude-assets/skills/cfn-docker-redis-coordination/SKILL.md +0 -435
- package/claude-assets/skills/cfn-docker-redis-coordination/coordinate.sh +0 -650
- package/claude-assets/skills/cfn-docker-redis-coordination/coordinate.sh.backup-1763145142 +0 -641
- package/claude-assets/skills/cfn-docker-redis-coordination/jest.config.js +0 -37
- package/claude-assets/skills/cfn-docker-redis-coordination/package-lock.json +0 -5259
- package/claude-assets/skills/cfn-docker-redis-coordination/package.json +0 -40
- package/claude-assets/skills/cfn-docker-redis-coordination/src/coordinator.ts +0 -801
- package/claude-assets/skills/cfn-docker-redis-coordination/src/index.ts +0 -42
- package/claude-assets/skills/cfn-docker-redis-coordination/src/types.ts +0 -351
- package/claude-assets/skills/cfn-docker-redis-coordination/tests/coordinator.test.ts +0 -1464
- package/claude-assets/skills/cfn-loop-orchestration/helpers/auto-tune-timeouts.sh +0 -228
- package/claude-assets/skills/cfn-loop-orchestration/helpers/consensus-ts.sh +0 -104
- package/claude-assets/skills/cfn-loop-orchestration/helpers/consensus.sh +0 -94
- package/claude-assets/skills/cfn-loop-orchestration/helpers/context-injection.sh +0 -142
- package/claude-assets/skills/cfn-loop-orchestration/helpers/context-lookup.sh +0 -359
- package/claude-assets/skills/cfn-loop-orchestration/helpers/deliverable-verifier-ts.sh +0 -123
- package/claude-assets/skills/cfn-loop-orchestration/helpers/deliverable-verifier.sh +0 -71
- package/claude-assets/skills/cfn-loop-orchestration/helpers/gate-check.sh +0 -56
- package/claude-assets/skills/cfn-loop-orchestration/helpers/iteration-manager-ts.sh +0 -89
- package/claude-assets/skills/cfn-loop-orchestration/helpers/iteration-manager.sh +0 -87
- package/claude-assets/skills/cfn-loop-orchestration/helpers/orchestrate-ts.sh +0 -104
- package/claude-assets/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +0 -56
- package/claude-assets/skills/cfn-loop-orchestration/helpers/spawn-agents.sh +0 -290
- package/claude-assets/skills/cfn-loop-orchestration/helpers/timeout-calculator-ts.sh +0 -47
- package/claude-assets/skills/cfn-loop-orchestration/helpers/timeout-calculator.sh +0 -51
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh +0 -1345
- package/claude-assets/skills/cfn-redis-cleanup/cleanup-redis.sh +0 -130
- package/claude-assets/skills/cfn-redis-coordination/AGENT_LOGGING.md +0 -280
- package/claude-assets/skills/cfn-redis-coordination/BZPOPMIN_FIX_SUMMARY.md +0 -209
- package/claude-assets/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +0 -319
- package/claude-assets/skills/cfn-redis-coordination/agent-log.sh.bak +0 -124
- package/claude-assets/skills/cfn-redis-coordination/config.json +0 -61
- package/claude-assets/skills/cfn-redis-coordination/demos/phase4-wake-queue-test-report.md +0 -82
- package/claude-assets/skills/cfn-redis-coordination/demos/test-bzpopmin-fix.sh +0 -274
- package/claude-assets/skills/cfn-redis-coordination/demos/test-cancel-swarm.sh +0 -0
- package/claude-assets/skills/cfn-redis-coordination/docs/migration/PHASE_3_REDIS_COORDINATION_COMPLETION_REPORT.md +0 -553
- package/claude-assets/skills/cfn-redis-coordination/jest.config.js +0 -23
- package/claude-assets/skills/cfn-redis-coordination/package-lock.json +0 -5272
- package/claude-assets/skills/cfn-redis-coordination/package.json +0 -45
- package/claude-assets/skills/cfn-redis-coordination/src/agent-logger.ts +0 -446
- package/claude-assets/skills/cfn-redis-coordination/src/agent-recovery.ts +0 -454
- package/claude-assets/skills/cfn-redis-coordination/src/completion-reporter.ts +0 -396
- package/claude-assets/skills/cfn-redis-coordination/src/context-manager.ts +0 -327
- package/claude-assets/skills/cfn-redis-coordination/src/index.ts +0 -82
- package/claude-assets/skills/cfn-redis-coordination/src/mode-detector.ts +0 -155
- package/claude-assets/skills/cfn-redis-coordination/src/redis/redis-client.ts +0 -305
- package/claude-assets/skills/cfn-redis-coordination/src/redis/redis-functions.ts +0 -283
- package/claude-assets/skills/cfn-redis-coordination/src/redis-client.ts +0 -654
- package/claude-assets/skills/cfn-redis-coordination/src/result-collector.ts +0 -437
- package/claude-assets/skills/cfn-redis-coordination/src/swarm-manager.ts +0 -494
- package/claude-assets/skills/cfn-redis-coordination/src/task-analyzer.ts +0 -404
- package/claude-assets/skills/cfn-redis-coordination/src/task-executor.ts +0 -423
- package/claude-assets/skills/cfn-redis-coordination/src/types.ts +0 -235
- package/claude-assets/skills/cfn-redis-coordination/src/waiting-coordinator.ts +0 -587
- package/claude-assets/skills/cfn-redis-coordination/store-success-criteria.sh +0 -85
- package/claude-assets/skills/cfn-redis-coordination/test-connection-attempts.js +0 -70
- package/claude-assets/skills/cfn-redis-coordination/test-mode-simple.js +0 -121
- package/claude-assets/skills/cfn-redis-coordination/test-redis-check.js +0 -84
- package/claude-assets/skills/cfn-redis-coordination/test-task-mode-redis.cjs +0 -391
- package/claude-assets/skills/cfn-redis-coordination/tests/coordination.test.ts +0 -788
- package/claude-assets/skills/cfn-redis-coordination/update-all-scripts.sh +0 -67
- package/claude-assets/skills/cfn-redis-data-extraction/SKILL.md +0 -442
- package/claude-assets/skills/cfn-redis-data-extraction/extract.sh +0 -306
- package/dist/coordination/index.js +0 -25
- package/dist/coordination/index.js.map +0 -1
- package/docs/BUG_19_MEMORY_LEAK_TASK_MODE.md +0 -405
- package/docs/MEMORY_CLEANUP_GUIDE.md +0 -358
- package/docs/MEMORY_LEAK_FIX_SUMMARY.md +0 -322
- package/docs/REDIS_CLEANUP_EXECUTIVE_SUMMARY.md +0 -319
- package/docs/REDIS_CLEANUP_VERIFICATION_REPORT.md +0 -574
- /package/.claude/skills/cfn-loop-orchestration/{inject-loop-context.sh → archive/legacy-bash/inject-loop-context.sh} +0 -0
- /package/.claude/skills/cfn-loop-orchestration/{monitor-execution.sh → archive/legacy-bash/monitor-execution.sh} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{agent-log.sh → agent-log.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{agent-recovery.sh → agent-recovery.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{analyze-task-complexity.sh → analyze-task-complexity.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/bash-wrappers/{store-context.sh → store-context.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{cancel-swarm.sh → cancel-swarm.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{cfn-loop-exec.sh → cfn-loop-exec.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{cfn-loop-relaunch.sh → cfn-loop-relaunch.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{collect-confidence-scores.sh → collect-confidence-scores.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{collect-results.sh → collect-results.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{complete-swarm.sh → complete-swarm.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{get-context.sh → get-context.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{get-success-criteria.sh → get-success-criteria.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{invoke-waiting-mode.sh → invoke-waiting-mode.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{redis-cli-wrapper.sh → redis-cli-wrapper.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{redis-functions.sh → redis-functions.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{report-completion.sh → report-completion.sh.backup} +0 -0
- /package/.claude/skills/cfn-redis-coordination/{store-context.sh → store-context.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-loop-orchestration/{inject-loop-context.sh → archive/legacy-bash/inject-loop-context.sh} +0 -0
- /package/claude-assets/skills/cfn-loop-orchestration/{monitor-execution.sh → archive/legacy-bash/monitor-execution.sh} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{agent-log.sh → agent-log.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{agent-recovery.sh → agent-recovery.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{analyze-task-complexity.sh → analyze-task-complexity.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/bash-wrappers/{store-context.sh → store-context.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{cancel-swarm.sh → cancel-swarm.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{cfn-loop-exec.sh → cfn-loop-exec.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{cfn-loop-relaunch.sh → cfn-loop-relaunch.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{collect-confidence-scores.sh → collect-confidence-scores.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{collect-results.sh → collect-results.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{complete-swarm.sh → complete-swarm.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{get-context.sh → get-context.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{get-success-criteria.sh → get-success-criteria.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{invoke-waiting-mode.sh → invoke-waiting-mode.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{redis-cli-wrapper.sh → redis-cli-wrapper.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{redis-functions.sh → redis-functions.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{report-completion.sh → report-completion.sh.backup} +0 -0
- /package/claude-assets/skills/cfn-redis-coordination/{store-context.sh → store-context.sh.backup} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/redis-queue-manager.ts"],"sourcesContent":["/**\r\n * Redis Queue Manager\r\n *\r\n * Provides reliable queue operations with idempotency, acknowledgment protocol,\r\n * and message visibility timeout for Docker agent ↔ Redis communication.\r\n * Part of Task 3.4: Redis Queue Consistency & Recovery (Integration Standardization Sprint 3)\r\n *\r\n * Features:\r\n * - Enqueue with idempotency (prevents duplicate messages)\r\n * - Dequeue with acknowledgment protocol\r\n * - Message visibility timeout\r\n * - Queue monitoring (depth, age, throughput)\r\n * - Multiple queue support (task, result, coordination)\r\n * - Performance: <100ms per operation\r\n *\r\n * Usage:\r\n * const queueManager = new RedisQueueManager(redisClient);\r\n *\r\n * // Producer\r\n * await queueManager.enqueue('task-queue', {\r\n * taskId: 'task-001',\r\n * agentType: 'backend-developer',\r\n * payload: { ... }\r\n * });\r\n *\r\n * // Consumer\r\n * const message = await queueManager.dequeue('task-queue', { timeout: 30000 });\r\n * try {\r\n * await processTask(message.payload);\r\n * await queueManager.acknowledge(message.id);\r\n * } catch (error) {\r\n * await queueManager.reject(message.id, { retry: true });\r\n * }\r\n */\r\n\r\nimport { RedisClientType } from 'redis';\r\nimport { v4 as uuidv4 } from 'uuid';\r\nimport { createLogger } from './logging.js';\r\nimport { createError, ErrorCode, isRetryableError } from './errors.js';\r\nimport { withRetry } from './retry.js';\r\nimport { MessageDeduplicator } from './message-deduplicator.js';\r\n\r\nconst logger = createLogger('redis-queue-manager');\r\n\r\n/**\r\n * Queue message\r\n */\r\nexport interface QueueMessage<T = any> {\r\n /** Unique message ID */\r\n id: string;\r\n /** Queue name */\r\n queue: string;\r\n /** Message payload */\r\n payload: T;\r\n /** Message creation timestamp */\r\n createdAt: Date;\r\n /** Message enqueue timestamp */\r\n enqueuedAt: Date;\r\n /** Message dequeue timestamp (if dequeued) */\r\n dequeuedAt?: Date;\r\n /** Number of delivery attempts */\r\n deliveryAttempts: number;\r\n /** Message visibility timeout (milliseconds) */\r\n visibilityTimeout?: number;\r\n /** Message metadata */\r\n metadata?: Record<string, any>;\r\n}\r\n\r\n/**\r\n * Enqueue options\r\n */\r\nexport interface EnqueueOptions {\r\n /** Enable deduplication (default: true) */\r\n deduplicate?: boolean;\r\n /** Message metadata */\r\n metadata?: Record<string, any>;\r\n /** Message visibility timeout in milliseconds (default: 30000) */\r\n visibilityTimeout?: number;\r\n}\r\n\r\n/**\r\n * Dequeue options\r\n */\r\nexport interface DequeueOptions {\r\n /** Maximum wait time in milliseconds (default: 0 - no wait) */\r\n timeout?: number;\r\n /** Message visibility timeout in milliseconds (default: 30000) */\r\n visibilityTimeout?: number;\r\n /** Number of messages to dequeue (default: 1) */\r\n count?: number;\r\n}\r\n\r\n/**\r\n * Reject options\r\n */\r\nexport interface RejectOptions {\r\n /** Retry message (re-enqueue) (default: false) */\r\n retry?: boolean;\r\n /** Error message */\r\n error?: string;\r\n /** Metadata to attach */\r\n metadata?: Record<string, any>;\r\n}\r\n\r\n/**\r\n * Queue statistics\r\n */\r\nexport interface QueueStats {\r\n /** Queue name */\r\n queue: string;\r\n /** Number of messages in queue */\r\n depth: number;\r\n /** Number of messages in processing (invisible) */\r\n inFlight: number;\r\n /** Age of oldest message in seconds */\r\n oldestMessageAge: number;\r\n /** Total messages enqueued */\r\n totalEnqueued: number;\r\n /** Total messages dequeued */\r\n totalDequeued: number;\r\n /** Total messages acknowledged */\r\n totalAcknowledged: number;\r\n /** Total messages rejected */\r\n totalRejected: number;\r\n /** Throughput (messages per second) */\r\n throughput: number;\r\n}\r\n\r\n/**\r\n * Default queue options\r\n */\r\nconst DEFAULT_ENQUEUE_OPTIONS: Required<EnqueueOptions> = {\r\n deduplicate: true,\r\n metadata: {},\r\n visibilityTimeout: 30000, // 30 seconds\r\n};\r\n\r\nconst DEFAULT_DEQUEUE_OPTIONS: Required<DequeueOptions> = {\r\n timeout: 0,\r\n visibilityTimeout: 30000, // 30 seconds\r\n count: 1,\r\n};\r\n\r\n/**\r\n * Redis Queue Manager\r\n *\r\n * Provides reliable queue operations with at-least-once delivery guarantees.\r\n */\r\nexport class RedisQueueManager {\r\n private redis: RedisClientType;\r\n private deduplicator: MessageDeduplicator;\r\n private stats: Map<string, {\r\n enqueued: number;\r\n dequeued: number;\r\n acknowledged: number;\r\n rejected: number;\r\n startTime: Date;\r\n }> = new Map();\r\n\r\n /**\r\n * Create a new RedisQueueManager instance\r\n *\r\n * @param redis - Redis client instance\r\n * @param deduplicator - Optional custom deduplicator instance\r\n */\r\n constructor(redis: RedisClientType, deduplicator?: MessageDeduplicator) {\r\n this.redis = redis;\r\n this.deduplicator = deduplicator || new MessageDeduplicator(redis);\r\n\r\n logger.info('RedisQueueManager initialized');\r\n }\r\n\r\n /**\r\n * Enqueue a message to a queue\r\n *\r\n * @param queue - Queue name\r\n * @param payload - Message payload\r\n * @param options - Enqueue options\r\n * @returns Message ID\r\n */\r\n public async enqueue<T = any>(\r\n queue: string,\r\n payload: T,\r\n options: EnqueueOptions = {}\r\n ): Promise<string> {\r\n const opts = { ...DEFAULT_ENQUEUE_OPTIONS, ...options };\r\n const startTime = Date.now();\r\n\r\n try {\r\n // Check for duplicates if enabled\r\n if (opts.deduplicate) {\r\n const isDuplicate = await this.deduplicator.isDuplicate(payload);\r\n\r\n if (isDuplicate) {\r\n logger.warn('Duplicate message detected, skipping enqueue', {\r\n queue,\r\n payloadHash: this.deduplicator.createFingerprint(payload).substring(0, 16) + '...',\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_DUPLICATE_KEY,\r\n 'Duplicate message detected',\r\n { queue }\r\n );\r\n }\r\n }\r\n\r\n // Create message\r\n const message: QueueMessage<T> = {\r\n id: uuidv4(),\r\n queue,\r\n payload,\r\n createdAt: new Date(),\r\n enqueuedAt: new Date(),\r\n deliveryAttempts: 0,\r\n visibilityTimeout: opts.visibilityTimeout,\r\n metadata: opts.metadata,\r\n };\r\n\r\n // Push to queue (RPUSH for FIFO)\r\n await withRetry(\r\n async () => {\r\n const queueKey = this.getQueueKey(queue);\r\n await this.redis.rPush(queueKey, JSON.stringify(message));\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n // Mark as processed in deduplicator if enabled\r\n if (opts.deduplicate) {\r\n await this.deduplicator.markProcessed(payload, {\r\n messageId: message.id,\r\n queue,\r\n });\r\n }\r\n\r\n // Update stats\r\n this.updateStats(queue, 'enqueued');\r\n\r\n const duration = Date.now() - startTime;\r\n\r\n logger.debug('Message enqueued', {\r\n queue,\r\n messageId: message.id,\r\n durationMs: duration,\r\n });\r\n\r\n // Validate performance requirement (<100ms)\r\n if (duration > 100) {\r\n logger.warn('Enqueue operation exceeded 100ms target', {\r\n queue,\r\n durationMs: duration,\r\n });\r\n }\r\n\r\n return message.id;\r\n } catch (error) {\r\n logger.error('Failed to enqueue message', error instanceof Error ? error : new Error(String(error)), {\r\n queue,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to enqueue message',\r\n { queue },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Dequeue a message from a queue\r\n *\r\n * @param queue - Queue name\r\n * @param options - Dequeue options\r\n * @returns Message or null if queue is empty\r\n */\r\n public async dequeue<T = any>(\r\n queue: string,\r\n options: DequeueOptions = {}\r\n ): Promise<QueueMessage<T> | null> {\r\n const opts = { ...DEFAULT_DEQUEUE_OPTIONS, ...options };\r\n const startTime = Date.now();\r\n\r\n try {\r\n const queueKey = this.getQueueKey(queue);\r\n const processingKey = this.getProcessingKey(queue);\r\n\r\n let messageData: string | null = null;\r\n\r\n // Use blocking pop if timeout specified\r\n if (opts.timeout > 0) {\r\n const result = await withRetry(\r\n async () => {\r\n // BLMOVE atomically moves from queue to processing set with timeout\r\n return await this.redis.blMove(\r\n queueKey,\r\n processingKey,\r\n 'LEFT',\r\n 'RIGHT',\r\n opts.timeout / 1000 // Convert to seconds\r\n );\r\n },\r\n { maxAttempts: 1 } // Don't retry blocking operations\r\n );\r\n\r\n messageData = result;\r\n } else {\r\n // Non-blocking pop\r\n messageData = await withRetry(\r\n async () => {\r\n return await this.redis.lMove(\r\n queueKey,\r\n processingKey,\r\n 'LEFT',\r\n 'RIGHT'\r\n );\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n }\r\n\r\n if (!messageData) {\r\n return null;\r\n }\r\n\r\n // Parse message\r\n const message = JSON.parse(messageData) as QueueMessage<T>;\r\n\r\n // Convert date strings back to Date objects\r\n message.createdAt = new Date(message.createdAt);\r\n message.enqueuedAt = new Date(message.enqueuedAt);\r\n message.dequeuedAt = new Date();\r\n message.deliveryAttempts++;\r\n\r\n // Store message with visibility timeout\r\n await this.storeInFlight(message, opts.visibilityTimeout);\r\n\r\n // Update stats\r\n this.updateStats(queue, 'dequeued');\r\n\r\n const duration = Date.now() - startTime;\r\n\r\n logger.debug('Message dequeued', {\r\n queue,\r\n messageId: message.id,\r\n deliveryAttempts: message.deliveryAttempts,\r\n durationMs: duration,\r\n });\r\n\r\n // Validate performance requirement (<100ms)\r\n if (duration > 100) {\r\n logger.warn('Dequeue operation exceeded 100ms target', {\r\n queue,\r\n durationMs: duration,\r\n });\r\n }\r\n\r\n return message;\r\n } catch (error) {\r\n logger.error('Failed to dequeue message', error instanceof Error ? error : new Error(String(error)), {\r\n queue,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to dequeue message',\r\n { queue },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Acknowledge successful message processing\r\n *\r\n * @param messageId - Message ID to acknowledge\r\n */\r\n public async acknowledge(messageId: string): Promise<void> {\r\n const startTime = Date.now();\r\n\r\n try {\r\n // Remove from in-flight storage\r\n const message = await this.getInFlight(messageId);\r\n\r\n if (!message) {\r\n logger.warn('Message not found for acknowledgment', { messageId });\r\n return;\r\n }\r\n\r\n // Remove from processing set\r\n const processingKey = this.getProcessingKey(message.queue);\r\n await withRetry(\r\n async () => {\r\n await this.redis.lRem(processingKey, 1, JSON.stringify(message));\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n // Remove from in-flight storage\r\n await this.removeInFlight(messageId);\r\n\r\n // Update stats\r\n this.updateStats(message.queue, 'acknowledged');\r\n\r\n const duration = Date.now() - startTime;\r\n\r\n logger.debug('Message acknowledged', {\r\n queue: message.queue,\r\n messageId,\r\n durationMs: duration,\r\n });\r\n } catch (error) {\r\n logger.error('Failed to acknowledge message', error instanceof Error ? error : new Error(String(error)), {\r\n messageId,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to acknowledge message',\r\n { messageId },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Reject message processing (with optional retry)\r\n *\r\n * @param messageId - Message ID to reject\r\n * @param options - Reject options\r\n */\r\n public async reject(messageId: string, options: RejectOptions = {}): Promise<void> {\r\n const startTime = Date.now();\r\n\r\n try {\r\n // Get message from in-flight storage\r\n const message = await this.getInFlight(messageId);\r\n\r\n if (!message) {\r\n logger.warn('Message not found for rejection', { messageId });\r\n return;\r\n }\r\n\r\n // Remove from processing set\r\n const processingKey = this.getProcessingKey(message.queue);\r\n await withRetry(\r\n async () => {\r\n await this.redis.lRem(processingKey, 1, JSON.stringify(message));\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n // Remove from in-flight storage\r\n await this.removeInFlight(messageId);\r\n\r\n if (options.retry) {\r\n // Re-enqueue message\r\n message.metadata = {\r\n ...message.metadata,\r\n ...options.metadata,\r\n rejectedAt: new Date().toISOString(),\r\n rejectionReason: options.error,\r\n };\r\n\r\n const queueKey = this.getQueueKey(message.queue);\r\n await withRetry(\r\n async () => {\r\n await this.redis.rPush(queueKey, JSON.stringify(message));\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n logger.debug('Message rejected and re-enqueued', {\r\n queue: message.queue,\r\n messageId,\r\n deliveryAttempts: message.deliveryAttempts,\r\n });\r\n } else {\r\n logger.debug('Message rejected without retry', {\r\n queue: message.queue,\r\n messageId,\r\n });\r\n }\r\n\r\n // Update stats\r\n this.updateStats(message.queue, 'rejected');\r\n\r\n const duration = Date.now() - startTime;\r\n\r\n logger.debug('Message rejected', {\r\n queue: message.queue,\r\n messageId,\r\n retry: options.retry,\r\n durationMs: duration,\r\n });\r\n } catch (error) {\r\n logger.error('Failed to reject message', error instanceof Error ? error : new Error(String(error)), {\r\n messageId,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to reject message',\r\n { messageId },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Get queue statistics\r\n *\r\n * @param queue - Queue name\r\n * @returns Queue statistics\r\n */\r\n public async getStats(queue: string): Promise<QueueStats> {\r\n try {\r\n const queueKey = this.getQueueKey(queue);\r\n const processingKey = this.getProcessingKey(queue);\r\n\r\n // Get queue depth\r\n const depth = await this.redis.lLen(queueKey);\r\n\r\n // Get in-flight count\r\n const inFlight = await this.redis.lLen(processingKey);\r\n\r\n // Get oldest message age\r\n let oldestMessageAge = 0;\r\n const oldestMessage = await this.redis.lIndex(queueKey, 0);\r\n\r\n if (oldestMessage) {\r\n const message = JSON.parse(oldestMessage) as QueueMessage;\r\n const age = Date.now() - new Date(message.enqueuedAt).getTime();\r\n oldestMessageAge = Math.floor(age / 1000); // Convert to seconds\r\n }\r\n\r\n // Get stats from tracking\r\n const stats = this.stats.get(queue) || {\r\n enqueued: 0,\r\n dequeued: 0,\r\n acknowledged: 0,\r\n rejected: 0,\r\n startTime: new Date(),\r\n };\r\n\r\n // Calculate throughput (messages per second)\r\n const elapsed = (Date.now() - stats.startTime.getTime()) / 1000;\r\n const throughput = elapsed > 0 ? stats.dequeued / elapsed : 0;\r\n\r\n return {\r\n queue,\r\n depth,\r\n inFlight,\r\n oldestMessageAge,\r\n totalEnqueued: stats.enqueued,\r\n totalDequeued: stats.dequeued,\r\n totalAcknowledged: stats.acknowledged,\r\n totalRejected: stats.rejected,\r\n throughput,\r\n };\r\n } catch (error) {\r\n logger.error('Failed to get queue stats', error instanceof Error ? error : new Error(String(error)), {\r\n queue,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to get queue stats',\r\n { queue },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Purge all messages from a queue\r\n *\r\n * @param queue - Queue name\r\n * @returns Number of messages purged\r\n */\r\n public async purge(queue: string): Promise<number> {\r\n try {\r\n const queueKey = this.getQueueKey(queue);\r\n\r\n const count = await withRetry(\r\n async () => {\r\n const len = await this.redis.lLen(queueKey);\r\n await this.redis.del(queueKey);\r\n return len;\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n logger.info('Queue purged', {\r\n queue,\r\n messagesPurged: count,\r\n });\r\n\r\n return count;\r\n } catch (error) {\r\n logger.error('Failed to purge queue', error instanceof Error ? error : new Error(String(error)), {\r\n queue,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to purge queue',\r\n { queue },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Get all queue names\r\n *\r\n * @returns Array of queue names\r\n */\r\n public async getQueues(): Promise<string[]> {\r\n try {\r\n const pattern = 'queue:*';\r\n const keys = await this.redis.keys(pattern);\r\n\r\n const queues = keys\r\n .filter(key => !key.includes(':processing'))\r\n .map(key => key.replace('queue:', ''));\r\n\r\n return queues;\r\n } catch (error) {\r\n logger.error('Failed to get queues', error instanceof Error ? error : new Error(String(error)));\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to get queues',\r\n {},\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown queue manager (cleanup resources)\r\n */\r\n public shutdown(): void {\r\n this.deduplicator.shutdown();\r\n logger.info('RedisQueueManager shutdown');\r\n }\r\n\r\n /**\r\n * Get Redis key for queue\r\n */\r\n private getQueueKey(queue: string): string {\r\n return `queue:${queue}`;\r\n }\r\n\r\n /**\r\n * Get Redis key for processing set\r\n */\r\n private getProcessingKey(queue: string): string {\r\n return `queue:${queue}:processing`;\r\n }\r\n\r\n /**\r\n * Get Redis key for in-flight message storage\r\n */\r\n private getInFlightKey(messageId: string): string {\r\n return `inflight:${messageId}`;\r\n }\r\n\r\n /**\r\n * Store message in in-flight storage with TTL\r\n */\r\n private async storeInFlight<T = any>(\r\n message: QueueMessage<T>,\r\n visibilityTimeout: number\r\n ): Promise<void> {\r\n const key = this.getInFlightKey(message.id);\r\n\r\n await withRetry(\r\n async () => {\r\n await this.redis.set(\r\n key,\r\n JSON.stringify(message),\r\n { PX: visibilityTimeout }\r\n );\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n }\r\n\r\n /**\r\n * Get message from in-flight storage\r\n */\r\n private async getInFlight<T = any>(messageId: string): Promise<QueueMessage<T> | null> {\r\n const key = this.getInFlightKey(messageId);\r\n\r\n const data = await withRetry(\r\n async () => await this.redis.get(key),\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n if (!data) {\r\n return null;\r\n }\r\n\r\n const message = JSON.parse(data) as QueueMessage<T>;\r\n\r\n // Convert date strings back to Date objects\r\n message.createdAt = new Date(message.createdAt);\r\n message.enqueuedAt = new Date(message.enqueuedAt);\r\n if (message.dequeuedAt) {\r\n message.dequeuedAt = new Date(message.dequeuedAt);\r\n }\r\n\r\n return message;\r\n }\r\n\r\n /**\r\n * Remove message from in-flight storage\r\n */\r\n private async removeInFlight(messageId: string): Promise<void> {\r\n const key = this.getInFlightKey(messageId);\r\n\r\n await withRetry(\r\n async () => await this.redis.del(key),\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n }\r\n\r\n /**\r\n * Update queue statistics\r\n */\r\n private updateStats(queue: string, operation: 'enqueued' | 'dequeued' | 'acknowledged' | 'rejected'): void {\r\n let stats = this.stats.get(queue);\r\n\r\n if (!stats) {\r\n stats = {\r\n enqueued: 0,\r\n dequeued: 0,\r\n acknowledged: 0,\r\n rejected: 0,\r\n startTime: new Date(),\r\n };\r\n this.stats.set(queue, stats);\r\n }\r\n\r\n stats[operation]++;\r\n }\r\n}\r\n"],"names":["v4","uuidv4","createLogger","createError","ErrorCode","isRetryableError","withRetry","MessageDeduplicator","logger","DEFAULT_ENQUEUE_OPTIONS","deduplicate","metadata","visibilityTimeout","DEFAULT_DEQUEUE_OPTIONS","timeout","count","RedisQueueManager","redis","deduplicator","stats","Map","info","enqueue","queue","payload","options","opts","startTime","Date","now","isDuplicate","warn","payloadHash","createFingerprint","substring","DB_DUPLICATE_KEY","message","id","createdAt","enqueuedAt","deliveryAttempts","queueKey","getQueueKey","rPush","JSON","stringify","maxAttempts","shouldRetry","markProcessed","messageId","updateStats","duration","debug","durationMs","error","Error","String","DB_QUERY_FAILED","undefined","dequeue","processingKey","getProcessingKey","messageData","result","blMove","lMove","parse","dequeuedAt","storeInFlight","acknowledge","getInFlight","lRem","removeInFlight","reject","retry","rejectedAt","toISOString","rejectionReason","getStats","depth","lLen","inFlight","oldestMessageAge","oldestMessage","lIndex","age","getTime","Math","floor","get","enqueued","dequeued","acknowledged","rejected","elapsed","throughput","totalEnqueued","totalDequeued","totalAcknowledged","totalRejected","purge","len","del","messagesPurged","getQueues","pattern","keys","queues","filter","key","includes","map","replace","shutdown","getInFlightKey","set","PX","data","operation"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/redis-queue-manager.ts"],"sourcesContent":["/**\r\n * Redis Queue Manager\r\n *\r\n * Provides reliable queue operations with idempotency, acknowledgment protocol,\r\n * and message visibility timeout for Docker agent ↔ Redis communication.\r\n * Part of Task 3.4: Redis Queue Consistency & Recovery (Integration Standardization Sprint 3)\r\n *\r\n * Features:\r\n * - Enqueue with idempotency (prevents duplicate messages)\r\n * - Dequeue with acknowledgment protocol\r\n * - Message visibility timeout\r\n * - Queue monitoring (depth, age, throughput)\r\n * - Multiple queue support (task, result, coordination)\r\n * - Performance: <100ms per operation\r\n *\r\n * Usage:\r\n * const queueManager = new RedisQueueManager(redisClient);\r\n *\r\n * // Producer\r\n * await queueManager.enqueue('task-queue', {\r\n * taskId: 'task-001',\r\n * agentType: 'backend-developer',\r\n * payload: { ... }\r\n * });\r\n *\r\n * // Consumer\r\n * const message = await queueManager.dequeue('task-queue', { timeout: 30000 });\r\n * try {\r\n * await processTask(message.payload);\r\n * await queueManager.acknowledge(message.id);\r\n * } catch (error) {\r\n * await queueManager.reject(message.id, { retry: true });\r\n * }\r\n */\r\n\r\nimport { RedisClientType } from 'redis';\r\nimport { v4 as uuidv4 } from 'uuid';\r\nimport { createLogger } from './logging.js';\r\nimport { createError, ErrorCode, isRetryableError, StandardError } from './errors.js';\r\nimport { withRetry } from './retry.js';\r\nimport { MessageDeduplicator } from './message-deduplicator.js';\r\n\r\nconst logger = createLogger('redis-queue-manager');\r\n\r\n/**\r\n * Queue message\r\n */\r\nexport interface QueueMessage<T = any> {\r\n /** Unique message ID */\r\n id: string;\r\n /** Queue name */\r\n queue: string;\r\n /** Message payload */\r\n payload: T;\r\n /** Message creation timestamp */\r\n createdAt: Date;\r\n /** Message enqueue timestamp */\r\n enqueuedAt: Date;\r\n /** Message dequeue timestamp (if dequeued) */\r\n dequeuedAt?: Date;\r\n /** Number of delivery attempts */\r\n deliveryAttempts: number;\r\n /** Message visibility timeout (milliseconds) */\r\n visibilityTimeout?: number;\r\n /** Message metadata */\r\n metadata?: Record<string, any>;\r\n}\r\n\r\n/**\r\n * Enqueue options\r\n */\r\nexport interface EnqueueOptions {\r\n /** Enable deduplication (default: true) */\r\n deduplicate?: boolean;\r\n /** Message metadata */\r\n metadata?: Record<string, any>;\r\n /** Message visibility timeout in milliseconds (default: 30000) */\r\n visibilityTimeout?: number;\r\n}\r\n\r\n/**\r\n * Dequeue options\r\n */\r\nexport interface DequeueOptions {\r\n /** Maximum wait time in milliseconds (default: 0 - no wait) */\r\n timeout?: number;\r\n /** Message visibility timeout in milliseconds (default: 30000) */\r\n visibilityTimeout?: number;\r\n /** Number of messages to dequeue (default: 1) */\r\n count?: number;\r\n}\r\n\r\n/**\r\n * Reject options\r\n */\r\nexport interface RejectOptions {\r\n /** Retry message (re-enqueue) (default: false) */\r\n retry?: boolean;\r\n /** Error message */\r\n error?: string;\r\n /** Metadata to attach */\r\n metadata?: Record<string, any>;\r\n}\r\n\r\n/**\r\n * Queue statistics\r\n */\r\nexport interface QueueStats {\r\n /** Queue name */\r\n queue: string;\r\n /** Number of messages in queue */\r\n depth: number;\r\n /** Number of messages in processing (invisible) */\r\n inFlight: number;\r\n /** Age of oldest message in seconds */\r\n oldestMessageAge: number;\r\n /** Total messages enqueued */\r\n totalEnqueued: number;\r\n /** Total messages dequeued */\r\n totalDequeued: number;\r\n /** Total messages acknowledged */\r\n totalAcknowledged: number;\r\n /** Total messages rejected */\r\n totalRejected: number;\r\n /** Throughput (messages per second) */\r\n throughput: number;\r\n}\r\n\r\n/**\r\n * Default queue options\r\n */\r\nconst DEFAULT_ENQUEUE_OPTIONS: Required<EnqueueOptions> = {\r\n deduplicate: true,\r\n metadata: {},\r\n visibilityTimeout: 30000, // 30 seconds\r\n};\r\n\r\nconst DEFAULT_DEQUEUE_OPTIONS: Required<DequeueOptions> = {\r\n timeout: 0,\r\n visibilityTimeout: 30000, // 30 seconds\r\n count: 1,\r\n};\r\n\r\n/**\r\n * Redis Queue Manager\r\n *\r\n * Provides reliable queue operations with at-least-once delivery guarantees.\r\n */\r\nexport class RedisQueueManager {\r\n private redis: RedisClientType;\r\n private deduplicator: MessageDeduplicator;\r\n private stats: Map<string, {\r\n enqueued: number;\r\n dequeued: number;\r\n acknowledged: number;\r\n rejected: number;\r\n startTime: Date;\r\n }> = new Map();\r\n\r\n /**\r\n * Create a new RedisQueueManager instance\r\n *\r\n * @param redis - Redis client instance\r\n * @param deduplicator - Optional custom deduplicator instance\r\n */\r\n constructor(redis: RedisClientType, deduplicator?: MessageDeduplicator) {\r\n this.redis = redis;\r\n this.deduplicator = deduplicator || new MessageDeduplicator(redis);\r\n\r\n logger.info('RedisQueueManager initialized');\r\n }\r\n\r\n /**\r\n * Enqueue a message to a queue\r\n *\r\n * @param queue - Queue name\r\n * @param payload - Message payload\r\n * @param options - Enqueue options\r\n * @returns Message ID\r\n */\r\n public async enqueue<T = any>(\r\n queue: string,\r\n payload: T,\r\n options: EnqueueOptions = {}\r\n ): Promise<string> {\r\n const opts = { ...DEFAULT_ENQUEUE_OPTIONS, ...options };\r\n const startTime = Date.now();\r\n\r\n try {\r\n // Check for duplicates if enabled\r\n if (opts.deduplicate) {\r\n const isDuplicate = await this.deduplicator.isDuplicate(payload);\r\n\r\n if (isDuplicate) {\r\n logger.warn('Duplicate message detected, skipping enqueue', {\r\n queue,\r\n payloadHash: this.deduplicator.createFingerprint(payload).substring(0, 16) + '...',\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_DUPLICATE_KEY,\r\n 'Duplicate message detected',\r\n { queue }\r\n );\r\n }\r\n }\r\n\r\n // Create message\r\n const message: QueueMessage<T> = {\r\n id: uuidv4(),\r\n queue,\r\n payload,\r\n createdAt: new Date(),\r\n enqueuedAt: new Date(),\r\n deliveryAttempts: 0,\r\n visibilityTimeout: opts.visibilityTimeout,\r\n metadata: opts.metadata,\r\n };\r\n\r\n // Push to queue (RPUSH for FIFO)\r\n await withRetry(\r\n async () => {\r\n const queueKey = this.getQueueKey(queue);\r\n await this.redis.rPush(queueKey, JSON.stringify(message));\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n // Mark as processed in deduplicator if enabled\r\n if (opts.deduplicate) {\r\n await this.deduplicator.markProcessed(payload, {\r\n messageId: message.id,\r\n queue,\r\n });\r\n }\r\n\r\n // Update stats\r\n this.updateStats(queue, 'enqueued');\r\n\r\n const duration = Date.now() - startTime;\r\n\r\n logger.debug('Message enqueued', {\r\n queue,\r\n messageId: message.id,\r\n durationMs: duration,\r\n });\r\n\r\n // Validate performance requirement (<100ms)\r\n if (duration > 100) {\r\n logger.warn('Enqueue operation exceeded 100ms target', {\r\n queue,\r\n durationMs: duration,\r\n });\r\n }\r\n\r\n return message.id;\r\n } catch (error) {\r\n // Re-throw duplicate errors without wrapping\r\n if (error instanceof StandardError && error.code === ErrorCode.DB_DUPLICATE_KEY) {\r\n throw error;\r\n }\r\n\r\n logger.error('Failed to enqueue message', error instanceof Error ? error : new Error(String(error)), {\r\n queue,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to enqueue message',\r\n { queue },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Dequeue a message from a queue\r\n *\r\n * @param queue - Queue name\r\n * @param options - Dequeue options\r\n * @returns Message or null if queue is empty\r\n */\r\n public async dequeue<T = any>(\r\n queue: string,\r\n options: DequeueOptions = {}\r\n ): Promise<QueueMessage<T> | null> {\r\n const opts = { ...DEFAULT_DEQUEUE_OPTIONS, ...options };\r\n const startTime = Date.now();\r\n\r\n try {\r\n const queueKey = this.getQueueKey(queue);\r\n const processingKey = this.getProcessingKey(queue);\r\n\r\n let messageData: string | null = null;\r\n\r\n // Use blocking pop if timeout specified\r\n if (opts.timeout > 0) {\r\n const result = await withRetry(\r\n async () => {\r\n // BLMOVE atomically moves from queue to processing set with timeout\r\n return await this.redis.blMove(\r\n queueKey,\r\n processingKey,\r\n 'LEFT',\r\n 'RIGHT',\r\n opts.timeout / 1000 // Convert to seconds\r\n );\r\n },\r\n { maxAttempts: 1 } // Don't retry blocking operations\r\n );\r\n\r\n messageData = result;\r\n } else {\r\n // Non-blocking pop\r\n messageData = await withRetry(\r\n async () => {\r\n return await this.redis.lMove(\r\n queueKey,\r\n processingKey,\r\n 'LEFT',\r\n 'RIGHT'\r\n );\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n }\r\n\r\n if (!messageData) {\r\n return null;\r\n }\r\n\r\n // Parse message\r\n const message = JSON.parse(messageData) as QueueMessage<T>;\r\n\r\n // Convert date strings back to Date objects\r\n message.createdAt = new Date(message.createdAt);\r\n message.enqueuedAt = new Date(message.enqueuedAt);\r\n message.dequeuedAt = new Date();\r\n message.deliveryAttempts++;\r\n\r\n // Store message with visibility timeout\r\n await this.storeInFlight(message, opts.visibilityTimeout);\r\n\r\n // Update stats\r\n this.updateStats(queue, 'dequeued');\r\n\r\n const duration = Date.now() - startTime;\r\n\r\n logger.debug('Message dequeued', {\r\n queue,\r\n messageId: message.id,\r\n deliveryAttempts: message.deliveryAttempts,\r\n durationMs: duration,\r\n });\r\n\r\n // Validate performance requirement (<100ms)\r\n if (duration > 100) {\r\n logger.warn('Dequeue operation exceeded 100ms target', {\r\n queue,\r\n durationMs: duration,\r\n });\r\n }\r\n\r\n return message;\r\n } catch (error) {\r\n logger.error('Failed to dequeue message', error instanceof Error ? error : new Error(String(error)), {\r\n queue,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to dequeue message',\r\n { queue },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Acknowledge successful message processing\r\n *\r\n * @param messageId - Message ID to acknowledge\r\n */\r\n public async acknowledge(messageId: string): Promise<void> {\r\n const startTime = Date.now();\r\n\r\n try {\r\n // Remove from in-flight storage\r\n const message = await this.getInFlight(messageId);\r\n\r\n if (!message) {\r\n logger.warn('Message not found for acknowledgment', { messageId });\r\n return;\r\n }\r\n\r\n // Remove from processing set\r\n const processingKey = this.getProcessingKey(message.queue);\r\n await withRetry(\r\n async () => {\r\n await this.redis.lRem(processingKey, 1, JSON.stringify(message));\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n // Remove from in-flight storage\r\n await this.removeInFlight(messageId);\r\n\r\n // Update stats\r\n this.updateStats(message.queue, 'acknowledged');\r\n\r\n const duration = Date.now() - startTime;\r\n\r\n logger.debug('Message acknowledged', {\r\n queue: message.queue,\r\n messageId,\r\n durationMs: duration,\r\n });\r\n } catch (error) {\r\n logger.error('Failed to acknowledge message', error instanceof Error ? error : new Error(String(error)), {\r\n messageId,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to acknowledge message',\r\n { messageId },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Reject message processing (with optional retry)\r\n *\r\n * @param messageId - Message ID to reject\r\n * @param options - Reject options\r\n */\r\n public async reject(messageId: string, options: RejectOptions = {}): Promise<void> {\r\n const startTime = Date.now();\r\n\r\n try {\r\n // Get message from in-flight storage\r\n const message = await this.getInFlight(messageId);\r\n\r\n if (!message) {\r\n logger.warn('Message not found for rejection', { messageId });\r\n return;\r\n }\r\n\r\n // Remove from processing set\r\n const processingKey = this.getProcessingKey(message.queue);\r\n await withRetry(\r\n async () => {\r\n await this.redis.lRem(processingKey, 1, JSON.stringify(message));\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n // Remove from in-flight storage\r\n await this.removeInFlight(messageId);\r\n\r\n if (options.retry) {\r\n // Re-enqueue message\r\n message.metadata = {\r\n ...message.metadata,\r\n ...options.metadata,\r\n rejectedAt: new Date().toISOString(),\r\n rejectionReason: options.error,\r\n };\r\n\r\n const queueKey = this.getQueueKey(message.queue);\r\n await withRetry(\r\n async () => {\r\n await this.redis.rPush(queueKey, JSON.stringify(message));\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n logger.debug('Message rejected and re-enqueued', {\r\n queue: message.queue,\r\n messageId,\r\n deliveryAttempts: message.deliveryAttempts,\r\n });\r\n } else {\r\n logger.debug('Message rejected without retry', {\r\n queue: message.queue,\r\n messageId,\r\n });\r\n }\r\n\r\n // Update stats\r\n this.updateStats(message.queue, 'rejected');\r\n\r\n const duration = Date.now() - startTime;\r\n\r\n logger.debug('Message rejected', {\r\n queue: message.queue,\r\n messageId,\r\n retry: options.retry,\r\n durationMs: duration,\r\n });\r\n } catch (error) {\r\n logger.error('Failed to reject message', error instanceof Error ? error : new Error(String(error)), {\r\n messageId,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to reject message',\r\n { messageId },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Get queue statistics\r\n *\r\n * @param queue - Queue name\r\n * @returns Queue statistics\r\n */\r\n public async getStats(queue: string): Promise<QueueStats> {\r\n try {\r\n const queueKey = this.getQueueKey(queue);\r\n const processingKey = this.getProcessingKey(queue);\r\n\r\n // Get queue depth\r\n const depth = await this.redis.lLen(queueKey);\r\n\r\n // Get in-flight count\r\n const inFlight = await this.redis.lLen(processingKey);\r\n\r\n // Get oldest message age\r\n let oldestMessageAge = 0;\r\n const oldestMessage = await this.redis.lIndex(queueKey, 0);\r\n\r\n if (oldestMessage) {\r\n const message = JSON.parse(oldestMessage) as QueueMessage;\r\n const age = Date.now() - new Date(message.enqueuedAt).getTime();\r\n oldestMessageAge = Math.floor(age / 1000); // Convert to seconds\r\n }\r\n\r\n // Get stats from tracking\r\n const stats = this.stats.get(queue) || {\r\n enqueued: 0,\r\n dequeued: 0,\r\n acknowledged: 0,\r\n rejected: 0,\r\n startTime: new Date(),\r\n };\r\n\r\n // Calculate throughput (messages per second)\r\n const elapsed = (Date.now() - stats.startTime.getTime()) / 1000;\r\n const throughput = elapsed > 0 ? stats.dequeued / elapsed : 0;\r\n\r\n return {\r\n queue,\r\n depth,\r\n inFlight,\r\n oldestMessageAge,\r\n totalEnqueued: stats.enqueued,\r\n totalDequeued: stats.dequeued,\r\n totalAcknowledged: stats.acknowledged,\r\n totalRejected: stats.rejected,\r\n throughput,\r\n };\r\n } catch (error) {\r\n logger.error('Failed to get queue stats', error instanceof Error ? error : new Error(String(error)), {\r\n queue,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to get queue stats',\r\n { queue },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Purge all messages from a queue\r\n *\r\n * @param queue - Queue name\r\n * @returns Number of messages purged\r\n */\r\n public async purge(queue: string): Promise<number> {\r\n try {\r\n const queueKey = this.getQueueKey(queue);\r\n\r\n const count = await withRetry(\r\n async () => {\r\n const len = await this.redis.lLen(queueKey);\r\n await this.redis.del(queueKey);\r\n return len;\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n logger.info('Queue purged', {\r\n queue,\r\n messagesPurged: count,\r\n });\r\n\r\n return count;\r\n } catch (error) {\r\n logger.error('Failed to purge queue', error instanceof Error ? error : new Error(String(error)), {\r\n queue,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to purge queue',\r\n { queue },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Get all queue names\r\n *\r\n * @returns Array of queue names\r\n */\r\n public async getQueues(): Promise<string[]> {\r\n try {\r\n const pattern = 'queue:*';\r\n const keys = await this.redis.keys(pattern);\r\n\r\n const queues = keys\r\n .filter(key => !key.includes(':processing'))\r\n .map(key => key.replace('queue:', ''));\r\n\r\n return queues;\r\n } catch (error) {\r\n logger.error('Failed to get queues', error instanceof Error ? error : new Error(String(error)));\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to get queues',\r\n {},\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown queue manager (cleanup resources)\r\n */\r\n public shutdown(): void {\r\n this.deduplicator.shutdown();\r\n logger.info('RedisQueueManager shutdown');\r\n }\r\n\r\n /**\r\n * Get Redis key for queue\r\n */\r\n private getQueueKey(queue: string): string {\r\n return `queue:${queue}`;\r\n }\r\n\r\n /**\r\n * Get Redis key for processing set\r\n */\r\n private getProcessingKey(queue: string): string {\r\n return `queue:${queue}:processing`;\r\n }\r\n\r\n /**\r\n * Get Redis key for in-flight message storage\r\n */\r\n private getInFlightKey(messageId: string): string {\r\n return `inflight:${messageId}`;\r\n }\r\n\r\n /**\r\n * Store message in in-flight storage with TTL\r\n */\r\n private async storeInFlight<T = any>(\r\n message: QueueMessage<T>,\r\n visibilityTimeout: number\r\n ): Promise<void> {\r\n const key = this.getInFlightKey(message.id);\r\n\r\n await withRetry(\r\n async () => {\r\n await this.redis.set(\r\n key,\r\n JSON.stringify(message),\r\n { PX: visibilityTimeout }\r\n );\r\n },\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n }\r\n\r\n /**\r\n * Get message from in-flight storage\r\n */\r\n private async getInFlight<T = any>(messageId: string): Promise<QueueMessage<T> | null> {\r\n const key = this.getInFlightKey(messageId);\r\n\r\n const data = await withRetry(\r\n async () => await this.redis.get(key),\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n\r\n if (!data) {\r\n return null;\r\n }\r\n\r\n const message = JSON.parse(data) as QueueMessage<T>;\r\n\r\n // Convert date strings back to Date objects\r\n message.createdAt = new Date(message.createdAt);\r\n message.enqueuedAt = new Date(message.enqueuedAt);\r\n if (message.dequeuedAt) {\r\n message.dequeuedAt = new Date(message.dequeuedAt);\r\n }\r\n\r\n return message;\r\n }\r\n\r\n /**\r\n * Remove message from in-flight storage\r\n */\r\n private async removeInFlight(messageId: string): Promise<void> {\r\n const key = this.getInFlightKey(messageId);\r\n\r\n await withRetry(\r\n async () => await this.redis.del(key),\r\n { maxAttempts: 3, shouldRetry: isRetryableError }\r\n );\r\n }\r\n\r\n /**\r\n * Update queue statistics\r\n */\r\n private updateStats(queue: string, operation: 'enqueued' | 'dequeued' | 'acknowledged' | 'rejected'): void {\r\n let stats = this.stats.get(queue);\r\n\r\n if (!stats) {\r\n stats = {\r\n enqueued: 0,\r\n dequeued: 0,\r\n acknowledged: 0,\r\n rejected: 0,\r\n startTime: new Date(),\r\n };\r\n this.stats.set(queue, stats);\r\n }\r\n\r\n stats[operation]++;\r\n }\r\n}\r\n"],"names":["v4","uuidv4","createLogger","createError","ErrorCode","isRetryableError","StandardError","withRetry","MessageDeduplicator","logger","DEFAULT_ENQUEUE_OPTIONS","deduplicate","metadata","visibilityTimeout","DEFAULT_DEQUEUE_OPTIONS","timeout","count","RedisQueueManager","redis","deduplicator","stats","Map","info","enqueue","queue","payload","options","opts","startTime","Date","now","isDuplicate","warn","payloadHash","createFingerprint","substring","DB_DUPLICATE_KEY","message","id","createdAt","enqueuedAt","deliveryAttempts","queueKey","getQueueKey","rPush","JSON","stringify","maxAttempts","shouldRetry","markProcessed","messageId","updateStats","duration","debug","durationMs","error","code","Error","String","DB_QUERY_FAILED","undefined","dequeue","processingKey","getProcessingKey","messageData","result","blMove","lMove","parse","dequeuedAt","storeInFlight","acknowledge","getInFlight","lRem","removeInFlight","reject","retry","rejectedAt","toISOString","rejectionReason","getStats","depth","lLen","inFlight","oldestMessageAge","oldestMessage","lIndex","age","getTime","Math","floor","get","enqueued","dequeued","acknowledged","rejected","elapsed","throughput","totalEnqueued","totalDequeued","totalAcknowledged","totalRejected","purge","len","del","messagesPurged","getQueues","pattern","keys","queues","filter","key","includes","map","replace","shutdown","getInFlightKey","set","PX","data","operation"],"mappings":"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"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Middleware
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive authentication and authorization middleware for Express.js
|
|
5
|
+
* with JWT token validation, role-based access control, and security features.
|
|
6
|
+
*/ import jwt from 'jsonwebtoken';
|
|
7
|
+
import bcrypt from 'bcrypt';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { StandardError, ErrorCode } from '../../lib/errors.js';
|
|
10
|
+
/**
|
|
11
|
+
* Authentication middleware that validates JWT tokens
|
|
12
|
+
*
|
|
13
|
+
* @param config Authentication configuration
|
|
14
|
+
* @returns Express middleware function
|
|
15
|
+
*/ export function authenticationMiddleware(config) {
|
|
16
|
+
return async (req, res, next)=>{
|
|
17
|
+
try {
|
|
18
|
+
// Check for authorization header
|
|
19
|
+
const authHeader = req.headers.authorization;
|
|
20
|
+
if (!authHeader) {
|
|
21
|
+
res.status(401).json({
|
|
22
|
+
error: 'Unauthorized',
|
|
23
|
+
message: 'No authorization header provided',
|
|
24
|
+
errorCode: 'AUTH_001'
|
|
25
|
+
});
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Validate authorization header format
|
|
29
|
+
if (!authHeader.startsWith('Bearer ')) {
|
|
30
|
+
res.status(401).json({
|
|
31
|
+
error: 'Unauthorized',
|
|
32
|
+
message: 'Invalid authorization header format',
|
|
33
|
+
errorCode: 'AUTH_003'
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Extract and sanitize token
|
|
38
|
+
const token = authHeader.slice(7).trim();
|
|
39
|
+
if (!token) {
|
|
40
|
+
res.status(401).json({
|
|
41
|
+
error: 'Unauthorized',
|
|
42
|
+
message: 'No token provided',
|
|
43
|
+
errorCode: 'AUTH_005'
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Verify JWT token
|
|
48
|
+
let decoded;
|
|
49
|
+
try {
|
|
50
|
+
decoded = jwt.verify(token, config.jwtSecret);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
// Handle specific JWT errors
|
|
53
|
+
if (error instanceof jwt.TokenExpiredError) {
|
|
54
|
+
res.status(401).json({
|
|
55
|
+
error: 'Unauthorized',
|
|
56
|
+
message: 'Token has expired',
|
|
57
|
+
errorCode: 'AUTH_004'
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
} else if (error instanceof jwt.JsonWebTokenError) {
|
|
61
|
+
res.status(401).json({
|
|
62
|
+
error: 'Unauthorized',
|
|
63
|
+
message: 'Invalid or expired token',
|
|
64
|
+
errorCode: 'AUTH_002'
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
} else {
|
|
68
|
+
logger.error('Unexpected JWT verification error:', error);
|
|
69
|
+
res.status(401).json({
|
|
70
|
+
error: 'Unauthorized',
|
|
71
|
+
message: 'Token verification failed',
|
|
72
|
+
errorCode: 'AUTH_006'
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Attach user information to request
|
|
78
|
+
req.user = {
|
|
79
|
+
userId: decoded.userId,
|
|
80
|
+
email: decoded.email,
|
|
81
|
+
role: decoded.role,
|
|
82
|
+
iat: decoded.iat,
|
|
83
|
+
exp: decoded.exp
|
|
84
|
+
};
|
|
85
|
+
// Log successful authentication
|
|
86
|
+
logger.info(`User authenticated: ${decoded.userId} (${decoded.email})`, {
|
|
87
|
+
userId: decoded.userId,
|
|
88
|
+
role: decoded.role,
|
|
89
|
+
ip: req.ip,
|
|
90
|
+
userAgent: req.headers['user-agent']
|
|
91
|
+
});
|
|
92
|
+
next();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
logger.error('Authentication middleware error:', error);
|
|
95
|
+
res.status(500).json({
|
|
96
|
+
error: 'Internal Server Error',
|
|
97
|
+
message: 'Authentication service unavailable',
|
|
98
|
+
errorCode: 'AUTH_500'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Authorization middleware for role-based access control
|
|
105
|
+
*
|
|
106
|
+
* @param requiredRoles Array of roles that are allowed to access the resource
|
|
107
|
+
* @returns Express middleware function
|
|
108
|
+
*/ export function authorizationMiddleware(requiredRoles) {
|
|
109
|
+
return (req, res, next)=>{
|
|
110
|
+
try {
|
|
111
|
+
// Check if user is authenticated
|
|
112
|
+
if (!req.user) {
|
|
113
|
+
res.status(401).json({
|
|
114
|
+
error: 'Unauthorized',
|
|
115
|
+
message: 'User not authenticated'
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Check if user has required role
|
|
120
|
+
if (!requiredRoles.includes(req.user.role)) {
|
|
121
|
+
res.status(403).json({
|
|
122
|
+
error: 'Forbidden',
|
|
123
|
+
message: 'Insufficient permissions',
|
|
124
|
+
requiredRoles,
|
|
125
|
+
userRole: req.user.role
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// Log successful authorization
|
|
130
|
+
logger.info(`User authorized: ${req.user.userId} (${req.user.role})`, {
|
|
131
|
+
userId: req.user.userId,
|
|
132
|
+
role: req.user.role,
|
|
133
|
+
requiredRoles
|
|
134
|
+
});
|
|
135
|
+
next();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.error('Authorization middleware error:', error);
|
|
138
|
+
res.status(500).json({
|
|
139
|
+
error: 'Internal Server Error',
|
|
140
|
+
message: 'Authorization service unavailable'
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Password utilities for secure password handling
|
|
147
|
+
*/ export class PasswordUtils {
|
|
148
|
+
/**
|
|
149
|
+
* Hash a password using bcrypt
|
|
150
|
+
*
|
|
151
|
+
* @param password Plain text password
|
|
152
|
+
* @param rounds Number of bcrypt rounds (default 12)
|
|
153
|
+
* @returns Hashed password
|
|
154
|
+
*/ static async hashPassword(password, rounds = 12) {
|
|
155
|
+
try {
|
|
156
|
+
return await bcrypt.hash(password, rounds);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
logger.error('Password hashing error:', error);
|
|
159
|
+
throw new StandardError('Failed to hash password', ErrorCode.INTERNAL_ERROR);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Generate salt for password hashing
|
|
164
|
+
*
|
|
165
|
+
* @param rounds Number of bcrypt rounds (default 12)
|
|
166
|
+
* @returns Salt string
|
|
167
|
+
*/ static async generateSalt(rounds = 12) {
|
|
168
|
+
try {
|
|
169
|
+
return await bcrypt.genSalt(rounds);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
logger.error('Salt generation error:', error);
|
|
172
|
+
throw new StandardError('Failed to generate salt', ErrorCode.INTERNAL_ERROR);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Compare a plain text password with a hash
|
|
177
|
+
*
|
|
178
|
+
* @param password Plain text password
|
|
179
|
+
* @param hash Hashed password
|
|
180
|
+
* @returns True if passwords match
|
|
181
|
+
*/ static async comparePassword(password, hash) {
|
|
182
|
+
try {
|
|
183
|
+
return await bcrypt.compare(password, hash);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
logger.error('Password comparison error:', error);
|
|
186
|
+
throw new StandardError('Failed to compare passwords', ErrorCode.INTERNAL_ERROR);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Validate password strength
|
|
191
|
+
*
|
|
192
|
+
* @param password Password to validate
|
|
193
|
+
* @returns True if password meets strength requirements
|
|
194
|
+
*/ static validatePasswordStrength(password) {
|
|
195
|
+
// Minimum 8 characters, at least one uppercase, one lowercase, one number, and one special character
|
|
196
|
+
const minLength = 8;
|
|
197
|
+
const hasUpperCase = /[A-Z]/.test(password);
|
|
198
|
+
const hasLowerCase = /[a-z]/.test(password);
|
|
199
|
+
const hasNumbers = /\d/.test(password);
|
|
200
|
+
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
|
|
201
|
+
return password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* JWT utilities for token management
|
|
206
|
+
*/ export class JWTUtils {
|
|
207
|
+
/**
|
|
208
|
+
* Generate a JWT token
|
|
209
|
+
*
|
|
210
|
+
* @param payload Token payload
|
|
211
|
+
* @param secret JWT secret
|
|
212
|
+
* @param expiresIn Token expiration time
|
|
213
|
+
* @returns JWT token
|
|
214
|
+
*/ static generateToken(payload, secret, expiresIn = '24h') {
|
|
215
|
+
try {
|
|
216
|
+
return jwt.sign(payload, secret, {
|
|
217
|
+
expiresIn
|
|
218
|
+
});
|
|
219
|
+
} catch (error) {
|
|
220
|
+
logger.error('Token generation error:', error);
|
|
221
|
+
throw new StandardError('Failed to generate token', ErrorCode.INTERNAL_ERROR);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Verify a JWT token
|
|
226
|
+
*
|
|
227
|
+
* @param token JWT token
|
|
228
|
+
* @param secret JWT secret
|
|
229
|
+
* @returns Decoded payload
|
|
230
|
+
*/ static verifyToken(token, secret) {
|
|
231
|
+
try {
|
|
232
|
+
return jwt.verify(token, secret);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error instanceof jwt.TokenExpiredError) {
|
|
235
|
+
throw new StandardError('Token has expired', ErrorCode.TOKEN_EXPIRED);
|
|
236
|
+
} else if (error instanceof jwt.JsonWebTokenError) {
|
|
237
|
+
throw new StandardError('Invalid token', ErrorCode.INVALID_TOKEN);
|
|
238
|
+
} else {
|
|
239
|
+
throw new StandardError('Token verification failed', ErrorCode.INTERNAL_ERROR);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Decode a JWT token without verification (for debugging)
|
|
245
|
+
*
|
|
246
|
+
* @param token JWT token
|
|
247
|
+
* @returns Decoded payload
|
|
248
|
+
*/ static decodeToken(token) {
|
|
249
|
+
try {
|
|
250
|
+
return jwt.decode(token);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
logger.error('Token decoding error:', error);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Rate limiting utilities for authentication endpoints
|
|
259
|
+
*/ export class AuthRateLimit {
|
|
260
|
+
static attempts = new Map();
|
|
261
|
+
/**
|
|
262
|
+
* Check if user has exceeded login attempts
|
|
263
|
+
*
|
|
264
|
+
* @param identifier User identifier (email, IP, etc.)
|
|
265
|
+
* @param maxAttempts Maximum allowed attempts
|
|
266
|
+
* @param windowMs Time window in milliseconds
|
|
267
|
+
* @returns True if attempts exceeded
|
|
268
|
+
*/ static hasExceededAttempts(identifier, maxAttempts = 5, windowMs = 15 * 60 * 1000 // 15 minutes
|
|
269
|
+
) {
|
|
270
|
+
const now = Date.now();
|
|
271
|
+
const attempts = this.attempts.get(identifier);
|
|
272
|
+
if (!attempts) {
|
|
273
|
+
this.attempts.set(identifier, {
|
|
274
|
+
count: 1,
|
|
275
|
+
lastAttempt: now
|
|
276
|
+
});
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
// Reset if window has expired
|
|
280
|
+
if (now - attempts.lastAttempt > windowMs) {
|
|
281
|
+
this.attempts.set(identifier, {
|
|
282
|
+
count: 1,
|
|
283
|
+
lastAttempt: now
|
|
284
|
+
});
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
// Increment attempts
|
|
288
|
+
attempts.count++;
|
|
289
|
+
attempts.lastAttempt = now;
|
|
290
|
+
// Check if exceeded
|
|
291
|
+
if (attempts.count > maxAttempts) {
|
|
292
|
+
logger.warn(`Login attempts exceeded for ${identifier}`, {
|
|
293
|
+
identifier,
|
|
294
|
+
attempts: attempts.count,
|
|
295
|
+
windowMs
|
|
296
|
+
});
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Clear failed attempts for a user
|
|
303
|
+
*
|
|
304
|
+
* @param identifier User identifier
|
|
305
|
+
*/ static clearAttempts(identifier) {
|
|
306
|
+
this.attempts.delete(identifier);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
export default {
|
|
310
|
+
authenticationMiddleware,
|
|
311
|
+
authorizationMiddleware,
|
|
312
|
+
PasswordUtils,
|
|
313
|
+
JWTUtils,
|
|
314
|
+
AuthRateLimit
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
//# sourceMappingURL=authentication.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/authentication.ts"],"sourcesContent":["/**\r\n * Authentication Middleware\r\n * \r\n * Comprehensive authentication and authorization middleware for Express.js\r\n * with JWT token validation, role-based access control, and security features.\r\n */\r\n\r\nimport { Request, Response, NextFunction } from 'express';\r\nimport jwt from 'jsonwebtoken';\r\nimport bcrypt from 'bcrypt';\r\nimport { logger } from '../utils/logger.js';\r\nimport { StandardError, ErrorCode } from '../../lib/errors.js';\r\n\r\n// Extend Express Request type to include user\r\ndeclare global {\r\n namespace Express {\r\n interface Request {\r\n user?: {\r\n userId: string;\r\n email: string;\r\n role: string;\r\n iat?: number;\r\n exp?: number;\r\n };\r\n }\r\n }\r\n}\r\n\r\nexport interface AuthConfig {\r\n jwtSecret: string;\r\n jwtExpiration: string;\r\n bcryptRounds: number;\r\n}\r\n\r\nexport interface JWTPayload {\r\n userId: string;\r\n email: string;\r\n role: string;\r\n iat?: number;\r\n exp?: number;\r\n}\r\n\r\n/**\r\n * Authentication middleware that validates JWT tokens\r\n * \r\n * @param config Authentication configuration\r\n * @returns Express middleware function\r\n */\r\nexport function authenticationMiddleware(config: AuthConfig) {\r\n return async (req: Request, res: Response, next: NextFunction): Promise<void> => {\r\n try {\r\n // Check for authorization header\r\n const authHeader = req.headers.authorization;\r\n if (!authHeader) {\r\n res.status(401).json({\r\n error: 'Unauthorized',\r\n message: 'No authorization header provided',\r\n errorCode: 'AUTH_001',\r\n });\r\n return;\r\n }\r\n\r\n // Validate authorization header format\r\n if (!authHeader.startsWith('Bearer ')) {\r\n res.status(401).json({\r\n error: 'Unauthorized',\r\n message: 'Invalid authorization header format',\r\n errorCode: 'AUTH_003',\r\n });\r\n return;\r\n }\r\n\r\n // Extract and sanitize token\r\n const token = authHeader.slice(7).trim();\r\n if (!token) {\r\n res.status(401).json({\r\n error: 'Unauthorized',\r\n message: 'No token provided',\r\n errorCode: 'AUTH_005',\r\n });\r\n return;\r\n }\r\n\r\n // Verify JWT token\r\n let decoded: JWTPayload;\r\n try {\r\n decoded = jwt.verify(token, config.jwtSecret) as JWTPayload;\r\n } catch (error) {\r\n // Handle specific JWT errors\r\n if (error instanceof jwt.TokenExpiredError) {\r\n res.status(401).json({\r\n error: 'Unauthorized',\r\n message: 'Token has expired',\r\n errorCode: 'AUTH_004',\r\n });\r\n return;\r\n } else if (error instanceof jwt.JsonWebTokenError) {\r\n res.status(401).json({\r\n error: 'Unauthorized',\r\n message: 'Invalid or expired token',\r\n errorCode: 'AUTH_002',\r\n });\r\n return;\r\n } else {\r\n logger.error('Unexpected JWT verification error:', error);\r\n res.status(401).json({\r\n error: 'Unauthorized',\r\n message: 'Token verification failed',\r\n errorCode: 'AUTH_006',\r\n });\r\n return;\r\n }\r\n }\r\n\r\n // Attach user information to request\r\n req.user = {\r\n userId: decoded.userId,\r\n email: decoded.email,\r\n role: decoded.role,\r\n iat: decoded.iat,\r\n exp: decoded.exp,\r\n };\r\n\r\n // Log successful authentication\r\n logger.info(`User authenticated: ${decoded.userId} (${decoded.email})`, {\r\n userId: decoded.userId,\r\n role: decoded.role,\r\n ip: req.ip,\r\n userAgent: req.headers['user-agent'],\r\n });\r\n\r\n next();\r\n } catch (error) {\r\n logger.error('Authentication middleware error:', error);\r\n res.status(500).json({\r\n error: 'Internal Server Error',\r\n message: 'Authentication service unavailable',\r\n errorCode: 'AUTH_500',\r\n });\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Authorization middleware for role-based access control\r\n * \r\n * @param requiredRoles Array of roles that are allowed to access the resource\r\n * @returns Express middleware function\r\n */\r\nexport function authorizationMiddleware(requiredRoles: string[]) {\r\n return (req: Request, res: Response, next: NextFunction): void => {\r\n try {\r\n // Check if user is authenticated\r\n if (!req.user) {\r\n res.status(401).json({\r\n error: 'Unauthorized',\r\n message: 'User not authenticated',\r\n });\r\n return;\r\n }\r\n\r\n // Check if user has required role\r\n if (!requiredRoles.includes(req.user.role)) {\r\n res.status(403).json({\r\n error: 'Forbidden',\r\n message: 'Insufficient permissions',\r\n requiredRoles,\r\n userRole: req.user.role,\r\n });\r\n return;\r\n }\r\n\r\n // Log successful authorization\r\n logger.info(`User authorized: ${req.user.userId} (${req.user.role})`, {\r\n userId: req.user.userId,\r\n role: req.user.role,\r\n requiredRoles,\r\n });\r\n\r\n next();\r\n } catch (error) {\r\n logger.error('Authorization middleware error:', error);\r\n res.status(500).json({\r\n error: 'Internal Server Error',\r\n message: 'Authorization service unavailable',\r\n });\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Password utilities for secure password handling\r\n */\r\nexport class PasswordUtils {\r\n /**\r\n * Hash a password using bcrypt\r\n * \r\n * @param password Plain text password\r\n * @param rounds Number of bcrypt rounds (default 12)\r\n * @returns Hashed password\r\n */\r\n static async hashPassword(password: string, rounds: number = 12): Promise<string> {\r\n try {\r\n return await bcrypt.hash(password, rounds);\r\n } catch (error) {\r\n logger.error('Password hashing error:', error);\r\n throw new StandardError('Failed to hash password', ErrorCode.INTERNAL_ERROR);\r\n }\r\n }\r\n\r\n /**\r\n * Generate salt for password hashing\r\n * \r\n * @param rounds Number of bcrypt rounds (default 12)\r\n * @returns Salt string\r\n */\r\n static async generateSalt(rounds: number = 12): Promise<string> {\r\n try {\r\n return await bcrypt.genSalt(rounds);\r\n } catch (error) {\r\n logger.error('Salt generation error:', error);\r\n throw new StandardError('Failed to generate salt', ErrorCode.INTERNAL_ERROR);\r\n }\r\n }\r\n\r\n /**\r\n * Compare a plain text password with a hash\r\n * \r\n * @param password Plain text password\r\n * @param hash Hashed password\r\n * @returns True if passwords match\r\n */\r\n static async comparePassword(password: string, hash: string): Promise<boolean> {\r\n try {\r\n return await bcrypt.compare(password, hash);\r\n } catch (error) {\r\n logger.error('Password comparison error:', error);\r\n throw new StandardError('Failed to compare passwords', ErrorCode.INTERNAL_ERROR);\r\n }\r\n }\r\n\r\n /**\r\n * Validate password strength\r\n * \r\n * @param password Password to validate\r\n * @returns True if password meets strength requirements\r\n */\r\n static validatePasswordStrength(password: string): boolean {\r\n // Minimum 8 characters, at least one uppercase, one lowercase, one number, and one special character\r\n const minLength = 8;\r\n const hasUpperCase = /[A-Z]/.test(password);\r\n const hasLowerCase = /[a-z]/.test(password);\r\n const hasNumbers = /\\d/.test(password);\r\n const hasSpecialChar = /[!@#$%^&*(),.?\":{}|<>]/.test(password);\r\n\r\n return (\r\n password.length >= minLength &&\r\n hasUpperCase &&\r\n hasLowerCase &&\r\n hasNumbers &&\r\n hasSpecialChar\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * JWT utilities for token management\r\n */\r\nexport class JWTUtils {\r\n /**\r\n * Generate a JWT token\r\n * \r\n * @param payload Token payload\r\n * @param secret JWT secret\r\n * @param expiresIn Token expiration time\r\n * @returns JWT token\r\n */\r\n static generateToken(\r\n payload: Omit<JWTPayload, 'iat' | 'exp'>,\r\n secret: string,\r\n expiresIn: string = '24h'\r\n ): string {\r\n try {\r\n return jwt.sign(payload, secret, { expiresIn });\r\n } catch (error) {\r\n logger.error('Token generation error:', error);\r\n throw new StandardError('Failed to generate token', ErrorCode.INTERNAL_ERROR);\r\n }\r\n }\r\n\r\n /**\r\n * Verify a JWT token\r\n * \r\n * @param token JWT token\r\n * @param secret JWT secret\r\n * @returns Decoded payload\r\n */\r\n static verifyToken(token: string, secret: string): JWTPayload {\r\n try {\r\n return jwt.verify(token, secret) as JWTPayload;\r\n } catch (error) {\r\n if (error instanceof jwt.TokenExpiredError) {\r\n throw new StandardError('Token has expired', ErrorCode.TOKEN_EXPIRED);\r\n } else if (error instanceof jwt.JsonWebTokenError) {\r\n throw new StandardError('Invalid token', ErrorCode.INVALID_TOKEN);\r\n } else {\r\n throw new StandardError('Token verification failed', ErrorCode.INTERNAL_ERROR);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Decode a JWT token without verification (for debugging)\r\n * \r\n * @param token JWT token\r\n * @returns Decoded payload\r\n */\r\n static decodeToken(token: string): JWTPayload | null {\r\n try {\r\n return jwt.decode(token) as JWTPayload;\r\n } catch (error) {\r\n logger.error('Token decoding error:', error);\r\n return null;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Rate limiting utilities for authentication endpoints\r\n */\r\nexport class AuthRateLimit {\r\n private static attempts = new Map<string, { count: number; lastAttempt: number }>();\r\n\r\n /**\r\n * Check if user has exceeded login attempts\r\n * \r\n * @param identifier User identifier (email, IP, etc.)\r\n * @param maxAttempts Maximum allowed attempts\r\n * @param windowMs Time window in milliseconds\r\n * @returns True if attempts exceeded\r\n */\r\n static hasExceededAttempts(\r\n identifier: string,\r\n maxAttempts: number = 5,\r\n windowMs: number = 15 * 60 * 1000 // 15 minutes\r\n ): boolean {\r\n const now = Date.now();\r\n const attempts = this.attempts.get(identifier);\r\n\r\n if (!attempts) {\r\n this.attempts.set(identifier, { count: 1, lastAttempt: now });\r\n return false;\r\n }\r\n\r\n // Reset if window has expired\r\n if (now - attempts.lastAttempt > windowMs) {\r\n this.attempts.set(identifier, { count: 1, lastAttempt: now });\r\n return false;\r\n }\r\n\r\n // Increment attempts\r\n attempts.count++;\r\n attempts.lastAttempt = now;\r\n\r\n // Check if exceeded\r\n if (attempts.count > maxAttempts) {\r\n logger.warn(`Login attempts exceeded for ${identifier}`, {\r\n identifier,\r\n attempts: attempts.count,\r\n windowMs,\r\n });\r\n return true;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Clear failed attempts for a user\r\n * \r\n * @param identifier User identifier\r\n */\r\n static clearAttempts(identifier: string): void {\r\n this.attempts.delete(identifier);\r\n }\r\n}\r\n\r\nexport default {\r\n authenticationMiddleware,\r\n authorizationMiddleware,\r\n PasswordUtils,\r\n JWTUtils,\r\n AuthRateLimit,\r\n};"],"names":["jwt","bcrypt","logger","StandardError","ErrorCode","authenticationMiddleware","config","req","res","next","authHeader","headers","authorization","status","json","error","message","errorCode","startsWith","token","slice","trim","decoded","verify","jwtSecret","TokenExpiredError","JsonWebTokenError","user","userId","email","role","iat","exp","info","ip","userAgent","authorizationMiddleware","requiredRoles","includes","userRole","PasswordUtils","hashPassword","password","rounds","hash","INTERNAL_ERROR","generateSalt","genSalt","comparePassword","compare","validatePasswordStrength","minLength","hasUpperCase","test","hasLowerCase","hasNumbers","hasSpecialChar","length","JWTUtils","generateToken","payload","secret","expiresIn","sign","verifyToken","TOKEN_EXPIRED","INVALID_TOKEN","decodeToken","decode","AuthRateLimit","attempts","Map","hasExceededAttempts","identifier","maxAttempts","windowMs","now","Date","get","set","count","lastAttempt","warn","clearAttempts","delete"],"mappings":"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"}
|