claude-flow-novice 2.15.3 → 2.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
- package/.claude/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
- package/.claude/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
- package/.claude/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
- package/.claude/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
- package/.claude/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
- package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
- package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
- package/.claude/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
- package/.claude/commands/cfn-loop-cli.md +16 -2
- package/.claude/commands/switch-api.md +31 -10
- package/.claude/hooks/cfn-lint-sql-injection.sh +61 -0
- package/.claude/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
- package/.claude/hooks/cfn-pre-edit-security-warning.sh +40 -0
- package/.claude/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
- package/.claude/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
- package/.claude/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
- package/.claude/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
- package/.claude/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
- package/.claude/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
- package/.claude/skills/cfn-loop-orchestration/orchestrate.sh +184 -23
- package/.claude/skills/cfn-loop-orchestration/security_utils.sh +24 -0
- package/.claude/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
- package/.claude/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
- package/.claude/skills/cfn-redis-coordination/agent-log.sh +4 -0
- package/.claude/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
- package/.claude/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
- package/.claude/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
- package/.claude/skills/cfn-redis-coordination/get-context.sh +33 -0
- package/.claude/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
- package/.claude/skills/cfn-redis-coordination/invoke-waiting-mode.sh +3 -0
- package/.claude/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
- package/.claude/skills/cfn-redis-coordination/redis-functions.sh +33 -0
- package/.claude/skills/cfn-redis-coordination/report-completion.sh +24 -31
- package/.claude/skills/cfn-redis-coordination/store-context.sh +4 -0
- package/.claude/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
- package/.claude/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
- package/.claude/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
- package/.claude/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
- package/.claude/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
- package/README.md +116 -475
- package/claude-assets/agents/cfn-dev-team/README.md +103 -0
- package/claude-assets/agents/cfn-dev-team/architecture/goal-planner.md +1 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +77 -15
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +355 -6
- package/claude-assets/agents/cfn-dev-team/coordinators/consensus-builder.md +82 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +82 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/multi-sprint-coordinator.md +77 -15
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +99 -12
- package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +1 -1
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +97 -0
- package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +20 -1
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +97 -0
- package/claude-assets/agents/cfn-dev-team/developers/backend-developer.md +110 -13
- package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +106 -15
- package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +115 -11
- package/claude-assets/agents/cfn-dev-team/developers/frontend/mobile-dev.md +94 -7
- package/claude-assets/agents/cfn-dev-team/developers/frontend/react-frontend-engineer.md +87 -9
- package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +85 -7
- package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +160 -28
- package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +101 -19
- package/claude-assets/agents/cfn-dev-team/developers/rust-developer.md +108 -14
- package/claude-assets/agents/cfn-dev-team/reviewers/{reviewer.md → code-reviewer.md} +95 -8
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/code-quality-validator.md +107 -7
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/perf-analyzer.md +98 -7
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/performance-benchmarker.md +95 -7
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +136 -9
- package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +108 -1
- package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +107 -13
- package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +737 -0
- package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +828 -0
- package/claude-assets/agents/cfn-dev-team/testers/interaction-tester.md +106 -7
- package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +77 -0
- package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +684 -0
- package/claude-assets/agents/cfn-dev-team/testers/playwright-tester.md +110 -1
- package/claude-assets/agents/cfn-dev-team/testers/tester.md +94 -7
- package/claude-assets/agents/cfn-dev-team/utility/code-booster.md +1 -3
- package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +87 -13
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +103 -7
- package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -3
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +94 -7
- package/claude-assets/agents/docker-coordinators/cfn-docker-v3-coordinator.md +46 -0
- package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
- package/claude-assets/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
- package/claude-assets/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
- package/claude-assets/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
- package/claude-assets/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
- package/claude-assets/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
- package/claude-assets/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
- package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
- package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
- package/claude-assets/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
- package/claude-assets/commands/cfn-loop-cli.md +16 -2
- package/claude-assets/commands/switch-api.md +31 -10
- package/claude-assets/hooks/cfn-lint-sql-injection.sh +61 -0
- package/claude-assets/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
- package/claude-assets/hooks/cfn-pre-edit-security-warning.sh +40 -0
- package/claude-assets/hooks/detect-hardcoded-credentials.sh +212 -0
- package/claude-assets/skills/SKILL_TEMPLATE.md +774 -0
- package/claude-assets/skills/agent-lifecycle/execute-lifecycle-hook.sh +84 -113
- package/claude-assets/skills/agent-lifecycle/simple-audit.sh +33 -6
- package/claude-assets/skills/agent-template-generator/SKILL.md +440 -0
- package/claude-assets/skills/agent-template-generator/generate-agent.sh +405 -0
- package/claude-assets/skills/agent-validation-linter/SKILL.md +589 -0
- package/claude-assets/skills/agent-validation-linter/lint-agents.sh +271 -0
- package/claude-assets/skills/bootstrap/bash-fundamentals.md +786 -0
- package/claude-assets/skills/bootstrap/database-connection.md +464 -0
- package/claude-assets/skills/bootstrap/error-handling.md +580 -0
- package/claude-assets/skills/bootstrap/file-operations.md +699 -0
- package/claude-assets/skills/bootstrap/skill-loader.md +616 -0
- package/claude-assets/skills/bootstrap/sqlite-params.sh +287 -0
- package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
- package/claude-assets/skills/cfn-automatic-memory-persistence/test-memory-persistence.sh +17 -16
- package/claude-assets/skills/cfn-deployment/SKILL.md +293 -0
- package/claude-assets/skills/cfn-deployment/execute.sh +21 -0
- package/claude-assets/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
- package/claude-assets/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
- package/claude-assets/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
- package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +38 -0
- package/claude-assets/skills/cfn-error-batching-strategy/lib/core-functions.sh +47 -47
- package/claude-assets/skills/cfn-file-operations/SKILL.md +290 -0
- package/claude-assets/skills/cfn-file-operations/execute.sh +129 -0
- package/claude-assets/skills/cfn-file-operations/lib/atomic-write.sh +294 -0
- package/claude-assets/skills/cfn-file-operations/lib/lock.sh +361 -0
- package/claude-assets/skills/cfn-file-operations/test.sh +369 -0
- package/claude-assets/skills/cfn-log-operations/SKILL.md +308 -0
- package/claude-assets/skills/cfn-log-operations/execute.sh +420 -0
- package/claude-assets/skills/cfn-log-operations/lib/rotate.sh +406 -0
- package/claude-assets/skills/cfn-log-operations/lib/search.sh +448 -0
- package/claude-assets/skills/cfn-log-operations/test.sh +394 -0
- package/claude-assets/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
- package/claude-assets/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh +184 -23
- package/claude-assets/skills/cfn-loop-orchestration/security_utils.sh +24 -0
- package/claude-assets/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
- package/claude-assets/skills/cfn-parameterized-queries/SKILL.md +339 -0
- package/claude-assets/skills/cfn-playbook/query-playbook.sh +19 -15
- package/claude-assets/skills/cfn-playbook/update-playbook.sh +25 -14
- package/claude-assets/skills/cfn-process-instrumentation/instrument-process.sh +44 -0
- package/claude-assets/skills/cfn-promotion/SKILL.md +305 -0
- package/claude-assets/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
- package/claude-assets/skills/cfn-redis-coordination/agent-log.sh +4 -0
- package/claude-assets/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
- package/claude-assets/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
- package/claude-assets/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
- package/claude-assets/skills/cfn-redis-coordination/get-context.sh +33 -0
- package/claude-assets/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
- package/claude-assets/skills/cfn-redis-coordination/invoke-waiting-mode.sh +3 -0
- package/claude-assets/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
- package/claude-assets/skills/cfn-redis-coordination/redis-functions.sh +33 -0
- package/claude-assets/skills/cfn-redis-coordination/report-completion.sh +24 -31
- package/claude-assets/skills/cfn-redis-coordination/store-context.sh +4 -0
- package/claude-assets/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
- package/claude-assets/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
- package/claude-assets/skills/cfn-skill-loader/SKILL.md +466 -0
- package/claude-assets/skills/cfn-skill-loader/execute.sh +344 -0
- package/claude-assets/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
- package/claude-assets/skills/cfn-task-audit/get-audit-data.sh +42 -21
- package/claude-assets/skills/cfn-task-audit/store-task-audit.sh +17 -10
- package/claude-assets/skills/cfn-test-runner/detect-regressions.sh +17 -14
- package/claude-assets/skills/cfn-test-runner/detect-regressions.sh.backup-1763392821 +55 -0
- package/claude-assets/skills/cfn-test-runner/store-benchmarks.sh +17 -19
- package/claude-assets/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
- package/claude-assets/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
- package/claude-assets/skills/cfn-utilities/SKILL.md +237 -0
- package/claude-assets/skills/cfn-utilities/execute.sh +32 -0
- package/claude-assets/skills/cfn-utilities/lib/errors.sh +56 -0
- package/claude-assets/skills/cfn-utilities/lib/file-ops.sh +164 -0
- package/claude-assets/skills/cfn-utilities/lib/logging.sh +77 -0
- package/claude-assets/skills/cfn-utilities/lib/retry.sh +127 -0
- package/claude-assets/skills/cfn-utilities/test.sh +317 -0
- package/claude-assets/skills/integration/agent-handoff.sh +62 -64
- package/claude-assets/skills/json-validation/SKILL.md +431 -0
- package/claude-assets/skills/json-validation/test-validate-success-criteria.sh +421 -0
- package/claude-assets/skills/json-validation/validate-success-criteria.sh +197 -0
- package/claude-assets/skills/redis-coordination/validate-parameters.sh +34 -0
- package/claude-assets/skills/workflow-codification/DEPLOY_QUICK_REFERENCE.md +106 -0
- package/claude-assets/skills/workflow-codification/PROPAGATE_UPDATE_QUICK_REFERENCE.md +366 -0
- package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh +481 -0
- package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh.backup-1763392820 +512 -0
- package/claude-assets/skills/workflow-codification/lib/security-utils.sh +204 -0
- package/claude-assets/skills/workflow-codification/propagate-skill-update.sh +648 -0
- package/claude-assets/skills/workflow-codification/propagate-skill-update.sh.backup-1763392820 +664 -0
- package/claude-assets/skills/workflow-codification/test-integration.sh +15 -0
- package/claude-assets/skills/workflow-codification/test-metadata-update.sh +350 -0
- package/claude-assets/skills/workflow-codification/track-cost-savings.sh +55 -14
- package/claude-assets/skills/workflow-codification/track-cost-savings.sh.backup-1763392821 +445 -0
- package/claude-assets/skills/workflow-codification/track-edge-case.sh +27 -60
- package/claude-assets/skills/workflow-codification/workflow-codification.db +0 -0
- package/dist/ace/ace-curator.js +10 -2
- package/dist/ace/ace-curator.js.map +1 -1
- package/dist/ace/ace-generator.js +4 -0
- package/dist/ace/ace-generator.js.map +1 -1
- package/dist/ace/ace-reflector.js +1 -1
- package/dist/ace/ace-reflector.js.map +1 -1
- package/dist/ace/context-injection.js +24 -2
- package/dist/ace/context-injection.js.map +1 -1
- package/dist/agents/agent-loader.js +146 -165
- package/dist/agents/agent-loader.js.map +1 -1
- package/dist/agents/task-agent-integration.js +1 -1
- package/dist/agents/task-agent-integration.js.map +1 -1
- package/dist/api/health-endpoints.js +390 -0
- package/dist/api/health-endpoints.js.map +1 -0
- package/dist/cli/agent-executor.js +4 -1
- package/dist/cli/agent-executor.js.map +1 -1
- package/dist/cli/agent-prompt-builder.js +89 -1
- package/dist/cli/agent-prompt-builder.js.map +1 -1
- package/dist/cli/agent-spawn.js +130 -37
- package/dist/cli/agent-spawn.js.map +1 -1
- package/dist/cli/skill-cache-validator.js +412 -0
- package/dist/cli/skill-cache-validator.js.map +1 -0
- package/dist/cli/skill-cli.js +991 -0
- package/dist/cli/skill-cli.js.map +1 -0
- package/dist/cli/skill-execution-logger.js +284 -0
- package/dist/cli/skill-execution-logger.js.map +1 -0
- package/dist/cli/skill-loader.js +457 -0
- package/dist/cli/skill-loader.js.map +1 -0
- package/dist/coordination/event-bus.js +2 -2
- package/dist/coordination/event-bus.js.map +1 -1
- package/dist/coordination/fleet-manager.js +1 -1
- package/dist/coordination/fleet-manager.js.map +1 -1
- package/dist/coordination/index.js +23 -9
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/types/fleet-manager.types.js.map +1 -1
- package/dist/db/migration-manager.js +483 -0
- package/dist/db/migration-manager.js.map +1 -0
- package/dist/db/skills-query.js +535 -0
- package/dist/db/skills-query.js.map +1 -0
- package/dist/integration/DatabaseHandoff.js +1 -1
- package/dist/integration/DatabaseHandoff.js.map +1 -1
- package/dist/jobs/edge-case-analyzer.js +367 -0
- package/dist/jobs/edge-case-analyzer.js.map +1 -0
- package/dist/jobs/promotion-sla-enforcer.js +288 -0
- package/dist/jobs/promotion-sla-enforcer.js.map +1 -0
- package/dist/lib/agent-output-parser.js.map +1 -1
- package/dist/lib/agent-output-validator.js.map +1 -1
- package/dist/lib/agent-workspace.js +281 -0
- package/dist/lib/agent-workspace.js.map +1 -0
- package/dist/lib/atomic-file-writer.js +377 -0
- package/dist/lib/atomic-file-writer.js.map +1 -0
- package/dist/lib/backup-manager.js +779 -0
- package/dist/lib/backup-manager.js.map +1 -0
- package/dist/lib/checkpoint-manager.js +837 -0
- package/dist/lib/checkpoint-manager.js.map +1 -0
- package/dist/lib/circuit-breaker.js +340 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/completion-signal-handler.js +243 -0
- package/dist/lib/completion-signal-handler.js.map +1 -0
- package/dist/lib/config-manager.js +312 -0
- package/dist/lib/config-manager.js.map +1 -0
- package/dist/lib/config-migrator.js +386 -0
- package/dist/lib/config-migrator.js.map +1 -0
- package/dist/lib/config-validator.js.map +1 -1
- package/dist/lib/correlation-cache.js +311 -0
- package/dist/lib/correlation-cache.js.map +1 -0
- package/dist/lib/correlation.js +263 -0
- package/dist/lib/correlation.js.map +1 -0
- package/dist/lib/database-service/connection-pool-manager.js +520 -0
- package/dist/lib/database-service/connection-pool-manager.js.map +1 -0
- package/dist/lib/database-service/correlation.js +329 -0
- package/dist/lib/database-service/correlation.js.map +1 -0
- package/dist/lib/database-service/errors.js +120 -0
- package/dist/lib/database-service/errors.js.map +1 -0
- package/dist/lib/database-service/index.js +168 -0
- package/dist/lib/database-service/index.js.map +1 -0
- package/dist/lib/database-service/postgres-adapter.js +526 -0
- package/dist/lib/database-service/postgres-adapter.js.map +1 -0
- package/dist/lib/database-service/redis-adapter.js +360 -0
- package/dist/lib/database-service/redis-adapter.js.map +1 -0
- package/dist/lib/database-service/sqlite-adapter.js +544 -0
- package/dist/lib/database-service/sqlite-adapter.js.map +1 -0
- package/dist/lib/database-service/transaction-manager.js +773 -0
- package/dist/lib/database-service/transaction-manager.js.map +1 -0
- package/dist/lib/database-service/types.js +23 -0
- package/dist/lib/database-service/types.js.map +1 -0
- package/dist/lib/deadlock-resolver.js +292 -0
- package/dist/lib/deadlock-resolver.js.map +1 -0
- package/dist/lib/distributed-lock.js +451 -0
- package/dist/lib/distributed-lock.js.map +1 -0
- package/dist/lib/edge-case-deduplicator.js +227 -0
- package/dist/lib/edge-case-deduplicator.js.map +1 -0
- package/dist/lib/encryption-manager.js +322 -0
- package/dist/lib/encryption-manager.js.map +1 -0
- package/dist/lib/error-aggregator.js +234 -0
- package/dist/lib/error-aggregator.js.map +1 -0
- package/dist/lib/errors.js +287 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/file-lock-manager.js +578 -0
- package/dist/lib/file-lock-manager.js.map +1 -0
- package/dist/lib/file-operations.js +367 -0
- package/dist/lib/file-operations.js.map +1 -0
- package/dist/lib/idempotent-write.js +237 -0
- package/dist/lib/idempotent-write.js.map +1 -0
- package/dist/lib/integration-schema-validator.js +522 -0
- package/dist/lib/integration-schema-validator.js.map +1 -0
- package/dist/lib/lock-health-monitor.js +298 -0
- package/dist/lib/lock-health-monitor.js.map +1 -0
- package/dist/lib/log-shipper.js +422 -0
- package/dist/lib/log-shipper.js.map +1 -0
- package/dist/lib/logging.js +146 -0
- package/dist/lib/logging.js.map +1 -0
- package/dist/lib/message-deduplicator.js +439 -0
- package/dist/lib/message-deduplicator.js.map +1 -0
- package/dist/lib/multi-system-query.js +604 -0
- package/dist/lib/multi-system-query.js.map +1 -0
- package/dist/lib/orphan-detector.js +332 -0
- package/dist/lib/orphan-detector.js.map +1 -0
- package/dist/lib/password-generator.js +166 -0
- package/dist/lib/password-generator.js.map +1 -0
- package/dist/lib/path-validator.js +429 -0
- package/dist/lib/path-validator.js.map +1 -0
- package/dist/lib/query-translator.js +905 -0
- package/dist/lib/query-translator.js.map +1 -0
- package/dist/lib/queue-recovery.js +469 -0
- package/dist/lib/queue-recovery.js.map +1 -0
- package/dist/lib/redis-queue-manager.js +512 -0
- package/dist/lib/redis-queue-manager.js.map +1 -0
- package/dist/lib/reflection-archiver.js +272 -0
- package/dist/lib/reflection-archiver.js.map +1 -0
- package/dist/lib/retry-manager.js +453 -0
- package/dist/lib/retry-manager.js.map +1 -0
- package/dist/lib/retry.js +262 -0
- package/dist/lib/retry.js.map +1 -0
- package/dist/lib/schema-transform.js +695 -0
- package/dist/lib/schema-transform.js.map +1 -0
- package/dist/lib/schema-validator.js +491 -0
- package/dist/lib/schema-validator.js.map +1 -0
- package/dist/lib/skill-cache.js +297 -0
- package/dist/lib/skill-cache.js.map +1 -0
- package/dist/lib/skill-content-manager.js +337 -0
- package/dist/lib/skill-content-manager.js.map +1 -0
- package/dist/lib/skill-frontmatter-parser.js +237 -0
- package/dist/lib/skill-frontmatter-parser.js.map +1 -0
- package/dist/lib/skill-git-integration.js +275 -0
- package/dist/lib/skill-git-integration.js.map +1 -0
- package/dist/lib/skill-markdown-validator.js +396 -0
- package/dist/lib/skill-markdown-validator.js.map +1 -0
- package/dist/lib/skill-output-parser.js +312 -0
- package/dist/lib/skill-output-parser.js.map +1 -0
- package/dist/lib/unified-query-api.js +467 -0
- package/dist/lib/unified-query-api.js.map +1 -0
- package/dist/middleware/auth-middleware.js +350 -0
- package/dist/middleware/auth-middleware.js.map +1 -0
- package/dist/middleware/schema-validation.js +347 -0
- package/dist/middleware/schema-validation.js.map +1 -0
- package/dist/providers/anthropic-provider.js +1 -1
- package/dist/providers/anthropic-provider.js.map +1 -1
- package/dist/providers/provider-factory.js +2 -2
- package/dist/providers/provider-factory.js.map +1 -1
- package/dist/services/edge-case-analyzer.js +321 -0
- package/dist/services/edge-case-analyzer.js.map +1 -0
- package/dist/services/edge-case-deduplicator.js +266 -0
- package/dist/services/edge-case-deduplicator.js.map +1 -0
- package/dist/services/edge-case-detector.js +337 -0
- package/dist/services/edge-case-detector.js.map +1 -0
- package/dist/services/edge-case-tracker.js +547 -0
- package/dist/services/edge-case-tracker.js.map +1 -0
- package/dist/services/health-check-system.js +586 -0
- package/dist/services/health-check-system.js.map +1 -0
- package/dist/services/metrics-logger.js +412 -0
- package/dist/services/metrics-logger.js.map +1 -0
- package/dist/services/patch-generator.js +378 -0
- package/dist/services/patch-generator.js.map +1 -0
- package/dist/services/patch-validator.js +337 -0
- package/dist/services/patch-validator.js.map +1 -0
- package/dist/services/performance-monitor.js +811 -0
- package/dist/services/performance-monitor.js.map +1 -0
- package/dist/services/promotion-pipeline.js +918 -0
- package/dist/services/promotion-pipeline.js.map +1 -0
- package/dist/services/promotion-validator.js +394 -0
- package/dist/services/promotion-validator.js.map +1 -0
- package/dist/services/reflection-logger.js +388 -0
- package/dist/services/reflection-logger.js.map +1 -0
- package/dist/services/skill-deployment.js +472 -0
- package/dist/services/skill-deployment.js.map +1 -0
- package/dist/services/skill-loader.js +427 -0
- package/dist/services/skill-loader.js.map +1 -0
- package/dist/services/skill-promotion.js +372 -0
- package/dist/services/skill-promotion.js.map +1 -0
- package/dist/services/skill-validator.js +454 -0
- package/dist/services/skill-validator.js.map +1 -0
- package/dist/services/skill-versioning.js +244 -0
- package/dist/services/skill-versioning.js.map +1 -0
- package/dist/services/workspace-supervisor.js +597 -0
- package/dist/services/workspace-supervisor.js.map +1 -0
- package/dist/types/edge-case.js +45 -0
- package/dist/types/edge-case.js.map +1 -0
- package/package.json +201 -177
- package/readme/README.md +19 -4
- package/scripts/backup-cleanup.sh +627 -0
- package/scripts/cleanup-workspaces.sh +412 -0
- package/scripts/cleanup-yaml-configs.sh +141 -0
- package/scripts/deploy-approved-skills.sh +263 -0
- package/scripts/health-check.sh +447 -0
- package/scripts/log-aggregator.sh +554 -0
- package/scripts/log-monitor.sh +629 -0
- package/scripts/manage-agent-workspaces.sh +434 -0
- package/scripts/migrate-schema.sh +533 -0
- package/scripts/promote-staged-skills.sh +423 -0
- package/scripts/verify-no-secrets.sh +88 -35
- package/.claude/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
- package/.claude/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
- package/.claude/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
- package/.claude/skills/agent-lifecycle/SKILL.md +0 -60
- package/.claude/skills/agent-lifecycle/execute-lifecycle-hook.sh +0 -573
- package/.claude/skills/agent-lifecycle/simple-audit.sh +0 -31
- package/.claude/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
- package/.claude/skills/cfn-loop-orchestration/orchestrate.sh.backup +0 -949
- package/README.md.backup_before_replace +0 -781
- package/claude-assets/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
- package/claude-assets/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
- package/claude-assets/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
- package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh.backup +0 -949
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/multi-system-query.ts"],"sourcesContent":["/**\r\n * Multi-System Query Engine\r\n *\r\n * Provides fluent interface for cross-database queries with priority-ordered execution.\r\n * Part of Task 3.3: Query Correlation Key Layer\r\n *\r\n * @example\r\n * ```typescript\r\n * const query = new MultiSystemQuery(dbService);\r\n * const results = await query\r\n * .forTask('task-001')\r\n * .includingEntities(['agent', 'skill', 'artifact'])\r\n * .fromSystems(['redis', 'sqlite', 'postgres'])\r\n * .withCache(true)\r\n * .execute();\r\n * // Returns: { agents: [...], skills: [...], artifacts: [...] }\r\n * ```\r\n */\r\n\r\nimport { DatabaseService } from './database-service.js';\r\nimport {\r\n CorrelationKey,\r\n DatabaseError,\r\n DatabaseErrorCode,\r\n QueryFilter,\r\n QueryOptions,\r\n} from './database-service/types.js';\r\nimport {\r\n buildCorrelationKey,\r\n buildWildcardPattern,\r\n getEntityTypes,\r\n WildcardPattern,\r\n} from './database-service/correlation.js';\r\nimport { CorrelationCache } from './correlation-cache.js';\r\nimport { createLogger, Logger } from './logging.js';\r\nimport { createDatabaseError } from './database-service/errors.js';\r\nimport {\r\n ErrorAggregator,\r\n createErrorAggregator,\r\n ErrorSeverity,\r\n} from './error-aggregator.js';\r\n\r\n/**\r\n * Database system type\r\n */\r\nexport type DatabaseSystem = 'redis' | 'sqlite' | 'postgres';\r\n\r\n/**\r\n * Query execution priority\r\n */\r\nexport type ExecutionPriority = 'fastest' | 'balanced' | 'comprehensive';\r\n\r\n/**\r\n * Multi-system query result\r\n */\r\nexport interface MultiSystemResult<T = any> {\r\n /** Correlation key used for query */\r\n correlationKey: string;\r\n /** Results from Redis */\r\n redis?: T[];\r\n /** Results from SQLite */\r\n sqlite?: T[];\r\n /** Results from PostgreSQL */\r\n postgres?: T[];\r\n /** Merged and deduplicated results */\r\n merged: T[];\r\n /** Query execution time in milliseconds */\r\n executionTime: number;\r\n /** Timestamp of query execution */\r\n timestamp: Date;\r\n /** Cache hit status */\r\n cacheHit?: boolean;\r\n /** Errors encountered during query */\r\n errors?: DatabaseError[];\r\n}\r\n\r\n/**\r\n * Query builder configuration\r\n */\r\nexport interface QueryBuilderConfig {\r\n /** Database service instance */\r\n dbService: DatabaseService;\r\n /** Correlation cache instance (optional) */\r\n cache?: CorrelationCache;\r\n /** Enable caching (default: false) */\r\n enableCache?: boolean;\r\n /** Default timeout in milliseconds (default: 2000) */\r\n defaultTimeout?: number;\r\n /** Logger instance (optional) */\r\n logger?: Logger;\r\n}\r\n\r\n/**\r\n * Multi-system query builder\r\n *\r\n * Provides fluent interface for building and executing cross-database queries.\r\n */\r\nexport class MultiSystemQuery {\r\n private dbService: DatabaseService;\r\n private cache?: CorrelationCache;\r\n private logger: Logger;\r\n private errorAggregator?: ErrorAggregator;\r\n\r\n // Query configuration\r\n private correlationKey?: CorrelationKey;\r\n private wildcardPattern?: WildcardPattern;\r\n private entities: string[] = [];\r\n private systems: DatabaseSystem[] = ['redis', 'sqlite', 'postgres'];\r\n private priority: ExecutionPriority = 'balanced';\r\n private useCache: boolean = false;\r\n private timeout: number = 2000; // 2 seconds\r\n private filters: QueryFilter[] = [];\r\n private strictMode: boolean = false; // Fail operation if any system fails\r\n\r\n constructor(config: QueryBuilderConfig) {\r\n this.dbService = config.dbService;\r\n this.cache = config.cache;\r\n this.useCache = config.enableCache || false;\r\n this.timeout = config.defaultTimeout || 2000;\r\n this.logger = config.logger || createLogger('multi-system-query');\r\n }\r\n\r\n /**\r\n * Query for specific task\r\n *\r\n * @param taskId - Task ID to query\r\n * @returns Query builder (fluent)\r\n */\r\n forTask(taskId: string): this {\r\n this.correlationKey = {\r\n type: 'task',\r\n id: taskId,\r\n };\r\n return this;\r\n }\r\n\r\n /**\r\n * Query for specific agent\r\n *\r\n * @param agentId - Agent ID to query\r\n * @returns Query builder (fluent)\r\n */\r\n forAgent(agentId: string): this {\r\n this.correlationKey = {\r\n type: 'agent',\r\n id: agentId,\r\n };\r\n return this;\r\n }\r\n\r\n /**\r\n * Query for specific skill\r\n *\r\n * @param skillId - Skill ID to query\r\n * @returns Query builder (fluent)\r\n */\r\n forSkill(skillId: string): this {\r\n this.correlationKey = {\r\n type: 'skill',\r\n id: skillId,\r\n };\r\n return this;\r\n }\r\n\r\n /**\r\n * Query for specific execution\r\n *\r\n * @param executionId - Execution ID to query\r\n * @returns Query builder (fluent)\r\n */\r\n forExecution(executionId: string): this {\r\n this.correlationKey = {\r\n type: 'execution',\r\n id: executionId,\r\n };\r\n return this;\r\n }\r\n\r\n /**\r\n * Query with custom correlation key\r\n *\r\n * @param key - Correlation key\r\n * @returns Query builder (fluent)\r\n */\r\n withKey(key: CorrelationKey): this {\r\n this.correlationKey = key;\r\n return this;\r\n }\r\n\r\n /**\r\n * Query with wildcard pattern\r\n *\r\n * @param pattern - Wildcard pattern\r\n * @returns Query builder (fluent)\r\n */\r\n withPattern(pattern: WildcardPattern): this {\r\n this.wildcardPattern = pattern;\r\n return this;\r\n }\r\n\r\n /**\r\n * Include specific entity types\r\n *\r\n * @param entities - Entity types to include\r\n * @returns Query builder (fluent)\r\n */\r\n includingEntities(entities: string[]): this {\r\n this.entities = entities;\r\n return this;\r\n }\r\n\r\n /**\r\n * Query from specific database systems\r\n *\r\n * @param systems - Database systems to query\r\n * @returns Query builder (fluent)\r\n */\r\n fromSystems(systems: DatabaseSystem[]): this {\r\n this.systems = systems;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set execution priority\r\n *\r\n * @param priority - Execution priority\r\n * @returns Query builder (fluent)\r\n */\r\n withPriority(priority: ExecutionPriority): this {\r\n this.priority = priority;\r\n return this;\r\n }\r\n\r\n /**\r\n * Enable or disable caching\r\n *\r\n * @param enabled - Enable cache\r\n * @returns Query builder (fluent)\r\n */\r\n withCache(enabled: boolean): this {\r\n this.useCache = enabled;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set query timeout\r\n *\r\n * @param timeout - Timeout in milliseconds\r\n * @returns Query builder (fluent)\r\n */\r\n withTimeout(timeout: number): this {\r\n this.timeout = timeout;\r\n return this;\r\n }\r\n\r\n /**\r\n * Add query filter\r\n *\r\n * @param filter - Query filter\r\n * @returns Query builder (fluent)\r\n */\r\n addFilter(filter: QueryFilter): this {\r\n this.filters.push(filter);\r\n return this;\r\n }\r\n\r\n /**\r\n * Set whether to fail on partial errors\r\n *\r\n * @param fail - Fail if any system fails\r\n * @returns Query builder (fluent)\r\n */\r\n failOnPartialError(fail: boolean = true): this {\r\n this.strictMode = fail;\r\n return this;\r\n }\r\n\r\n /**\r\n * Execute multi-system query\r\n *\r\n * @returns Multi-system query results\r\n * @throws {DatabaseError} If all systems fail or critical error occurs\r\n */\r\n async execute<T = any>(): Promise<MultiSystemResult<T>> {\r\n const startTime = Date.now();\r\n\r\n // Validate query configuration\r\n this.validateQuery();\r\n\r\n // Initialize error aggregator with correlation ID\r\n this.errorAggregator = createErrorAggregator();\r\n const correlationId = this.errorAggregator.getCorrelationId();\r\n\r\n this.logger.info('Starting multi-system query', {\r\n correlationId,\r\n systems: this.systems,\r\n priority: this.priority,\r\n });\r\n\r\n // Build cache key\r\n const cacheKey = this.buildCacheKey();\r\n\r\n // Check cache first\r\n if (this.useCache && this.cache) {\r\n const cached = this.cache.get<MultiSystemResult<T>>(cacheKey);\r\n if (cached) {\r\n this.logger.debug('Cache hit', { cacheKey, correlationId });\r\n return {\r\n ...cached,\r\n cacheHit: true,\r\n };\r\n }\r\n }\r\n\r\n // Execute query based on priority\r\n let result: MultiSystemResult<T>;\r\n try {\r\n result = await this.executeByPriority<T>();\r\n } catch (error) {\r\n // Critical error during execution\r\n this.logger.error(\r\n 'Critical error during query execution',\r\n error instanceof Error ? error : undefined,\r\n {\r\n correlationId,\r\n errorMessage: error instanceof Error ? error.message : String(error),\r\n }\r\n );\r\n\r\n throw createDatabaseError(\r\n DatabaseErrorCode.QUERY_FAILED,\r\n 'Multi-system query failed with critical error',\r\n error instanceof Error ? error : undefined,\r\n { correlationId, systems: this.systems }\r\n );\r\n }\r\n\r\n // Check if we should fail based on errors\r\n if (this.errorAggregator.shouldFailOperation(this.systems)) {\r\n const errorReport = this.errorAggregator.createReport();\r\n this.logger.error(\r\n 'Multi-system query failed',\r\n undefined,\r\n {\r\n correlationId,\r\n errorReport,\r\n }\r\n );\r\n\r\n const aggregationResult = this.errorAggregator.getResult(this.systems);\r\n\r\n throw createDatabaseError(\r\n aggregationResult.hasCriticalErrors\r\n ? DatabaseErrorCode.QUERY_FAILED\r\n : DatabaseErrorCode.QUERY_FAILED,\r\n aggregationResult.allSystemsFailed\r\n ? 'All database systems failed'\r\n : 'Critical errors occurred during query execution',\r\n undefined,\r\n {\r\n correlationId,\r\n errorCount: aggregationResult.totalErrors,\r\n failedSystems: Object.keys(aggregationResult.errorsBySystem),\r\n errors: aggregationResult.allErrors.map(e => ({\r\n system: e.system,\r\n code: e.error.code,\r\n message: e.error.message,\r\n severity: e.severity,\r\n })),\r\n }\r\n );\r\n }\r\n\r\n // Check for partial errors\r\n if (this.strictMode && result.errors && result.errors.length > 0) {\r\n const errorReport = this.errorAggregator.createReport();\r\n this.logger.error(\r\n 'Query failed due to partial errors',\r\n undefined,\r\n {\r\n correlationId,\r\n errorCount: result.errors.length,\r\n errorReport,\r\n }\r\n );\r\n\r\n throw createDatabaseError(\r\n DatabaseErrorCode.QUERY_FAILED,\r\n `Query failed with ${result.errors.length} error(s)`,\r\n undefined,\r\n {\r\n correlationId,\r\n errors: result.errors,\r\n }\r\n );\r\n }\r\n\r\n // Merge and deduplicate results\r\n result.merged = this.mergeResults(result);\r\n\r\n // Calculate execution time\r\n result.executionTime = Date.now() - startTime;\r\n result.timestamp = new Date();\r\n result.cacheHit = false;\r\n\r\n // Validate performance requirement (<2s)\r\n if (result.executionTime >= this.timeout) {\r\n this.logger.warn('Query exceeded timeout', {\r\n correlationId,\r\n executionTime: result.executionTime,\r\n timeout: this.timeout,\r\n });\r\n }\r\n\r\n // Log success\r\n this.logger.info('Multi-system query completed', {\r\n correlationId,\r\n executionTime: result.executionTime,\r\n resultCount: result.merged.length,\r\n errorCount: result.errors?.length || 0,\r\n });\r\n\r\n // Store in cache\r\n if (this.useCache && this.cache) {\r\n this.cache.set(cacheKey, result);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Execute query based on priority strategy\r\n */\r\n private async executeByPriority<T>(): Promise<MultiSystemResult<T>> {\r\n const result: MultiSystemResult<T> = {\r\n correlationKey: this.buildCorrelationKeyString(),\r\n merged: [],\r\n executionTime: 0,\r\n timestamp: new Date(),\r\n errors: [],\r\n };\r\n\r\n switch (this.priority) {\r\n case 'fastest':\r\n return this.executeFastest<T>(result);\r\n\r\n case 'balanced':\r\n return this.executeBalanced<T>(result);\r\n\r\n case 'comprehensive':\r\n return this.executeComprehensive<T>(result);\r\n\r\n default:\r\n return this.executeBalanced<T>(result);\r\n }\r\n }\r\n\r\n /**\r\n * Execute fastest strategy (stop on first result)\r\n */\r\n private async executeFastest<T>(result: MultiSystemResult<T>): Promise<MultiSystemResult<T>> {\r\n if (!this.errorAggregator) {\r\n throw new Error('Error aggregator not initialized');\r\n }\r\n\r\n // Query in priority order: Redis → SQLite → PostgreSQL\r\n const orderedSystems = this.getOrderedSystems();\r\n\r\n for (const system of orderedSystems) {\r\n // Check circuit breaker\r\n if (this.errorAggregator.isCircuitOpen(system)) {\r\n this.logger.warn(`Circuit breaker open for ${system}, skipping`, {\r\n correlationId: this.errorAggregator.getCorrelationId(),\r\n });\r\n const error = this.createError(system, new Error('Circuit breaker open'));\r\n result.errors?.push(error);\r\n this.errorAggregator.addError(system, error, { strategy: 'fastest' });\r\n continue;\r\n }\r\n\r\n try {\r\n const data = await this.querySystem<T>(system);\r\n this.errorAggregator.recordSuccess(system);\r\n\r\n if (data && data.length > 0) {\r\n result[system] = data;\r\n return result; // Stop on first non-empty result\r\n }\r\n } catch (error) {\r\n const dbError = this.createError(system, error);\r\n this.logger.warn(`Query failed for ${system}`, {\r\n error: dbError,\r\n correlationId: this.errorAggregator.getCorrelationId(),\r\n });\r\n result.errors?.push(dbError);\r\n this.errorAggregator.addError(system, dbError, { strategy: 'fastest' });\r\n }\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Execute balanced strategy (priority-ordered parallel)\r\n */\r\n private async executeBalanced<T>(result: MultiSystemResult<T>): Promise<MultiSystemResult<T>> {\r\n if (!this.errorAggregator) {\r\n throw new Error('Error aggregator not initialized');\r\n }\r\n\r\n const orderedSystems = this.getOrderedSystems();\r\n const promises = orderedSystems.map(async (system) => {\r\n // Check circuit breaker\r\n if (this.errorAggregator!.isCircuitOpen(system)) {\r\n this.logger.warn(`Circuit breaker open for ${system}, skipping`, {\r\n correlationId: this.errorAggregator!.getCorrelationId(),\r\n });\r\n const error = this.createError(system, new Error('Circuit breaker open'));\r\n result.errors?.push(error);\r\n this.errorAggregator!.addError(system, error, { strategy: 'balanced' });\r\n return;\r\n }\r\n\r\n try {\r\n const data = await this.querySystem<T>(system);\r\n this.errorAggregator!.recordSuccess(system);\r\n\r\n if (data && data.length > 0) {\r\n result[system] = data;\r\n }\r\n } catch (error) {\r\n const dbError = this.createError(system, error);\r\n this.logger.warn(`Query failed for ${system}`, {\r\n error: dbError,\r\n correlationId: this.errorAggregator!.getCorrelationId(),\r\n });\r\n result.errors?.push(dbError);\r\n this.errorAggregator!.addError(system, dbError, { strategy: 'balanced' });\r\n }\r\n });\r\n\r\n await Promise.all(promises);\r\n return result;\r\n }\r\n\r\n /**\r\n * Execute comprehensive strategy (all systems in parallel)\r\n */\r\n private async executeComprehensive<T>(result: MultiSystemResult<T>): Promise<MultiSystemResult<T>> {\r\n if (!this.errorAggregator) {\r\n throw new Error('Error aggregator not initialized');\r\n }\r\n\r\n const promises = this.systems.map(async (system) => {\r\n // Check circuit breaker\r\n if (this.errorAggregator!.isCircuitOpen(system)) {\r\n this.logger.warn(`Circuit breaker open for ${system}, skipping`, {\r\n correlationId: this.errorAggregator!.getCorrelationId(),\r\n });\r\n const error = this.createError(system, new Error('Circuit breaker open'));\r\n result.errors?.push(error);\r\n this.errorAggregator!.addError(system, error, { strategy: 'comprehensive' });\r\n result[system] = [];\r\n return;\r\n }\r\n\r\n try {\r\n const data = await this.querySystem<T>(system);\r\n this.errorAggregator!.recordSuccess(system);\r\n result[system] = data || [];\r\n } catch (error) {\r\n const dbError = this.createError(system, error);\r\n this.logger.warn(`Query failed for ${system}`, {\r\n error: dbError,\r\n correlationId: this.errorAggregator!.getCorrelationId(),\r\n });\r\n result.errors?.push(dbError);\r\n this.errorAggregator!.addError(system, dbError, { strategy: 'comprehensive' });\r\n result[system] = [];\r\n }\r\n });\r\n\r\n await Promise.all(promises);\r\n return result;\r\n }\r\n\r\n /**\r\n * Query specific database system\r\n */\r\n private async querySystem<T>(system: DatabaseSystem): Promise<T[]> {\r\n const adapter = this.dbService.getAdapter(system);\r\n const results: T[] = [];\r\n\r\n // If wildcard pattern, use pattern matching\r\n if (this.wildcardPattern) {\r\n const pattern = buildWildcardPattern(this.wildcardPattern);\r\n // For Redis, use SCAN with pattern\r\n if (system === 'redis') {\r\n const keys = await this.scanRedisKeys(pattern);\r\n for (const key of keys) {\r\n const data = await adapter.get<T>(key);\r\n if (data) results.push(data);\r\n }\r\n } else {\r\n // For SQLite/PostgreSQL, use raw query with LIKE\r\n const data = await adapter.raw<T>(\r\n 'SELECT * FROM correlation_data WHERE key LIKE ?',\r\n [pattern.replace('*', '%')]\r\n );\r\n results.push(...(Array.isArray(data) ? data : []));\r\n }\r\n } else if (this.correlationKey) {\r\n // Query specific entities for correlation key\r\n if (this.entities.length > 0) {\r\n for (const entity of this.entities) {\r\n const key = buildCorrelationKey({\r\n ...this.correlationKey,\r\n entity,\r\n });\r\n const data = await adapter.get<T>(key);\r\n if (data) results.push(data);\r\n }\r\n } else {\r\n // Query just the correlation key\r\n const key = buildCorrelationKey(this.correlationKey);\r\n const data = await adapter.get<T>(key);\r\n if (data) results.push(data);\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Scan Redis keys with pattern\r\n */\r\n private async scanRedisKeys(pattern: string): Promise<string[]> {\r\n const adapter = this.dbService.getAdapter('redis');\r\n const keys: string[] = [];\r\n\r\n // Use SCAN command for efficient key iteration\r\n let cursor = '0';\r\n do {\r\n const result = await adapter.raw<any>('SCAN', [cursor, 'MATCH', pattern, 'COUNT', '100']);\r\n cursor = result[0];\r\n keys.push(...result[1]);\r\n } while (cursor !== '0');\r\n\r\n return keys;\r\n }\r\n\r\n /**\r\n * Merge results from multiple databases\r\n */\r\n private mergeResults<T>(result: MultiSystemResult<T>): T[] {\r\n const allResults: T[] = [];\r\n const seen = new Set<string>();\r\n\r\n // Merge in priority order: Redis → SQLite → PostgreSQL\r\n const orderedSystems = this.getOrderedSystems();\r\n\r\n for (const system of orderedSystems) {\r\n const systemResults = result[system];\r\n if (!systemResults) continue;\r\n\r\n for (const item of systemResults) {\r\n const key = this.getDeduplicationKey(item);\r\n if (!seen.has(key)) {\r\n seen.add(key);\r\n allResults.push(item);\r\n }\r\n }\r\n }\r\n\r\n return allResults;\r\n }\r\n\r\n /**\r\n * Get deduplication key for result item\r\n */\r\n private getDeduplicationKey(item: any): string {\r\n if (item && typeof item === 'object') {\r\n // Try common ID fields\r\n if (item.id) return `id:${item.id}`;\r\n if (item._id) return `_id:${item._id}`;\r\n if (item.key) return `key:${item.key}`;\r\n // Fallback to JSON stringify\r\n return JSON.stringify(item);\r\n }\r\n return String(item);\r\n }\r\n\r\n /**\r\n * Get ordered systems based on priority\r\n */\r\n private getOrderedSystems(): DatabaseSystem[] {\r\n // Priority order: Redis (fastest) → SQLite → PostgreSQL\r\n const order: DatabaseSystem[] = ['redis', 'sqlite', 'postgres'];\r\n return order.filter(system => this.systems.includes(system));\r\n }\r\n\r\n /**\r\n * Validate query configuration\r\n */\r\n private validateQuery(): void {\r\n if (!this.correlationKey && !this.wildcardPattern) {\r\n throw createDatabaseError(\r\n DatabaseErrorCode.VALIDATION_FAILED,\r\n 'Query must specify either correlationKey or wildcardPattern',\r\n undefined,\r\n { query: this }\r\n );\r\n }\r\n\r\n if (this.systems.length === 0) {\r\n throw createDatabaseError(\r\n DatabaseErrorCode.VALIDATION_FAILED,\r\n 'Query must specify at least one database system',\r\n undefined,\r\n { query: this }\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Build cache key for query\r\n */\r\n private buildCacheKey(): string {\r\n const parts: string[] = ['msq']; // multi-system-query\r\n\r\n if (this.correlationKey) {\r\n parts.push(buildCorrelationKey(this.correlationKey));\r\n } else if (this.wildcardPattern) {\r\n parts.push(buildWildcardPattern(this.wildcardPattern));\r\n }\r\n\r\n if (this.entities.length > 0) {\r\n parts.push(`entities:${this.entities.join(',')}`);\r\n }\r\n\r\n parts.push(`systems:${this.systems.join(',')}`);\r\n parts.push(`priority:${this.priority}`);\r\n\r\n return parts.join(':');\r\n }\r\n\r\n /**\r\n * Build correlation key string\r\n */\r\n private buildCorrelationKeyString(): string {\r\n if (this.correlationKey) {\r\n return buildCorrelationKey(this.correlationKey);\r\n } else if (this.wildcardPattern) {\r\n return buildWildcardPattern(this.wildcardPattern);\r\n }\r\n return 'unknown';\r\n }\r\n\r\n /**\r\n * Create database error\r\n */\r\n private createError(system: DatabaseSystem, error: any): DatabaseError {\r\n return {\r\n code: DatabaseErrorCode.QUERY_FAILED,\r\n message: `Query failed for ${system}: ${error.message || error}`,\r\n originalError: error instanceof Error ? error : undefined,\r\n context: { system },\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Create multi-system query builder\r\n *\r\n * @param config - Query builder configuration\r\n * @returns Multi-system query builder instance\r\n */\r\nexport function createMultiSystemQuery(config: QueryBuilderConfig): MultiSystemQuery {\r\n return new MultiSystemQuery(config);\r\n}\r\n"],"names":["DatabaseErrorCode","buildCorrelationKey","buildWildcardPattern","createLogger","createDatabaseError","createErrorAggregator","MultiSystemQuery","dbService","cache","logger","errorAggregator","correlationKey","wildcardPattern","entities","systems","priority","useCache","timeout","filters","strictMode","config","enableCache","defaultTimeout","forTask","taskId","type","id","forAgent","agentId","forSkill","skillId","forExecution","executionId","withKey","key","withPattern","pattern","includingEntities","fromSystems","withPriority","withCache","enabled","withTimeout","addFilter","filter","push","failOnPartialError","fail","execute","startTime","Date","now","validateQuery","correlationId","getCorrelationId","info","cacheKey","buildCacheKey","cached","get","debug","cacheHit","result","executeByPriority","error","Error","undefined","errorMessage","message","String","QUERY_FAILED","shouldFailOperation","errorReport","createReport","aggregationResult","getResult","hasCriticalErrors","allSystemsFailed","errorCount","totalErrors","failedSystems","Object","keys","errorsBySystem","errors","allErrors","map","e","system","code","severity","length","merged","mergeResults","executionTime","timestamp","warn","resultCount","set","buildCorrelationKeyString","executeFastest","executeBalanced","executeComprehensive","orderedSystems","getOrderedSystems","isCircuitOpen","createError","addError","strategy","data","querySystem","recordSuccess","dbError","promises","Promise","all","adapter","getAdapter","results","scanRedisKeys","raw","replace","Array","isArray","entity","cursor","allResults","seen","Set","systemResults","item","getDeduplicationKey","has","add","_id","JSON","stringify","order","includes","VALIDATION_FAILED","query","parts","join","originalError","context","createMultiSystemQuery"],"mappings":"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"}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orphan Workspace Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects and cleans up orphaned workspaces (no active agent process).
|
|
5
|
+
* Implements grace period logic to prevent premature cleanup during restarts.
|
|
6
|
+
*
|
|
7
|
+
* Part of Task P2-1.3: Supervised Workspace Cleanup (Phase 2)
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Detect orphaned workspaces (no active process)
|
|
11
|
+
* - Process monitoring (PID tracking)
|
|
12
|
+
* - Cleanup stale workspaces
|
|
13
|
+
* - Grace period (10 minutes default before cleanup)
|
|
14
|
+
* - Automatic background scanning
|
|
15
|
+
* - Audit trail for orphan cleanup
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* const detector = new OrphanDetector({
|
|
19
|
+
* workspaceRoot: '/tmp/cfn-workspaces',
|
|
20
|
+
* gracePeriodMinutes: 10
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* const orphans = await detector.detectOrphans();
|
|
24
|
+
* console.log(`Found ${orphans.length} orphaned workspaces`);
|
|
25
|
+
*
|
|
26
|
+
* const stats = await detector.cleanupOrphans();
|
|
27
|
+
* console.log(`Cleaned up ${stats.cleanedCount} workspaces`);
|
|
28
|
+
*/ import * as fs from 'fs/promises';
|
|
29
|
+
import * as path from 'path';
|
|
30
|
+
import { createLogger } from './logging.js';
|
|
31
|
+
import { createError, ErrorCode } from './errors.js';
|
|
32
|
+
const logger = createLogger('orphan-detector');
|
|
33
|
+
/**
|
|
34
|
+
* OrphanDetector: Detects and cleans up orphaned workspaces
|
|
35
|
+
*/ export class OrphanDetector {
|
|
36
|
+
config;
|
|
37
|
+
scanInterval = null;
|
|
38
|
+
constructor(config){
|
|
39
|
+
this.config = {
|
|
40
|
+
gracePeriodMinutes: 10,
|
|
41
|
+
scanIntervalMinutes: 30,
|
|
42
|
+
...config
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Start background orphan detection scanner
|
|
47
|
+
*/ start() {
|
|
48
|
+
if (this.scanInterval) {
|
|
49
|
+
return; // Already running
|
|
50
|
+
}
|
|
51
|
+
const intervalMs = (this.config.scanIntervalMinutes || 30) * 60 * 1000;
|
|
52
|
+
this.scanInterval = setInterval(async ()=>{
|
|
53
|
+
try {
|
|
54
|
+
const stats = await this.cleanupOrphans();
|
|
55
|
+
if (stats.cleanedCount > 0) {
|
|
56
|
+
logger.info('Background orphan cleanup completed', {
|
|
57
|
+
cleanedCount: stats.cleanedCount,
|
|
58
|
+
totalFreed: stats.totalSizeFreed
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.error('Error in orphan detection scan', {
|
|
63
|
+
error: String(error)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}, intervalMs);
|
|
67
|
+
logger.info('Orphan detector started', {
|
|
68
|
+
gracePeriodMinutes: this.config.gracePeriodMinutes,
|
|
69
|
+
scanIntervalMinutes: this.config.scanIntervalMinutes
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Stop background scanner
|
|
74
|
+
*/ stop() {
|
|
75
|
+
if (this.scanInterval) {
|
|
76
|
+
clearInterval(this.scanInterval);
|
|
77
|
+
this.scanInterval = null;
|
|
78
|
+
logger.info('Orphan detector stopped');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Detect orphaned workspaces
|
|
83
|
+
*/ async detectOrphans() {
|
|
84
|
+
const orphans = [];
|
|
85
|
+
const gracePeriodMs = (this.config.gracePeriodMinutes || 10) * 60 * 1000;
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
try {
|
|
88
|
+
const entries = await fs.readdir(this.config.workspaceRoot, {
|
|
89
|
+
withFileTypes: true
|
|
90
|
+
});
|
|
91
|
+
for (const entry of entries){
|
|
92
|
+
if (!entry.isDirectory()) continue;
|
|
93
|
+
try {
|
|
94
|
+
const workspaceInfo = await this.analyzeWorkspace(entry.path);
|
|
95
|
+
if (!workspaceInfo) continue;
|
|
96
|
+
// Check if process is still active
|
|
97
|
+
const isProcessActive = this.isProcessActive(workspaceInfo.processId);
|
|
98
|
+
if (!isProcessActive) {
|
|
99
|
+
// Check grace period
|
|
100
|
+
const timeSinceLastAccess = now - workspaceInfo.lastAccessedAt.getTime();
|
|
101
|
+
if (timeSinceLastAccess > gracePeriodMs) {
|
|
102
|
+
orphans.push(workspaceInfo);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
logger.warn('Error analyzing workspace', {
|
|
107
|
+
path: entry.path,
|
|
108
|
+
error: String(error)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
logger.info('Orphan detection scan completed', {
|
|
113
|
+
found: orphans.length,
|
|
114
|
+
gracePeriodMs
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.error('Error scanning for orphans', {
|
|
118
|
+
error: String(error)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return orphans;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Clean up detected orphaned workspaces
|
|
125
|
+
*/ async cleanupOrphans() {
|
|
126
|
+
const orphans = await this.detectOrphans();
|
|
127
|
+
let cleanedCount = 0;
|
|
128
|
+
let totalSizeFreed = 0;
|
|
129
|
+
let totalFilesRemoved = 0;
|
|
130
|
+
let gracePeriodCount = 0;
|
|
131
|
+
const gracePeriodMs = (this.config.gracePeriodMinutes || 10) * 60 * 1000;
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
for (const orphan of orphans){
|
|
134
|
+
try {
|
|
135
|
+
// Double-check if in grace period
|
|
136
|
+
const timeSinceAccess = now - orphan.lastAccessedAt.getTime();
|
|
137
|
+
if (timeSinceAccess <= gracePeriodMs) {
|
|
138
|
+
gracePeriodCount++;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// Clean up workspace
|
|
142
|
+
const sizeFreed = await this.removeWorkspace(orphan.path);
|
|
143
|
+
cleanedCount++;
|
|
144
|
+
totalSizeFreed += sizeFreed;
|
|
145
|
+
totalFilesRemoved += orphan.fileCount;
|
|
146
|
+
logger.info('Cleaned orphaned workspace', {
|
|
147
|
+
id: orphan.id,
|
|
148
|
+
agentId: orphan.agentId,
|
|
149
|
+
sizeFreed
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
logger.error('Error cleaning up orphan workspace', {
|
|
153
|
+
id: orphan.id,
|
|
154
|
+
error: String(error)
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
cleanedCount,
|
|
160
|
+
totalSizeFreed,
|
|
161
|
+
filesRemoved: totalFilesRemoved,
|
|
162
|
+
gracePeriodCount
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Force cleanup of specific orphan (skip grace period)
|
|
167
|
+
*/ async forceCleanupOrphan(workspacePath) {
|
|
168
|
+
try {
|
|
169
|
+
const size = await this.removeWorkspace(workspacePath);
|
|
170
|
+
logger.info('Force cleaned orphan workspace', {
|
|
171
|
+
path: workspacePath,
|
|
172
|
+
size
|
|
173
|
+
});
|
|
174
|
+
return size;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
logger.error('Error force cleaning orphan', {
|
|
177
|
+
path: workspacePath,
|
|
178
|
+
error: String(error)
|
|
179
|
+
});
|
|
180
|
+
throw createError(ErrorCode.FILE_WRITE_FAILED, 'Failed to cleanup orphan workspace', {
|
|
181
|
+
cause: String(error)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Private Helper Methods
|
|
187
|
+
// ============================================================================
|
|
188
|
+
/**
|
|
189
|
+
* Analyze workspace to extract metadata
|
|
190
|
+
*/ async analyzeWorkspace(workspacePath) {
|
|
191
|
+
try {
|
|
192
|
+
const stats = await fs.stat(workspacePath);
|
|
193
|
+
const name = path.basename(workspacePath);
|
|
194
|
+
// Parse workspace name: sanitizedAgentId-sanitizedTaskId-uuid
|
|
195
|
+
// UUID format: 8hex-4hex-4hex-4hex-12hex (36 chars total)
|
|
196
|
+
// We need to find where the UUID starts
|
|
197
|
+
const uuidRegex = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
198
|
+
const uuidMatch = name.match(uuidRegex);
|
|
199
|
+
if (!uuidMatch) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const id = uuidMatch[0];
|
|
203
|
+
const prefix = name.substring(0, name.length - id.length - 1); // -1 for the dash before UUID
|
|
204
|
+
// Parse agent and task IDs from prefix
|
|
205
|
+
// Format: sanitizedAgentId-sanitizedTaskId
|
|
206
|
+
// Since both can contain characters, we use a simple heuristic:
|
|
207
|
+
// Find "task" in the prefix and assume that's where taskId starts
|
|
208
|
+
let agentId = '';
|
|
209
|
+
let taskId = '';
|
|
210
|
+
const taskStartIndex = prefix.indexOf('task-');
|
|
211
|
+
if (taskStartIndex > 0) {
|
|
212
|
+
agentId = prefix.substring(0, taskStartIndex - 1); // -1 to remove trailing dash
|
|
213
|
+
taskId = prefix.substring(taskStartIndex);
|
|
214
|
+
} else {
|
|
215
|
+
// Fallback: just use the whole prefix as agentId
|
|
216
|
+
agentId = prefix;
|
|
217
|
+
taskId = 'unknown';
|
|
218
|
+
}
|
|
219
|
+
// Get workspace metadata if it exists
|
|
220
|
+
let processId;
|
|
221
|
+
let lastAccessedAt = stats.mtime;
|
|
222
|
+
try {
|
|
223
|
+
const metadataPath = path.join(workspacePath, '.metadata.json');
|
|
224
|
+
const metadata = await fs.readFile(metadataPath, 'utf-8');
|
|
225
|
+
const parsed = JSON.parse(metadata);
|
|
226
|
+
processId = parsed.processId;
|
|
227
|
+
if (parsed.lastAccessedAt) {
|
|
228
|
+
lastAccessedAt = new Date(parsed.lastAccessedAt);
|
|
229
|
+
}
|
|
230
|
+
} catch (e) {
|
|
231
|
+
// Metadata file doesn't exist - use current mtime
|
|
232
|
+
}
|
|
233
|
+
const size = await this.getDirectorySize(workspacePath);
|
|
234
|
+
const fileCount = await this.countFiles(workspacePath);
|
|
235
|
+
return {
|
|
236
|
+
id,
|
|
237
|
+
agentId,
|
|
238
|
+
taskId,
|
|
239
|
+
path: workspacePath,
|
|
240
|
+
createdAt: stats.birthtime || stats.mtime,
|
|
241
|
+
lastAccessedAt,
|
|
242
|
+
processId,
|
|
243
|
+
sizeBytes: size,
|
|
244
|
+
fileCount
|
|
245
|
+
};
|
|
246
|
+
} catch (error) {
|
|
247
|
+
logger.debug('Error analyzing workspace', {
|
|
248
|
+
path: workspacePath,
|
|
249
|
+
error: String(error)
|
|
250
|
+
});
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Check if process is still active
|
|
256
|
+
*/ isProcessActive(processId) {
|
|
257
|
+
if (!processId) {
|
|
258
|
+
return false; // No process ID = definitely orphaned
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
// Send signal 0 (check if process exists without sending signal)
|
|
262
|
+
// Returns true if process is still running
|
|
263
|
+
process.kill(processId, 0);
|
|
264
|
+
return true;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
// Process does not exist
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Remove workspace directory and return freed size
|
|
272
|
+
*/ async removeWorkspace(workspacePath) {
|
|
273
|
+
const size = await this.getDirectorySize(workspacePath);
|
|
274
|
+
try {
|
|
275
|
+
await fs.rm(workspacePath, {
|
|
276
|
+
recursive: true,
|
|
277
|
+
force: true
|
|
278
|
+
});
|
|
279
|
+
logger.debug('Removed workspace directory', {
|
|
280
|
+
path: workspacePath
|
|
281
|
+
});
|
|
282
|
+
return size;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
logger.error('Error removing workspace', {
|
|
285
|
+
path: workspacePath,
|
|
286
|
+
error: String(error)
|
|
287
|
+
});
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get directory size in bytes
|
|
293
|
+
*/ async getDirectorySize(dir) {
|
|
294
|
+
try {
|
|
295
|
+
const entries = await fs.readdir(dir, {
|
|
296
|
+
recursive: true,
|
|
297
|
+
withFileTypes: false
|
|
298
|
+
});
|
|
299
|
+
let totalSize = 0;
|
|
300
|
+
for (const file of entries){
|
|
301
|
+
try {
|
|
302
|
+
const filePath = path.join(dir, file);
|
|
303
|
+
const stats = await fs.stat(filePath).catch(()=>null);
|
|
304
|
+
if (stats?.isFile()) {
|
|
305
|
+
totalSize += stats.size;
|
|
306
|
+
}
|
|
307
|
+
} catch (e) {
|
|
308
|
+
// Ignore inaccessible files
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return totalSize;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Count files in directory
|
|
318
|
+
*/ async countFiles(dir) {
|
|
319
|
+
try {
|
|
320
|
+
const entries = await fs.readdir(dir, {
|
|
321
|
+
recursive: true,
|
|
322
|
+
withFileTypes: false
|
|
323
|
+
});
|
|
324
|
+
return Array.isArray(entries) ? entries.length : 0;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
return 0;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
export default OrphanDetector;
|
|
331
|
+
|
|
332
|
+
//# sourceMappingURL=orphan-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/orphan-detector.ts"],"sourcesContent":["/**\r\n * Orphan Workspace Detector\r\n *\r\n * Detects and cleans up orphaned workspaces (no active agent process).\r\n * Implements grace period logic to prevent premature cleanup during restarts.\r\n *\r\n * Part of Task P2-1.3: Supervised Workspace Cleanup (Phase 2)\r\n *\r\n * Features:\r\n * - Detect orphaned workspaces (no active process)\r\n * - Process monitoring (PID tracking)\r\n * - Cleanup stale workspaces\r\n * - Grace period (10 minutes default before cleanup)\r\n * - Automatic background scanning\r\n * - Audit trail for orphan cleanup\r\n *\r\n * Usage:\r\n * const detector = new OrphanDetector({\r\n * workspaceRoot: '/tmp/cfn-workspaces',\r\n * gracePeriodMinutes: 10\r\n * });\r\n *\r\n * const orphans = await detector.detectOrphans();\r\n * console.log(`Found ${orphans.length} orphaned workspaces`);\r\n *\r\n * const stats = await detector.cleanupOrphans();\r\n * console.log(`Cleaned up ${stats.cleanedCount} workspaces`);\r\n */\r\n\r\nimport * as fs from 'fs/promises';\r\nimport * as path from 'path';\r\nimport { createLogger } from './logging.js';\r\nimport { createError, ErrorCode, StandardError } from './errors.js';\r\n\r\nconst logger = createLogger('orphan-detector');\r\n\r\n/**\r\n * Orphan workspace metadata\r\n */\r\nexport interface OrphanWorkspace {\r\n id: string;\r\n agentId: string;\r\n taskId: string;\r\n path: string;\r\n createdAt: Date;\r\n lastAccessedAt: Date;\r\n processId?: number;\r\n sizeBytes: number;\r\n fileCount: number;\r\n}\r\n\r\n/**\r\n * Cleanup statistics\r\n */\r\nexport interface OrphanCleanupStats {\r\n /** Number of workspaces cleaned */\r\n cleanedCount: number;\r\n /** Total size freed in bytes */\r\n totalSizeFreed: number;\r\n /** Number of files removed */\r\n filesRemoved: number;\r\n /** Number of workspaces still in grace period */\r\n gracePeriodCount: number;\r\n}\r\n\r\n/**\r\n * Detector configuration\r\n */\r\nexport interface OrphanDetectorConfig {\r\n /** Root directory for workspaces */\r\n workspaceRoot: string;\r\n /** Grace period in minutes (default: 10) */\r\n gracePeriodMinutes?: number;\r\n /** Scan interval in minutes (default: 30) */\r\n scanIntervalMinutes?: number;\r\n}\r\n\r\n/**\r\n * OrphanDetector: Detects and cleans up orphaned workspaces\r\n */\r\nexport class OrphanDetector {\r\n private config: OrphanDetectorConfig;\r\n private scanInterval: NodeJS.Timer | null = null;\r\n\r\n constructor(config: OrphanDetectorConfig) {\r\n this.config = {\r\n gracePeriodMinutes: 10,\r\n scanIntervalMinutes: 30,\r\n ...config,\r\n };\r\n }\r\n\r\n /**\r\n * Start background orphan detection scanner\r\n */\r\n start(): void {\r\n if (this.scanInterval) {\r\n return; // Already running\r\n }\r\n\r\n const intervalMs = (this.config.scanIntervalMinutes || 30) * 60 * 1000;\r\n\r\n this.scanInterval = setInterval(async () => {\r\n try {\r\n const stats = await this.cleanupOrphans();\r\n if (stats.cleanedCount > 0) {\r\n logger.info('Background orphan cleanup completed', {\r\n cleanedCount: stats.cleanedCount,\r\n totalFreed: stats.totalSizeFreed,\r\n });\r\n }\r\n } catch (error) {\r\n logger.error('Error in orphan detection scan', { error: String(error) });\r\n }\r\n }, intervalMs);\r\n\r\n logger.info('Orphan detector started', {\r\n gracePeriodMinutes: this.config.gracePeriodMinutes,\r\n scanIntervalMinutes: this.config.scanIntervalMinutes,\r\n });\r\n }\r\n\r\n /**\r\n * Stop background scanner\r\n */\r\n stop(): void {\r\n if (this.scanInterval) {\r\n clearInterval(this.scanInterval);\r\n this.scanInterval = null;\r\n logger.info('Orphan detector stopped');\r\n }\r\n }\r\n\r\n /**\r\n * Detect orphaned workspaces\r\n */\r\n async detectOrphans(): Promise<OrphanWorkspace[]> {\r\n const orphans: OrphanWorkspace[] = [];\r\n const gracePeriodMs = (this.config.gracePeriodMinutes || 10) * 60 * 1000;\r\n const now = Date.now();\r\n\r\n try {\r\n const entries = await fs.readdir(this.config.workspaceRoot, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n if (!entry.isDirectory()) continue;\r\n\r\n try {\r\n const workspaceInfo = await this.analyzeWorkspace(entry.path);\r\n if (!workspaceInfo) continue;\r\n\r\n // Check if process is still active\r\n const isProcessActive = this.isProcessActive(workspaceInfo.processId);\r\n\r\n if (!isProcessActive) {\r\n // Check grace period\r\n const timeSinceLastAccess = now - workspaceInfo.lastAccessedAt.getTime();\r\n\r\n if (timeSinceLastAccess > gracePeriodMs) {\r\n orphans.push(workspaceInfo);\r\n }\r\n }\r\n } catch (error) {\r\n logger.warn('Error analyzing workspace', { path: entry.path, error: String(error) });\r\n }\r\n }\r\n\r\n logger.info('Orphan detection scan completed', {\r\n found: orphans.length,\r\n gracePeriodMs,\r\n });\r\n } catch (error) {\r\n logger.error('Error scanning for orphans', { error: String(error) });\r\n }\r\n\r\n return orphans;\r\n }\r\n\r\n /**\r\n * Clean up detected orphaned workspaces\r\n */\r\n async cleanupOrphans(): Promise<OrphanCleanupStats> {\r\n const orphans = await this.detectOrphans();\r\n let cleanedCount = 0;\r\n let totalSizeFreed = 0;\r\n let totalFilesRemoved = 0;\r\n let gracePeriodCount = 0;\r\n\r\n const gracePeriodMs = (this.config.gracePeriodMinutes || 10) * 60 * 1000;\r\n const now = Date.now();\r\n\r\n for (const orphan of orphans) {\r\n try {\r\n // Double-check if in grace period\r\n const timeSinceAccess = now - orphan.lastAccessedAt.getTime();\r\n if (timeSinceAccess <= gracePeriodMs) {\r\n gracePeriodCount++;\r\n continue;\r\n }\r\n\r\n // Clean up workspace\r\n const sizeFreed = await this.removeWorkspace(orphan.path);\r\n cleanedCount++;\r\n totalSizeFreed += sizeFreed;\r\n totalFilesRemoved += orphan.fileCount;\r\n\r\n logger.info('Cleaned orphaned workspace', {\r\n id: orphan.id,\r\n agentId: orphan.agentId,\r\n sizeFreed,\r\n });\r\n } catch (error) {\r\n logger.error('Error cleaning up orphan workspace', {\r\n id: orphan.id,\r\n error: String(error),\r\n });\r\n }\r\n }\r\n\r\n return {\r\n cleanedCount,\r\n totalSizeFreed,\r\n filesRemoved: totalFilesRemoved,\r\n gracePeriodCount,\r\n };\r\n }\r\n\r\n /**\r\n * Force cleanup of specific orphan (skip grace period)\r\n */\r\n async forceCleanupOrphan(workspacePath: string): Promise<number> {\r\n try {\r\n const size = await this.removeWorkspace(workspacePath);\r\n logger.info('Force cleaned orphan workspace', { path: workspacePath, size });\r\n return size;\r\n } catch (error) {\r\n logger.error('Error force cleaning orphan', { path: workspacePath, error: String(error) });\r\n throw createError(ErrorCode.FILE_WRITE_FAILED, 'Failed to cleanup orphan workspace', {\r\n cause: String(error),\r\n });\r\n }\r\n }\r\n\r\n // ============================================================================\r\n // Private Helper Methods\r\n // ============================================================================\r\n\r\n /**\r\n * Analyze workspace to extract metadata\r\n */\r\n private async analyzeWorkspace(workspacePath: string): Promise<OrphanWorkspace | null> {\r\n try {\r\n const stats = await fs.stat(workspacePath);\r\n const name = path.basename(workspacePath);\r\n\r\n // Parse workspace name: sanitizedAgentId-sanitizedTaskId-uuid\r\n // UUID format: 8hex-4hex-4hex-4hex-12hex (36 chars total)\r\n // We need to find where the UUID starts\r\n const uuidRegex = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;\r\n const uuidMatch = name.match(uuidRegex);\r\n\r\n if (!uuidMatch) {\r\n return null;\r\n }\r\n\r\n const id = uuidMatch[0];\r\n const prefix = name.substring(0, name.length - id.length - 1); // -1 for the dash before UUID\r\n\r\n // Parse agent and task IDs from prefix\r\n // Format: sanitizedAgentId-sanitizedTaskId\r\n // Since both can contain characters, we use a simple heuristic:\r\n // Find \"task\" in the prefix and assume that's where taskId starts\r\n let agentId = '';\r\n let taskId = '';\r\n\r\n const taskStartIndex = prefix.indexOf('task-');\r\n if (taskStartIndex > 0) {\r\n agentId = prefix.substring(0, taskStartIndex - 1); // -1 to remove trailing dash\r\n taskId = prefix.substring(taskStartIndex);\r\n } else {\r\n // Fallback: just use the whole prefix as agentId\r\n agentId = prefix;\r\n taskId = 'unknown';\r\n }\r\n\r\n // Get workspace metadata if it exists\r\n let processId: number | undefined;\r\n let lastAccessedAt = stats.mtime;\r\n\r\n try {\r\n const metadataPath = path.join(workspacePath, '.metadata.json');\r\n const metadata = await fs.readFile(metadataPath, 'utf-8');\r\n const parsed = JSON.parse(metadata);\r\n processId = parsed.processId;\r\n if (parsed.lastAccessedAt) {\r\n lastAccessedAt = new Date(parsed.lastAccessedAt);\r\n }\r\n } catch (e) {\r\n // Metadata file doesn't exist - use current mtime\r\n }\r\n\r\n const size = await this.getDirectorySize(workspacePath);\r\n const fileCount = await this.countFiles(workspacePath);\r\n\r\n return {\r\n id,\r\n agentId,\r\n taskId,\r\n path: workspacePath,\r\n createdAt: stats.birthtime || stats.mtime,\r\n lastAccessedAt,\r\n processId,\r\n sizeBytes: size,\r\n fileCount,\r\n };\r\n } catch (error) {\r\n logger.debug('Error analyzing workspace', { path: workspacePath, error: String(error) });\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Check if process is still active\r\n */\r\n private isProcessActive(processId?: number): boolean {\r\n if (!processId) {\r\n return false; // No process ID = definitely orphaned\r\n }\r\n\r\n try {\r\n // Send signal 0 (check if process exists without sending signal)\r\n // Returns true if process is still running\r\n process.kill(processId, 0);\r\n return true;\r\n } catch (error) {\r\n // Process does not exist\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Remove workspace directory and return freed size\r\n */\r\n private async removeWorkspace(workspacePath: string): Promise<number> {\r\n const size = await this.getDirectorySize(workspacePath);\r\n\r\n try {\r\n await fs.rm(workspacePath, { recursive: true, force: true });\r\n logger.debug('Removed workspace directory', { path: workspacePath });\r\n return size;\r\n } catch (error) {\r\n logger.error('Error removing workspace', { path: workspacePath, error: String(error) });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Get directory size in bytes\r\n */\r\n private async getDirectorySize(dir: string): Promise<number> {\r\n try {\r\n const entries = await fs.readdir(dir, { recursive: true, withFileTypes: false });\r\n let totalSize = 0;\r\n\r\n for (const file of entries as string[]) {\r\n try {\r\n const filePath = path.join(dir, file);\r\n const stats = await fs.stat(filePath).catch(() => null);\r\n if (stats?.isFile()) {\r\n totalSize += stats.size;\r\n }\r\n } catch (e) {\r\n // Ignore inaccessible files\r\n }\r\n }\r\n\r\n return totalSize;\r\n } catch (error) {\r\n return 0;\r\n }\r\n }\r\n\r\n /**\r\n * Count files in directory\r\n */\r\n private async countFiles(dir: string): Promise<number> {\r\n try {\r\n const entries = await fs.readdir(dir, { recursive: true, withFileTypes: false });\r\n return Array.isArray(entries) ? entries.length : 0;\r\n } catch (error) {\r\n return 0;\r\n }\r\n }\r\n}\r\n\r\nexport default OrphanDetector;\r\n"],"names":["fs","path","createLogger","createError","ErrorCode","logger","OrphanDetector","config","scanInterval","gracePeriodMinutes","scanIntervalMinutes","start","intervalMs","setInterval","stats","cleanupOrphans","cleanedCount","info","totalFreed","totalSizeFreed","error","String","stop","clearInterval","detectOrphans","orphans","gracePeriodMs","now","Date","entries","readdir","workspaceRoot","withFileTypes","entry","isDirectory","workspaceInfo","analyzeWorkspace","isProcessActive","processId","timeSinceLastAccess","lastAccessedAt","getTime","push","warn","found","length","totalFilesRemoved","gracePeriodCount","orphan","timeSinceAccess","sizeFreed","removeWorkspace","fileCount","id","agentId","filesRemoved","forceCleanupOrphan","workspacePath","size","FILE_WRITE_FAILED","cause","stat","name","basename","uuidRegex","uuidMatch","match","prefix","substring","taskId","taskStartIndex","indexOf","mtime","metadataPath","join","metadata","readFile","parsed","JSON","parse","e","getDirectorySize","countFiles","createdAt","birthtime","sizeBytes","debug","process","kill","rm","recursive","force","dir","totalSize","file","filePath","catch","isFile","Array","isArray"],"mappings":"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"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure Password Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates cryptographically secure passwords for database authentication
|
|
5
|
+
* with configurable complexity requirements and validation.
|
|
6
|
+
*
|
|
7
|
+
* Security Standards:
|
|
8
|
+
* - Uses crypto.randomBytes() for cryptographic randomness
|
|
9
|
+
* - Default minimum 32 characters for high entropy
|
|
10
|
+
* - Requires mixed character types (uppercase, lowercase, digits, special)
|
|
11
|
+
* - No ambiguous characters (0/O, 1/l, etc.)
|
|
12
|
+
* - Suitable for Redis requirepass and PostgreSQL authentication
|
|
13
|
+
*/ import { randomBytes } from 'crypto';
|
|
14
|
+
/**
|
|
15
|
+
* Default character sets for password generation
|
|
16
|
+
*/ const CHAR_SETS = {
|
|
17
|
+
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
18
|
+
uppercaseNoAmbiguous: 'ABCDEFGHJKMNPQRSTUVWXYZ',
|
|
19
|
+
lowercase: 'abcdefghijklmnopqrstuvwxyz',
|
|
20
|
+
lowercaseNoAmbiguous: 'abcdefghjkmnpqrstuvwxyz',
|
|
21
|
+
digits: '0123456789',
|
|
22
|
+
digitsNoAmbiguous: '23456789',
|
|
23
|
+
special: '!@#$%^&*()_+-=[]{}|;:,.<>?',
|
|
24
|
+
specialSafe: '!@#%^&*_+-='
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Generate a cryptographically secure random password
|
|
28
|
+
*/ export function generatePassword(options = {}) {
|
|
29
|
+
const { length = 32, uppercase = true, lowercase = true, digits = true, special = true, excludeAmbiguous = true } = options;
|
|
30
|
+
if (length < 16) {
|
|
31
|
+
throw new Error('Password length must be at least 16 characters');
|
|
32
|
+
}
|
|
33
|
+
// Build character pool
|
|
34
|
+
let charPool = '';
|
|
35
|
+
const selectedSets = [];
|
|
36
|
+
if (uppercase) {
|
|
37
|
+
const chars = excludeAmbiguous ? CHAR_SETS.uppercaseNoAmbiguous : CHAR_SETS.uppercase;
|
|
38
|
+
charPool += chars;
|
|
39
|
+
selectedSets.push('uppercase');
|
|
40
|
+
}
|
|
41
|
+
if (lowercase) {
|
|
42
|
+
const chars = excludeAmbiguous ? CHAR_SETS.lowercaseNoAmbiguous : CHAR_SETS.lowercase;
|
|
43
|
+
charPool += chars;
|
|
44
|
+
selectedSets.push('lowercase');
|
|
45
|
+
}
|
|
46
|
+
if (digits) {
|
|
47
|
+
const chars = excludeAmbiguous ? CHAR_SETS.digitsNoAmbiguous : CHAR_SETS.digits;
|
|
48
|
+
charPool += chars;
|
|
49
|
+
selectedSets.push('digits');
|
|
50
|
+
}
|
|
51
|
+
if (special) {
|
|
52
|
+
charPool += CHAR_SETS.specialSafe;
|
|
53
|
+
selectedSets.push('special');
|
|
54
|
+
}
|
|
55
|
+
if (charPool.length === 0) {
|
|
56
|
+
throw new Error('At least one character type must be enabled');
|
|
57
|
+
}
|
|
58
|
+
// Ensure password contains at least one character from each required type
|
|
59
|
+
const password = new Array(length);
|
|
60
|
+
let generated = 0;
|
|
61
|
+
// First, place one character from each required set
|
|
62
|
+
for (const setName of selectedSets){
|
|
63
|
+
let setChars;
|
|
64
|
+
switch(setName){
|
|
65
|
+
case 'uppercase':
|
|
66
|
+
setChars = uppercase ? excludeAmbiguous ? CHAR_SETS.uppercaseNoAmbiguous : CHAR_SETS.uppercase : '';
|
|
67
|
+
break;
|
|
68
|
+
case 'lowercase':
|
|
69
|
+
setChars = lowercase ? excludeAmbiguous ? CHAR_SETS.lowercaseNoAmbiguous : CHAR_SETS.lowercase : '';
|
|
70
|
+
break;
|
|
71
|
+
case 'digits':
|
|
72
|
+
setChars = digits ? excludeAmbiguous ? CHAR_SETS.digitsNoAmbiguous : CHAR_SETS.digits : '';
|
|
73
|
+
break;
|
|
74
|
+
case 'special':
|
|
75
|
+
setChars = special ? CHAR_SETS.specialSafe : '';
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
setChars = '';
|
|
79
|
+
}
|
|
80
|
+
if (setChars.length > 0 && generated < length) {
|
|
81
|
+
const index = cryptoRandom(0, setChars.length - 1);
|
|
82
|
+
password[generated] = setChars[index];
|
|
83
|
+
generated++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Fill remaining positions with random characters from pool
|
|
87
|
+
while(generated < length){
|
|
88
|
+
const index = cryptoRandom(0, charPool.length - 1);
|
|
89
|
+
password[generated] = charPool[index];
|
|
90
|
+
generated++;
|
|
91
|
+
}
|
|
92
|
+
// Shuffle password to avoid predictable patterns
|
|
93
|
+
return shuffleArray(password).join('');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Validate password meets security requirements
|
|
97
|
+
*/ export function validatePassword(password, minLength = 32) {
|
|
98
|
+
const errors = [];
|
|
99
|
+
const hasUppercase = /[A-Z]/.test(password);
|
|
100
|
+
const hasLowercase = /[a-z]/.test(password);
|
|
101
|
+
const hasDigits = /\d/.test(password);
|
|
102
|
+
const hasSpecial = /[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(password);
|
|
103
|
+
if (password.length < minLength) {
|
|
104
|
+
errors.push(`Password must be at least ${minLength} characters long (current: ${password.length})`);
|
|
105
|
+
}
|
|
106
|
+
if (!hasUppercase) {
|
|
107
|
+
errors.push('Password must contain at least one uppercase letter');
|
|
108
|
+
}
|
|
109
|
+
if (!hasLowercase) {
|
|
110
|
+
errors.push('Password must contain at least one lowercase letter');
|
|
111
|
+
}
|
|
112
|
+
if (!hasDigits) {
|
|
113
|
+
errors.push('Password must contain at least one digit');
|
|
114
|
+
}
|
|
115
|
+
if (!hasSpecial) {
|
|
116
|
+
errors.push('Password must contain at least one special character');
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
valid: errors.length === 0,
|
|
120
|
+
length: password.length,
|
|
121
|
+
hasUppercase,
|
|
122
|
+
hasLowercase,
|
|
123
|
+
hasDigits,
|
|
124
|
+
hasSpecial,
|
|
125
|
+
errors
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Generate a cryptographically secure random integer in range [min, max]
|
|
130
|
+
*/ function cryptoRandom(min, max) {
|
|
131
|
+
if (min < 0 || max < 0 || min > max) {
|
|
132
|
+
throw new Error('Invalid range: min must be >= 0 and min must be <= max');
|
|
133
|
+
}
|
|
134
|
+
const range = max - min + 1;
|
|
135
|
+
const bytesNeeded = Math.ceil(Math.log2(range) / 8);
|
|
136
|
+
const randomBytes_ = randomBytes(bytesNeeded);
|
|
137
|
+
// Convert bytes to integer and apply modulo to ensure uniform distribution
|
|
138
|
+
let randomValue = 0;
|
|
139
|
+
for(let i = 0; i < bytesNeeded; i++){
|
|
140
|
+
randomValue = randomValue << 8 | randomBytes_[i];
|
|
141
|
+
}
|
|
142
|
+
// Use rejection sampling to ensure uniform distribution
|
|
143
|
+
const limit = Math.floor(256 ** bytesNeeded / range) * range;
|
|
144
|
+
if (randomValue < limit) {
|
|
145
|
+
return min + randomValue % range;
|
|
146
|
+
}
|
|
147
|
+
// Recursively try again if we exceeded the limit (very rare)
|
|
148
|
+
return cryptoRandom(min, max);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Shuffle array in-place using Fisher-Yates algorithm
|
|
152
|
+
*/ function shuffleArray(array) {
|
|
153
|
+
const shuffled = [
|
|
154
|
+
...array
|
|
155
|
+
];
|
|
156
|
+
for(let i = shuffled.length - 1; i > 0; i--){
|
|
157
|
+
const j = cryptoRandom(0, i);
|
|
158
|
+
[shuffled[i], shuffled[j]] = [
|
|
159
|
+
shuffled[j],
|
|
160
|
+
shuffled[i]
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
return shuffled;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=password-generator.js.map
|