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,578 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Lock Manager
|
|
3
|
+
*
|
|
4
|
+
* Centralized file locking system with queuing, renewal, and monitoring.
|
|
5
|
+
* Part of Task 4.2: Centralized File Locking & Atomic Operations
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Lock acquisition with configurable timeout (300s default)
|
|
9
|
+
* - Waiting queue for blocked processes
|
|
10
|
+
* - Lock renewal for long-running operations
|
|
11
|
+
* - Force release capability for stuck locks
|
|
12
|
+
* - Owner tracking (process ID + agent ID)
|
|
13
|
+
* - Stale lock detection and cleanup
|
|
14
|
+
* - Performance monitoring (<100ms acquisition target)
|
|
15
|
+
* - Automatic lock release on process exit
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* const manager = new FileLockManager();
|
|
19
|
+
* const lock = await manager.acquireLock('/path/to/file.txt', {
|
|
20
|
+
* agentId: 'backend-dev-001',
|
|
21
|
+
* timeout: 30000
|
|
22
|
+
* });
|
|
23
|
+
* try {
|
|
24
|
+
* // Perform file operations
|
|
25
|
+
* } finally {
|
|
26
|
+
* await manager.releaseLock(lock.id);
|
|
27
|
+
* }
|
|
28
|
+
*/ import * as fs from 'fs';
|
|
29
|
+
import * as path from 'path';
|
|
30
|
+
import { promisify } from 'util';
|
|
31
|
+
import { randomUUID } from 'crypto';
|
|
32
|
+
import { createLogger } from './logging.js';
|
|
33
|
+
import { createError, ErrorCode, createTimeoutError } from './errors.js';
|
|
34
|
+
const logger = createLogger('file-lock-manager');
|
|
35
|
+
const fsWriteFile = promisify(fs.writeFile);
|
|
36
|
+
const fsReadFile = promisify(fs.readFile);
|
|
37
|
+
const fsUnlink = promisify(fs.unlink);
|
|
38
|
+
const fsStat = promisify(fs.stat);
|
|
39
|
+
const fsMkdir = promisify(fs.mkdir);
|
|
40
|
+
const fsAccess = promisify(fs.access);
|
|
41
|
+
/**
|
|
42
|
+
* Lock directory (defaults to /tmp/cfn-locks/)
|
|
43
|
+
*/ const LOCK_DIR = process.env.CFN_LOCK_DIR || '/tmp/cfn-locks';
|
|
44
|
+
/**
|
|
45
|
+
* File Lock Manager
|
|
46
|
+
*
|
|
47
|
+
* Manages file locks with queuing, renewal, and monitoring capabilities.
|
|
48
|
+
*/ export class FileLockManager {
|
|
49
|
+
activeLocks = new Map();
|
|
50
|
+
lockQueues = new Map();
|
|
51
|
+
metrics = {
|
|
52
|
+
acquisitions: 0,
|
|
53
|
+
releases: 0,
|
|
54
|
+
activeLocks: 0,
|
|
55
|
+
timeouts: 0,
|
|
56
|
+
staleLocksRemoved: 0,
|
|
57
|
+
avgAcquisitionTimeMs: 0,
|
|
58
|
+
totalAcquisitionTimeMs: 0,
|
|
59
|
+
renewals: 0,
|
|
60
|
+
forceReleases: 0
|
|
61
|
+
};
|
|
62
|
+
cleanupInterval = null;
|
|
63
|
+
constructor(){
|
|
64
|
+
this.ensureLockDirectory();
|
|
65
|
+
this.setupCleanupInterval();
|
|
66
|
+
this.setupProcessExitHandlers();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Acquire a file lock
|
|
70
|
+
*
|
|
71
|
+
* @param filePath - Path to file to lock
|
|
72
|
+
* @param options - Lock acquisition options
|
|
73
|
+
* @returns Promise<FileLock> - Lock instance
|
|
74
|
+
* @throws LOCK_TIMEOUT if acquisition times out
|
|
75
|
+
*/ async acquireLock(filePath, options = {}) {
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
const opts = {
|
|
78
|
+
timeout: options.timeout || 300000,
|
|
79
|
+
retryInterval: options.retryInterval || 100,
|
|
80
|
+
agentId: options.agentId,
|
|
81
|
+
waitInQueue: options.waitInQueue !== false,
|
|
82
|
+
staleTimeout: options.staleTimeout || 300000
|
|
83
|
+
};
|
|
84
|
+
const absolutePath = path.resolve(filePath);
|
|
85
|
+
const lockPath = this.getLockPath(absolutePath);
|
|
86
|
+
const lockId = randomUUID();
|
|
87
|
+
logger.debug('Attempting lock acquisition', {
|
|
88
|
+
filePath: absolutePath,
|
|
89
|
+
lockId,
|
|
90
|
+
agentId: opts.agentId
|
|
91
|
+
});
|
|
92
|
+
// Try immediate acquisition
|
|
93
|
+
const immediateLock = await this.tryAcquireLock(absolutePath, lockPath, lockId, opts);
|
|
94
|
+
if (immediateLock) {
|
|
95
|
+
const acquisitionTime = Date.now() - startTime;
|
|
96
|
+
this.recordAcquisition(acquisitionTime);
|
|
97
|
+
logger.info('Lock acquired immediately', {
|
|
98
|
+
filePath: absolutePath,
|
|
99
|
+
lockId,
|
|
100
|
+
acquisitionTimeMs: acquisitionTime
|
|
101
|
+
});
|
|
102
|
+
return immediateLock;
|
|
103
|
+
}
|
|
104
|
+
// If immediate acquisition failed and queuing is enabled, wait in queue
|
|
105
|
+
if (opts.waitInQueue) {
|
|
106
|
+
return this.waitInQueue(absolutePath, lockPath, lockId, opts, startTime);
|
|
107
|
+
}
|
|
108
|
+
throw createTimeoutError(`acquire lock on ${absolutePath}`, 0);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Try to acquire lock immediately
|
|
112
|
+
*/ async tryAcquireLock(filePath, lockPath, lockId, options) {
|
|
113
|
+
try {
|
|
114
|
+
// Check if lock file exists
|
|
115
|
+
const exists = await this.fileExists(lockPath);
|
|
116
|
+
if (exists) {
|
|
117
|
+
// Check if lock is stale
|
|
118
|
+
const isStale = await this.isLockStale(lockPath, options.staleTimeout);
|
|
119
|
+
if (isStale) {
|
|
120
|
+
logger.warn('Removing stale lock', {
|
|
121
|
+
lockPath
|
|
122
|
+
});
|
|
123
|
+
await this.forceReleaseLock(lockPath);
|
|
124
|
+
this.metrics.staleLocksRemoved++;
|
|
125
|
+
} else {
|
|
126
|
+
return null; // Lock is held by another process
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Create lock
|
|
130
|
+
const owner = {
|
|
131
|
+
pid: process.pid,
|
|
132
|
+
agentId: options.agentId,
|
|
133
|
+
hostname: require('os').hostname()
|
|
134
|
+
};
|
|
135
|
+
const expiresAt = new Date(Date.now() + options.timeout);
|
|
136
|
+
const metadata = {
|
|
137
|
+
lockId,
|
|
138
|
+
filePath,
|
|
139
|
+
owner,
|
|
140
|
+
acquiredAt: new Date().toISOString(),
|
|
141
|
+
expiresAt: expiresAt.toISOString(),
|
|
142
|
+
timeoutMs: options.timeout,
|
|
143
|
+
renewalCount: 0
|
|
144
|
+
};
|
|
145
|
+
// Write lock file atomically
|
|
146
|
+
await this.writeLockFile(lockPath, metadata);
|
|
147
|
+
// Verify we won the race
|
|
148
|
+
const verify = await this.readLockFile(lockPath);
|
|
149
|
+
if (verify.lockId !== lockId) {
|
|
150
|
+
logger.debug('Lost lock race', {
|
|
151
|
+
lockId,
|
|
152
|
+
winnerId: verify.lockId
|
|
153
|
+
});
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
// Create and store lock instance
|
|
157
|
+
const lock = {
|
|
158
|
+
id: lockId,
|
|
159
|
+
filePath,
|
|
160
|
+
lockPath,
|
|
161
|
+
owner,
|
|
162
|
+
acquiredAt: new Date(),
|
|
163
|
+
expiresAt,
|
|
164
|
+
timeoutMs: options.timeout,
|
|
165
|
+
renewalCount: 0
|
|
166
|
+
};
|
|
167
|
+
this.activeLocks.set(lockId, lock);
|
|
168
|
+
this.metrics.activeLocks++;
|
|
169
|
+
return lock;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
logger.error('Error during lock acquisition attempt', error instanceof Error ? error : undefined, {
|
|
172
|
+
filePath,
|
|
173
|
+
lockId
|
|
174
|
+
});
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Wait in queue for lock availability
|
|
180
|
+
*/ async waitInQueue(filePath, lockPath, lockId, options, startTime) {
|
|
181
|
+
return new Promise((resolve, reject)=>{
|
|
182
|
+
const queueEntry = {
|
|
183
|
+
id: randomUUID(),
|
|
184
|
+
filePath,
|
|
185
|
+
agentId: options.agentId,
|
|
186
|
+
pid: process.pid,
|
|
187
|
+
queuedAt: new Date(),
|
|
188
|
+
resolve,
|
|
189
|
+
reject
|
|
190
|
+
};
|
|
191
|
+
// Set timeout
|
|
192
|
+
queueEntry.timeoutTimer = setTimeout(()=>{
|
|
193
|
+
this.removeFromQueue(filePath, queueEntry.id);
|
|
194
|
+
this.metrics.timeouts++;
|
|
195
|
+
reject(createTimeoutError(`acquire lock on ${filePath}`, options.timeout));
|
|
196
|
+
}, options.timeout);
|
|
197
|
+
// Add to queue
|
|
198
|
+
if (!this.lockQueues.has(filePath)) {
|
|
199
|
+
this.lockQueues.set(filePath, []);
|
|
200
|
+
}
|
|
201
|
+
this.lockQueues.get(filePath).push(queueEntry);
|
|
202
|
+
logger.debug('Added to lock queue', {
|
|
203
|
+
filePath,
|
|
204
|
+
queuePosition: this.lockQueues.get(filePath).length,
|
|
205
|
+
agentId: options.agentId
|
|
206
|
+
});
|
|
207
|
+
// Start polling for lock availability
|
|
208
|
+
this.pollForLock(filePath, lockPath, lockId, options, queueEntry, startTime);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Poll for lock availability
|
|
213
|
+
*/ async pollForLock(filePath, lockPath, lockId, options, queueEntry, startTime) {
|
|
214
|
+
const pollInterval = setInterval(async ()=>{
|
|
215
|
+
try {
|
|
216
|
+
const lock = await this.tryAcquireLock(filePath, lockPath, lockId, options);
|
|
217
|
+
if (lock) {
|
|
218
|
+
clearInterval(pollInterval);
|
|
219
|
+
if (queueEntry.timeoutTimer) {
|
|
220
|
+
clearTimeout(queueEntry.timeoutTimer);
|
|
221
|
+
}
|
|
222
|
+
this.removeFromQueue(filePath, queueEntry.id);
|
|
223
|
+
const acquisitionTime = Date.now() - startTime;
|
|
224
|
+
this.recordAcquisition(acquisitionTime);
|
|
225
|
+
logger.info('Lock acquired from queue', {
|
|
226
|
+
filePath,
|
|
227
|
+
lockId,
|
|
228
|
+
waitTimeMs: acquisitionTime
|
|
229
|
+
});
|
|
230
|
+
queueEntry.resolve(lock);
|
|
231
|
+
}
|
|
232
|
+
} catch (error) {
|
|
233
|
+
clearInterval(pollInterval);
|
|
234
|
+
if (queueEntry.timeoutTimer) {
|
|
235
|
+
clearTimeout(queueEntry.timeoutTimer);
|
|
236
|
+
}
|
|
237
|
+
this.removeFromQueue(filePath, queueEntry.id);
|
|
238
|
+
queueEntry.reject(error instanceof Error ? error : new Error(`Lock acquisition failed: ${String(error)}`));
|
|
239
|
+
}
|
|
240
|
+
}, options.retryInterval);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Release a lock
|
|
244
|
+
*
|
|
245
|
+
* @param lockId - Lock ID to release
|
|
246
|
+
* @returns Promise that resolves when lock is released
|
|
247
|
+
*/ async releaseLock(lockId) {
|
|
248
|
+
const lock = this.activeLocks.get(lockId);
|
|
249
|
+
if (!lock) {
|
|
250
|
+
logger.warn('Attempted to release non-existent lock', {
|
|
251
|
+
lockId
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
// Remove lock file
|
|
257
|
+
await fsUnlink(lock.lockPath);
|
|
258
|
+
// Remove from active locks
|
|
259
|
+
this.activeLocks.delete(lockId);
|
|
260
|
+
this.metrics.activeLocks--;
|
|
261
|
+
this.metrics.releases++;
|
|
262
|
+
logger.info('Lock released', {
|
|
263
|
+
lockId,
|
|
264
|
+
filePath: lock.filePath,
|
|
265
|
+
heldDurationMs: Date.now() - lock.acquiredAt.getTime()
|
|
266
|
+
});
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.error('Error releasing lock', error instanceof Error ? error : undefined, {
|
|
269
|
+
lockId,
|
|
270
|
+
filePath: lock.filePath
|
|
271
|
+
});
|
|
272
|
+
throw createError(ErrorCode.LOCK_RELEASE_FAILED, `Failed to release lock ${lockId}`, {
|
|
273
|
+
lockId,
|
|
274
|
+
filePath: lock.filePath
|
|
275
|
+
}, error instanceof Error ? error : undefined);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Renew a lock (extend expiration time)
|
|
280
|
+
*
|
|
281
|
+
* @param lockId - Lock ID to renew
|
|
282
|
+
* @param extensionMs - Extension time in milliseconds
|
|
283
|
+
* @returns Promise that resolves when lock is renewed
|
|
284
|
+
*/ async renewLock(lockId, extensionMs = 300000) {
|
|
285
|
+
const lock = this.activeLocks.get(lockId);
|
|
286
|
+
if (!lock) {
|
|
287
|
+
throw createError(ErrorCode.LOCK_NOT_FOUND, `Lock not found: ${lockId}`, {
|
|
288
|
+
lockId
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
// Read current metadata
|
|
293
|
+
const metadata = await this.readLockFile(lock.lockPath);
|
|
294
|
+
// Verify ownership
|
|
295
|
+
if (metadata.lockId !== lockId) {
|
|
296
|
+
throw createError(ErrorCode.LOCK_OWNERSHIP_MISMATCH, 'Lock ownership mismatch during renewal', {
|
|
297
|
+
lockId,
|
|
298
|
+
actualOwnerId: metadata.lockId
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
// Update expiration
|
|
302
|
+
const newExpiresAt = new Date(Date.now() + extensionMs);
|
|
303
|
+
metadata.expiresAt = newExpiresAt.toISOString();
|
|
304
|
+
metadata.lastRenewedAt = new Date().toISOString();
|
|
305
|
+
metadata.renewalCount++;
|
|
306
|
+
// Write updated metadata
|
|
307
|
+
await this.writeLockFile(lock.lockPath, metadata);
|
|
308
|
+
// Update in-memory lock
|
|
309
|
+
lock.expiresAt = newExpiresAt;
|
|
310
|
+
lock.renewalCount = metadata.renewalCount;
|
|
311
|
+
this.metrics.renewals++;
|
|
312
|
+
logger.info('Lock renewed', {
|
|
313
|
+
lockId,
|
|
314
|
+
filePath: lock.filePath,
|
|
315
|
+
newExpiresAt: newExpiresAt.toISOString(),
|
|
316
|
+
renewalCount: metadata.renewalCount
|
|
317
|
+
});
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logger.error('Error renewing lock', error instanceof Error ? error : undefined, {
|
|
320
|
+
lockId,
|
|
321
|
+
filePath: lock.filePath
|
|
322
|
+
});
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Force release a lock (for stuck locks)
|
|
328
|
+
*
|
|
329
|
+
* @param lockPath - Path to lock file
|
|
330
|
+
* @returns Promise that resolves when lock is force-released
|
|
331
|
+
*/ async forceReleaseLock(lockPath) {
|
|
332
|
+
try {
|
|
333
|
+
const exists = await this.fileExists(lockPath);
|
|
334
|
+
if (exists) {
|
|
335
|
+
const metadata = await this.readLockFile(lockPath);
|
|
336
|
+
await fsUnlink(lockPath);
|
|
337
|
+
// Remove from active locks if present
|
|
338
|
+
if (this.activeLocks.has(metadata.lockId)) {
|
|
339
|
+
this.activeLocks.delete(metadata.lockId);
|
|
340
|
+
this.metrics.activeLocks--;
|
|
341
|
+
}
|
|
342
|
+
this.metrics.forceReleases++;
|
|
343
|
+
logger.warn('Lock force-released', {
|
|
344
|
+
lockPath,
|
|
345
|
+
lockId: metadata.lockId,
|
|
346
|
+
owner: metadata.owner
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
logger.error('Error force-releasing lock', error instanceof Error ? error : undefined, {
|
|
351
|
+
lockPath
|
|
352
|
+
});
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Get lock metrics
|
|
358
|
+
*/ getMetrics() {
|
|
359
|
+
return {
|
|
360
|
+
...this.metrics
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Get queue status for a file
|
|
365
|
+
*/ getQueueStatus(filePath) {
|
|
366
|
+
const absolutePath = path.resolve(filePath);
|
|
367
|
+
const queue = this.lockQueues.get(absolutePath);
|
|
368
|
+
if (!queue || queue.length === 0) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
position: 1,
|
|
373
|
+
total: queue.length
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Helper: Get lock file path
|
|
378
|
+
*/ getLockPath(filePath) {
|
|
379
|
+
const hash = this.hashFilePath(filePath);
|
|
380
|
+
return path.join(LOCK_DIR, `${hash}.lock`);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Helper: Hash file path for lock file naming
|
|
384
|
+
*/ hashFilePath(filePath) {
|
|
385
|
+
const crypto = require('crypto');
|
|
386
|
+
return crypto.createHash('sha256').update(filePath).digest('hex').substring(0, 16);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Helper: Check if file exists
|
|
390
|
+
*/ async fileExists(filePath) {
|
|
391
|
+
try {
|
|
392
|
+
await fsAccess(filePath, fs.constants.F_OK);
|
|
393
|
+
return true;
|
|
394
|
+
} catch {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Helper: Check if lock is stale
|
|
400
|
+
*/ async isLockStale(lockPath, staleTimeout) {
|
|
401
|
+
try {
|
|
402
|
+
const metadata = await this.readLockFile(lockPath);
|
|
403
|
+
const expiresAt = new Date(metadata.expiresAt);
|
|
404
|
+
const now = new Date();
|
|
405
|
+
return now > expiresAt;
|
|
406
|
+
} catch {
|
|
407
|
+
// If we can't read the lock file, consider it stale
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Helper: Write lock file
|
|
413
|
+
*/ async writeLockFile(lockPath, metadata) {
|
|
414
|
+
await fsWriteFile(lockPath, JSON.stringify(metadata, null, 2), 'utf8');
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Helper: Read lock file
|
|
418
|
+
*/ async readLockFile(lockPath) {
|
|
419
|
+
const content = await fsReadFile(lockPath, 'utf8');
|
|
420
|
+
return JSON.parse(content);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Helper: Remove entry from queue
|
|
424
|
+
*/ removeFromQueue(filePath, entryId) {
|
|
425
|
+
const queue = this.lockQueues.get(filePath);
|
|
426
|
+
if (queue) {
|
|
427
|
+
const index = queue.findIndex((e)=>e.id === entryId);
|
|
428
|
+
if (index !== -1) {
|
|
429
|
+
queue.splice(index, 1);
|
|
430
|
+
}
|
|
431
|
+
if (queue.length === 0) {
|
|
432
|
+
this.lockQueues.delete(filePath);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Helper: Record acquisition metrics
|
|
438
|
+
*/ recordAcquisition(acquisitionTimeMs) {
|
|
439
|
+
this.metrics.acquisitions++;
|
|
440
|
+
this.metrics.totalAcquisitionTimeMs += acquisitionTimeMs;
|
|
441
|
+
this.metrics.avgAcquisitionTimeMs = this.metrics.totalAcquisitionTimeMs / this.metrics.acquisitions;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Ensure lock directory exists
|
|
445
|
+
*/ ensureLockDirectory() {
|
|
446
|
+
try {
|
|
447
|
+
if (!fs.existsSync(LOCK_DIR)) {
|
|
448
|
+
fs.mkdirSync(LOCK_DIR, {
|
|
449
|
+
recursive: true,
|
|
450
|
+
mode: 0o755
|
|
451
|
+
});
|
|
452
|
+
logger.info('Created lock directory', {
|
|
453
|
+
directory: LOCK_DIR
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
} catch (error) {
|
|
457
|
+
logger.error('Failed to create lock directory', error instanceof Error ? error : undefined, {
|
|
458
|
+
directory: LOCK_DIR
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Setup periodic cleanup of stale locks
|
|
464
|
+
*/ setupCleanupInterval() {
|
|
465
|
+
// Clean up stale locks every 60 seconds
|
|
466
|
+
this.cleanupInterval = setInterval(async ()=>{
|
|
467
|
+
await this.cleanupStaleLocks();
|
|
468
|
+
}, 60000);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Cleanup stale locks
|
|
472
|
+
*/ async cleanupStaleLocks() {
|
|
473
|
+
try {
|
|
474
|
+
const files = fs.readdirSync(LOCK_DIR);
|
|
475
|
+
for (const file of files){
|
|
476
|
+
if (file.endsWith('.lock')) {
|
|
477
|
+
const lockPath = path.join(LOCK_DIR, file);
|
|
478
|
+
const isStale = await this.isLockStale(lockPath, 300000);
|
|
479
|
+
if (isStale) {
|
|
480
|
+
await this.forceReleaseLock(lockPath);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
} catch (error) {
|
|
485
|
+
logger.error('Error during stale lock cleanup', error instanceof Error ? error : undefined);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Setup process exit handlers to release locks
|
|
490
|
+
*/ setupProcessExitHandlers() {
|
|
491
|
+
const cleanup = async ()=>{
|
|
492
|
+
logger.info('Process exiting, releasing all locks', {
|
|
493
|
+
activeLocksCount: this.activeLocks.size
|
|
494
|
+
});
|
|
495
|
+
for (const [lockId, lock] of this.activeLocks.entries()){
|
|
496
|
+
try {
|
|
497
|
+
await this.releaseLock(lockId);
|
|
498
|
+
} catch (error) {
|
|
499
|
+
logger.error('Error releasing lock on exit', error instanceof Error ? error : undefined, {
|
|
500
|
+
lockId,
|
|
501
|
+
filePath: lock.filePath
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (this.cleanupInterval) {
|
|
506
|
+
clearInterval(this.cleanupInterval);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
process.on('exit', ()=>{
|
|
510
|
+
// Synchronous cleanup only
|
|
511
|
+
for (const [, lock] of this.activeLocks.entries()){
|
|
512
|
+
try {
|
|
513
|
+
fs.unlinkSync(lock.lockPath);
|
|
514
|
+
} catch {}
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
process.on('SIGINT', async ()=>{
|
|
518
|
+
await cleanup();
|
|
519
|
+
process.exit(0);
|
|
520
|
+
});
|
|
521
|
+
process.on('SIGTERM', async ()=>{
|
|
522
|
+
await cleanup();
|
|
523
|
+
process.exit(0);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Shutdown the lock manager
|
|
528
|
+
*/ async shutdown() {
|
|
529
|
+
logger.info('Shutting down file lock manager');
|
|
530
|
+
// Clear cleanup interval
|
|
531
|
+
if (this.cleanupInterval) {
|
|
532
|
+
clearInterval(this.cleanupInterval);
|
|
533
|
+
}
|
|
534
|
+
// Release all active locks
|
|
535
|
+
for (const lockId of this.activeLocks.keys()){
|
|
536
|
+
await this.releaseLock(lockId);
|
|
537
|
+
}
|
|
538
|
+
// Clear all queues
|
|
539
|
+
for (const [, queue] of this.lockQueues.entries()){
|
|
540
|
+
for (const entry of queue){
|
|
541
|
+
if (entry.timeoutTimer) {
|
|
542
|
+
clearTimeout(entry.timeoutTimer);
|
|
543
|
+
}
|
|
544
|
+
entry.reject(new Error('Lock manager shutting down'));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
this.lockQueues.clear();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Singleton instance
|
|
552
|
+
*/ let defaultManager = null;
|
|
553
|
+
/**
|
|
554
|
+
* Get the default file lock manager instance
|
|
555
|
+
*/ export function getFileLockManager() {
|
|
556
|
+
if (!defaultManager) {
|
|
557
|
+
defaultManager = new FileLockManager();
|
|
558
|
+
}
|
|
559
|
+
return defaultManager;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Execute a function with file lock
|
|
563
|
+
*
|
|
564
|
+
* @param filePath - File to lock
|
|
565
|
+
* @param fn - Function to execute
|
|
566
|
+
* @param options - Lock options
|
|
567
|
+
* @returns Promise that resolves with function result
|
|
568
|
+
*/ export async function withFileLock(filePath, fn, options = {}) {
|
|
569
|
+
const manager = getFileLockManager();
|
|
570
|
+
const lock = await manager.acquireLock(filePath, options);
|
|
571
|
+
try {
|
|
572
|
+
return await fn();
|
|
573
|
+
} finally{
|
|
574
|
+
await manager.releaseLock(lock.id);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
//# sourceMappingURL=file-lock-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/file-lock-manager.ts"],"sourcesContent":["/**\r\n * File Lock Manager\r\n *\r\n * Centralized file locking system with queuing, renewal, and monitoring.\r\n * Part of Task 4.2: Centralized File Locking & Atomic Operations\r\n *\r\n * Features:\r\n * - Lock acquisition with configurable timeout (300s default)\r\n * - Waiting queue for blocked processes\r\n * - Lock renewal for long-running operations\r\n * - Force release capability for stuck locks\r\n * - Owner tracking (process ID + agent ID)\r\n * - Stale lock detection and cleanup\r\n * - Performance monitoring (<100ms acquisition target)\r\n * - Automatic lock release on process exit\r\n *\r\n * Usage:\r\n * const manager = new FileLockManager();\r\n * const lock = await manager.acquireLock('/path/to/file.txt', {\r\n * agentId: 'backend-dev-001',\r\n * timeout: 30000\r\n * });\r\n * try {\r\n * // Perform file operations\r\n * } finally {\r\n * await manager.releaseLock(lock.id);\r\n * }\r\n */\r\n\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\nimport { promisify } from 'util';\r\nimport { randomUUID } from 'crypto';\r\nimport { createLogger } from './logging.js';\r\nimport { createError, ErrorCode, createTimeoutError } from './errors.js';\r\n\r\nconst logger = createLogger('file-lock-manager');\r\n\r\nconst fsWriteFile = promisify(fs.writeFile);\r\nconst fsReadFile = promisify(fs.readFile);\r\nconst fsUnlink = promisify(fs.unlink);\r\nconst fsStat = promisify(fs.stat);\r\nconst fsMkdir = promisify(fs.mkdir);\r\nconst fsAccess = promisify(fs.access);\r\n\r\n/**\r\n * Lock directory (defaults to /tmp/cfn-locks/)\r\n */\r\nconst LOCK_DIR = process.env.CFN_LOCK_DIR || '/tmp/cfn-locks';\r\n\r\n/**\r\n * Lock owner information\r\n */\r\nexport interface LockOwner {\r\n /** Process ID */\r\n pid: number;\r\n /** Agent ID (optional) */\r\n agentId?: string;\r\n /** Hostname (for distributed systems) */\r\n hostname: string;\r\n}\r\n\r\n/**\r\n * Lock metadata stored in lock file\r\n */\r\nexport interface LockMetadata {\r\n /** Unique lock ID */\r\n lockId: string;\r\n /** File path being locked */\r\n filePath: string;\r\n /** Lock owner information */\r\n owner: LockOwner;\r\n /** Acquisition timestamp */\r\n acquiredAt: string;\r\n /** Expiration timestamp */\r\n expiresAt: string;\r\n /** Timeout in milliseconds */\r\n timeoutMs: number;\r\n /** Last renewal timestamp */\r\n lastRenewedAt?: string;\r\n /** Number of renewals */\r\n renewalCount: number;\r\n}\r\n\r\n/**\r\n * Active lock instance\r\n */\r\nexport interface FileLock {\r\n /** Lock ID */\r\n id: string;\r\n /** File path */\r\n filePath: string;\r\n /** Lock file path */\r\n lockPath: string;\r\n /** Owner information */\r\n owner: LockOwner;\r\n /** Acquisition time */\r\n acquiredAt: Date;\r\n /** Expiration time */\r\n expiresAt: Date;\r\n /** Timeout in milliseconds */\r\n timeoutMs: number;\r\n /** Renewal count */\r\n renewalCount: number;\r\n}\r\n\r\n/**\r\n * Lock acquisition options\r\n */\r\nexport interface LockAcquisitionOptions {\r\n /** Timeout in milliseconds (default: 300000 = 5 minutes) */\r\n timeout?: number;\r\n /** Retry interval in milliseconds (default: 100) */\r\n retryInterval?: number;\r\n /** Agent ID for tracking */\r\n agentId?: string;\r\n /** Wait in queue if lock is held (default: true) */\r\n waitInQueue?: boolean;\r\n /** Stale lock detection timeout in milliseconds (default: 300000) */\r\n staleTimeout?: number;\r\n}\r\n\r\n/**\r\n * Queue entry for waiting processes\r\n */\r\ninterface QueueEntry {\r\n /** Entry ID */\r\n id: string;\r\n /** File path */\r\n filePath: string;\r\n /** Agent ID */\r\n agentId?: string;\r\n /** Process ID */\r\n pid: number;\r\n /** Queued timestamp */\r\n queuedAt: Date;\r\n /** Resolve function */\r\n resolve: (lock: FileLock) => void;\r\n /** Reject function */\r\n reject: (error: Error) => void;\r\n /** Timeout timer */\r\n timeoutTimer?: NodeJS.Timeout;\r\n}\r\n\r\n/**\r\n * Lock acquisition metrics\r\n */\r\nexport interface LockMetrics {\r\n /** Total lock acquisitions */\r\n acquisitions: number;\r\n /** Total lock releases */\r\n releases: number;\r\n /** Current active locks */\r\n activeLocks: number;\r\n /** Total lock timeouts */\r\n timeouts: number;\r\n /** Total stale locks cleaned */\r\n staleLocksRemoved: number;\r\n /** Average acquisition time (ms) */\r\n avgAcquisitionTimeMs: number;\r\n /** Total time spent acquiring locks (ms) */\r\n totalAcquisitionTimeMs: number;\r\n /** Lock renewals */\r\n renewals: number;\r\n /** Force releases */\r\n forceReleases: number;\r\n}\r\n\r\n/**\r\n * File Lock Manager\r\n *\r\n * Manages file locks with queuing, renewal, and monitoring capabilities.\r\n */\r\nexport class FileLockManager {\r\n private activeLocks: Map<string, FileLock> = new Map();\r\n private lockQueues: Map<string, QueueEntry[]> = new Map();\r\n private metrics: LockMetrics = {\r\n acquisitions: 0,\r\n releases: 0,\r\n activeLocks: 0,\r\n timeouts: 0,\r\n staleLocksRemoved: 0,\r\n avgAcquisitionTimeMs: 0,\r\n totalAcquisitionTimeMs: 0,\r\n renewals: 0,\r\n forceReleases: 0,\r\n };\r\n private cleanupInterval: NodeJS.Timeout | null = null;\r\n\r\n constructor() {\r\n this.ensureLockDirectory();\r\n this.setupCleanupInterval();\r\n this.setupProcessExitHandlers();\r\n }\r\n\r\n /**\r\n * Acquire a file lock\r\n *\r\n * @param filePath - Path to file to lock\r\n * @param options - Lock acquisition options\r\n * @returns Promise<FileLock> - Lock instance\r\n * @throws LOCK_TIMEOUT if acquisition times out\r\n */\r\n async acquireLock(\r\n filePath: string,\r\n options: LockAcquisitionOptions = {}\r\n ): Promise<FileLock> {\r\n const startTime = Date.now();\r\n const opts = {\r\n timeout: options.timeout || 300000, // 5 minutes default\r\n retryInterval: options.retryInterval || 100,\r\n agentId: options.agentId,\r\n waitInQueue: options.waitInQueue !== false,\r\n staleTimeout: options.staleTimeout || 300000,\r\n };\r\n\r\n const absolutePath = path.resolve(filePath);\r\n const lockPath = this.getLockPath(absolutePath);\r\n const lockId = randomUUID();\r\n\r\n logger.debug('Attempting lock acquisition', {\r\n filePath: absolutePath,\r\n lockId,\r\n agentId: opts.agentId,\r\n });\r\n\r\n // Try immediate acquisition\r\n const immediateLock = await this.tryAcquireLock(\r\n absolutePath,\r\n lockPath,\r\n lockId,\r\n opts\r\n );\r\n\r\n if (immediateLock) {\r\n const acquisitionTime = Date.now() - startTime;\r\n this.recordAcquisition(acquisitionTime);\r\n logger.info('Lock acquired immediately', {\r\n filePath: absolutePath,\r\n lockId,\r\n acquisitionTimeMs: acquisitionTime,\r\n });\r\n return immediateLock;\r\n }\r\n\r\n // If immediate acquisition failed and queuing is enabled, wait in queue\r\n if (opts.waitInQueue) {\r\n return this.waitInQueue(absolutePath, lockPath, lockId, opts, startTime);\r\n }\r\n\r\n throw createTimeoutError(`acquire lock on ${absolutePath}`, 0);\r\n }\r\n\r\n /**\r\n * Try to acquire lock immediately\r\n */\r\n private async tryAcquireLock(\r\n filePath: string,\r\n lockPath: string,\r\n lockId: string,\r\n options: Required<LockAcquisitionOptions>\r\n ): Promise<FileLock | null> {\r\n try {\r\n // Check if lock file exists\r\n const exists = await this.fileExists(lockPath);\r\n\r\n if (exists) {\r\n // Check if lock is stale\r\n const isStale = await this.isLockStale(lockPath, options.staleTimeout);\r\n\r\n if (isStale) {\r\n logger.warn('Removing stale lock', { lockPath });\r\n await this.forceReleaseLock(lockPath);\r\n this.metrics.staleLocksRemoved++;\r\n } else {\r\n return null; // Lock is held by another process\r\n }\r\n }\r\n\r\n // Create lock\r\n const owner: LockOwner = {\r\n pid: process.pid,\r\n agentId: options.agentId,\r\n hostname: require('os').hostname(),\r\n };\r\n\r\n const expiresAt = new Date(Date.now() + options.timeout);\r\n const metadata: LockMetadata = {\r\n lockId,\r\n filePath,\r\n owner,\r\n acquiredAt: new Date().toISOString(),\r\n expiresAt: expiresAt.toISOString(),\r\n timeoutMs: options.timeout,\r\n renewalCount: 0,\r\n };\r\n\r\n // Write lock file atomically\r\n await this.writeLockFile(lockPath, metadata);\r\n\r\n // Verify we won the race\r\n const verify = await this.readLockFile(lockPath);\r\n if (verify.lockId !== lockId) {\r\n logger.debug('Lost lock race', { lockId, winnerId: verify.lockId });\r\n return null;\r\n }\r\n\r\n // Create and store lock instance\r\n const lock: FileLock = {\r\n id: lockId,\r\n filePath,\r\n lockPath,\r\n owner,\r\n acquiredAt: new Date(),\r\n expiresAt,\r\n timeoutMs: options.timeout,\r\n renewalCount: 0,\r\n };\r\n\r\n this.activeLocks.set(lockId, lock);\r\n this.metrics.activeLocks++;\r\n\r\n return lock;\r\n } catch (error) {\r\n logger.error(\r\n 'Error during lock acquisition attempt',\r\n error instanceof Error ? error : undefined,\r\n { filePath, lockId }\r\n );\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Wait in queue for lock availability\r\n */\r\n private async waitInQueue(\r\n filePath: string,\r\n lockPath: string,\r\n lockId: string,\r\n options: Required<LockAcquisitionOptions>,\r\n startTime: number\r\n ): Promise<FileLock> {\r\n return new Promise((resolve, reject) => {\r\n const queueEntry: QueueEntry = {\r\n id: randomUUID(),\r\n filePath,\r\n agentId: options.agentId,\r\n pid: process.pid,\r\n queuedAt: new Date(),\r\n resolve,\r\n reject,\r\n };\r\n\r\n // Set timeout\r\n queueEntry.timeoutTimer = setTimeout(() => {\r\n this.removeFromQueue(filePath, queueEntry.id);\r\n this.metrics.timeouts++;\r\n reject(createTimeoutError(`acquire lock on ${filePath}`, options.timeout));\r\n }, options.timeout);\r\n\r\n // Add to queue\r\n if (!this.lockQueues.has(filePath)) {\r\n this.lockQueues.set(filePath, []);\r\n }\r\n this.lockQueues.get(filePath)!.push(queueEntry);\r\n\r\n logger.debug('Added to lock queue', {\r\n filePath,\r\n queuePosition: this.lockQueues.get(filePath)!.length,\r\n agentId: options.agentId,\r\n });\r\n\r\n // Start polling for lock availability\r\n this.pollForLock(filePath, lockPath, lockId, options, queueEntry, startTime);\r\n });\r\n }\r\n\r\n /**\r\n * Poll for lock availability\r\n */\r\n private async pollForLock(\r\n filePath: string,\r\n lockPath: string,\r\n lockId: string,\r\n options: Required<LockAcquisitionOptions>,\r\n queueEntry: QueueEntry,\r\n startTime: number\r\n ): Promise<void> {\r\n const pollInterval = setInterval(async () => {\r\n try {\r\n const lock = await this.tryAcquireLock(filePath, lockPath, lockId, options);\r\n\r\n if (lock) {\r\n clearInterval(pollInterval);\r\n if (queueEntry.timeoutTimer) {\r\n clearTimeout(queueEntry.timeoutTimer);\r\n }\r\n this.removeFromQueue(filePath, queueEntry.id);\r\n\r\n const acquisitionTime = Date.now() - startTime;\r\n this.recordAcquisition(acquisitionTime);\r\n\r\n logger.info('Lock acquired from queue', {\r\n filePath,\r\n lockId,\r\n waitTimeMs: acquisitionTime,\r\n });\r\n\r\n queueEntry.resolve(lock);\r\n }\r\n } catch (error) {\r\n clearInterval(pollInterval);\r\n if (queueEntry.timeoutTimer) {\r\n clearTimeout(queueEntry.timeoutTimer);\r\n }\r\n this.removeFromQueue(filePath, queueEntry.id);\r\n queueEntry.reject(\r\n error instanceof Error\r\n ? error\r\n : new Error(`Lock acquisition failed: ${String(error)}`)\r\n );\r\n }\r\n }, options.retryInterval);\r\n }\r\n\r\n /**\r\n * Release a lock\r\n *\r\n * @param lockId - Lock ID to release\r\n * @returns Promise that resolves when lock is released\r\n */\r\n async releaseLock(lockId: string): Promise<void> {\r\n const lock = this.activeLocks.get(lockId);\r\n\r\n if (!lock) {\r\n logger.warn('Attempted to release non-existent lock', { lockId });\r\n return;\r\n }\r\n\r\n try {\r\n // Remove lock file\r\n await fsUnlink(lock.lockPath);\r\n\r\n // Remove from active locks\r\n this.activeLocks.delete(lockId);\r\n this.metrics.activeLocks--;\r\n this.metrics.releases++;\r\n\r\n logger.info('Lock released', {\r\n lockId,\r\n filePath: lock.filePath,\r\n heldDurationMs: Date.now() - lock.acquiredAt.getTime(),\r\n });\r\n } catch (error) {\r\n logger.error(\r\n 'Error releasing lock',\r\n error instanceof Error ? error : undefined,\r\n { lockId, filePath: lock.filePath }\r\n );\r\n throw createError(\r\n ErrorCode.LOCK_RELEASE_FAILED,\r\n `Failed to release lock ${lockId}`,\r\n { lockId, filePath: lock.filePath },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Renew a lock (extend expiration time)\r\n *\r\n * @param lockId - Lock ID to renew\r\n * @param extensionMs - Extension time in milliseconds\r\n * @returns Promise that resolves when lock is renewed\r\n */\r\n async renewLock(lockId: string, extensionMs: number = 300000): Promise<void> {\r\n const lock = this.activeLocks.get(lockId);\r\n\r\n if (!lock) {\r\n throw createError(ErrorCode.LOCK_NOT_FOUND, `Lock not found: ${lockId}`, {\r\n lockId,\r\n });\r\n }\r\n\r\n try {\r\n // Read current metadata\r\n const metadata = await this.readLockFile(lock.lockPath);\r\n\r\n // Verify ownership\r\n if (metadata.lockId !== lockId) {\r\n throw createError(\r\n ErrorCode.LOCK_OWNERSHIP_MISMATCH,\r\n 'Lock ownership mismatch during renewal',\r\n { lockId, actualOwnerId: metadata.lockId }\r\n );\r\n }\r\n\r\n // Update expiration\r\n const newExpiresAt = new Date(Date.now() + extensionMs);\r\n metadata.expiresAt = newExpiresAt.toISOString();\r\n metadata.lastRenewedAt = new Date().toISOString();\r\n metadata.renewalCount++;\r\n\r\n // Write updated metadata\r\n await this.writeLockFile(lock.lockPath, metadata);\r\n\r\n // Update in-memory lock\r\n lock.expiresAt = newExpiresAt;\r\n lock.renewalCount = metadata.renewalCount;\r\n\r\n this.metrics.renewals++;\r\n\r\n logger.info('Lock renewed', {\r\n lockId,\r\n filePath: lock.filePath,\r\n newExpiresAt: newExpiresAt.toISOString(),\r\n renewalCount: metadata.renewalCount,\r\n });\r\n } catch (error) {\r\n logger.error(\r\n 'Error renewing lock',\r\n error instanceof Error ? error : undefined,\r\n { lockId, filePath: lock.filePath }\r\n );\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Force release a lock (for stuck locks)\r\n *\r\n * @param lockPath - Path to lock file\r\n * @returns Promise that resolves when lock is force-released\r\n */\r\n async forceReleaseLock(lockPath: string): Promise<void> {\r\n try {\r\n const exists = await this.fileExists(lockPath);\r\n\r\n if (exists) {\r\n const metadata = await this.readLockFile(lockPath);\r\n await fsUnlink(lockPath);\r\n\r\n // Remove from active locks if present\r\n if (this.activeLocks.has(metadata.lockId)) {\r\n this.activeLocks.delete(metadata.lockId);\r\n this.metrics.activeLocks--;\r\n }\r\n\r\n this.metrics.forceReleases++;\r\n\r\n logger.warn('Lock force-released', {\r\n lockPath,\r\n lockId: metadata.lockId,\r\n owner: metadata.owner,\r\n });\r\n }\r\n } catch (error) {\r\n logger.error(\r\n 'Error force-releasing lock',\r\n error instanceof Error ? error : undefined,\r\n { lockPath }\r\n );\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Get lock metrics\r\n */\r\n getMetrics(): LockMetrics {\r\n return { ...this.metrics };\r\n }\r\n\r\n /**\r\n * Get queue status for a file\r\n */\r\n getQueueStatus(filePath: string): { position: number; total: number } | null {\r\n const absolutePath = path.resolve(filePath);\r\n const queue = this.lockQueues.get(absolutePath);\r\n\r\n if (!queue || queue.length === 0) {\r\n return null;\r\n }\r\n\r\n return {\r\n position: 1, // First in queue\r\n total: queue.length,\r\n };\r\n }\r\n\r\n /**\r\n * Helper: Get lock file path\r\n */\r\n private getLockPath(filePath: string): string {\r\n const hash = this.hashFilePath(filePath);\r\n return path.join(LOCK_DIR, `${hash}.lock`);\r\n }\r\n\r\n /**\r\n * Helper: Hash file path for lock file naming\r\n */\r\n private hashFilePath(filePath: string): string {\r\n const crypto = require('crypto');\r\n return crypto.createHash('sha256').update(filePath).digest('hex').substring(0, 16);\r\n }\r\n\r\n /**\r\n * Helper: Check if file exists\r\n */\r\n private async fileExists(filePath: string): Promise<boolean> {\r\n try {\r\n await fsAccess(filePath, fs.constants.F_OK);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Helper: Check if lock is stale\r\n */\r\n private async isLockStale(lockPath: string, staleTimeout: number): Promise<boolean> {\r\n try {\r\n const metadata = await this.readLockFile(lockPath);\r\n const expiresAt = new Date(metadata.expiresAt);\r\n const now = new Date();\r\n\r\n return now > expiresAt;\r\n } catch {\r\n // If we can't read the lock file, consider it stale\r\n return true;\r\n }\r\n }\r\n\r\n /**\r\n * Helper: Write lock file\r\n */\r\n private async writeLockFile(lockPath: string, metadata: LockMetadata): Promise<void> {\r\n await fsWriteFile(lockPath, JSON.stringify(metadata, null, 2), 'utf8');\r\n }\r\n\r\n /**\r\n * Helper: Read lock file\r\n */\r\n private async readLockFile(lockPath: string): Promise<LockMetadata> {\r\n const content = await fsReadFile(lockPath, 'utf8');\r\n return JSON.parse(content) as LockMetadata;\r\n }\r\n\r\n /**\r\n * Helper: Remove entry from queue\r\n */\r\n private removeFromQueue(filePath: string, entryId: string): void {\r\n const queue = this.lockQueues.get(filePath);\r\n if (queue) {\r\n const index = queue.findIndex((e) => e.id === entryId);\r\n if (index !== -1) {\r\n queue.splice(index, 1);\r\n }\r\n if (queue.length === 0) {\r\n this.lockQueues.delete(filePath);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Helper: Record acquisition metrics\r\n */\r\n private recordAcquisition(acquisitionTimeMs: number): void {\r\n this.metrics.acquisitions++;\r\n this.metrics.totalAcquisitionTimeMs += acquisitionTimeMs;\r\n this.metrics.avgAcquisitionTimeMs =\r\n this.metrics.totalAcquisitionTimeMs / this.metrics.acquisitions;\r\n }\r\n\r\n /**\r\n * Ensure lock directory exists\r\n */\r\n private ensureLockDirectory(): void {\r\n try {\r\n if (!fs.existsSync(LOCK_DIR)) {\r\n fs.mkdirSync(LOCK_DIR, { recursive: true, mode: 0o755 });\r\n logger.info('Created lock directory', { directory: LOCK_DIR });\r\n }\r\n } catch (error) {\r\n logger.error(\r\n 'Failed to create lock directory',\r\n error instanceof Error ? error : undefined,\r\n { directory: LOCK_DIR }\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Setup periodic cleanup of stale locks\r\n */\r\n private setupCleanupInterval(): void {\r\n // Clean up stale locks every 60 seconds\r\n this.cleanupInterval = setInterval(async () => {\r\n await this.cleanupStaleLocks();\r\n }, 60000);\r\n }\r\n\r\n /**\r\n * Cleanup stale locks\r\n */\r\n private async cleanupStaleLocks(): Promise<void> {\r\n try {\r\n const files = fs.readdirSync(LOCK_DIR);\r\n\r\n for (const file of files) {\r\n if (file.endsWith('.lock')) {\r\n const lockPath = path.join(LOCK_DIR, file);\r\n const isStale = await this.isLockStale(lockPath, 300000);\r\n\r\n if (isStale) {\r\n await this.forceReleaseLock(lockPath);\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n logger.error(\r\n 'Error during stale lock cleanup',\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Setup process exit handlers to release locks\r\n */\r\n private setupProcessExitHandlers(): void {\r\n const cleanup = async () => {\r\n logger.info('Process exiting, releasing all locks', {\r\n activeLocksCount: this.activeLocks.size,\r\n });\r\n\r\n for (const [lockId, lock] of this.activeLocks.entries()) {\r\n try {\r\n await this.releaseLock(lockId);\r\n } catch (error) {\r\n logger.error(\r\n 'Error releasing lock on exit',\r\n error instanceof Error ? error : undefined,\r\n { lockId, filePath: lock.filePath }\r\n );\r\n }\r\n }\r\n\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n }\r\n };\r\n\r\n process.on('exit', () => {\r\n // Synchronous cleanup only\r\n for (const [, lock] of this.activeLocks.entries()) {\r\n try {\r\n fs.unlinkSync(lock.lockPath);\r\n } catch {}\r\n }\r\n });\r\n\r\n process.on('SIGINT', async () => {\r\n await cleanup();\r\n process.exit(0);\r\n });\r\n\r\n process.on('SIGTERM', async () => {\r\n await cleanup();\r\n process.exit(0);\r\n });\r\n }\r\n\r\n /**\r\n * Shutdown the lock manager\r\n */\r\n async shutdown(): Promise<void> {\r\n logger.info('Shutting down file lock manager');\r\n\r\n // Clear cleanup interval\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n }\r\n\r\n // Release all active locks\r\n for (const lockId of this.activeLocks.keys()) {\r\n await this.releaseLock(lockId);\r\n }\r\n\r\n // Clear all queues\r\n for (const [, queue] of this.lockQueues.entries()) {\r\n for (const entry of queue) {\r\n if (entry.timeoutTimer) {\r\n clearTimeout(entry.timeoutTimer);\r\n }\r\n entry.reject(new Error('Lock manager shutting down'));\r\n }\r\n }\r\n this.lockQueues.clear();\r\n }\r\n}\r\n\r\n/**\r\n * Singleton instance\r\n */\r\nlet defaultManager: FileLockManager | null = null;\r\n\r\n/**\r\n * Get the default file lock manager instance\r\n */\r\nexport function getFileLockManager(): FileLockManager {\r\n if (!defaultManager) {\r\n defaultManager = new FileLockManager();\r\n }\r\n return defaultManager;\r\n}\r\n\r\n/**\r\n * Execute a function with file lock\r\n *\r\n * @param filePath - File to lock\r\n * @param fn - Function to execute\r\n * @param options - Lock options\r\n * @returns Promise that resolves with function result\r\n */\r\nexport async function withFileLock<T>(\r\n filePath: string,\r\n fn: () => Promise<T>,\r\n options: LockAcquisitionOptions = {}\r\n): Promise<T> {\r\n const manager = getFileLockManager();\r\n const lock = await manager.acquireLock(filePath, options);\r\n\r\n try {\r\n return await fn();\r\n } finally {\r\n await manager.releaseLock(lock.id);\r\n }\r\n}\r\n"],"names":["fs","path","promisify","randomUUID","createLogger","createError","ErrorCode","createTimeoutError","logger","fsWriteFile","writeFile","fsReadFile","readFile","fsUnlink","unlink","fsStat","stat","fsMkdir","mkdir","fsAccess","access","LOCK_DIR","process","env","CFN_LOCK_DIR","FileLockManager","activeLocks","Map","lockQueues","metrics","acquisitions","releases","timeouts","staleLocksRemoved","avgAcquisitionTimeMs","totalAcquisitionTimeMs","renewals","forceReleases","cleanupInterval","ensureLockDirectory","setupCleanupInterval","setupProcessExitHandlers","acquireLock","filePath","options","startTime","Date","now","opts","timeout","retryInterval","agentId","waitInQueue","staleTimeout","absolutePath","resolve","lockPath","getLockPath","lockId","debug","immediateLock","tryAcquireLock","acquisitionTime","recordAcquisition","info","acquisitionTimeMs","exists","fileExists","isStale","isLockStale","warn","forceReleaseLock","owner","pid","hostname","require","expiresAt","metadata","acquiredAt","toISOString","timeoutMs","renewalCount","writeLockFile","verify","readLockFile","winnerId","lock","id","set","error","Error","undefined","Promise","reject","queueEntry","queuedAt","timeoutTimer","setTimeout","removeFromQueue","has","get","push","queuePosition","length","pollForLock","pollInterval","setInterval","clearInterval","clearTimeout","waitTimeMs","String","releaseLock","delete","heldDurationMs","getTime","LOCK_RELEASE_FAILED","renewLock","extensionMs","LOCK_NOT_FOUND","LOCK_OWNERSHIP_MISMATCH","actualOwnerId","newExpiresAt","lastRenewedAt","getMetrics","getQueueStatus","queue","position","total","hash","hashFilePath","join","crypto","createHash","update","digest","substring","constants","F_OK","JSON","stringify","content","parse","entryId","index","findIndex","e","splice","existsSync","mkdirSync","recursive","mode","directory","cleanupStaleLocks","files","readdirSync","file","endsWith","cleanup","activeLocksCount","size","entries","on","unlinkSync","exit","shutdown","keys","entry","clear","defaultManager","getFileLockManager","withFileLock","fn","manager"],"mappings":"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"}
|