claude-flow-novice 2.15.11 → 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/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 +10 -0
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +56 -33
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +46 -36
- package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +9 -0
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +17 -17
- 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/product-owner.md +1 -5
- 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 +7 -35
- package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +8 -37
- package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +16 -54
- package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +17 -55
- package/claude-assets/agents/cfn-dev-team/testers/interaction-tester.md +9 -37
- package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +17 -55
- package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +17 -48
- package/claude-assets/agents/cfn-dev-team/testers/playwright-tester.md +8 -37
- package/claude-assets/agents/cfn-dev-team/testers/tester.md +7 -27
- package/claude-assets/agents/cfn-dev-team/utility/analyst.md +12 -28
- package/claude-assets/agents/cfn-dev-team/utility/code-booster.md +13 -13
- package/claude-assets/agents/cfn-dev-team/utility/context-curator.md +7 -2
- package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +5 -10
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +120 -714
- package/claude-assets/agents/cfn-dev-team/utility/researcher.md +12 -21
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +146 -572
- 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-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 +91 -109
- 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 -980
- 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
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trigger.dev Webhook Handlers for CFN Loop Integration
|
|
3
|
+
*
|
|
4
|
+
* Express router providing secure webhook endpoints for:
|
|
5
|
+
* - Agent completion events
|
|
6
|
+
* - Gate check results
|
|
7
|
+
* - Consensus collection results
|
|
8
|
+
* - Product Owner decisions
|
|
9
|
+
*
|
|
10
|
+
* Configuration:
|
|
11
|
+
* - WEBHOOK_SECRET: Shared secret for HMAC signature verification
|
|
12
|
+
* - WEBHOOK_ALGORITHM: Hash algorithm (default: sha256)
|
|
13
|
+
*/ import { Router } from 'express';
|
|
14
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
15
|
+
/**
|
|
16
|
+
* Webhook validation error class
|
|
17
|
+
*/ export class WebhookValidationError extends Error {
|
|
18
|
+
reason;
|
|
19
|
+
statusCode;
|
|
20
|
+
constructor(message, reason, statusCode = 401){
|
|
21
|
+
super(message), this.reason = reason, this.statusCode = statusCode;
|
|
22
|
+
this.name = 'WebhookValidationError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* TriggerDevWebhooks - Express router for secure webhook handling
|
|
27
|
+
*
|
|
28
|
+
* Provides endpoints:
|
|
29
|
+
* - POST /webhooks/agent-complete
|
|
30
|
+
* - POST /webhooks/gate-result
|
|
31
|
+
* - POST /webhooks/consensus-result
|
|
32
|
+
* - POST /webhooks/po-decision
|
|
33
|
+
*/ export class TriggerDevWebhooks {
|
|
34
|
+
router;
|
|
35
|
+
config;
|
|
36
|
+
verificationOptions;
|
|
37
|
+
// Handler maps for dependency injection
|
|
38
|
+
agentCompleteHandler;
|
|
39
|
+
gateResultHandler;
|
|
40
|
+
consensusResultHandler;
|
|
41
|
+
poDecisionHandler;
|
|
42
|
+
constructor(config){
|
|
43
|
+
this.router = Router();
|
|
44
|
+
this.config = {
|
|
45
|
+
secret: config?.secret || process.env.WEBHOOK_SECRET || 'default-secret',
|
|
46
|
+
algorithm: config?.algorithm || process.env.WEBHOOK_ALGORITHM || 'sha256',
|
|
47
|
+
verifySignature: config?.verifySignature !== false
|
|
48
|
+
};
|
|
49
|
+
this.verificationOptions = {
|
|
50
|
+
algorithmType: this.config.algorithm || 'sha256',
|
|
51
|
+
headerName: 'x-trigger-signature'
|
|
52
|
+
};
|
|
53
|
+
this.setupRoutes();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Register handler for agent completion events
|
|
57
|
+
*/ onAgentComplete(handler) {
|
|
58
|
+
this.agentCompleteHandler = handler;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Register handler for gate result events
|
|
63
|
+
*/ onGateResult(handler) {
|
|
64
|
+
this.gateResultHandler = handler;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Register handler for consensus result events
|
|
69
|
+
*/ onConsensusResult(handler) {
|
|
70
|
+
this.consensusResultHandler = handler;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Register handler for Product Owner decision events
|
|
75
|
+
*/ onPODecision(handler) {
|
|
76
|
+
this.poDecisionHandler = handler;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get Express router for mounting
|
|
81
|
+
*/ getRouter() {
|
|
82
|
+
return this.router;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Setup all webhook routes
|
|
86
|
+
*
|
|
87
|
+
* @private
|
|
88
|
+
*/ setupRoutes() {
|
|
89
|
+
// Middleware for signature verification
|
|
90
|
+
this.router.use(this.signatureVerificationMiddleware.bind(this));
|
|
91
|
+
// Agent completion endpoint
|
|
92
|
+
this.router.post('/agent-complete', this.handleAgentComplete.bind(this));
|
|
93
|
+
// Gate result endpoint
|
|
94
|
+
this.router.post('/gate-result', this.handleGateResult.bind(this));
|
|
95
|
+
// Consensus result endpoint
|
|
96
|
+
this.router.post('/consensus-result', this.handleConsensusResult.bind(this));
|
|
97
|
+
// PO decision endpoint
|
|
98
|
+
this.router.post('/po-decision', this.handlePODecision.bind(this));
|
|
99
|
+
// Error handling middleware
|
|
100
|
+
this.router.use(this.errorHandler.bind(this));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Signature verification middleware
|
|
104
|
+
*
|
|
105
|
+
* TODO: RUNTIME_TEST: Verify HMAC signature validation with valid/invalid keys
|
|
106
|
+
*
|
|
107
|
+
* @private
|
|
108
|
+
*/ async signatureVerificationMiddleware(req, res, next) {
|
|
109
|
+
try {
|
|
110
|
+
if (!this.config.verifySignature) {
|
|
111
|
+
return next();
|
|
112
|
+
}
|
|
113
|
+
const signature = req.headers[this.verificationOptions.headerName];
|
|
114
|
+
if (!signature) {
|
|
115
|
+
throw new WebhookValidationError('Missing webhook signature', 'MISSING_SIGNATURE');
|
|
116
|
+
}
|
|
117
|
+
const body = JSON.stringify(req.body);
|
|
118
|
+
const isValid = await this.verifySignature(body, signature);
|
|
119
|
+
if (!isValid) {
|
|
120
|
+
throw new WebhookValidationError('Invalid webhook signature', 'INVALID_SIGNATURE');
|
|
121
|
+
}
|
|
122
|
+
next();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error instanceof WebhookValidationError) {
|
|
125
|
+
return res.status(error.statusCode).json({
|
|
126
|
+
success: false,
|
|
127
|
+
message: error.message,
|
|
128
|
+
reason: error.reason
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return res.status(500).json({
|
|
132
|
+
success: false,
|
|
133
|
+
message: 'Signature verification failed',
|
|
134
|
+
reason: 'INTERNAL_ERROR'
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Handle agent completion webhook
|
|
140
|
+
*
|
|
141
|
+
* @private
|
|
142
|
+
*/ async handleAgentComplete(req, res) {
|
|
143
|
+
try {
|
|
144
|
+
const payload = req.body;
|
|
145
|
+
// Validate required fields
|
|
146
|
+
if (!payload.agentId || !payload.taskId) {
|
|
147
|
+
return res.status(400).json({
|
|
148
|
+
success: false,
|
|
149
|
+
message: 'Missing required fields',
|
|
150
|
+
data: null
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const context = {
|
|
154
|
+
payload,
|
|
155
|
+
isVerified: this.config.verifySignature,
|
|
156
|
+
timestamp: Date.now(),
|
|
157
|
+
signature: req.headers[this.verificationOptions.headerName]
|
|
158
|
+
};
|
|
159
|
+
if (this.agentCompleteHandler) {
|
|
160
|
+
const result = await this.agentCompleteHandler(context);
|
|
161
|
+
return res.status(200).json(result);
|
|
162
|
+
}
|
|
163
|
+
res.status(200).json({
|
|
164
|
+
success: true,
|
|
165
|
+
message: 'Agent completion event received',
|
|
166
|
+
data: {
|
|
167
|
+
agentId: payload.agentId
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return res.status(500).json({
|
|
172
|
+
success: false,
|
|
173
|
+
message: 'Failed to handle agent completion',
|
|
174
|
+
data: null
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Handle gate result webhook
|
|
180
|
+
*
|
|
181
|
+
* @private
|
|
182
|
+
*/ async handleGateResult(req, res) {
|
|
183
|
+
try {
|
|
184
|
+
const payload = req.body;
|
|
185
|
+
if (!payload.taskId || payload.gateType === undefined) {
|
|
186
|
+
return res.status(400).json({
|
|
187
|
+
success: false,
|
|
188
|
+
message: 'Missing required fields',
|
|
189
|
+
data: null
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const context = {
|
|
193
|
+
payload,
|
|
194
|
+
isVerified: this.config.verifySignature,
|
|
195
|
+
timestamp: Date.now(),
|
|
196
|
+
signature: req.headers[this.verificationOptions.headerName]
|
|
197
|
+
};
|
|
198
|
+
if (this.gateResultHandler) {
|
|
199
|
+
const result = await this.gateResultHandler(context);
|
|
200
|
+
return res.status(200).json(result);
|
|
201
|
+
}
|
|
202
|
+
res.status(200).json({
|
|
203
|
+
success: true,
|
|
204
|
+
message: 'Gate result received',
|
|
205
|
+
data: {
|
|
206
|
+
taskId: payload.taskId,
|
|
207
|
+
passed: payload.passed
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
} catch (error) {
|
|
211
|
+
return res.status(500).json({
|
|
212
|
+
success: false,
|
|
213
|
+
message: 'Failed to handle gate result',
|
|
214
|
+
data: null
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Handle consensus result webhook
|
|
220
|
+
*
|
|
221
|
+
* @private
|
|
222
|
+
*/ async handleConsensusResult(req, res) {
|
|
223
|
+
try {
|
|
224
|
+
const payload = req.body;
|
|
225
|
+
if (!payload.taskId || payload.validatorCount === undefined) {
|
|
226
|
+
return res.status(400).json({
|
|
227
|
+
success: false,
|
|
228
|
+
message: 'Missing required fields',
|
|
229
|
+
data: null
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
const context = {
|
|
233
|
+
payload,
|
|
234
|
+
isVerified: this.config.verifySignature,
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
signature: req.headers[this.verificationOptions.headerName]
|
|
237
|
+
};
|
|
238
|
+
if (this.consensusResultHandler) {
|
|
239
|
+
const result = await this.consensusResultHandler(context);
|
|
240
|
+
return res.status(200).json(result);
|
|
241
|
+
}
|
|
242
|
+
res.status(200).json({
|
|
243
|
+
success: true,
|
|
244
|
+
message: 'Consensus result received',
|
|
245
|
+
data: {
|
|
246
|
+
taskId: payload.taskId,
|
|
247
|
+
consensusScore: payload.consensusScore
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
} catch (error) {
|
|
251
|
+
return res.status(500).json({
|
|
252
|
+
success: false,
|
|
253
|
+
message: 'Failed to handle consensus result',
|
|
254
|
+
data: null
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Handle Product Owner decision webhook
|
|
260
|
+
*
|
|
261
|
+
* @private
|
|
262
|
+
*/ async handlePODecision(req, res) {
|
|
263
|
+
try {
|
|
264
|
+
const payload = req.body;
|
|
265
|
+
if (!payload.taskId || !payload.decision) {
|
|
266
|
+
return res.status(400).json({
|
|
267
|
+
success: false,
|
|
268
|
+
message: 'Missing required fields',
|
|
269
|
+
data: null
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
// Validate decision enum
|
|
273
|
+
if (![
|
|
274
|
+
'PROCEED',
|
|
275
|
+
'ITERATE',
|
|
276
|
+
'ABORT'
|
|
277
|
+
].includes(payload.decision)) {
|
|
278
|
+
return res.status(400).json({
|
|
279
|
+
success: false,
|
|
280
|
+
message: 'Invalid decision value',
|
|
281
|
+
data: null
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
const context = {
|
|
285
|
+
payload,
|
|
286
|
+
isVerified: this.config.verifySignature,
|
|
287
|
+
timestamp: Date.now(),
|
|
288
|
+
signature: req.headers[this.verificationOptions.headerName]
|
|
289
|
+
};
|
|
290
|
+
if (this.poDecisionHandler) {
|
|
291
|
+
const result = await this.poDecisionHandler(context);
|
|
292
|
+
return res.status(200).json(result);
|
|
293
|
+
}
|
|
294
|
+
res.status(200).json({
|
|
295
|
+
success: true,
|
|
296
|
+
message: 'PO decision received',
|
|
297
|
+
data: {
|
|
298
|
+
taskId: payload.taskId,
|
|
299
|
+
decision: payload.decision
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return res.status(500).json({
|
|
304
|
+
success: false,
|
|
305
|
+
message: 'Failed to handle PO decision',
|
|
306
|
+
data: null
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Verify HMAC signature of webhook payload
|
|
312
|
+
*
|
|
313
|
+
* @private
|
|
314
|
+
* @param payload Raw request body as string
|
|
315
|
+
* @param signature Signature header value
|
|
316
|
+
* @returns Promise resolving to true if signature is valid
|
|
317
|
+
*/ async verifySignature(payload, signature) {
|
|
318
|
+
try {
|
|
319
|
+
const hmac = createHmac(this.verificationOptions.algorithmType, this.config.secret);
|
|
320
|
+
hmac.update(payload, 'utf8');
|
|
321
|
+
const expectedSignature = hmac.digest('hex');
|
|
322
|
+
// Use timing-safe comparison
|
|
323
|
+
return timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error('Signature verification error:', error);
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Error handling middleware
|
|
331
|
+
*
|
|
332
|
+
* @private
|
|
333
|
+
*/ errorHandler(error, req, res, next) {
|
|
334
|
+
console.error('Webhook error:', error);
|
|
335
|
+
res.status(500).json({
|
|
336
|
+
success: false,
|
|
337
|
+
message: 'Internal server error',
|
|
338
|
+
data: null
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Create webhook router with optional handlers
|
|
344
|
+
*/ export const createWebhookRouter = (config, handlers)=>{
|
|
345
|
+
const webhooks = new TriggerDevWebhooks(config);
|
|
346
|
+
if (handlers?.onAgentComplete) {
|
|
347
|
+
webhooks.onAgentComplete(handlers.onAgentComplete);
|
|
348
|
+
}
|
|
349
|
+
if (handlers?.onGateResult) {
|
|
350
|
+
webhooks.onGateResult(handlers.onGateResult);
|
|
351
|
+
}
|
|
352
|
+
if (handlers?.onConsensusResult) {
|
|
353
|
+
webhooks.onConsensusResult(handlers.onConsensusResult);
|
|
354
|
+
}
|
|
355
|
+
if (handlers?.onPODecision) {
|
|
356
|
+
webhooks.onPODecision(handlers.onPODecision);
|
|
357
|
+
}
|
|
358
|
+
return webhooks.getRouter();
|
|
359
|
+
};
|
|
360
|
+
export default TriggerDevWebhooks;
|
|
361
|
+
|
|
362
|
+
//# sourceMappingURL=trigger-dev-webhooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/integration/trigger-dev-webhooks.ts"],"sourcesContent":["/**\r\n * Trigger.dev Webhook Handlers for CFN Loop Integration\r\n *\r\n * Express router providing secure webhook endpoints for:\r\n * - Agent completion events\r\n * - Gate check results\r\n * - Consensus collection results\r\n * - Product Owner decisions\r\n *\r\n * Configuration:\r\n * - WEBHOOK_SECRET: Shared secret for HMAC signature verification\r\n * - WEBHOOK_ALGORITHM: Hash algorithm (default: sha256)\r\n */\r\n\r\nimport { Router } from 'express';\r\nimport type { Request, Response, NextFunction } from 'express';\r\nimport { createHmac, timingSafeEqual } from 'node:crypto';\r\n\r\nimport type {\r\n AgentCompletePayload,\r\n GateResultPayload,\r\n ConsensusResultPayload,\r\n PODecisionPayload,\r\n WebhookContext,\r\n WebhookHandlerResult,\r\n WebhookVerificationOptions,\r\n} from '../types/trigger-dev-events.d.js';\r\n\r\n/**\r\n * Webhook validation error class\r\n */\r\nexport class WebhookValidationError extends Error {\r\n constructor(\r\n message: string,\r\n public readonly reason: string,\r\n public readonly statusCode: number = 401\r\n ) {\r\n super(message);\r\n this.name = 'WebhookValidationError';\r\n }\r\n}\r\n\r\n/**\r\n * Webhook handler type\r\n */\r\nexport type WebhookHandler<T> = (\r\n context: WebhookContext<T>\r\n) => Promise<WebhookHandlerResult>;\r\n\r\n/**\r\n * Configuration for webhook handlers\r\n */\r\nexport interface WebhookConfig {\r\n secret?: string;\r\n algorithm?: string;\r\n verifySignature?: boolean;\r\n}\r\n\r\n/**\r\n * TriggerDevWebhooks - Express router for secure webhook handling\r\n *\r\n * Provides endpoints:\r\n * - POST /webhooks/agent-complete\r\n * - POST /webhooks/gate-result\r\n * - POST /webhooks/consensus-result\r\n * - POST /webhooks/po-decision\r\n */\r\nexport class TriggerDevWebhooks {\r\n private readonly router: Router;\r\n private readonly config: Required<WebhookConfig>;\r\n private readonly verificationOptions: WebhookVerificationOptions;\r\n\r\n // Handler maps for dependency injection\r\n private agentCompleteHandler?: WebhookHandler<AgentCompletePayload>;\r\n private gateResultHandler?: WebhookHandler<GateResultPayload>;\r\n private consensusResultHandler?: WebhookHandler<ConsensusResultPayload>;\r\n private poDecisionHandler?: WebhookHandler<PODecisionPayload>;\r\n\r\n constructor(config?: WebhookConfig) {\r\n this.router = Router();\r\n this.config = {\r\n secret: config?.secret || process.env.WEBHOOK_SECRET || 'default-secret',\r\n algorithm: config?.algorithm || process.env.WEBHOOK_ALGORITHM || 'sha256',\r\n verifySignature: config?.verifySignature !== false,\r\n };\r\n\r\n this.verificationOptions = {\r\n algorithmType: (this.config.algorithm as 'sha256' | 'sha512') || 'sha256',\r\n headerName: 'x-trigger-signature',\r\n };\r\n\r\n this.setupRoutes();\r\n }\r\n\r\n /**\r\n * Register handler for agent completion events\r\n */\r\n onAgentComplete(handler: WebhookHandler<AgentCompletePayload>): this {\r\n this.agentCompleteHandler = handler;\r\n return this;\r\n }\r\n\r\n /**\r\n * Register handler for gate result events\r\n */\r\n onGateResult(handler: WebhookHandler<GateResultPayload>): this {\r\n this.gateResultHandler = handler;\r\n return this;\r\n }\r\n\r\n /**\r\n * Register handler for consensus result events\r\n */\r\n onConsensusResult(handler: WebhookHandler<ConsensusResultPayload>): this {\r\n this.consensusResultHandler = handler;\r\n return this;\r\n }\r\n\r\n /**\r\n * Register handler for Product Owner decision events\r\n */\r\n onPODecision(handler: WebhookHandler<PODecisionPayload>): this {\r\n this.poDecisionHandler = handler;\r\n return this;\r\n }\r\n\r\n /**\r\n * Get Express router for mounting\r\n */\r\n getRouter(): Router {\r\n return this.router;\r\n }\r\n\r\n /**\r\n * Setup all webhook routes\r\n *\r\n * @private\r\n */\r\n private setupRoutes(): void {\r\n // Middleware for signature verification\r\n this.router.use(this.signatureVerificationMiddleware.bind(this));\r\n\r\n // Agent completion endpoint\r\n this.router.post('/agent-complete', this.handleAgentComplete.bind(this));\r\n\r\n // Gate result endpoint\r\n this.router.post('/gate-result', this.handleGateResult.bind(this));\r\n\r\n // Consensus result endpoint\r\n this.router.post('/consensus-result', this.handleConsensusResult.bind(this));\r\n\r\n // PO decision endpoint\r\n this.router.post('/po-decision', this.handlePODecision.bind(this));\r\n\r\n // Error handling middleware\r\n this.router.use(this.errorHandler.bind(this));\r\n }\r\n\r\n /**\r\n * Signature verification middleware\r\n *\r\n * TODO: RUNTIME_TEST: Verify HMAC signature validation with valid/invalid keys\r\n *\r\n * @private\r\n */\r\n private async signatureVerificationMiddleware(\r\n req: Request,\r\n res: Response,\r\n next: NextFunction\r\n ): Promise<void> {\r\n try {\r\n if (!this.config.verifySignature) {\r\n return next();\r\n }\r\n\r\n const signature = req.headers[this.verificationOptions.headerName];\r\n\r\n if (!signature) {\r\n throw new WebhookValidationError(\r\n 'Missing webhook signature',\r\n 'MISSING_SIGNATURE'\r\n );\r\n }\r\n\r\n const body = JSON.stringify(req.body);\r\n const isValid = await this.verifySignature(body, signature as string);\r\n\r\n if (!isValid) {\r\n throw new WebhookValidationError(\r\n 'Invalid webhook signature',\r\n 'INVALID_SIGNATURE'\r\n );\r\n }\r\n\r\n next();\r\n } catch (error) {\r\n if (error instanceof WebhookValidationError) {\r\n return res.status(error.statusCode).json({\r\n success: false,\r\n message: error.message,\r\n reason: error.reason,\r\n });\r\n }\r\n\r\n return res.status(500).json({\r\n success: false,\r\n message: 'Signature verification failed',\r\n reason: 'INTERNAL_ERROR',\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Handle agent completion webhook\r\n *\r\n * @private\r\n */\r\n private async handleAgentComplete(\r\n req: Request,\r\n res: Response\r\n ): Promise<void> {\r\n try {\r\n const payload = req.body as AgentCompletePayload;\r\n\r\n // Validate required fields\r\n if (!payload.agentId || !payload.taskId) {\r\n return res.status(400).json({\r\n success: false,\r\n message: 'Missing required fields',\r\n data: null,\r\n });\r\n }\r\n\r\n const context: WebhookContext<AgentCompletePayload> = {\r\n payload,\r\n isVerified: this.config.verifySignature,\r\n timestamp: Date.now(),\r\n signature: req.headers[this.verificationOptions.headerName] as string,\r\n };\r\n\r\n if (this.agentCompleteHandler) {\r\n const result = await this.agentCompleteHandler(context);\r\n return res.status(200).json(result);\r\n }\r\n\r\n res.status(200).json({\r\n success: true,\r\n message: 'Agent completion event received',\r\n data: { agentId: payload.agentId },\r\n });\r\n } catch (error) {\r\n return res.status(500).json({\r\n success: false,\r\n message: 'Failed to handle agent completion',\r\n data: null,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Handle gate result webhook\r\n *\r\n * @private\r\n */\r\n private async handleGateResult(req: Request, res: Response): Promise<void> {\r\n try {\r\n const payload = req.body as GateResultPayload;\r\n\r\n if (!payload.taskId || payload.gateType === undefined) {\r\n return res.status(400).json({\r\n success: false,\r\n message: 'Missing required fields',\r\n data: null,\r\n });\r\n }\r\n\r\n const context: WebhookContext<GateResultPayload> = {\r\n payload,\r\n isVerified: this.config.verifySignature,\r\n timestamp: Date.now(),\r\n signature: req.headers[this.verificationOptions.headerName] as string,\r\n };\r\n\r\n if (this.gateResultHandler) {\r\n const result = await this.gateResultHandler(context);\r\n return res.status(200).json(result);\r\n }\r\n\r\n res.status(200).json({\r\n success: true,\r\n message: 'Gate result received',\r\n data: { taskId: payload.taskId, passed: payload.passed },\r\n });\r\n } catch (error) {\r\n return res.status(500).json({\r\n success: false,\r\n message: 'Failed to handle gate result',\r\n data: null,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Handle consensus result webhook\r\n *\r\n * @private\r\n */\r\n private async handleConsensusResult(\r\n req: Request,\r\n res: Response\r\n ): Promise<void> {\r\n try {\r\n const payload = req.body as ConsensusResultPayload;\r\n\r\n if (!payload.taskId || payload.validatorCount === undefined) {\r\n return res.status(400).json({\r\n success: false,\r\n message: 'Missing required fields',\r\n data: null,\r\n });\r\n }\r\n\r\n const context: WebhookContext<ConsensusResultPayload> = {\r\n payload,\r\n isVerified: this.config.verifySignature,\r\n timestamp: Date.now(),\r\n signature: req.headers[this.verificationOptions.headerName] as string,\r\n };\r\n\r\n if (this.consensusResultHandler) {\r\n const result = await this.consensusResultHandler(context);\r\n return res.status(200).json(result);\r\n }\r\n\r\n res.status(200).json({\r\n success: true,\r\n message: 'Consensus result received',\r\n data: {\r\n taskId: payload.taskId,\r\n consensusScore: payload.consensusScore,\r\n },\r\n });\r\n } catch (error) {\r\n return res.status(500).json({\r\n success: false,\r\n message: 'Failed to handle consensus result',\r\n data: null,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Handle Product Owner decision webhook\r\n *\r\n * @private\r\n */\r\n private async handlePODecision(req: Request, res: Response): Promise<void> {\r\n try {\r\n const payload = req.body as PODecisionPayload;\r\n\r\n if (!payload.taskId || !payload.decision) {\r\n return res.status(400).json({\r\n success: false,\r\n message: 'Missing required fields',\r\n data: null,\r\n });\r\n }\r\n\r\n // Validate decision enum\r\n if (!['PROCEED', 'ITERATE', 'ABORT'].includes(payload.decision)) {\r\n return res.status(400).json({\r\n success: false,\r\n message: 'Invalid decision value',\r\n data: null,\r\n });\r\n }\r\n\r\n const context: WebhookContext<PODecisionPayload> = {\r\n payload,\r\n isVerified: this.config.verifySignature,\r\n timestamp: Date.now(),\r\n signature: req.headers[this.verificationOptions.headerName] as string,\r\n };\r\n\r\n if (this.poDecisionHandler) {\r\n const result = await this.poDecisionHandler(context);\r\n return res.status(200).json(result);\r\n }\r\n\r\n res.status(200).json({\r\n success: true,\r\n message: 'PO decision received',\r\n data: { taskId: payload.taskId, decision: payload.decision },\r\n });\r\n } catch (error) {\r\n return res.status(500).json({\r\n success: false,\r\n message: 'Failed to handle PO decision',\r\n data: null,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Verify HMAC signature of webhook payload\r\n *\r\n * @private\r\n * @param payload Raw request body as string\r\n * @param signature Signature header value\r\n * @returns Promise resolving to true if signature is valid\r\n */\r\n private async verifySignature(\r\n payload: string,\r\n signature: string\r\n ): Promise<boolean> {\r\n try {\r\n const hmac = createHmac(\r\n this.verificationOptions.algorithmType,\r\n this.config.secret\r\n );\r\n hmac.update(payload, 'utf8');\r\n const expectedSignature = hmac.digest('hex');\r\n\r\n // Use timing-safe comparison\r\n return timingSafeEqual(\r\n Buffer.from(signature),\r\n Buffer.from(expectedSignature)\r\n );\r\n } catch (error) {\r\n console.error('Signature verification error:', error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Error handling middleware\r\n *\r\n * @private\r\n */\r\n private errorHandler(\r\n error: Error,\r\n req: Request,\r\n res: Response,\r\n next: NextFunction\r\n ): void {\r\n console.error('Webhook error:', error);\r\n\r\n res.status(500).json({\r\n success: false,\r\n message: 'Internal server error',\r\n data: null,\r\n });\r\n }\r\n}\r\n\r\n/**\r\n * Create webhook router with optional handlers\r\n */\r\nexport const createWebhookRouter = (\r\n config?: WebhookConfig,\r\n handlers?: {\r\n onAgentComplete?: WebhookHandler<AgentCompletePayload>;\r\n onGateResult?: WebhookHandler<GateResultPayload>;\r\n onConsensusResult?: WebhookHandler<ConsensusResultPayload>;\r\n onPODecision?: WebhookHandler<PODecisionPayload>;\r\n }\r\n): Router => {\r\n const webhooks = new TriggerDevWebhooks(config);\r\n\r\n if (handlers?.onAgentComplete) {\r\n webhooks.onAgentComplete(handlers.onAgentComplete);\r\n }\r\n if (handlers?.onGateResult) {\r\n webhooks.onGateResult(handlers.onGateResult);\r\n }\r\n if (handlers?.onConsensusResult) {\r\n webhooks.onConsensusResult(handlers.onConsensusResult);\r\n }\r\n if (handlers?.onPODecision) {\r\n webhooks.onPODecision(handlers.onPODecision);\r\n }\r\n\r\n return webhooks.getRouter();\r\n};\r\n\r\nexport default TriggerDevWebhooks;\r\n"],"names":["Router","createHmac","timingSafeEqual","WebhookValidationError","Error","message","reason","statusCode","name","TriggerDevWebhooks","router","config","verificationOptions","agentCompleteHandler","gateResultHandler","consensusResultHandler","poDecisionHandler","secret","process","env","WEBHOOK_SECRET","algorithm","WEBHOOK_ALGORITHM","verifySignature","algorithmType","headerName","setupRoutes","onAgentComplete","handler","onGateResult","onConsensusResult","onPODecision","getRouter","use","signatureVerificationMiddleware","bind","post","handleAgentComplete","handleGateResult","handleConsensusResult","handlePODecision","errorHandler","req","res","next","signature","headers","body","JSON","stringify","isValid","error","status","json","success","payload","agentId","taskId","data","context","isVerified","timestamp","Date","now","result","gateType","undefined","passed","validatorCount","consensusScore","decision","includes","hmac","update","expectedSignature","digest","Buffer","from","console","createWebhookRouter","handlers","webhooks"],"mappings":"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"}
|
|
@@ -60,9 +60,15 @@ import { StandardError } from './errors';
|
|
|
60
60
|
try {
|
|
61
61
|
decoded = decodeURIComponent(decoded);
|
|
62
62
|
} catch (error) {
|
|
63
|
-
// Invalid URL encoding (
|
|
64
|
-
//
|
|
65
|
-
|
|
63
|
+
// Invalid URL encoding can be legitimate (e.g., file100%, %PATH%)
|
|
64
|
+
// These are literal percent signs in filenames, not attacks
|
|
65
|
+
// Only flag as attack if it's malformed UTF-8 (overlong encoding)
|
|
66
|
+
const errorMessage = error.message || '';
|
|
67
|
+
// Malformed UTF-8 sequences like %c0%ae are attacks
|
|
68
|
+
if (decoded.match(/%[cC][0-9a-fA-F]/) || decoded.match(/%[eE][0-9a-fA-F]/)) {
|
|
69
|
+
invalidEncodingDetected = true;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
66
72
|
break;
|
|
67
73
|
}
|
|
68
74
|
iterations++;
|
|
@@ -85,8 +91,11 @@ import { StandardError } from './errors';
|
|
|
85
91
|
reason: 'INVALID_ENCODING_DETECTED'
|
|
86
92
|
});
|
|
87
93
|
}
|
|
88
|
-
// Detect
|
|
89
|
-
|
|
94
|
+
// Detect double+ encoding attacks (3+ iterations = double-encoded or more)
|
|
95
|
+
// Single-level URL encoding requires 2 iterations: decode + stability check
|
|
96
|
+
// Example: subdir%2ffile.txt → subdir/file.txt → stable (2 iterations, legitimate)
|
|
97
|
+
// Example: %252e%252e%252f → %2e%2e%2f → ../ (3 iterations, attack)
|
|
98
|
+
const encodingAttackDetected = iterations > 2;
|
|
90
99
|
// Unicode normalization to handle overlong UTF-8 sequences
|
|
91
100
|
// e.g., %c0%ae (%c0%ae = UTF-8 overlong encoding for ".")
|
|
92
101
|
let normalized = decoded;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/path-validator.ts"],"sourcesContent":["/**\r\n * Path Validator - Security Utility for Safe File Operations\r\n *\r\n * Provides robust path sanitization and validation to prevent path traversal attacks (CVSS 7.0+).\r\n * Enforces strict rules on file access with protection against encoding attacks:\r\n *\r\n * CRITICAL FIXES (CVSS 7.0+):\r\n * - Iterative URL decoding prevents double-encoding bypasses (%252e%252e%252f → ../)\r\n * - Unicode normalization (NFC) prevents overlong UTF-8 bypasses (%c0%ae → .)\r\n * - Null byte detection prevents null injection attacks\r\n * - All decoding performed BEFORE path normalization to prevent layered attacks\r\n * - Encoding attack detection with security logging\r\n *\r\n * Base Security Controls:\r\n * - Path normalization to resolve \"..\" and \".\" sequences\r\n * - Validation that resolved paths stay within allowed directories\r\n * - Detection and rejection of symlinks\r\n * - Rejection of absolute paths outside allowed directories\r\n * - Prevention of home directory access (\"~\")\r\n *\r\n * @module path-validator\r\n * @version 2.0.0 (SECURITY CRITICAL)\r\n */\r\n\r\nimport * as path from 'path';\r\nimport * as fs from 'fs';\r\nimport { StandardError } from './errors';\r\n\r\n/**\r\n * Path validation error - thrown when a path violates security constraints\r\n */\r\nexport class PathValidationError extends StandardError {\r\n constructor(message: string, context?: Record<string, unknown>) {\r\n super('PATH_VALIDATION_ERROR', message, context);\r\n this.name = 'PathValidationError';\r\n }\r\n}\r\n\r\n/**\r\n * Security encoding attack detection\r\n */\r\nexport interface EncodingAttackDetection {\r\n detected: boolean;\r\n type?: 'double_encoding' | 'unicode_encoding' | 'mixed_encoding';\r\n originalInput: string;\r\n decodedOutput: string;\r\n iterationsRequired: number;\r\n}\r\n\r\n/**\r\n * Path validation result with detailed information\r\n */\r\nexport interface PathValidationResult {\r\n valid: boolean;\r\n resolvedPath: string;\r\n normalizedPath: string;\r\n isWithinBase: boolean;\r\n isSymlink: boolean;\r\n reason?: string;\r\n}\r\n\r\n/**\r\n * Safely decode a path with protection against encoding attacks\r\n *\r\n * Performs iterative URL decoding and Unicode normalization to prevent:\r\n * - Double encoding bypass (e.g., %252e%252e%252f → %2e%2e%2f → ../)\r\n * - Overlong UTF-8 encoding (e.g., %c0%ae%c0%ae/ → ../)\r\n * - Mixed encoding attacks\r\n *\r\n * @param inputPath - The potentially encoded path\r\n * @returns Decoded path and attack detection info\r\n * @throws PathValidationError if encoding attack detected\r\n *\r\n * @example\r\n * const { decoded, encoding } = decodePathSafely('%252e%252e%252f');\r\n * // Detects double-encoding attempt\r\n */\r\nfunction decodePathSafely(inputPath: string): {\r\n decoded: string;\r\n encoding: EncodingAttackDetection;\r\n} {\r\n let decoded = inputPath;\r\n let previous = '';\r\n let iterations = 0;\r\n const MAX_ITERATIONS = 5;\r\n const originalInput = inputPath;\r\n let invalidEncodingDetected = false;\r\n\r\n // Iteratively decode URL-encoded characters until stable\r\n // This prevents bypass attacks using multiple encoding layers\r\n while (decoded !== previous && iterations < MAX_ITERATIONS) {\r\n previous = decoded;\r\n try {\r\n decoded = decodeURIComponent(decoded);\r\n } catch (error) {\r\n // Invalid URL encoding (including malformed UTF-8) indicates attack\r\n // e.g., %c0%ae is malformed UTF-8 for overlong encoding\r\n invalidEncodingDetected = true;\r\n // Treat invalid encoding as a suspicious attack indicator\r\n // but continue with the path as-is for analysis\r\n break;\r\n }\r\n iterations++;\r\n }\r\n\r\n // Check if we hit max iterations (indicates potential encoding attack)\r\n if (iterations >= MAX_ITERATIONS && decoded !== previous) {\r\n throw new PathValidationError(\r\n 'Path validation failed: excessive encoding layers detected',\r\n {\r\n originalInput,\r\n decodedOutput: decoded,\r\n iterations,\r\n reason: 'ENCODING_ATTACK_DETECTED',\r\n }\r\n );\r\n }\r\n\r\n // Invalid encoding like malformed UTF-8 is itself an attack indicator\r\n if (invalidEncodingDetected) {\r\n throw new PathValidationError(\r\n 'Path validation failed: invalid encoding detected (possible encoding attack)',\r\n {\r\n originalInput,\r\n decodedOutput: decoded,\r\n iterations,\r\n reason: 'INVALID_ENCODING_DETECTED',\r\n }\r\n );\r\n }\r\n\r\n // Detect if decoding required multiple iterations (possible double-encoding attack)\r\n const encodingAttackDetected = iterations > 1;\r\n\r\n // Unicode normalization to handle overlong UTF-8 sequences\r\n // e.g., %c0%ae (%c0%ae = UTF-8 overlong encoding for \".\")\r\n let normalized = decoded;\r\n try {\r\n normalized = decoded.normalize('NFC');\r\n } catch (error) {\r\n // Some paths may not be valid Unicode, continue with non-normalized version\r\n }\r\n\r\n // Check for null bytes (another common encoding attack vector)\r\n if (normalized.includes('\\0')) {\r\n throw new PathValidationError(\r\n 'Path validation failed: null byte injection detected',\r\n {\r\n originalInput,\r\n decodedOutput: normalized,\r\n reason: 'NULL_BYTE_INJECTION',\r\n }\r\n );\r\n }\r\n\r\n return {\r\n decoded: normalized,\r\n encoding: {\r\n detected: encodingAttackDetected,\r\n type: encodingAttackDetected ? 'double_encoding' : undefined,\r\n originalInput,\r\n decodedOutput: normalized,\r\n iterationsRequired: iterations,\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Validate a file path to prevent directory traversal attacks\r\n *\r\n * Security checks performed (in order):\r\n * 1. CRITICAL: Iteratively decode URL encoding to handle %252e%252e%252f and similar bypasses\r\n * 2. CRITICAL: Normalize Unicode (NFC) to handle overlong UTF-8 like %c0%ae%c0%ae/\r\n * 3. CRITICAL: Detect null bytes and excessive encoding layers\r\n * 4. Check for home directory expansion (\"~\") on DECODED path\r\n * 5. Normalize path to resolve \"..\" and \".\"\r\n * 6. Verify all suspicious patterns are eliminated\r\n * 7. Check if path is within base directory\r\n * 8. Reject symlinks to prevent symlink attacks\r\n * 9. Log any encoding attacks detected for security monitoring\r\n *\r\n * @param filePath - The file path to validate (may be encoded)\r\n * @param baseDirectory - The base directory that file must reside within\r\n * @returns PathValidationResult with validation details\r\n * @throws PathValidationError if path validation fails or encoding attack detected\r\n *\r\n * @example\r\n * const result = validatePath('docs/FEATURE.md', './.claude/skills');\r\n * if (!result.valid) {\r\n * throw result; // Safe to throw, contains all context\r\n * }\r\n * // Use result.resolvedPath\r\n *\r\n * @security\r\n * Designed to prevent:\r\n * - Double-encoding bypasses: %252e%252e%252f\r\n * - Overlong UTF-8: %c0%ae%c0%ae/\r\n * - Mixed encoding: URL + Unicode combinations\r\n * - Null byte injection: file.txt%00.jpg\r\n * - Traditional path traversal: ../../../etc/passwd\r\n */\r\nexport function validatePath(filePath: string, baseDirectory: string): PathValidationResult {\r\n // SECURITY FIX: Decode all encoding layers first before any path normalization\r\n // This prevents double-encoding bypasses like %252e%252e%252f\r\n const { decoded: decodedPath, encoding: pathEncoding } = decodePathSafely(filePath);\r\n const { decoded: decodedBase } = decodePathSafely(baseDirectory);\r\n\r\n // Log encoding attacks for security monitoring\r\n if (pathEncoding.detected) {\r\n // In production, this should trigger security alerts\r\n console.warn('Security: Encoding attack detected in path input', {\r\n originalInput: pathEncoding.originalInput,\r\n decodedOutput: pathEncoding.decodedOutput,\r\n iterationsRequired: pathEncoding.iterationsRequired,\r\n });\r\n }\r\n\r\n // Check for home directory expansion attempts on DECODED path\r\n if (decodedPath.startsWith('~') || decodedPath.includes('/~') || decodedPath.includes('\\\\~')) {\r\n throw new PathValidationError(\r\n 'Path validation failed: home directory access denied',\r\n {\r\n filePath,\r\n decodedPath,\r\n baseDirectory,\r\n reason: 'HOME_DIRECTORY_ACCESS',\r\n }\r\n );\r\n }\r\n\r\n // Check for home directory expansion attempts in baseDirectory\r\n if (decodedBase.startsWith('~')) {\r\n throw new PathValidationError(\r\n 'Base directory validation failed: home directory access denied',\r\n {\r\n baseDirectory,\r\n decodedBase,\r\n reason: 'BASE_HOME_DIRECTORY_ACCESS',\r\n }\r\n );\r\n }\r\n\r\n // Normalize the base directory first\r\n const normalizedBase = path.normalize(decodedBase);\r\n const resolvedBase = path.resolve(normalizedBase);\r\n\r\n // Normalize and resolve the file path relative to base\r\n // NOW normalized on already-decoded path to prevent encoding bypasses\r\n const normalizedPath = path.normalize(decodedPath);\r\n\r\n // Check if path contains suspicious patterns after normalization\r\n if (normalizedPath.includes('..') || normalizedPath === '.' || normalizedPath.includes('/./')) {\r\n throw new PathValidationError(\r\n 'Path validation failed: path contains directory traversal patterns',\r\n {\r\n filePath,\r\n decodedPath,\r\n normalizedPath,\r\n baseDirectory,\r\n reason: 'TRAVERSAL_PATTERN_DETECTED',\r\n }\r\n );\r\n }\r\n\r\n // Resolve the path relative to base directory\r\n const resolvedPath = path.resolve(resolvedBase, normalizedPath);\r\n\r\n // Check if resolved path is within base directory\r\n const isWithinBase = isPathWithinBase(resolvedPath, resolvedBase);\r\n\r\n if (!isWithinBase) {\r\n throw new PathValidationError(\r\n 'Path validation failed: resolved path is outside allowed directory',\r\n {\r\n filePath,\r\n resolvedPath,\r\n baseDirectory: resolvedBase,\r\n reason: 'PATH_OUTSIDE_BASE',\r\n }\r\n );\r\n }\r\n\r\n // Check for symlinks (prevents symlink attacks)\r\n let isSymlink = false;\r\n try {\r\n const stats = fs.lstatSync(resolvedPath);\r\n isSymlink = stats.isSymbolicLink();\r\n\r\n if (isSymlink) {\r\n throw new PathValidationError(\r\n 'Path validation failed: symbolic links are not allowed',\r\n {\r\n filePath,\r\n resolvedPath,\r\n reason: 'SYMLINK_NOT_ALLOWED',\r\n }\r\n );\r\n }\r\n } catch (error) {\r\n // File doesn't exist yet (expected for validation before creation)\r\n // or it's a symlink that was rejected above\r\n if (error instanceof PathValidationError) {\r\n throw error;\r\n }\r\n // Other errors (permission denied, etc.) are not path validation failures\r\n // The file validation happens later\r\n }\r\n\r\n return {\r\n valid: true,\r\n resolvedPath,\r\n normalizedPath,\r\n isWithinBase: true,\r\n isSymlink: false,\r\n };\r\n}\r\n\r\n/**\r\n * Check if a path is within a base directory\r\n *\r\n * Uses path resolution and string comparison to ensure the resolved path\r\n * is actually within the base directory (not just sharing a prefix).\r\n *\r\n * @param filePath - The path to check\r\n * @param baseDirectory - The base directory\r\n * @returns True if filePath is within baseDirectory\r\n *\r\n * @example\r\n * isPathWithinBase('/home/user/project/src/file.ts', '/home/user/project') // true\r\n * isPathWithinBase('/home/user/project-evil/file.ts', '/home/user/project') // false\r\n */\r\nexport function isPathWithinBase(filePath: string, baseDirectory: string): boolean {\r\n // Ensure both paths are normalized and absolute\r\n const normalizedFile = path.normalize(path.resolve(filePath));\r\n const normalizedBase = path.normalize(path.resolve(baseDirectory));\r\n\r\n // Exact match\r\n if (normalizedFile === normalizedBase) {\r\n return true;\r\n }\r\n\r\n // Check if file is within base (use path.relative to ensure it's not going up)\r\n const relative = path.relative(normalizedBase, normalizedFile);\r\n\r\n // If relative path starts with \"..\", it's outside the base directory\r\n if (relative.startsWith('..')) {\r\n return false;\r\n }\r\n\r\n // If relative path is absolute, it's outside the base directory\r\n if (path.isAbsolute(relative)) {\r\n return false;\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Validate multiple paths within the same base directory\r\n *\r\n * Efficiently validates multiple paths, returning results for each.\r\n *\r\n * @param filePaths - Array of file paths to validate\r\n * @param baseDirectory - The base directory that all files must reside within\r\n * @returns Map of file path to validation result\r\n *\r\n * @example\r\n * const results = validatePaths(['docs/SKILL.md', 'src/index.ts'], './.claude/skills');\r\n * results.forEach((result, filePath) => {\r\n * if (!result.valid) {\r\n * console.error(`Invalid path: ${filePath}`, result.reason);\r\n * }\r\n * });\r\n */\r\nexport function validatePaths(\r\n filePaths: string[],\r\n baseDirectory: string\r\n): Map<string, PathValidationResult> {\r\n const results = new Map<string, PathValidationResult>();\r\n\r\n for (const filePath of filePaths) {\r\n try {\r\n const result = validatePath(filePath, baseDirectory);\r\n results.set(filePath, result);\r\n } catch (error) {\r\n if (error instanceof PathValidationError) {\r\n results.set(filePath, {\r\n valid: false,\r\n resolvedPath: '',\r\n normalizedPath: '',\r\n isWithinBase: false,\r\n isSymlink: false,\r\n reason: error.context?.reason as string | undefined,\r\n });\r\n } else {\r\n throw error;\r\n }\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * Get the safe path for a file (or throw if validation fails)\r\n *\r\n * Convenience function that validates and returns the resolved path,\r\n * or throws an error if validation fails.\r\n *\r\n * @param filePath - The file path to validate\r\n * @param baseDirectory - The base directory that file must reside within\r\n * @returns The resolved, validated absolute path\r\n * @throws PathValidationError if path validation fails\r\n *\r\n * @example\r\n * const safePath = getSafePath('docs/SKILL.md', './.claude/skills');\r\n * fs.readFileSync(safePath); // Safe to use\r\n */\r\nexport function getSafePath(filePath: string, baseDirectory: string): string {\r\n const result = validatePath(filePath, baseDirectory);\r\n return result.resolvedPath;\r\n}\r\n\r\n/**\r\n * Check if a path is considered safe for operations\r\n *\r\n * This is a non-throwing version of validatePath for conditional logic.\r\n *\r\n * @param filePath - The file path to check\r\n * @param baseDirectory - The base directory\r\n * @returns True if path is safe, false otherwise\r\n *\r\n * @example\r\n * if (isPathSafe(userInput, './.claude/skills')) {\r\n * // Process the file\r\n * } else {\r\n * // Reject the request\r\n * }\r\n */\r\nexport function isPathSafe(filePath: string, baseDirectory: string): boolean {\r\n try {\r\n validatePath(filePath, baseDirectory);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Get validation error details for a path (if invalid)\r\n *\r\n * Useful for logging and diagnostics.\r\n *\r\n * @param filePath - The file path to validate\r\n * @param baseDirectory - The base directory\r\n * @returns Error with details, or undefined if path is valid\r\n *\r\n * @example\r\n * const error = getPathValidationError('../../etc/passwd', './.claude/skills');\r\n * if (error) {\r\n * logger.error(error.message, error.context);\r\n * }\r\n */\r\nexport function getPathValidationError(\r\n filePath: string,\r\n baseDirectory: string\r\n): PathValidationError | undefined {\r\n try {\r\n validatePath(filePath, baseDirectory);\r\n return undefined;\r\n } catch (error) {\r\n if (error instanceof PathValidationError) {\r\n return error;\r\n }\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * List allowed files within a base directory (safely)\r\n *\r\n * Recursively lists all files within base directory, validating\r\n * each path to ensure it's within bounds.\r\n *\r\n * @param baseDirectory - The base directory to scan\r\n * @param options - Options for listing (maxDepth, filter)\r\n * @returns Array of validated, safe paths relative to baseDirectory\r\n *\r\n * @example\r\n * const files = safeListDirectory('./.claude/skills');\r\n * files.forEach(file => {\r\n * // All paths are guaranteed safe\r\n * console.log(file);\r\n * });\r\n */\r\nexport function safeListDirectory(\r\n baseDirectory: string,\r\n options?: {\r\n maxDepth?: number;\r\n filter?: (path: string) => boolean;\r\n }\r\n): string[] {\r\n const safeFiles: string[] = [];\r\n const maxDepth = options?.maxDepth ?? Infinity;\r\n const filter = options?.filter ?? (() => true);\r\n\r\n function walkDirectory(dir: string, currentDepth: number = 0): void {\r\n if (currentDepth > maxDepth) {\r\n return;\r\n }\r\n\r\n try {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n const relativePath = path.relative(baseDirectory, fullPath);\r\n\r\n // Validate the path is still within base\r\n if (!isPathWithinBase(fullPath, baseDirectory)) {\r\n continue;\r\n }\r\n\r\n // Apply filter\r\n if (!filter(relativePath)) {\r\n continue;\r\n }\r\n\r\n safeFiles.push(relativePath);\r\n\r\n // Recursively walk directories\r\n if (entry.isDirectory() && !entry.isSymbolicLink()) {\r\n walkDirectory(fullPath, currentDepth + 1);\r\n }\r\n }\r\n } catch (error) {\r\n // Silently skip directories we can't read (permission denied, etc.)\r\n }\r\n }\r\n\r\n walkDirectory(baseDirectory);\r\n return safeFiles;\r\n}\r\n"],"names":["path","fs","StandardError","PathValidationError","message","context","name","decodePathSafely","inputPath","decoded","previous","iterations","MAX_ITERATIONS","originalInput","invalidEncodingDetected","decodeURIComponent","error","decodedOutput","reason","encodingAttackDetected","normalized","normalize","includes","encoding","detected","type","undefined","iterationsRequired","validatePath","filePath","baseDirectory","decodedPath","pathEncoding","decodedBase","console","warn","startsWith","normalizedBase","resolvedBase","resolve","normalizedPath","resolvedPath","isWithinBase","isPathWithinBase","isSymlink","stats","lstatSync","isSymbolicLink","valid","normalizedFile","relative","isAbsolute","validatePaths","filePaths","results","Map","result","set","getSafePath","isPathSafe","getPathValidationError","safeListDirectory","options","safeFiles","maxDepth","Infinity","filter","walkDirectory","dir","currentDepth","entries","readdirSync","withFileTypes","entry","fullPath","join","relativePath","push","isDirectory"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/path-validator.ts"],"sourcesContent":["/**\r\n * Path Validator - Security Utility for Safe File Operations\r\n *\r\n * Provides robust path sanitization and validation to prevent path traversal attacks (CVSS 7.0+).\r\n * Enforces strict rules on file access with protection against encoding attacks:\r\n *\r\n * CRITICAL FIXES (CVSS 7.0+):\r\n * - Iterative URL decoding prevents double-encoding bypasses (%252e%252e%252f → ../)\r\n * - Unicode normalization (NFC) prevents overlong UTF-8 bypasses (%c0%ae → .)\r\n * - Null byte detection prevents null injection attacks\r\n * - All decoding performed BEFORE path normalization to prevent layered attacks\r\n * - Encoding attack detection with security logging\r\n *\r\n * Base Security Controls:\r\n * - Path normalization to resolve \"..\" and \".\" sequences\r\n * - Validation that resolved paths stay within allowed directories\r\n * - Detection and rejection of symlinks\r\n * - Rejection of absolute paths outside allowed directories\r\n * - Prevention of home directory access (\"~\")\r\n *\r\n * @module path-validator\r\n * @version 2.0.0 (SECURITY CRITICAL)\r\n */\r\n\r\nimport * as path from 'path';\r\nimport * as fs from 'fs';\r\nimport { StandardError } from './errors';\r\n\r\n/**\r\n * Path validation error - thrown when a path violates security constraints\r\n */\r\nexport class PathValidationError extends StandardError {\r\n constructor(message: string, context?: Record<string, unknown>) {\r\n super('PATH_VALIDATION_ERROR', message, context);\r\n this.name = 'PathValidationError';\r\n }\r\n}\r\n\r\n/**\r\n * Security encoding attack detection\r\n */\r\nexport interface EncodingAttackDetection {\r\n detected: boolean;\r\n type?: 'double_encoding' | 'unicode_encoding' | 'mixed_encoding';\r\n originalInput: string;\r\n decodedOutput: string;\r\n iterationsRequired: number;\r\n}\r\n\r\n/**\r\n * Path validation result with detailed information\r\n */\r\nexport interface PathValidationResult {\r\n valid: boolean;\r\n resolvedPath: string;\r\n normalizedPath: string;\r\n isWithinBase: boolean;\r\n isSymlink: boolean;\r\n reason?: string;\r\n}\r\n\r\n/**\r\n * Safely decode a path with protection against encoding attacks\r\n *\r\n * Performs iterative URL decoding and Unicode normalization to prevent:\r\n * - Double encoding bypass (e.g., %252e%252e%252f → %2e%2e%2f → ../)\r\n * - Overlong UTF-8 encoding (e.g., %c0%ae%c0%ae/ → ../)\r\n * - Mixed encoding attacks\r\n *\r\n * @param inputPath - The potentially encoded path\r\n * @returns Decoded path and attack detection info\r\n * @throws PathValidationError if encoding attack detected\r\n *\r\n * @example\r\n * const { decoded, encoding } = decodePathSafely('%252e%252e%252f');\r\n * // Detects double-encoding attempt\r\n */\r\nfunction decodePathSafely(inputPath: string): {\r\n decoded: string;\r\n encoding: EncodingAttackDetection;\r\n} {\r\n let decoded = inputPath;\r\n let previous = '';\r\n let iterations = 0;\r\n const MAX_ITERATIONS = 5;\r\n const originalInput = inputPath;\r\n let invalidEncodingDetected = false;\r\n\r\n // Iteratively decode URL-encoded characters until stable\r\n // This prevents bypass attacks using multiple encoding layers\r\n while (decoded !== previous && iterations < MAX_ITERATIONS) {\r\n previous = decoded;\r\n try {\r\n decoded = decodeURIComponent(decoded);\r\n } catch (error) {\r\n // Invalid URL encoding can be legitimate (e.g., file100%, %PATH%)\r\n // These are literal percent signs in filenames, not attacks\r\n // Only flag as attack if it's malformed UTF-8 (overlong encoding)\r\n const errorMessage = (error as Error).message || '';\r\n\r\n // Malformed UTF-8 sequences like %c0%ae are attacks\r\n if (decoded.match(/%[cC][0-9a-fA-F]/) || decoded.match(/%[eE][0-9a-fA-F]/)) {\r\n invalidEncodingDetected = true;\r\n break;\r\n }\r\n\r\n // Otherwise, it's just a literal % in the filename - OK\r\n // Keep the path as-is and stop decoding\r\n break;\r\n }\r\n iterations++;\r\n }\r\n\r\n // Check if we hit max iterations (indicates potential encoding attack)\r\n if (iterations >= MAX_ITERATIONS && decoded !== previous) {\r\n throw new PathValidationError(\r\n 'Path validation failed: excessive encoding layers detected',\r\n {\r\n originalInput,\r\n decodedOutput: decoded,\r\n iterations,\r\n reason: 'ENCODING_ATTACK_DETECTED',\r\n }\r\n );\r\n }\r\n\r\n // Invalid encoding like malformed UTF-8 is itself an attack indicator\r\n if (invalidEncodingDetected) {\r\n throw new PathValidationError(\r\n 'Path validation failed: invalid encoding detected (possible encoding attack)',\r\n {\r\n originalInput,\r\n decodedOutput: decoded,\r\n iterations,\r\n reason: 'INVALID_ENCODING_DETECTED',\r\n }\r\n );\r\n }\r\n\r\n // Detect double+ encoding attacks (3+ iterations = double-encoded or more)\r\n // Single-level URL encoding requires 2 iterations: decode + stability check\r\n // Example: subdir%2ffile.txt → subdir/file.txt → stable (2 iterations, legitimate)\r\n // Example: %252e%252e%252f → %2e%2e%2f → ../ (3 iterations, attack)\r\n const encodingAttackDetected = iterations > 2;\r\n\r\n // Unicode normalization to handle overlong UTF-8 sequences\r\n // e.g., %c0%ae (%c0%ae = UTF-8 overlong encoding for \".\")\r\n let normalized = decoded;\r\n try {\r\n normalized = decoded.normalize('NFC');\r\n } catch (error) {\r\n // Some paths may not be valid Unicode, continue with non-normalized version\r\n }\r\n\r\n // Check for null bytes (another common encoding attack vector)\r\n if (normalized.includes('\\0')) {\r\n throw new PathValidationError(\r\n 'Path validation failed: null byte injection detected',\r\n {\r\n originalInput,\r\n decodedOutput: normalized,\r\n reason: 'NULL_BYTE_INJECTION',\r\n }\r\n );\r\n }\r\n\r\n return {\r\n decoded: normalized,\r\n encoding: {\r\n detected: encodingAttackDetected,\r\n type: encodingAttackDetected ? 'double_encoding' : undefined,\r\n originalInput,\r\n decodedOutput: normalized,\r\n iterationsRequired: iterations,\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Validate a file path to prevent directory traversal attacks\r\n *\r\n * Security checks performed (in order):\r\n * 1. CRITICAL: Iteratively decode URL encoding to handle %252e%252e%252f and similar bypasses\r\n * 2. CRITICAL: Normalize Unicode (NFC) to handle overlong UTF-8 like %c0%ae%c0%ae/\r\n * 3. CRITICAL: Detect null bytes and excessive encoding layers\r\n * 4. Check for home directory expansion (\"~\") on DECODED path\r\n * 5. Normalize path to resolve \"..\" and \".\"\r\n * 6. Verify all suspicious patterns are eliminated\r\n * 7. Check if path is within base directory\r\n * 8. Reject symlinks to prevent symlink attacks\r\n * 9. Log any encoding attacks detected for security monitoring\r\n *\r\n * @param filePath - The file path to validate (may be encoded)\r\n * @param baseDirectory - The base directory that file must reside within\r\n * @returns PathValidationResult with validation details\r\n * @throws PathValidationError if path validation fails or encoding attack detected\r\n *\r\n * @example\r\n * const result = validatePath('docs/FEATURE.md', './.claude/skills');\r\n * if (!result.valid) {\r\n * throw result; // Safe to throw, contains all context\r\n * }\r\n * // Use result.resolvedPath\r\n *\r\n * @security\r\n * Designed to prevent:\r\n * - Double-encoding bypasses: %252e%252e%252f\r\n * - Overlong UTF-8: %c0%ae%c0%ae/\r\n * - Mixed encoding: URL + Unicode combinations\r\n * - Null byte injection: file.txt%00.jpg\r\n * - Traditional path traversal: ../../../etc/passwd\r\n */\r\nexport function validatePath(filePath: string, baseDirectory: string): PathValidationResult {\r\n // SECURITY FIX: Decode all encoding layers first before any path normalization\r\n // This prevents double-encoding bypasses like %252e%252e%252f\r\n const { decoded: decodedPath, encoding: pathEncoding } = decodePathSafely(filePath);\r\n const { decoded: decodedBase } = decodePathSafely(baseDirectory);\r\n\r\n // Log encoding attacks for security monitoring\r\n if (pathEncoding.detected) {\r\n // In production, this should trigger security alerts\r\n console.warn('Security: Encoding attack detected in path input', {\r\n originalInput: pathEncoding.originalInput,\r\n decodedOutput: pathEncoding.decodedOutput,\r\n iterationsRequired: pathEncoding.iterationsRequired,\r\n });\r\n }\r\n\r\n // Check for home directory expansion attempts on DECODED path\r\n if (decodedPath.startsWith('~') || decodedPath.includes('/~') || decodedPath.includes('\\\\~')) {\r\n throw new PathValidationError(\r\n 'Path validation failed: home directory access denied',\r\n {\r\n filePath,\r\n decodedPath,\r\n baseDirectory,\r\n reason: 'HOME_DIRECTORY_ACCESS',\r\n }\r\n );\r\n }\r\n\r\n // Check for home directory expansion attempts in baseDirectory\r\n if (decodedBase.startsWith('~')) {\r\n throw new PathValidationError(\r\n 'Base directory validation failed: home directory access denied',\r\n {\r\n baseDirectory,\r\n decodedBase,\r\n reason: 'BASE_HOME_DIRECTORY_ACCESS',\r\n }\r\n );\r\n }\r\n\r\n // Normalize the base directory first\r\n const normalizedBase = path.normalize(decodedBase);\r\n const resolvedBase = path.resolve(normalizedBase);\r\n\r\n // Normalize and resolve the file path relative to base\r\n // NOW normalized on already-decoded path to prevent encoding bypasses\r\n const normalizedPath = path.normalize(decodedPath);\r\n\r\n // Check if path contains suspicious patterns after normalization\r\n if (normalizedPath.includes('..') || normalizedPath === '.' || normalizedPath.includes('/./')) {\r\n throw new PathValidationError(\r\n 'Path validation failed: path contains directory traversal patterns',\r\n {\r\n filePath,\r\n decodedPath,\r\n normalizedPath,\r\n baseDirectory,\r\n reason: 'TRAVERSAL_PATTERN_DETECTED',\r\n }\r\n );\r\n }\r\n\r\n // Resolve the path relative to base directory\r\n const resolvedPath = path.resolve(resolvedBase, normalizedPath);\r\n\r\n // Check if resolved path is within base directory\r\n const isWithinBase = isPathWithinBase(resolvedPath, resolvedBase);\r\n\r\n if (!isWithinBase) {\r\n throw new PathValidationError(\r\n 'Path validation failed: resolved path is outside allowed directory',\r\n {\r\n filePath,\r\n resolvedPath,\r\n baseDirectory: resolvedBase,\r\n reason: 'PATH_OUTSIDE_BASE',\r\n }\r\n );\r\n }\r\n\r\n // Check for symlinks (prevents symlink attacks)\r\n let isSymlink = false;\r\n try {\r\n const stats = fs.lstatSync(resolvedPath);\r\n isSymlink = stats.isSymbolicLink();\r\n\r\n if (isSymlink) {\r\n throw new PathValidationError(\r\n 'Path validation failed: symbolic links are not allowed',\r\n {\r\n filePath,\r\n resolvedPath,\r\n reason: 'SYMLINK_NOT_ALLOWED',\r\n }\r\n );\r\n }\r\n } catch (error) {\r\n // File doesn't exist yet (expected for validation before creation)\r\n // or it's a symlink that was rejected above\r\n if (error instanceof PathValidationError) {\r\n throw error;\r\n }\r\n // Other errors (permission denied, etc.) are not path validation failures\r\n // The file validation happens later\r\n }\r\n\r\n return {\r\n valid: true,\r\n resolvedPath,\r\n normalizedPath,\r\n isWithinBase: true,\r\n isSymlink: false,\r\n };\r\n}\r\n\r\n/**\r\n * Check if a path is within a base directory\r\n *\r\n * Uses path resolution and string comparison to ensure the resolved path\r\n * is actually within the base directory (not just sharing a prefix).\r\n *\r\n * @param filePath - The path to check\r\n * @param baseDirectory - The base directory\r\n * @returns True if filePath is within baseDirectory\r\n *\r\n * @example\r\n * isPathWithinBase('/home/user/project/src/file.ts', '/home/user/project') // true\r\n * isPathWithinBase('/home/user/project-evil/file.ts', '/home/user/project') // false\r\n */\r\nexport function isPathWithinBase(filePath: string, baseDirectory: string): boolean {\r\n // Ensure both paths are normalized and absolute\r\n const normalizedFile = path.normalize(path.resolve(filePath));\r\n const normalizedBase = path.normalize(path.resolve(baseDirectory));\r\n\r\n // Exact match\r\n if (normalizedFile === normalizedBase) {\r\n return true;\r\n }\r\n\r\n // Check if file is within base (use path.relative to ensure it's not going up)\r\n const relative = path.relative(normalizedBase, normalizedFile);\r\n\r\n // If relative path starts with \"..\", it's outside the base directory\r\n if (relative.startsWith('..')) {\r\n return false;\r\n }\r\n\r\n // If relative path is absolute, it's outside the base directory\r\n if (path.isAbsolute(relative)) {\r\n return false;\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Validate multiple paths within the same base directory\r\n *\r\n * Efficiently validates multiple paths, returning results for each.\r\n *\r\n * @param filePaths - Array of file paths to validate\r\n * @param baseDirectory - The base directory that all files must reside within\r\n * @returns Map of file path to validation result\r\n *\r\n * @example\r\n * const results = validatePaths(['docs/SKILL.md', 'src/index.ts'], './.claude/skills');\r\n * results.forEach((result, filePath) => {\r\n * if (!result.valid) {\r\n * console.error(`Invalid path: ${filePath}`, result.reason);\r\n * }\r\n * });\r\n */\r\nexport function validatePaths(\r\n filePaths: string[],\r\n baseDirectory: string\r\n): Map<string, PathValidationResult> {\r\n const results = new Map<string, PathValidationResult>();\r\n\r\n for (const filePath of filePaths) {\r\n try {\r\n const result = validatePath(filePath, baseDirectory);\r\n results.set(filePath, result);\r\n } catch (error) {\r\n if (error instanceof PathValidationError) {\r\n results.set(filePath, {\r\n valid: false,\r\n resolvedPath: '',\r\n normalizedPath: '',\r\n isWithinBase: false,\r\n isSymlink: false,\r\n reason: error.context?.reason as string | undefined,\r\n });\r\n } else {\r\n throw error;\r\n }\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * Get the safe path for a file (or throw if validation fails)\r\n *\r\n * Convenience function that validates and returns the resolved path,\r\n * or throws an error if validation fails.\r\n *\r\n * @param filePath - The file path to validate\r\n * @param baseDirectory - The base directory that file must reside within\r\n * @returns The resolved, validated absolute path\r\n * @throws PathValidationError if path validation fails\r\n *\r\n * @example\r\n * const safePath = getSafePath('docs/SKILL.md', './.claude/skills');\r\n * fs.readFileSync(safePath); // Safe to use\r\n */\r\nexport function getSafePath(filePath: string, baseDirectory: string): string {\r\n const result = validatePath(filePath, baseDirectory);\r\n return result.resolvedPath;\r\n}\r\n\r\n/**\r\n * Check if a path is considered safe for operations\r\n *\r\n * This is a non-throwing version of validatePath for conditional logic.\r\n *\r\n * @param filePath - The file path to check\r\n * @param baseDirectory - The base directory\r\n * @returns True if path is safe, false otherwise\r\n *\r\n * @example\r\n * if (isPathSafe(userInput, './.claude/skills')) {\r\n * // Process the file\r\n * } else {\r\n * // Reject the request\r\n * }\r\n */\r\nexport function isPathSafe(filePath: string, baseDirectory: string): boolean {\r\n try {\r\n validatePath(filePath, baseDirectory);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Get validation error details for a path (if invalid)\r\n *\r\n * Useful for logging and diagnostics.\r\n *\r\n * @param filePath - The file path to validate\r\n * @param baseDirectory - The base directory\r\n * @returns Error with details, or undefined if path is valid\r\n *\r\n * @example\r\n * const error = getPathValidationError('../../etc/passwd', './.claude/skills');\r\n * if (error) {\r\n * logger.error(error.message, error.context);\r\n * }\r\n */\r\nexport function getPathValidationError(\r\n filePath: string,\r\n baseDirectory: string\r\n): PathValidationError | undefined {\r\n try {\r\n validatePath(filePath, baseDirectory);\r\n return undefined;\r\n } catch (error) {\r\n if (error instanceof PathValidationError) {\r\n return error;\r\n }\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * List allowed files within a base directory (safely)\r\n *\r\n * Recursively lists all files within base directory, validating\r\n * each path to ensure it's within bounds.\r\n *\r\n * @param baseDirectory - The base directory to scan\r\n * @param options - Options for listing (maxDepth, filter)\r\n * @returns Array of validated, safe paths relative to baseDirectory\r\n *\r\n * @example\r\n * const files = safeListDirectory('./.claude/skills');\r\n * files.forEach(file => {\r\n * // All paths are guaranteed safe\r\n * console.log(file);\r\n * });\r\n */\r\nexport function safeListDirectory(\r\n baseDirectory: string,\r\n options?: {\r\n maxDepth?: number;\r\n filter?: (path: string) => boolean;\r\n }\r\n): string[] {\r\n const safeFiles: string[] = [];\r\n const maxDepth = options?.maxDepth ?? Infinity;\r\n const filter = options?.filter ?? (() => true);\r\n\r\n function walkDirectory(dir: string, currentDepth: number = 0): void {\r\n if (currentDepth > maxDepth) {\r\n return;\r\n }\r\n\r\n try {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n const relativePath = path.relative(baseDirectory, fullPath);\r\n\r\n // Validate the path is still within base\r\n if (!isPathWithinBase(fullPath, baseDirectory)) {\r\n continue;\r\n }\r\n\r\n // Apply filter\r\n if (!filter(relativePath)) {\r\n continue;\r\n }\r\n\r\n safeFiles.push(relativePath);\r\n\r\n // Recursively walk directories\r\n if (entry.isDirectory() && !entry.isSymbolicLink()) {\r\n walkDirectory(fullPath, currentDepth + 1);\r\n }\r\n }\r\n } catch (error) {\r\n // Silently skip directories we can't read (permission denied, etc.)\r\n }\r\n }\r\n\r\n walkDirectory(baseDirectory);\r\n return safeFiles;\r\n}\r\n"],"names":["path","fs","StandardError","PathValidationError","message","context","name","decodePathSafely","inputPath","decoded","previous","iterations","MAX_ITERATIONS","originalInput","invalidEncodingDetected","decodeURIComponent","error","errorMessage","match","decodedOutput","reason","encodingAttackDetected","normalized","normalize","includes","encoding","detected","type","undefined","iterationsRequired","validatePath","filePath","baseDirectory","decodedPath","pathEncoding","decodedBase","console","warn","startsWith","normalizedBase","resolvedBase","resolve","normalizedPath","resolvedPath","isWithinBase","isPathWithinBase","isSymlink","stats","lstatSync","isSymbolicLink","valid","normalizedFile","relative","isAbsolute","validatePaths","filePaths","results","Map","result","set","getSafePath","isPathSafe","getPathValidationError","safeListDirectory","options","safeFiles","maxDepth","Infinity","filter","walkDirectory","dir","currentDepth","entries","readdirSync","withFileTypes","entry","fullPath","join","relativePath","push","isDirectory"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;CAsBC,GAED,YAAYA,UAAU,OAAO;AAC7B,YAAYC,QAAQ,KAAK;AACzB,SAASC,aAAa,QAAQ,WAAW;AAEzC;;CAEC,GACD,OAAO,MAAMC,4BAA4BD;IACvC,YAAYE,OAAe,EAAEC,OAAiC,CAAE;QAC9D,KAAK,CAAC,yBAAyBD,SAASC;QACxC,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAyBA;;;;;;;;;;;;;;;CAeC,GACD,SAASC,iBAAiBC,SAAiB;IAIzC,IAAIC,UAAUD;IACd,IAAIE,WAAW;IACf,IAAIC,aAAa;IACjB,MAAMC,iBAAiB;IACvB,MAAMC,gBAAgBL;IACtB,IAAIM,0BAA0B;IAE9B,yDAAyD;IACzD,8DAA8D;IAC9D,MAAOL,YAAYC,YAAYC,aAAaC,eAAgB;QAC1DF,WAAWD;QACX,IAAI;YACFA,UAAUM,mBAAmBN;QAC/B,EAAE,OAAOO,OAAO;YACd,kEAAkE;YAClE,4DAA4D;YAC5D,kEAAkE;YAClE,MAAMC,eAAe,AAACD,MAAgBZ,OAAO,IAAI;YAEjD,oDAAoD;YACpD,IAAIK,QAAQS,KAAK,CAAC,uBAAuBT,QAAQS,KAAK,CAAC,qBAAqB;gBAC1EJ,0BAA0B;gBAC1B;YACF;YAIA;QACF;QACAH;IACF;IAEA,uEAAuE;IACvE,IAAIA,cAAcC,kBAAkBH,YAAYC,UAAU;QACxD,MAAM,IAAIP,oBACR,8DACA;YACEU;YACAM,eAAeV;YACfE;YACAS,QAAQ;QACV;IAEJ;IAEA,sEAAsE;IACtE,IAAIN,yBAAyB;QAC3B,MAAM,IAAIX,oBACR,gFACA;YACEU;YACAM,eAAeV;YACfE;YACAS,QAAQ;QACV;IAEJ;IAEA,2EAA2E;IAC3E,4EAA4E;IAC5E,mFAAmF;IACnF,oEAAoE;IACpE,MAAMC,yBAAyBV,aAAa;IAE5C,2DAA2D;IAC3D,0DAA0D;IAC1D,IAAIW,aAAab;IACjB,IAAI;QACFa,aAAab,QAAQc,SAAS,CAAC;IACjC,EAAE,OAAOP,OAAO;IACd,4EAA4E;IAC9E;IAEA,+DAA+D;IAC/D,IAAIM,WAAWE,QAAQ,CAAC,OAAO;QAC7B,MAAM,IAAIrB,oBACR,wDACA;YACEU;YACAM,eAAeG;YACfF,QAAQ;QACV;IAEJ;IAEA,OAAO;QACLX,SAASa;QACTG,UAAU;YACRC,UAAUL;YACVM,MAAMN,yBAAyB,oBAAoBO;YACnDf;YACAM,eAAeG;YACfO,oBAAoBlB;QACtB;IACF;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCC,GACD,OAAO,SAASmB,aAAaC,QAAgB,EAAEC,aAAqB;IAClE,+EAA+E;IAC/E,8DAA8D;IAC9D,MAAM,EAAEvB,SAASwB,WAAW,EAAER,UAAUS,YAAY,EAAE,GAAG3B,iBAAiBwB;IAC1E,MAAM,EAAEtB,SAAS0B,WAAW,EAAE,GAAG5B,iBAAiByB;IAElD,+CAA+C;IAC/C,IAAIE,aAAaR,QAAQ,EAAE;QACzB,qDAAqD;QACrDU,QAAQC,IAAI,CAAC,oDAAoD;YAC/DxB,eAAeqB,aAAarB,aAAa;YACzCM,eAAee,aAAaf,aAAa;YACzCU,oBAAoBK,aAAaL,kBAAkB;QACrD;IACF;IAEA,8DAA8D;IAC9D,IAAII,YAAYK,UAAU,CAAC,QAAQL,YAAYT,QAAQ,CAAC,SAASS,YAAYT,QAAQ,CAAC,QAAQ;QAC5F,MAAM,IAAIrB,oBACR,wDACA;YACE4B;YACAE;YACAD;YACAZ,QAAQ;QACV;IAEJ;IAEA,+DAA+D;IAC/D,IAAIe,YAAYG,UAAU,CAAC,MAAM;QAC/B,MAAM,IAAInC,oBACR,kEACA;YACE6B;YACAG;YACAf,QAAQ;QACV;IAEJ;IAEA,qCAAqC;IACrC,MAAMmB,iBAAiBvC,KAAKuB,SAAS,CAACY;IACtC,MAAMK,eAAexC,KAAKyC,OAAO,CAACF;IAElC,uDAAuD;IACvD,sEAAsE;IACtE,MAAMG,iBAAiB1C,KAAKuB,SAAS,CAACU;IAEtC,iEAAiE;IACjE,IAAIS,eAAelB,QAAQ,CAAC,SAASkB,mBAAmB,OAAOA,eAAelB,QAAQ,CAAC,QAAQ;QAC7F,MAAM,IAAIrB,oBACR,sEACA;YACE4B;YACAE;YACAS;YACAV;YACAZ,QAAQ;QACV;IAEJ;IAEA,8CAA8C;IAC9C,MAAMuB,eAAe3C,KAAKyC,OAAO,CAACD,cAAcE;IAEhD,kDAAkD;IAClD,MAAME,eAAeC,iBAAiBF,cAAcH;IAEpD,IAAI,CAACI,cAAc;QACjB,MAAM,IAAIzC,oBACR,sEACA;YACE4B;YACAY;YACAX,eAAeQ;YACfpB,QAAQ;QACV;IAEJ;IAEA,gDAAgD;IAChD,IAAI0B,YAAY;IAChB,IAAI;QACF,MAAMC,QAAQ9C,GAAG+C,SAAS,CAACL;QAC3BG,YAAYC,MAAME,cAAc;QAEhC,IAAIH,WAAW;YACb,MAAM,IAAI3C,oBACR,0DACA;gBACE4B;gBACAY;gBACAvB,QAAQ;YACV;QAEJ;IACF,EAAE,OAAOJ,OAAO;QACd,mEAAmE;QACnE,4CAA4C;QAC5C,IAAIA,iBAAiBb,qBAAqB;YACxC,MAAMa;QACR;IACA,0EAA0E;IAC1E,oCAAoC;IACtC;IAEA,OAAO;QACLkC,OAAO;QACPP;QACAD;QACAE,cAAc;QACdE,WAAW;IACb;AACF;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASD,iBAAiBd,QAAgB,EAAEC,aAAqB;IACtE,gDAAgD;IAChD,MAAMmB,iBAAiBnD,KAAKuB,SAAS,CAACvB,KAAKyC,OAAO,CAACV;IACnD,MAAMQ,iBAAiBvC,KAAKuB,SAAS,CAACvB,KAAKyC,OAAO,CAACT;IAEnD,cAAc;IACd,IAAImB,mBAAmBZ,gBAAgB;QACrC,OAAO;IACT;IAEA,+EAA+E;IAC/E,MAAMa,WAAWpD,KAAKoD,QAAQ,CAACb,gBAAgBY;IAE/C,qEAAqE;IACrE,IAAIC,SAASd,UAAU,CAAC,OAAO;QAC7B,OAAO;IACT;IAEA,gEAAgE;IAChE,IAAItC,KAAKqD,UAAU,CAACD,WAAW;QAC7B,OAAO;IACT;IAEA,OAAO;AACT;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASE,cACdC,SAAmB,EACnBvB,aAAqB;IAErB,MAAMwB,UAAU,IAAIC;IAEpB,KAAK,MAAM1B,YAAYwB,UAAW;QAChC,IAAI;YACF,MAAMG,SAAS5B,aAAaC,UAAUC;YACtCwB,QAAQG,GAAG,CAAC5B,UAAU2B;QACxB,EAAE,OAAO1C,OAAO;YACd,IAAIA,iBAAiBb,qBAAqB;gBACxCqD,QAAQG,GAAG,CAAC5B,UAAU;oBACpBmB,OAAO;oBACPP,cAAc;oBACdD,gBAAgB;oBAChBE,cAAc;oBACdE,WAAW;oBACX1B,QAAQJ,MAAMX,OAAO,EAAEe;gBACzB;YACF,OAAO;gBACL,MAAMJ;YACR;QACF;IACF;IAEA,OAAOwC;AACT;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASI,YAAY7B,QAAgB,EAAEC,aAAqB;IACjE,MAAM0B,SAAS5B,aAAaC,UAAUC;IACtC,OAAO0B,OAAOf,YAAY;AAC5B;AAEA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASkB,WAAW9B,QAAgB,EAAEC,aAAqB;IAChE,IAAI;QACFF,aAAaC,UAAUC;QACvB,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAAS8B,uBACd/B,QAAgB,EAChBC,aAAqB;IAErB,IAAI;QACFF,aAAaC,UAAUC;QACvB,OAAOJ;IACT,EAAE,OAAOZ,OAAO;QACd,IAAIA,iBAAiBb,qBAAqB;YACxC,OAAOa;QACT;QACA,MAAMA;IACR;AACF;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAAS+C,kBACd/B,aAAqB,EACrBgC,OAGC;IAED,MAAMC,YAAsB,EAAE;IAC9B,MAAMC,WAAWF,SAASE,YAAYC;IACtC,MAAMC,SAASJ,SAASI,UAAW,CAAA,IAAM,IAAG;IAE5C,SAASC,cAAcC,GAAW,EAAEC,eAAuB,CAAC;QAC1D,IAAIA,eAAeL,UAAU;YAC3B;QACF;QAEA,IAAI;YACF,MAAMM,UAAUvE,GAAGwE,WAAW,CAACH,KAAK;gBAAEI,eAAe;YAAK;YAE1D,KAAK,MAAMC,SAASH,QAAS;gBAC3B,MAAMI,WAAW5E,KAAK6E,IAAI,CAACP,KAAKK,MAAMrE,IAAI;gBAC1C,MAAMwE,eAAe9E,KAAKoD,QAAQ,CAACpB,eAAe4C;gBAElD,yCAAyC;gBACzC,IAAI,CAAC/B,iBAAiB+B,UAAU5C,gBAAgB;oBAC9C;gBACF;gBAEA,eAAe;gBACf,IAAI,CAACoC,OAAOU,eAAe;oBACzB;gBACF;gBAEAb,UAAUc,IAAI,CAACD;gBAEf,+BAA+B;gBAC/B,IAAIH,MAAMK,WAAW,MAAM,CAACL,MAAM1B,cAAc,IAAI;oBAClDoB,cAAcO,UAAUL,eAAe;gBACzC;YACF;QACF,EAAE,OAAOvD,OAAO;QACd,oEAAoE;QACtE;IACF;IAEAqD,cAAcrC;IACd,OAAOiC;AACT"}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* }
|
|
34
34
|
*/ import { v4 as uuidv4 } from 'uuid';
|
|
35
35
|
import { createLogger } from './logging.js';
|
|
36
|
-
import { createError, ErrorCode, isRetryableError } from './errors.js';
|
|
36
|
+
import { createError, ErrorCode, isRetryableError, StandardError } from './errors.js';
|
|
37
37
|
import { withRetry } from './retry.js';
|
|
38
38
|
import { MessageDeduplicator } from './message-deduplicator.js';
|
|
39
39
|
const logger = createLogger('redis-queue-manager');
|
|
@@ -137,6 +137,10 @@ const DEFAULT_DEQUEUE_OPTIONS = {
|
|
|
137
137
|
}
|
|
138
138
|
return message.id;
|
|
139
139
|
} catch (error) {
|
|
140
|
+
// Re-throw duplicate errors without wrapping
|
|
141
|
+
if (error instanceof StandardError && error.code === ErrorCode.DB_DUPLICATE_KEY) {
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
140
144
|
logger.error('Failed to enqueue message', error instanceof Error ? error : new Error(String(error)), {
|
|
141
145
|
queue
|
|
142
146
|
});
|