claude-flow-novice 2.15.11 → 2.16.1
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/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
- package/.claude/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
- package/.claude/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
- package/.claude/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
- package/.claude/commands/CFN_LOOP_FRONTEND.md +1 -1
- package/.claude/commands/cfn-loop-cli.md +214 -442
- package/.claude/commands/cfn-loop-frontend.md +1 -1
- package/.claude/commands/cfn-loop-task.md +2 -2
- package/.claude/commands/cfn-loop-trigger.md +114 -0
- package/.claude/commands/deprecated/cfn-loop.md +2 -2
- 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 +53 -5
- 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/hooks/cfn-post-edit.config.json +9 -2
- package/.claude/root-claude-distribute/CFN-CLAUDE.md +1 -1
- 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-backlog-management/SKILL.md +1 -1
- 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/skills/cfn-loop-orchestration/{orchestrate-wrapper.sh → 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/skills/cfn-product-owner-decision/{execute-decision.sh → 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/analysts/root-cause-analyst.md +2 -2
- package/claude-assets/agents/cfn-dev-team/architecture/base-template-generator.md +1 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +3 -2
- package/claude-assets/agents/cfn-dev-team/coordinators/consensus-builder.md +1 -0
- package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +2 -1
- 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 +11 -1
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +58 -35
- package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +2 -2
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +47 -37
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +18 -18
- 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 +19 -21
- package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +20 -29
- 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 +16 -11
- package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +16 -26
- package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +18 -22
- package/claude-assets/agents/cfn-dev-team/developers/rust-developer.md +17 -21
- package/claude-assets/agents/cfn-dev-team/documentation/pseudocode.md +1 -1
- package/claude-assets/agents/cfn-dev-team/product-owners/accessibility-advocate-persona.md +1 -1
- package/claude-assets/agents/cfn-dev-team/product-owners/cto-agent.md +1 -1
- package/claude-assets/agents/cfn-dev-team/product-owners/power-user-persona.md +1 -1
- 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 +24 -68
- package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +8 -36
- package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +9 -38
- package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +17 -55
- package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +18 -56
- 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 +18 -56
- package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +18 -49
- 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/testers/unit/tdd-london-unit-swarm.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/agent-builder.md +11 -0
- package/claude-assets/agents/cfn-dev-team/utility/analyst.md +13 -29
- package/claude-assets/agents/cfn-dev-team/utility/claude-code-expert.md +1 -1
- 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 +6 -11
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +121 -715
- package/claude-assets/agents/cfn-dev-team/utility/researcher.md +13 -22
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +147 -573
- package/claude-assets/agents/custom/cfn-docker-expert.md +103 -0
- package/claude-assets/agents/custom/cfn-loops-cli-expert.md +438 -0
- package/claude-assets/agents/custom/cfn-redis-operations.md +529 -529
- package/claude-assets/agents/custom/cfn-system-expert.md +1 -1
- package/claude-assets/agents/custom/trigger-dev-expert.md +369 -0
- package/claude-assets/agents/docker-team/micro-sprint-planner.md +747 -747
- package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
- package/claude-assets/cfn-extras/agents/cfn-v3-coordinator.md +517 -0
- package/claude-assets/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
- package/claude-assets/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
- package/claude-assets/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
- package/claude-assets/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
- package/claude-assets/commands/CFN_LOOP_FRONTEND.md +1 -1
- package/claude-assets/commands/cfn-loop-cli.md +214 -442
- package/claude-assets/commands/cfn-loop-frontend.md +1 -1
- package/claude-assets/commands/cfn-loop-task.md +2 -2
- package/claude-assets/commands/cfn-loop-trigger.md +114 -0
- package/claude-assets/commands/deprecated/cfn-loop.md +2 -2
- package/claude-assets/hooks/GIT-HOOKS-USAGE-EXAMPLES.md +116 -0
- package/claude-assets/hooks/README-GIT-HOOKS.md +443 -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 +53 -5
- 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/hooks/cfn-post-edit.config.json +9 -2
- package/claude-assets/hooks/install-git-hooks.sh +243 -0
- package/claude-assets/hooks/subagent-start.sh +98 -0
- package/claude-assets/hooks/subagent-stop.sh +93 -0
- package/claude-assets/hooks/validators/credential-scanner.sh +172 -0
- package/claude-assets/root-claude-distribute/CFN-CLAUDE.md +1 -1
- 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-backlog-management/SKILL.md +1 -1
- 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 +397 -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/ingest.sh +237 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/cli-mode-dependencies.txt +73 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/shared-dependencies.txt +57 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-dev-dependencies.txt +82 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-mode-dependencies.txt +80 -0
- package/claude-assets/skills/cfn-dependency-ingestion/src/ingest-dependencies.ts +563 -0
- package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +14 -4
- 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-assets/skills/cfn-loop-orchestration/{orchestrate-wrapper.sh → 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-assets/skills/cfn-product-owner-decision/{execute-decision.sh → 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 +215 -0
- package/claude-assets/skills/cfn-provider-routing/resolve-provider-model.ts +223 -0
- package/claude-assets/skills/docker-build/build.sh +1 -1
- 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/agent/skill-mcp-selector.js +2 -1
- package/dist/agent/skill-mcp-selector.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-executor.js +470 -26
- package/dist/cli/agent-executor.js.map +1 -1
- package/dist/cli/agent-prompt-builder.js +83 -48
- package/dist/cli/agent-prompt-builder.js.map +1 -1
- package/dist/cli/agent-spawn.js +7 -4
- package/dist/cli/agent-spawn.js.map +1 -1
- package/dist/cli/agent-spawner.js +546 -0
- package/dist/cli/agent-spawner.js.map +1 -0
- package/dist/cli/agent-token-manager.js +2 -1
- package/dist/cli/agent-token-manager.js.map +1 -1
- package/dist/cli/anthropic-client.js +127 -14
- package/dist/cli/anthropic-client.js.map +1 -1
- package/dist/cli/cfn-context.js +2 -1
- package/dist/cli/cfn-context.js.map +1 -1
- package/dist/cli/cfn-metrics.js +2 -1
- package/dist/cli/cfn-metrics.js.map +1 -1
- package/dist/cli/cfn-redis.js +2 -1
- package/dist/cli/cfn-redis.js.map +1 -1
- package/dist/cli/cli-agent-context.js +2 -0
- package/dist/cli/cli-agent-context.js.map +1 -1
- package/dist/cli/config-manager.js +90 -356
- package/dist/cli/config-manager.js.map +1 -1
- package/dist/cli/conversation-fork-cleanup.js +2 -1
- package/dist/cli/conversation-fork-cleanup.js.map +1 -1
- package/dist/cli/conversation-fork.js +2 -1
- package/dist/cli/conversation-fork.js.map +1 -1
- package/dist/cli/coordination/agent-messaging.js +415 -0
- package/dist/cli/coordination/agent-messaging.js.map +1 -0
- package/dist/cli/coordination/wait-for-threshold.js +232 -0
- package/dist/cli/coordination/wait-for-threshold.js.map +1 -0
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/iteration-history.js +2 -1
- package/dist/cli/iteration-history.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/process-lifecycle.js +5 -1
- package/dist/cli/process-lifecycle.js.map +1 -1
- package/dist/cli/spawn-agent-cli.js +244 -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/redis-waiting-mode.js +4 -0
- package/dist/coordination/redis-waiting-mode.js.map +1 -1
- 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/artifact-registry.js +4 -0
- package/dist/lib/artifact-registry.js.map +1 -1
- package/dist/lib/connection-pool.js +390 -0
- package/dist/lib/connection-pool.js.map +1 -0
- package/dist/lib/environment-contract.js +258 -0
- package/dist/lib/environment-contract.js.map +1 -0
- package/dist/lib/path-validator.js +14 -5
- package/dist/lib/path-validator.js.map +1 -1
- package/dist/lib/query-optimizer.js +388 -0
- package/dist/lib/query-optimizer.js.map +1 -0
- package/dist/lib/redis-queue-manager.js +5 -1
- package/dist/lib/redis-queue-manager.js.map +1 -1
- package/dist/lib/result-cache.js +285 -0
- package/dist/lib/result-cache.js.map +1 -0
- package/dist/mcp/auth-middleware.js +2 -1
- package/dist/mcp/auth-middleware.js.map +1 -1
- package/dist/mcp/playwright-mcp-server-auth.js +2 -1
- package/dist/mcp/playwright-mcp-server-auth.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 +15 -4
- package/scripts/build-agent-image.sh +1 -1
- package/scripts/compare-workflow-performance.sh +556 -0
- package/scripts/cost-allocation-tracker.sh +632 -0
- package/scripts/docker-rebuild-all-agents.sh +2 -2
- package/scripts/migrate-to-optimized-workflows.sh +438 -0
- package/scripts/organize-docs.sh +338 -0
- package/scripts/reorganize-tests.sh +280 -0
- package/scripts/trigger-dev-setup.sh +279 -0
- package/tests/README.md +45 -0
- package/.claude/commands/cost-savings-status.md +0 -34
- package/.claude/commands/metrics-summary.md +0 -58
- 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/cfn-dev-team/dev-ops/monitoring-specialist.md +0 -759
- package/claude-assets/agents/custom/test-mcp-access.md +0 -24
- package/claude-assets/agents/typescript-specialist.md +0 -280
- package/claude-assets/commands/cost-savings-status.md +0 -34
- package/claude-assets/commands/metrics-summary.md +0 -58
- 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/tests/test-memory-leak-task-mode.sh +0 -435
- /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,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Variable Contract Resolver
|
|
3
|
+
*
|
|
4
|
+
* Provides single source of truth for environment variables with mode-specific overrides.
|
|
5
|
+
* Implements Phase 3 of CLI/Trigger.dev collision mitigation strategy.
|
|
6
|
+
*
|
|
7
|
+
* Reference: planning/trigger/CLI_TRIGGER_COLLISION_ANALYSIS.md (Phase 3)
|
|
8
|
+
* Contract: docker/runtime/cfn-runtime.contract.yml
|
|
9
|
+
*
|
|
10
|
+
* Variable Resolution Order (First Set Wins):
|
|
11
|
+
* 1. Mode-specific overrides (if specified in contract)
|
|
12
|
+
* 2. Environment variable with CFN_ prefix (standard)
|
|
13
|
+
* 3. Legacy environment variable (with deprecation warning)
|
|
14
|
+
* 4. Default value from contract
|
|
15
|
+
* 5. Error if no value found and required=true
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* import { getEnvValue } from './environment-contract';
|
|
19
|
+
*
|
|
20
|
+
* // CLI mode - uses mcp-network
|
|
21
|
+
* const cliRedisHost = getEnvValue('redis_host', 'cli');
|
|
22
|
+
*
|
|
23
|
+
* // Trigger.dev mode - uses trigger-cfn-network
|
|
24
|
+
* const triggerRedisHost = getEnvValue('redis_host', 'trigger');
|
|
25
|
+
*
|
|
26
|
+
* // Environment override takes precedence
|
|
27
|
+
* process.env.CFN_REDIS_HOST = 'custom-redis';
|
|
28
|
+
* const overriddenHost = getEnvValue('redis_host', 'cli'); // Returns 'custom-redis'
|
|
29
|
+
*/ import * as fs from 'fs';
|
|
30
|
+
import * as path from 'path';
|
|
31
|
+
import { fileURLToPath } from 'url';
|
|
32
|
+
import * as yaml from 'js-yaml';
|
|
33
|
+
// ESM-compatible __dirname
|
|
34
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
35
|
+
const __dirname = path.dirname(__filename);
|
|
36
|
+
/**
|
|
37
|
+
* Loaded contract cache (lazy-loaded on first use)
|
|
38
|
+
*/ let contractCache = null;
|
|
39
|
+
/**
|
|
40
|
+
* Clears the contract cache (for testing purposes)
|
|
41
|
+
* @internal
|
|
42
|
+
*/ export function _clearContractCache() {
|
|
43
|
+
contractCache = null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Loads the environment variable contract from YAML file
|
|
47
|
+
* Cached after first load for performance
|
|
48
|
+
*
|
|
49
|
+
* @returns Contract specification mapping
|
|
50
|
+
* @throws Error if contract file not found or invalid YAML
|
|
51
|
+
*/ function loadContract() {
|
|
52
|
+
if (contractCache) {
|
|
53
|
+
return contractCache;
|
|
54
|
+
}
|
|
55
|
+
const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, '../../');
|
|
56
|
+
const contractPath = path.resolve(projectRoot, 'docker/runtime/cfn-runtime.contract.yml');
|
|
57
|
+
if (!fs.existsSync(contractPath)) {
|
|
58
|
+
throw new Error(`Environment contract not found at ${contractPath}. ` + `Ensure docker/runtime/cfn-runtime.contract.yml exists.`);
|
|
59
|
+
}
|
|
60
|
+
const contractYaml = fs.readFileSync(contractPath, 'utf8');
|
|
61
|
+
const contractData = yaml.load(contractYaml);
|
|
62
|
+
// Flatten nested contract structure (e.g., redis.CFN_REDIS_HOST becomes redis_host)
|
|
63
|
+
contractCache = flattenContract(contractData);
|
|
64
|
+
return contractCache;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Flattens nested contract structure into flat key mapping
|
|
68
|
+
* Maps environment variable names (CFN_REDIS_HOST) to their specs
|
|
69
|
+
*
|
|
70
|
+
* @param nested - Nested contract from YAML
|
|
71
|
+
* @returns Flattened mapping with both simple keys and spec metadata
|
|
72
|
+
*/ function flattenContract(nested) {
|
|
73
|
+
const flattened = {};
|
|
74
|
+
// Process each top-level category (redis, agent, task, etc.)
|
|
75
|
+
for (const [category, variables] of Object.entries(nested)){
|
|
76
|
+
// Skip metadata fields
|
|
77
|
+
if (category === 'version' || category === 'last_updated') {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (typeof variables !== 'object' || variables === null) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Process each variable in category
|
|
84
|
+
for (const [envVarName, spec] of Object.entries(variables)){
|
|
85
|
+
if (typeof spec === 'object' && spec !== null) {
|
|
86
|
+
// Create a simple key from env var name (e.g., CFN_REDIS_HOST -> redis_host)
|
|
87
|
+
const simpleKey = envVarName.replace(/^CFN_/, '').toLowerCase().replace(/_/g, '_');
|
|
88
|
+
const specWithCfnName = {
|
|
89
|
+
...spec,
|
|
90
|
+
_cfnVarName: envVarName
|
|
91
|
+
};
|
|
92
|
+
flattened[simpleKey] = specWithCfnName;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return flattened;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Gets environment value with mode-specific override support
|
|
100
|
+
*
|
|
101
|
+
* Resolution order:
|
|
102
|
+
* 1. Mode-specific override from contract (if defined)
|
|
103
|
+
* 2. CFN_-prefixed environment variable
|
|
104
|
+
* 3. Legacy environment variable (with warning)
|
|
105
|
+
* 4. Default from contract
|
|
106
|
+
* 5. Error if required and no value found
|
|
107
|
+
*
|
|
108
|
+
* @param key - Contract key (e.g., 'redis_host')
|
|
109
|
+
* @param mode - Execution mode ('cli' or 'trigger')
|
|
110
|
+
* @returns Resolved environment variable value as string
|
|
111
|
+
* @throws Error if key not found in contract or required value missing
|
|
112
|
+
*/ export function getEnvValue(key, mode) {
|
|
113
|
+
const contract = loadContract();
|
|
114
|
+
const spec = contract[key];
|
|
115
|
+
if (!spec) {
|
|
116
|
+
throw new Error(`Unknown contract key: '${key}'. ` + `Available keys: ${Object.keys(contract).filter((k)=>!k.startsWith('_')).join(', ')}`);
|
|
117
|
+
}
|
|
118
|
+
// Get the CFN variable name for this spec
|
|
119
|
+
const cfnVarName = spec._cfnVarName || 'CFN_VAR';
|
|
120
|
+
// Step 1: Check CFN_ prefixed environment variable (highest priority explicit env var)
|
|
121
|
+
if (process.env[cfnVarName]) {
|
|
122
|
+
return process.env[cfnVarName];
|
|
123
|
+
}
|
|
124
|
+
// Step 2: Check legacy environment variables
|
|
125
|
+
if (spec.legacy_aliases && spec.legacy_aliases.length > 0) {
|
|
126
|
+
for (const legacy of spec.legacy_aliases){
|
|
127
|
+
if (process.env[legacy]) {
|
|
128
|
+
console.warn(`[ENV DEPRECATION] Using legacy environment variable '${legacy}', ` + `migrate to '${cfnVarName}' (see docker/runtime/cfn-runtime.contract.yml)`);
|
|
129
|
+
return process.env[legacy];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Step 3: Use mode-specific override if no explicit env var was set
|
|
134
|
+
if (spec.modes?.[mode]?.override !== undefined) {
|
|
135
|
+
return String(spec.modes[mode].override);
|
|
136
|
+
}
|
|
137
|
+
// Step 4: Use default value
|
|
138
|
+
if (spec.default !== null && spec.default !== undefined) {
|
|
139
|
+
return String(spec.default);
|
|
140
|
+
}
|
|
141
|
+
// Step 5: Error if required
|
|
142
|
+
if (spec.required) {
|
|
143
|
+
throw new Error(`Required environment variable '${cfnVarName}' not set. ` + `See docker/runtime/cfn-runtime.contract.yml for configuration.`);
|
|
144
|
+
}
|
|
145
|
+
// No value found and not required - return empty string
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Gets mode-specific network name from contract
|
|
150
|
+
*
|
|
151
|
+
* @param mode - Execution mode ('cli' or 'trigger')
|
|
152
|
+
* @returns Network name for the mode
|
|
153
|
+
*/ export function getNetworkName(mode) {
|
|
154
|
+
const contract = loadContract();
|
|
155
|
+
const spec = contract['network_name'];
|
|
156
|
+
if (!spec) {
|
|
157
|
+
// Fallback to default if not in contract
|
|
158
|
+
return mode === 'cli' ? 'mcp-network' : 'trigger-cfn-network';
|
|
159
|
+
}
|
|
160
|
+
return getEnvValue('network_name', mode);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Gets all environment variables for a specific mode
|
|
164
|
+
* Useful for Docker environment setup
|
|
165
|
+
*
|
|
166
|
+
* @param mode - Execution mode ('cli' or 'trigger')
|
|
167
|
+
* @returns Object with resolved environment variables
|
|
168
|
+
*/ export function getAllEnvValues(mode) {
|
|
169
|
+
const contract = loadContract();
|
|
170
|
+
const envVars = {};
|
|
171
|
+
for (const [key, spec] of Object.entries(contract)){
|
|
172
|
+
if (spec && typeof spec === 'object' && 'description' in spec) {
|
|
173
|
+
try {
|
|
174
|
+
const value = getEnvValue(key, mode);
|
|
175
|
+
if (value) {
|
|
176
|
+
envVars[key] = value;
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// Skip variables that can't be resolved (optional)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return envVars;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Validates an environment variable against contract rules
|
|
187
|
+
*
|
|
188
|
+
* @param key - Contract key
|
|
189
|
+
* @param value - Value to validate
|
|
190
|
+
* @returns Validation result with optional error message
|
|
191
|
+
*/ export function validateEnvValue(key, value) {
|
|
192
|
+
const contract = loadContract();
|
|
193
|
+
const spec = contract[key];
|
|
194
|
+
if (!spec) {
|
|
195
|
+
return {
|
|
196
|
+
valid: false,
|
|
197
|
+
error: `Unknown contract key: '${key}'`
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const rules = spec.validation;
|
|
201
|
+
if (!rules) {
|
|
202
|
+
return {
|
|
203
|
+
valid: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Pattern validation
|
|
207
|
+
if (rules.pattern) {
|
|
208
|
+
const regex = new RegExp(rules.pattern);
|
|
209
|
+
if (!regex.test(value)) {
|
|
210
|
+
return {
|
|
211
|
+
valid: false,
|
|
212
|
+
error: `Value '${value}' does not match pattern '${rules.pattern}'`
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Numeric validations
|
|
217
|
+
if (spec.type === 'integer' || spec.type === 'float') {
|
|
218
|
+
const numValue = spec.type === 'integer' ? parseInt(value, 10) : parseFloat(value);
|
|
219
|
+
if (isNaN(numValue)) {
|
|
220
|
+
return {
|
|
221
|
+
valid: false,
|
|
222
|
+
error: `Value '${value}' is not a valid ${spec.type}`
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (rules.min !== undefined && numValue < rules.min) {
|
|
226
|
+
return {
|
|
227
|
+
valid: false,
|
|
228
|
+
error: `Value ${numValue} is less than minimum ${rules.min}`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (rules.max !== undefined && numValue > rules.max) {
|
|
232
|
+
return {
|
|
233
|
+
valid: false,
|
|
234
|
+
error: `Value ${numValue} is greater than maximum ${rules.max}`
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Allowed values
|
|
239
|
+
if (rules.allowed_values && !rules.allowed_values.includes(value)) {
|
|
240
|
+
return {
|
|
241
|
+
valid: false,
|
|
242
|
+
error: `Value '${value}' is not in allowed values: ${rules.allowed_values.join(', ')}`
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
valid: true
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Exports for barrel import
|
|
251
|
+
*/ export default {
|
|
252
|
+
getEnvValue,
|
|
253
|
+
getNetworkName,
|
|
254
|
+
getAllEnvValues,
|
|
255
|
+
validateEnvValue
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
//# sourceMappingURL=environment-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/environment-contract.ts"],"sourcesContent":["/**\r\n * Environment Variable Contract Resolver\r\n *\r\n * Provides single source of truth for environment variables with mode-specific overrides.\r\n * Implements Phase 3 of CLI/Trigger.dev collision mitigation strategy.\r\n *\r\n * Reference: planning/trigger/CLI_TRIGGER_COLLISION_ANALYSIS.md (Phase 3)\r\n * Contract: docker/runtime/cfn-runtime.contract.yml\r\n *\r\n * Variable Resolution Order (First Set Wins):\r\n * 1. Mode-specific overrides (if specified in contract)\r\n * 2. Environment variable with CFN_ prefix (standard)\r\n * 3. Legacy environment variable (with deprecation warning)\r\n * 4. Default value from contract\r\n * 5. Error if no value found and required=true\r\n *\r\n * Usage:\r\n * import { getEnvValue } from './environment-contract';\r\n *\r\n * // CLI mode - uses mcp-network\r\n * const cliRedisHost = getEnvValue('redis_host', 'cli');\r\n *\r\n * // Trigger.dev mode - uses trigger-cfn-network\r\n * const triggerRedisHost = getEnvValue('redis_host', 'trigger');\r\n *\r\n * // Environment override takes precedence\r\n * process.env.CFN_REDIS_HOST = 'custom-redis';\r\n * const overriddenHost = getEnvValue('redis_host', 'cli'); // Returns 'custom-redis'\r\n */\r\n\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\nimport { fileURLToPath } from 'url';\r\nimport * as yaml from 'js-yaml';\r\n\r\n// ESM-compatible __dirname\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\n/**\r\n * Validation constraints for contract values\r\n */\r\ninterface ValidationRules {\r\n pattern?: string;\r\n min?: number;\r\n max?: number;\r\n allowed_values?: string[];\r\n description?: string;\r\n}\r\n\r\n/**\r\n * Mode-specific overrides in contract\r\n */\r\ninterface ModeOverride {\r\n override?: string | number;\r\n network?: string;\r\n}\r\n\r\n/**\r\n * Contract specification for a single environment variable\r\n */\r\ninterface ContractSpec {\r\n description: string;\r\n default: string | number | null;\r\n type: 'string' | 'integer' | 'boolean' | 'float';\r\n scope: string[];\r\n legacy_aliases?: string[];\r\n required?: boolean;\r\n required_in_production?: boolean;\r\n example?: string;\r\n security_notes?: string;\r\n validation?: ValidationRules;\r\n mode_defaults?: Record<string, string | number>;\r\n modes?: {\r\n cli?: ModeOverride;\r\n trigger?: ModeOverride;\r\n };\r\n}\r\n\r\n/**\r\n * Loaded contract cache (lazy-loaded on first use)\r\n */\r\nlet contractCache: Record<string, ContractSpec & { _cfnVarName?: string }> | null = null;\r\n\r\n/**\r\n * Clears the contract cache (for testing purposes)\r\n * @internal\r\n */\r\nexport function _clearContractCache(): void {\r\n contractCache = null;\r\n}\r\n\r\n/**\r\n * Loads the environment variable contract from YAML file\r\n * Cached after first load for performance\r\n *\r\n * @returns Contract specification mapping\r\n * @throws Error if contract file not found or invalid YAML\r\n */\r\nfunction loadContract(): Record<string, ContractSpec> {\r\n if (contractCache) {\r\n return contractCache;\r\n }\r\n\r\n const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, '../../');\r\n const contractPath = path.resolve(projectRoot, 'docker/runtime/cfn-runtime.contract.yml');\r\n\r\n if (!fs.existsSync(contractPath)) {\r\n throw new Error(\r\n `Environment contract not found at ${contractPath}. ` +\r\n `Ensure docker/runtime/cfn-runtime.contract.yml exists.`\r\n );\r\n }\r\n\r\n const contractYaml = fs.readFileSync(contractPath, 'utf8');\r\n const contractData = yaml.load(contractYaml) as Record<string, any>;\r\n\r\n // Flatten nested contract structure (e.g., redis.CFN_REDIS_HOST becomes redis_host)\r\n contractCache = flattenContract(contractData);\r\n\r\n return contractCache;\r\n}\r\n\r\n/**\r\n * Flattens nested contract structure into flat key mapping\r\n * Maps environment variable names (CFN_REDIS_HOST) to their specs\r\n *\r\n * @param nested - Nested contract from YAML\r\n * @returns Flattened mapping with both simple keys and spec metadata\r\n */\r\nfunction flattenContract(nested: Record<string, any>): Record<string, ContractSpec & { _cfnVarName?: string }> {\r\n const flattened: Record<string, ContractSpec & { _cfnVarName?: string }> = {};\r\n\r\n // Process each top-level category (redis, agent, task, etc.)\r\n for (const [category, variables] of Object.entries(nested)) {\r\n // Skip metadata fields\r\n if (category === 'version' || category === 'last_updated') {\r\n continue;\r\n }\r\n\r\n if (typeof variables !== 'object' || variables === null) {\r\n continue;\r\n }\r\n\r\n // Process each variable in category\r\n for (const [envVarName, spec] of Object.entries(variables)) {\r\n if (typeof spec === 'object' && spec !== null) {\r\n // Create a simple key from env var name (e.g., CFN_REDIS_HOST -> redis_host)\r\n const simpleKey = envVarName\r\n .replace(/^CFN_/, '')\r\n .toLowerCase()\r\n .replace(/_/g, '_');\r\n\r\n const specWithCfnName = {\r\n ...(spec as ContractSpec),\r\n _cfnVarName: envVarName, // Store the CFN variable name for later lookup\r\n };\r\n\r\n flattened[simpleKey] = specWithCfnName;\r\n }\r\n }\r\n }\r\n\r\n return flattened;\r\n}\r\n\r\n/**\r\n * Gets environment value with mode-specific override support\r\n *\r\n * Resolution order:\r\n * 1. Mode-specific override from contract (if defined)\r\n * 2. CFN_-prefixed environment variable\r\n * 3. Legacy environment variable (with warning)\r\n * 4. Default from contract\r\n * 5. Error if required and no value found\r\n *\r\n * @param key - Contract key (e.g., 'redis_host')\r\n * @param mode - Execution mode ('cli' or 'trigger')\r\n * @returns Resolved environment variable value as string\r\n * @throws Error if key not found in contract or required value missing\r\n */\r\nexport function getEnvValue(key: string, mode: 'cli' | 'trigger'): string {\r\n const contract = loadContract();\r\n const spec = contract[key] as (ContractSpec & { _cfnVarName?: string }) | undefined;\r\n\r\n if (!spec) {\r\n throw new Error(\r\n `Unknown contract key: '${key}'. ` +\r\n `Available keys: ${Object.keys(contract).filter(k => !k.startsWith('_')).join(', ')}`\r\n );\r\n }\r\n\r\n // Get the CFN variable name for this spec\r\n const cfnVarName = spec._cfnVarName || 'CFN_VAR';\r\n\r\n // Step 1: Check CFN_ prefixed environment variable (highest priority explicit env var)\r\n if (process.env[cfnVarName]) {\r\n return process.env[cfnVarName];\r\n }\r\n\r\n // Step 2: Check legacy environment variables\r\n if (spec.legacy_aliases && spec.legacy_aliases.length > 0) {\r\n for (const legacy of spec.legacy_aliases) {\r\n if (process.env[legacy]) {\r\n console.warn(\r\n `[ENV DEPRECATION] Using legacy environment variable '${legacy}', ` +\r\n `migrate to '${cfnVarName}' (see docker/runtime/cfn-runtime.contract.yml)`\r\n );\r\n return process.env[legacy];\r\n }\r\n }\r\n }\r\n\r\n // Step 3: Use mode-specific override if no explicit env var was set\r\n if (spec.modes?.[mode]?.override !== undefined) {\r\n return String(spec.modes[mode].override);\r\n }\r\n\r\n // Step 4: Use default value\r\n if (spec.default !== null && spec.default !== undefined) {\r\n return String(spec.default);\r\n }\r\n\r\n // Step 5: Error if required\r\n if (spec.required) {\r\n throw new Error(\r\n `Required environment variable '${cfnVarName}' not set. ` +\r\n `See docker/runtime/cfn-runtime.contract.yml for configuration.`\r\n );\r\n }\r\n\r\n // No value found and not required - return empty string\r\n return '';\r\n}\r\n\r\n/**\r\n * Gets mode-specific network name from contract\r\n *\r\n * @param mode - Execution mode ('cli' or 'trigger')\r\n * @returns Network name for the mode\r\n */\r\nexport function getNetworkName(mode: 'cli' | 'trigger'): string {\r\n const contract = loadContract();\r\n const spec = contract['network_name'];\r\n\r\n if (!spec) {\r\n // Fallback to default if not in contract\r\n return mode === 'cli' ? 'mcp-network' : 'trigger-cfn-network';\r\n }\r\n\r\n return getEnvValue('network_name', mode);\r\n}\r\n\r\n/**\r\n * Gets all environment variables for a specific mode\r\n * Useful for Docker environment setup\r\n *\r\n * @param mode - Execution mode ('cli' or 'trigger')\r\n * @returns Object with resolved environment variables\r\n */\r\nexport function getAllEnvValues(mode: 'cli' | 'trigger'): Record<string, string> {\r\n const contract = loadContract();\r\n const envVars: Record<string, string> = {};\r\n\r\n for (const [key, spec] of Object.entries(contract)) {\r\n if (spec && typeof spec === 'object' && 'description' in spec) {\r\n try {\r\n const value = getEnvValue(key, mode);\r\n if (value) {\r\n envVars[key] = value;\r\n }\r\n } catch {\r\n // Skip variables that can't be resolved (optional)\r\n }\r\n }\r\n }\r\n\r\n return envVars;\r\n}\r\n\r\n/**\r\n * Validates an environment variable against contract rules\r\n *\r\n * @param key - Contract key\r\n * @param value - Value to validate\r\n * @returns Validation result with optional error message\r\n */\r\nexport function validateEnvValue(key: string, value: string): { valid: boolean; error?: string } {\r\n const contract = loadContract();\r\n const spec = contract[key];\r\n\r\n if (!spec) {\r\n return { valid: false, error: `Unknown contract key: '${key}'` };\r\n }\r\n\r\n const rules = spec.validation;\r\n if (!rules) {\r\n return { valid: true };\r\n }\r\n\r\n // Pattern validation\r\n if (rules.pattern) {\r\n const regex = new RegExp(rules.pattern);\r\n if (!regex.test(value)) {\r\n return {\r\n valid: false,\r\n error: `Value '${value}' does not match pattern '${rules.pattern}'`,\r\n };\r\n }\r\n }\r\n\r\n // Numeric validations\r\n if (spec.type === 'integer' || spec.type === 'float') {\r\n const numValue = spec.type === 'integer' ? parseInt(value, 10) : parseFloat(value);\r\n\r\n if (isNaN(numValue)) {\r\n return {\r\n valid: false,\r\n error: `Value '${value}' is not a valid ${spec.type}`,\r\n };\r\n }\r\n\r\n if (rules.min !== undefined && numValue < rules.min) {\r\n return {\r\n valid: false,\r\n error: `Value ${numValue} is less than minimum ${rules.min}`,\r\n };\r\n }\r\n\r\n if (rules.max !== undefined && numValue > rules.max) {\r\n return {\r\n valid: false,\r\n error: `Value ${numValue} is greater than maximum ${rules.max}`,\r\n };\r\n }\r\n }\r\n\r\n // Allowed values\r\n if (rules.allowed_values && !rules.allowed_values.includes(value)) {\r\n return {\r\n valid: false,\r\n error: `Value '${value}' is not in allowed values: ${rules.allowed_values.join(', ')}`,\r\n };\r\n }\r\n\r\n return { valid: true };\r\n}\r\n\r\n\r\n/**\r\n * Exports for barrel import\r\n */\r\nexport default {\r\n getEnvValue,\r\n getNetworkName,\r\n getAllEnvValues,\r\n validateEnvValue,\r\n};\r\n"],"names":["fs","path","fileURLToPath","yaml","__filename","url","__dirname","dirname","contractCache","_clearContractCache","loadContract","projectRoot","process","env","PROJECT_ROOT","resolve","contractPath","existsSync","Error","contractYaml","readFileSync","contractData","load","flattenContract","nested","flattened","category","variables","Object","entries","envVarName","spec","simpleKey","replace","toLowerCase","specWithCfnName","_cfnVarName","getEnvValue","key","mode","contract","keys","filter","k","startsWith","join","cfnVarName","legacy_aliases","length","legacy","console","warn","modes","override","undefined","String","default","required","getNetworkName","getAllEnvValues","envVars","value","validateEnvValue","valid","error","rules","validation","pattern","regex","RegExp","test","type","numValue","parseInt","parseFloat","isNaN","min","max","allowed_values","includes"],"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":"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"}
|