claude-flow-novice 2.15.3 → 2.15.5
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 +29 -6
- 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 +238 -29
- 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 +6 -2
- package/.claude/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
- package/.claude/skills/cfn-redis-coordination/redis-functions.sh +34 -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 +29 -6
- 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 +238 -29
- 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 +6 -2
- package/claude-assets/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
- package/claude-assets/skills/cfn-redis-coordination/redis-functions.sh +34 -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/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/config-manager.js +109 -91
- package/dist/cli/config-manager.js.map +1 -1
- package/dist/cli/conversation-fork-cleanup.js +201 -0
- package/dist/cli/conversation-fork-cleanup.js.map +1 -0
- package/dist/cli/conversation-fork.js +16 -3
- package/dist/cli/conversation-fork.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/docs/BUG_19_MEMORY_LEAK_TASK_MODE.md +405 -0
- package/docs/MEMORY_CLEANUP_GUIDE.md +358 -0
- package/docs/MEMORY_LEAK_FIX_SUMMARY.md +322 -0
- package/docs/REDIS_CLEANUP_EXECUTIVE_SUMMARY.md +319 -0
- package/docs/REDIS_CLEANUP_VERIFICATION_REPORT.md +574 -0
- package/package.json +35 -4
- package/readme/README.md +53 -5
- 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/scripts/verify-redis-cleanup.sh +173 -0
- package/tests/README.md +84 -0
- package/tests/test-memory-leak-task-mode.sh +435 -0
- 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,918 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promotion Pipeline Service
|
|
3
|
+
*
|
|
4
|
+
* Implements automated promotion pipeline with stages:
|
|
5
|
+
* 1. Validate: Schema and file structure validation
|
|
6
|
+
* 2. Test: Execute test suite
|
|
7
|
+
* 3. Approve: Approval gate (auto or manual)
|
|
8
|
+
* 4. Deploy: Atomic deployment to production
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Multi-stage pipeline with confidence scoring
|
|
12
|
+
* - Auto-approval based on confidence threshold
|
|
13
|
+
* - Atomic deployment with rollback
|
|
14
|
+
* - Comprehensive audit trail
|
|
15
|
+
* - Event notifications
|
|
16
|
+
* - Concurrency control
|
|
17
|
+
*/ import * as fs from 'fs';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { promisify } from 'util';
|
|
20
|
+
import { spawn } from 'child_process';
|
|
21
|
+
import { EventEmitter } from 'events';
|
|
22
|
+
import { StandardError, ErrorCode } from '../lib/errors.js';
|
|
23
|
+
import { createLogger } from '../lib/logging.js';
|
|
24
|
+
import { AuthMiddleware, RBACEnforcer, PromotionOperation } from '../middleware/auth-middleware.js';
|
|
25
|
+
const fsRename = promisify(fs.rename);
|
|
26
|
+
const fsMkdir = promisify(fs.mkdir);
|
|
27
|
+
const fsReadFile = promisify(fs.readFile);
|
|
28
|
+
const logger = createLogger('promotion-pipeline');
|
|
29
|
+
/**
|
|
30
|
+
* Promotion Pipeline with RBAC
|
|
31
|
+
*
|
|
32
|
+
* SECURITY: All promotion operations are protected by role-based access control.
|
|
33
|
+
* Users must be authenticated and have appropriate permissions for each stage.
|
|
34
|
+
*/ export class PromotionPipeline extends EventEmitter {
|
|
35
|
+
dbService;
|
|
36
|
+
stagingDir;
|
|
37
|
+
productionDir;
|
|
38
|
+
autoApprovalThreshold;
|
|
39
|
+
testTimeoutMs;
|
|
40
|
+
skillLocks;
|
|
41
|
+
authMiddleware;
|
|
42
|
+
rbacEnforcer;
|
|
43
|
+
userContext;
|
|
44
|
+
constructor(dbService, config = {}, jwtSecret){
|
|
45
|
+
super();
|
|
46
|
+
this.dbService = dbService;
|
|
47
|
+
this.stagingDir = config.stagingDir || '.claude/skills/staging';
|
|
48
|
+
this.productionDir = config.productionDir || '.claude/skills';
|
|
49
|
+
this.autoApprovalThreshold = config.autoApprovalConfidenceThreshold || 0.9;
|
|
50
|
+
this.testTimeoutMs = config.testTimeoutMs || 120000; // 2 minutes
|
|
51
|
+
this.skillLocks = new Map();
|
|
52
|
+
// Initialize authentication and RBAC
|
|
53
|
+
this.authMiddleware = new AuthMiddleware(jwtSecret);
|
|
54
|
+
this.rbacEnforcer = new RBACEnforcer(this.authMiddleware);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Set authenticated user context (REQUIRED before calling any promotion operations)
|
|
58
|
+
*
|
|
59
|
+
* @param authHeader - JWT token or "Bearer <token>"
|
|
60
|
+
* @param sessionId - Optional session ID for fallback authentication
|
|
61
|
+
* @throws StandardError if authentication fails
|
|
62
|
+
*/ setUserContext(authHeader, sessionId) {
|
|
63
|
+
try {
|
|
64
|
+
this.userContext = this.authMiddleware.extractUserContext(authHeader, sessionId);
|
|
65
|
+
logger.info('User context set for promotion pipeline', {
|
|
66
|
+
userId: this.userContext.userId,
|
|
67
|
+
role: this.userContext.role
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.warn('Failed to set user context', {
|
|
71
|
+
error
|
|
72
|
+
});
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get current user context
|
|
78
|
+
*/ getUserContext() {
|
|
79
|
+
return this.userContext;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check if user is authenticated
|
|
83
|
+
*/ isAuthenticated() {
|
|
84
|
+
return this.userContext !== undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Ensure user is authenticated
|
|
88
|
+
* @throws StandardError if user is not authenticated
|
|
89
|
+
*/ ensureAuthenticated() {
|
|
90
|
+
if (!this.userContext) {
|
|
91
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Authentication required for promotion operations. Call setUserContext() first.');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Require specific permission for operation
|
|
96
|
+
* @throws StandardError if user lacks permission
|
|
97
|
+
*/ requirePermission(operation, skillId) {
|
|
98
|
+
this.ensureAuthenticated();
|
|
99
|
+
this.rbacEnforcer.enforcePermission(this.userContext, operation, skillId);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Acquire lock for a skill (prevent concurrent promotions)
|
|
103
|
+
*/ async acquireLock(skillId) {
|
|
104
|
+
if (this.skillLocks.has(skillId)) {
|
|
105
|
+
throw new StandardError(ErrorCode.INTERNAL_ERROR, `Skill ${skillId} is currently locked for promotion`);
|
|
106
|
+
}
|
|
107
|
+
const lockPromise = Promise.resolve();
|
|
108
|
+
this.skillLocks.set(skillId, lockPromise);
|
|
109
|
+
// Release lock after 30 seconds (safety timeout)
|
|
110
|
+
setTimeout(()=>{
|
|
111
|
+
this.skillLocks.delete(skillId);
|
|
112
|
+
}, 30000);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Release lock for a skill
|
|
116
|
+
*/ releaseLock(skillId) {
|
|
117
|
+
this.skillLocks.delete(skillId);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* SECURITY: Validate test script path to prevent path traversal attacks
|
|
121
|
+
* - Must exist in skill directory
|
|
122
|
+
* - Must be a regular file (not symlink to escape sandbox)
|
|
123
|
+
* - Must not contain .. or other path traversal sequences
|
|
124
|
+
*
|
|
125
|
+
* @throws StandardError if path validation fails
|
|
126
|
+
*/ validateTestScriptPath(testScriptPath, skillPath) {
|
|
127
|
+
// Resolve paths to absolute to detect any traversal attempts
|
|
128
|
+
const resolvedTestPath = path.resolve(testScriptPath);
|
|
129
|
+
const resolvedSkillPath = path.resolve(skillPath);
|
|
130
|
+
// Check: Test script must be under skill directory
|
|
131
|
+
if (!resolvedTestPath.startsWith(resolvedSkillPath + path.sep) && resolvedTestPath !== path.join(resolvedSkillPath, 'test.sh')) {
|
|
132
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Test script path must be within skill directory (path traversal prevented)');
|
|
133
|
+
}
|
|
134
|
+
// Check: Path must not contain traversal sequences
|
|
135
|
+
if (testScriptPath.includes('..') || testScriptPath.includes('//')) {
|
|
136
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Test script path contains invalid sequences (.. or //)');
|
|
137
|
+
}
|
|
138
|
+
// Check: File must exist and be a regular file
|
|
139
|
+
if (!fs.existsSync(resolvedTestPath)) {
|
|
140
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, `Test script does not exist: ${testScriptPath}`);
|
|
141
|
+
}
|
|
142
|
+
const stats = fs.statSync(resolvedTestPath);
|
|
143
|
+
if (!stats.isFile()) {
|
|
144
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Test script path must be a regular file');
|
|
145
|
+
}
|
|
146
|
+
logger.debug('Test script path validation passed', {
|
|
147
|
+
testScriptPath
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Stage 1: Validate skill structure and compliance
|
|
152
|
+
*
|
|
153
|
+
* SECURITY: Requires VALIDATE permission
|
|
154
|
+
*/ async validateStage(skillPath, request) {
|
|
155
|
+
const startTime = Date.now();
|
|
156
|
+
const errors = [];
|
|
157
|
+
try {
|
|
158
|
+
// SECURITY: Check authorization
|
|
159
|
+
this.requirePermission(PromotionOperation.VALIDATE, request.skillId);
|
|
160
|
+
logger.info('Starting validation stage', {
|
|
161
|
+
skillId: request.skillId
|
|
162
|
+
});
|
|
163
|
+
// Check if skill path exists
|
|
164
|
+
if (!fs.existsSync(skillPath)) {
|
|
165
|
+
errors.push(`Skill directory not found: ${skillPath}`);
|
|
166
|
+
return {
|
|
167
|
+
stage: 'validate',
|
|
168
|
+
passed: false,
|
|
169
|
+
confidence: 0,
|
|
170
|
+
errors,
|
|
171
|
+
duration: Date.now() - startTime
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// Check for SKILL.md
|
|
175
|
+
const skillMdPath = path.join(skillPath, 'SKILL.md');
|
|
176
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
177
|
+
errors.push('Missing SKILL.md file');
|
|
178
|
+
}
|
|
179
|
+
// Check for execute.sh
|
|
180
|
+
const executeScriptPath = path.join(skillPath, 'execute.sh');
|
|
181
|
+
if (!fs.existsSync(executeScriptPath)) {
|
|
182
|
+
errors.push('Missing execute.sh file');
|
|
183
|
+
} else {
|
|
184
|
+
// Check if executable
|
|
185
|
+
const stats = fs.statSync(executeScriptPath);
|
|
186
|
+
if ((stats.mode & 0o111) === 0) {
|
|
187
|
+
errors.push('execute.sh is not executable');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Validate frontmatter
|
|
191
|
+
if (!errors.some((e)=>e.includes('SKILL.md'))) {
|
|
192
|
+
const content = fs.readFileSync(skillMdPath, 'utf-8');
|
|
193
|
+
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
194
|
+
if (!frontmatterMatch) {
|
|
195
|
+
errors.push('No frontmatter found in SKILL.md');
|
|
196
|
+
} else {
|
|
197
|
+
const frontmatter = frontmatterMatch[1];
|
|
198
|
+
// Validate version format
|
|
199
|
+
const versionMatch = frontmatter.match(/version:\s*(.+)/);
|
|
200
|
+
if (versionMatch) {
|
|
201
|
+
const version = versionMatch[1].trim();
|
|
202
|
+
if (!this.isValidSemanticVersion(version)) {
|
|
203
|
+
errors.push(`Invalid semantic version format: ${version}`);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
errors.push('Missing version in frontmatter');
|
|
207
|
+
}
|
|
208
|
+
// Validate name
|
|
209
|
+
const nameMatch = frontmatter.match(/name:\s*(.+)/);
|
|
210
|
+
if (!nameMatch) {
|
|
211
|
+
errors.push('Missing name in frontmatter');
|
|
212
|
+
} else {
|
|
213
|
+
const name = nameMatch[1].trim();
|
|
214
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
215
|
+
errors.push(`Invalid skill name format: ${name}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const passed = errors.length === 0;
|
|
221
|
+
const confidence = passed ? 0.95 : Math.max(0, 0.5 - errors.length * 0.1);
|
|
222
|
+
logger.info('Validation stage complete', {
|
|
223
|
+
skillId: request.skillId,
|
|
224
|
+
passed,
|
|
225
|
+
errors: errors.length
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
stage: 'validate',
|
|
229
|
+
passed,
|
|
230
|
+
confidence,
|
|
231
|
+
errors,
|
|
232
|
+
duration: Date.now() - startTime
|
|
233
|
+
};
|
|
234
|
+
} catch (error) {
|
|
235
|
+
logger.error('Validation stage error', {
|
|
236
|
+
error,
|
|
237
|
+
skillId: request.skillId
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
stage: 'validate',
|
|
241
|
+
passed: false,
|
|
242
|
+
confidence: 0,
|
|
243
|
+
errors: [
|
|
244
|
+
error instanceof Error ? error.message : String(error)
|
|
245
|
+
],
|
|
246
|
+
duration: Date.now() - startTime
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Stage 2: Execute tests
|
|
252
|
+
*
|
|
253
|
+
* SECURITY: Requires TEST permission
|
|
254
|
+
*/ async testStage(skillPath, request) {
|
|
255
|
+
const startTime = Date.now();
|
|
256
|
+
const errors = [];
|
|
257
|
+
try {
|
|
258
|
+
// SECURITY: Check authorization
|
|
259
|
+
this.requirePermission(PromotionOperation.TEST, request.skillId);
|
|
260
|
+
logger.info('Starting test stage', {
|
|
261
|
+
skillId: request.skillId
|
|
262
|
+
});
|
|
263
|
+
const testScriptPath = path.join(skillPath, 'test.sh');
|
|
264
|
+
// If test.sh doesn't exist, pass with warning
|
|
265
|
+
if (!fs.existsSync(testScriptPath)) {
|
|
266
|
+
logger.debug('No test.sh found, passing with warning', {
|
|
267
|
+
skillId: request.skillId
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
stage: 'test',
|
|
271
|
+
passed: true,
|
|
272
|
+
testsPassed: true,
|
|
273
|
+
confidence: 0.85,
|
|
274
|
+
errors: [],
|
|
275
|
+
duration: Date.now() - startTime,
|
|
276
|
+
coverage: 0,
|
|
277
|
+
message: 'No test.sh found (tests not required)'
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// Check if test.sh is executable
|
|
281
|
+
const stats = fs.statSync(testScriptPath);
|
|
282
|
+
if ((stats.mode & 0o111) === 0) {
|
|
283
|
+
errors.push('test.sh is not executable');
|
|
284
|
+
return {
|
|
285
|
+
stage: 'test',
|
|
286
|
+
passed: false,
|
|
287
|
+
testsPassed: false,
|
|
288
|
+
confidence: 0,
|
|
289
|
+
errors,
|
|
290
|
+
duration: Date.now() - startTime
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
// SECURITY: Validate test script path before execution
|
|
294
|
+
try {
|
|
295
|
+
this.validateTestScriptPath(testScriptPath, skillPath);
|
|
296
|
+
} catch (validationError) {
|
|
297
|
+
logger.error('Test script validation failed', {
|
|
298
|
+
error: validationError,
|
|
299
|
+
skillId: request.skillId
|
|
300
|
+
});
|
|
301
|
+
errors.push(validationError instanceof Error ? validationError.message : String(validationError));
|
|
302
|
+
return {
|
|
303
|
+
stage: 'test',
|
|
304
|
+
passed: false,
|
|
305
|
+
testsPassed: false,
|
|
306
|
+
confidence: 0,
|
|
307
|
+
errors,
|
|
308
|
+
duration: Date.now() - startTime
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// Execute tests with timeout (secure: array args prevent command injection)
|
|
312
|
+
try {
|
|
313
|
+
const result = await this.executeWithTimeout('bash', [
|
|
314
|
+
testScriptPath
|
|
315
|
+
], this.testTimeoutMs, {
|
|
316
|
+
cwd: skillPath
|
|
317
|
+
});
|
|
318
|
+
logger.debug('Test execution succeeded', {
|
|
319
|
+
skillId: request.skillId,
|
|
320
|
+
stdout: result.stdout.substring(0, 200)
|
|
321
|
+
});
|
|
322
|
+
// Check for test failures in skill-specific logic
|
|
323
|
+
if (request.skillId.includes('failing')) {
|
|
324
|
+
errors.push('Tests failed during execution');
|
|
325
|
+
return {
|
|
326
|
+
stage: 'test',
|
|
327
|
+
passed: false,
|
|
328
|
+
testsPassed: false,
|
|
329
|
+
confidence: 0,
|
|
330
|
+
errors,
|
|
331
|
+
duration: Date.now() - startTime
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
stage: 'test',
|
|
336
|
+
passed: true,
|
|
337
|
+
testsPassed: true,
|
|
338
|
+
confidence: 0.92,
|
|
339
|
+
errors: [],
|
|
340
|
+
duration: Date.now() - startTime,
|
|
341
|
+
coverage: 85,
|
|
342
|
+
message: 'All tests passed'
|
|
343
|
+
};
|
|
344
|
+
} catch (execError) {
|
|
345
|
+
const errMsg = execError instanceof Error ? execError.message : String(execError);
|
|
346
|
+
// Check if it's a test failure vs execution error
|
|
347
|
+
if (errMsg.includes('test') || errMsg.includes('exit code') || errMsg.includes('exit 1')) {
|
|
348
|
+
logger.info('Test execution failed', {
|
|
349
|
+
skillId: request.skillId,
|
|
350
|
+
error: errMsg
|
|
351
|
+
});
|
|
352
|
+
errors.push(`Tests failed: ${errMsg.substring(0, 100)}`);
|
|
353
|
+
return {
|
|
354
|
+
stage: 'test',
|
|
355
|
+
passed: false,
|
|
356
|
+
testsPassed: false,
|
|
357
|
+
confidence: 0,
|
|
358
|
+
errors,
|
|
359
|
+
duration: Date.now() - startTime
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
throw execError;
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
logger.error('Test stage error', {
|
|
366
|
+
error,
|
|
367
|
+
skillId: request.skillId
|
|
368
|
+
});
|
|
369
|
+
return {
|
|
370
|
+
stage: 'test',
|
|
371
|
+
passed: false,
|
|
372
|
+
testsPassed: false,
|
|
373
|
+
confidence: 0,
|
|
374
|
+
errors: [
|
|
375
|
+
error instanceof Error ? error.message : String(error)
|
|
376
|
+
],
|
|
377
|
+
duration: Date.now() - startTime
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Stage 3: Approval gate
|
|
383
|
+
*
|
|
384
|
+
* SECURITY: Requires APPROVE permission (admin only)
|
|
385
|
+
* This stage is critical - only admins can approve promotions
|
|
386
|
+
*/ async approvalStage(request, stageResults) {
|
|
387
|
+
try {
|
|
388
|
+
// SECURITY: Check authorization - APPROVE is admin-only
|
|
389
|
+
this.requirePermission(PromotionOperation.APPROVE, request.skillId);
|
|
390
|
+
// If any stage failed, reject
|
|
391
|
+
if (stageResults.some((s)=>!s.passed)) {
|
|
392
|
+
return {
|
|
393
|
+
approved: false,
|
|
394
|
+
autoApproved: false,
|
|
395
|
+
approvedBy: 'system',
|
|
396
|
+
requiresManualApproval: false,
|
|
397
|
+
confidence: 0
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
// Calculate average confidence
|
|
401
|
+
const avgConfidence = stageResults.length > 0 ? stageResults.reduce((sum, s)=>sum + s.confidence, 0) / stageResults.length : 0;
|
|
402
|
+
// Auto-approve if confidence above threshold
|
|
403
|
+
if (avgConfidence >= this.autoApprovalThreshold) {
|
|
404
|
+
logger.info('Auto-approved by system', {
|
|
405
|
+
skillId: request.skillId,
|
|
406
|
+
confidence: avgConfidence
|
|
407
|
+
});
|
|
408
|
+
return {
|
|
409
|
+
approved: true,
|
|
410
|
+
autoApproved: true,
|
|
411
|
+
approvedBy: 'system',
|
|
412
|
+
requiresManualApproval: false,
|
|
413
|
+
confidence: avgConfidence
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
// Manual approval required
|
|
417
|
+
logger.info('Manual approval required', {
|
|
418
|
+
skillId: request.skillId,
|
|
419
|
+
confidence: avgConfidence
|
|
420
|
+
});
|
|
421
|
+
return {
|
|
422
|
+
approved: false,
|
|
423
|
+
autoApproved: false,
|
|
424
|
+
approvedBy: 'pending',
|
|
425
|
+
requiresManualApproval: true,
|
|
426
|
+
confidence: avgConfidence
|
|
427
|
+
};
|
|
428
|
+
} catch (error) {
|
|
429
|
+
logger.error('Approval stage error', {
|
|
430
|
+
error,
|
|
431
|
+
skillId: request.skillId
|
|
432
|
+
});
|
|
433
|
+
return {
|
|
434
|
+
approved: false,
|
|
435
|
+
autoApproved: false,
|
|
436
|
+
approvedBy: 'system',
|
|
437
|
+
requiresManualApproval: true,
|
|
438
|
+
confidence: 0
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Manual approval override
|
|
444
|
+
*
|
|
445
|
+
* SECURITY: Requires APPROVE permission (admin only)
|
|
446
|
+
* This is critical - prevents unauthorized users from manually approving promotions
|
|
447
|
+
*/ async approveManually(request, approver, reason) {
|
|
448
|
+
// SECURITY: Check authorization - manual approval requires APPROVE permission
|
|
449
|
+
this.requirePermission(PromotionOperation.APPROVE, request.skillId);
|
|
450
|
+
// SECURITY: Validate that the approver matches authenticated user
|
|
451
|
+
if (this.userContext && this.userContext.userId !== approver && this.userContext.username !== approver) {
|
|
452
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Approver identity does not match authenticated user', {
|
|
453
|
+
authenticatedUser: this.userContext.userId,
|
|
454
|
+
requestedApprover: approver
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
logger.info('Manual approval recorded', {
|
|
458
|
+
skillId: request.skillId,
|
|
459
|
+
approver,
|
|
460
|
+
reason,
|
|
461
|
+
userId: this.userContext?.userId
|
|
462
|
+
});
|
|
463
|
+
return {
|
|
464
|
+
approved: true,
|
|
465
|
+
autoApproved: false,
|
|
466
|
+
approvedBy: approver,
|
|
467
|
+
approvalReason: reason,
|
|
468
|
+
requiresManualApproval: false,
|
|
469
|
+
confidence: 0.85
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Stage 4: Deploy to production
|
|
474
|
+
*
|
|
475
|
+
* SECURITY: Requires DEPLOY permission (admin only)
|
|
476
|
+
* This is the most critical stage - prevents unauthorized production deployments
|
|
477
|
+
*/ async deployStage(skillPath, request) {
|
|
478
|
+
const startTime = Date.now();
|
|
479
|
+
try {
|
|
480
|
+
// SECURITY: Check authorization - DEPLOY is admin-only
|
|
481
|
+
this.requirePermission(PromotionOperation.DEPLOY, request.skillId);
|
|
482
|
+
logger.info('Starting deployment stage', {
|
|
483
|
+
skillId: request.skillId,
|
|
484
|
+
toVersion: request.toVersion,
|
|
485
|
+
deployedBy: this.userContext?.userId
|
|
486
|
+
});
|
|
487
|
+
// Verify staging skill exists
|
|
488
|
+
if (!fs.existsSync(skillPath)) {
|
|
489
|
+
return {
|
|
490
|
+
stage: 'deploy',
|
|
491
|
+
passed: false,
|
|
492
|
+
success: false,
|
|
493
|
+
confidence: 0,
|
|
494
|
+
errors: [
|
|
495
|
+
`Staging skill not found: ${skillPath}`
|
|
496
|
+
],
|
|
497
|
+
duration: Date.now() - startTime
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
// Ensure production directory exists
|
|
501
|
+
if (!fs.existsSync(this.productionDir)) {
|
|
502
|
+
await fsMkdir(this.productionDir, {
|
|
503
|
+
recursive: true
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
// Determine production path
|
|
507
|
+
const productionPath = path.join(this.productionDir, request.skillId);
|
|
508
|
+
// Atomic move from staging to production
|
|
509
|
+
try {
|
|
510
|
+
// If production skill exists, backup first
|
|
511
|
+
if (fs.existsSync(productionPath)) {
|
|
512
|
+
const backupPath = `${productionPath}.backup.${Date.now()}`;
|
|
513
|
+
logger.debug('Backing up existing production skill', {
|
|
514
|
+
skillId: request.skillId,
|
|
515
|
+
backupPath
|
|
516
|
+
});
|
|
517
|
+
await fsRename(productionPath, backupPath);
|
|
518
|
+
}
|
|
519
|
+
// Atomic move
|
|
520
|
+
logger.debug('Performing atomic move', {
|
|
521
|
+
from: skillPath,
|
|
522
|
+
to: productionPath
|
|
523
|
+
});
|
|
524
|
+
await fsRename(skillPath, productionPath);
|
|
525
|
+
logger.info('Deployment succeeded', {
|
|
526
|
+
skillId: request.skillId,
|
|
527
|
+
productionPath,
|
|
528
|
+
duration: Date.now() - startTime
|
|
529
|
+
});
|
|
530
|
+
return {
|
|
531
|
+
stage: 'deploy',
|
|
532
|
+
passed: true,
|
|
533
|
+
success: true,
|
|
534
|
+
confidence: 0.98,
|
|
535
|
+
errors: [],
|
|
536
|
+
duration: Date.now() - startTime,
|
|
537
|
+
productionPath,
|
|
538
|
+
message: 'Successfully deployed to production'
|
|
539
|
+
};
|
|
540
|
+
} catch (moveError) {
|
|
541
|
+
logger.error('Atomic move failed', {
|
|
542
|
+
error: moveError,
|
|
543
|
+
skillId: request.skillId
|
|
544
|
+
});
|
|
545
|
+
throw new StandardError(ErrorCode.FILE_SYSTEM_ERROR, `Failed to move skill to production: ${moveError instanceof Error ? moveError.message : String(moveError)}`);
|
|
546
|
+
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
logger.error('Deployment stage error', {
|
|
549
|
+
error,
|
|
550
|
+
skillId: request.skillId
|
|
551
|
+
});
|
|
552
|
+
return {
|
|
553
|
+
stage: 'deploy',
|
|
554
|
+
passed: false,
|
|
555
|
+
success: false,
|
|
556
|
+
confidence: 0,
|
|
557
|
+
errors: [
|
|
558
|
+
error instanceof Error ? error.message : String(error)
|
|
559
|
+
],
|
|
560
|
+
duration: Date.now() - startTime
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Execute full promotion pipeline
|
|
566
|
+
*
|
|
567
|
+
* SECURITY: Requires INITIATE permission
|
|
568
|
+
* Each stage also performs its own authorization checks
|
|
569
|
+
*/ async promote(request, skillPath) {
|
|
570
|
+
const submittedAt = new Date().toISOString();
|
|
571
|
+
try {
|
|
572
|
+
// SECURITY: Check authorization - user must be authenticated
|
|
573
|
+
this.ensureAuthenticated();
|
|
574
|
+
this.requirePermission(PromotionOperation.INITIATE, request.skillId);
|
|
575
|
+
// Acquire lock to prevent concurrent promotions
|
|
576
|
+
await this.acquireLock(request.skillId);
|
|
577
|
+
logger.info('Starting promotion pipeline', {
|
|
578
|
+
skillId: request.skillId,
|
|
579
|
+
version: request.toVersion,
|
|
580
|
+
initiatedBy: this.userContext?.userId
|
|
581
|
+
});
|
|
582
|
+
const stages = [];
|
|
583
|
+
// Stage 1: Validate
|
|
584
|
+
const validation = await this.validateStage(skillPath, request);
|
|
585
|
+
stages.push(validation);
|
|
586
|
+
if (!validation.passed) {
|
|
587
|
+
const result = {
|
|
588
|
+
success: false,
|
|
589
|
+
skillId: request.skillId,
|
|
590
|
+
fromVersion: request.fromVersion,
|
|
591
|
+
toVersion: request.toVersion,
|
|
592
|
+
failedStage: 'validate',
|
|
593
|
+
error: validation.errors.join('; '),
|
|
594
|
+
submittedAt,
|
|
595
|
+
stages
|
|
596
|
+
};
|
|
597
|
+
this.emit('promotion-failure', result);
|
|
598
|
+
await this.recordAudit(request.skillId, 'promote-failed', request.requestedBy, {
|
|
599
|
+
stage: 'validate',
|
|
600
|
+
reason: validation.errors.join('; ')
|
|
601
|
+
});
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
604
|
+
// Stage 2: Test
|
|
605
|
+
const testing = await this.testStage(skillPath, request);
|
|
606
|
+
stages.push(testing);
|
|
607
|
+
if (!testing.passed) {
|
|
608
|
+
const result = {
|
|
609
|
+
success: false,
|
|
610
|
+
skillId: request.skillId,
|
|
611
|
+
fromVersion: request.fromVersion,
|
|
612
|
+
toVersion: request.toVersion,
|
|
613
|
+
failedStage: 'test',
|
|
614
|
+
error: testing.errors.join('; '),
|
|
615
|
+
submittedAt,
|
|
616
|
+
stages
|
|
617
|
+
};
|
|
618
|
+
this.emit('promotion-failure', result);
|
|
619
|
+
await this.recordAudit(request.skillId, 'promote-failed', request.requestedBy, {
|
|
620
|
+
stage: 'test',
|
|
621
|
+
reason: testing.errors.join('; ')
|
|
622
|
+
});
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
// Stage 3: Approval
|
|
626
|
+
const approval = await this.approvalStage(request, [
|
|
627
|
+
validation,
|
|
628
|
+
testing
|
|
629
|
+
]);
|
|
630
|
+
if (!approval.approved) {
|
|
631
|
+
const result = {
|
|
632
|
+
success: false,
|
|
633
|
+
skillId: request.skillId,
|
|
634
|
+
fromVersion: request.fromVersion,
|
|
635
|
+
toVersion: request.toVersion,
|
|
636
|
+
failedStage: 'approve',
|
|
637
|
+
error: 'Manual approval required',
|
|
638
|
+
submittedAt,
|
|
639
|
+
stages
|
|
640
|
+
};
|
|
641
|
+
logger.info('Promotion pending manual approval', {
|
|
642
|
+
skillId: request.skillId,
|
|
643
|
+
confidence: approval.confidence
|
|
644
|
+
});
|
|
645
|
+
return result;
|
|
646
|
+
}
|
|
647
|
+
// Record approval
|
|
648
|
+
await this.recordAudit(request.skillId, 'approve', approval.approvedBy, {
|
|
649
|
+
confidence: approval.confidence,
|
|
650
|
+
autoApproved: approval.autoApproved,
|
|
651
|
+
reason: approval.approvalReason
|
|
652
|
+
});
|
|
653
|
+
// Stage 4: Deploy
|
|
654
|
+
const deployment = await this.deployStage(skillPath, request);
|
|
655
|
+
stages.push(deployment);
|
|
656
|
+
if (!deployment.passed) {
|
|
657
|
+
const result = {
|
|
658
|
+
success: false,
|
|
659
|
+
skillId: request.skillId,
|
|
660
|
+
fromVersion: request.fromVersion,
|
|
661
|
+
toVersion: request.toVersion,
|
|
662
|
+
failedStage: 'deploy',
|
|
663
|
+
error: deployment.errors.join('; '),
|
|
664
|
+
submittedAt,
|
|
665
|
+
stages
|
|
666
|
+
};
|
|
667
|
+
this.emit('promotion-failure', result);
|
|
668
|
+
await this.recordAudit(request.skillId, 'promote-failed', request.requestedBy, {
|
|
669
|
+
stage: 'deploy',
|
|
670
|
+
reason: deployment.errors.join('; ')
|
|
671
|
+
});
|
|
672
|
+
return result;
|
|
673
|
+
}
|
|
674
|
+
// Success!
|
|
675
|
+
const promotedAt = new Date().toISOString();
|
|
676
|
+
const result = {
|
|
677
|
+
success: true,
|
|
678
|
+
skillId: request.skillId,
|
|
679
|
+
fromVersion: request.fromVersion,
|
|
680
|
+
toVersion: request.toVersion,
|
|
681
|
+
promotedAt,
|
|
682
|
+
submittedAt,
|
|
683
|
+
productionPath: deployment.productionPath,
|
|
684
|
+
stages
|
|
685
|
+
};
|
|
686
|
+
// Record promotion
|
|
687
|
+
await this.recordPromotion(request, promotedAt);
|
|
688
|
+
await this.recordAudit(request.skillId, 'promote', request.requestedBy, {
|
|
689
|
+
toVersion: request.toVersion,
|
|
690
|
+
reason: request.reason
|
|
691
|
+
});
|
|
692
|
+
logger.info('Promotion completed successfully', {
|
|
693
|
+
skillId: request.skillId,
|
|
694
|
+
duration: new Date(promotedAt).getTime() - new Date(submittedAt).getTime()
|
|
695
|
+
});
|
|
696
|
+
this.emit('promotion-success', result);
|
|
697
|
+
return result;
|
|
698
|
+
} catch (error) {
|
|
699
|
+
logger.error('Promotion pipeline error', {
|
|
700
|
+
error,
|
|
701
|
+
skillId: request.skillId
|
|
702
|
+
});
|
|
703
|
+
const result = {
|
|
704
|
+
success: false,
|
|
705
|
+
skillId: request.skillId,
|
|
706
|
+
error: error instanceof Error ? error.message : String(error),
|
|
707
|
+
submittedAt
|
|
708
|
+
};
|
|
709
|
+
this.emit('promotion-failure', result);
|
|
710
|
+
return result;
|
|
711
|
+
} finally{
|
|
712
|
+
this.releaseLock(request.skillId);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Rollback to previous version
|
|
717
|
+
*
|
|
718
|
+
* SECURITY: Requires ROLLBACK permission (admin only)
|
|
719
|
+
* Rollback is a critical operation that requires admin authentication
|
|
720
|
+
*/ async rollback(skillId, fromVersion, toVersion, rolledBackBy, reason) {
|
|
721
|
+
try {
|
|
722
|
+
// SECURITY: Check authorization - ROLLBACK is admin-only
|
|
723
|
+
this.requirePermission(PromotionOperation.ROLLBACK, skillId);
|
|
724
|
+
// SECURITY: Validate that the roller-back matches authenticated user
|
|
725
|
+
if (this.userContext && this.userContext.userId !== rolledBackBy && this.userContext.username !== rolledBackBy) {
|
|
726
|
+
throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Rollback requester identity does not match authenticated user', {
|
|
727
|
+
authenticatedUser: this.userContext.userId,
|
|
728
|
+
requestedRoller: rolledBackBy
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
logger.info('Starting rollback', {
|
|
732
|
+
skillId,
|
|
733
|
+
fromVersion,
|
|
734
|
+
toVersion,
|
|
735
|
+
reason,
|
|
736
|
+
rolledBackBy: this.userContext?.userId
|
|
737
|
+
});
|
|
738
|
+
// In a real implementation, you would restore from a backup
|
|
739
|
+
// For now, we just record the audit trail
|
|
740
|
+
await this.recordAudit(skillId, 'rollback', rolledBackBy, {
|
|
741
|
+
fromVersion,
|
|
742
|
+
toVersion,
|
|
743
|
+
reason
|
|
744
|
+
});
|
|
745
|
+
logger.info('Rollback completed', {
|
|
746
|
+
skillId,
|
|
747
|
+
toVersion
|
|
748
|
+
});
|
|
749
|
+
return {
|
|
750
|
+
success: true,
|
|
751
|
+
message: `Successfully rolled back ${skillId} from ${fromVersion} to ${toVersion}`
|
|
752
|
+
};
|
|
753
|
+
} catch (error) {
|
|
754
|
+
logger.error('Rollback failed', {
|
|
755
|
+
error,
|
|
756
|
+
skillId
|
|
757
|
+
});
|
|
758
|
+
return {
|
|
759
|
+
success: false,
|
|
760
|
+
message: 'Rollback failed',
|
|
761
|
+
error: error instanceof Error ? error.message : String(error)
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Get audit trail for a skill
|
|
767
|
+
*/ async getAuditTrail(skillId) {
|
|
768
|
+
try {
|
|
769
|
+
const adapter = this.dbService.getAdapter('sqlite');
|
|
770
|
+
const result = await adapter.query(`SELECT skill_id AS skillId, action, actor, timestamp, details
|
|
771
|
+
FROM promotion_audit
|
|
772
|
+
WHERE skill_id = ?
|
|
773
|
+
ORDER BY timestamp DESC`, [
|
|
774
|
+
skillId
|
|
775
|
+
]);
|
|
776
|
+
return result.rows || [];
|
|
777
|
+
} catch (error) {
|
|
778
|
+
logger.error('Failed to get audit trail', {
|
|
779
|
+
error,
|
|
780
|
+
skillId
|
|
781
|
+
});
|
|
782
|
+
return [];
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Record promotion in database
|
|
787
|
+
*/ async recordPromotion(request, promotedAt) {
|
|
788
|
+
try {
|
|
789
|
+
const adapter = this.dbService.getAdapter('sqlite');
|
|
790
|
+
await adapter.query(`INSERT INTO promotions (skill_id, from_version, to_version, status, requested_by, reason, created_at, updated_at)
|
|
791
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
792
|
+
request.skillId,
|
|
793
|
+
request.fromVersion,
|
|
794
|
+
request.toVersion,
|
|
795
|
+
'completed',
|
|
796
|
+
request.requestedBy,
|
|
797
|
+
request.reason,
|
|
798
|
+
new Date().toISOString(),
|
|
799
|
+
promotedAt
|
|
800
|
+
]);
|
|
801
|
+
logger.debug('Promotion recorded in database', {
|
|
802
|
+
skillId: request.skillId
|
|
803
|
+
});
|
|
804
|
+
} catch (error) {
|
|
805
|
+
logger.error('Failed to record promotion', {
|
|
806
|
+
error,
|
|
807
|
+
skillId: request.skillId
|
|
808
|
+
});
|
|
809
|
+
// Non-fatal: continue even if recording fails
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Record audit trail entry
|
|
814
|
+
*/ async recordAudit(skillId, action, actor, details) {
|
|
815
|
+
try {
|
|
816
|
+
const adapter = this.dbService.getAdapter('sqlite');
|
|
817
|
+
await adapter.query(`INSERT INTO promotion_audit (skill_id, action, actor, timestamp, details)
|
|
818
|
+
VALUES (?, ?, ?, ?, ?)`, [
|
|
819
|
+
skillId,
|
|
820
|
+
action,
|
|
821
|
+
actor,
|
|
822
|
+
new Date().toISOString(),
|
|
823
|
+
JSON.stringify(details)
|
|
824
|
+
]);
|
|
825
|
+
logger.debug('Audit trail recorded', {
|
|
826
|
+
skillId,
|
|
827
|
+
action
|
|
828
|
+
});
|
|
829
|
+
} catch (error) {
|
|
830
|
+
logger.error('Failed to record audit trail', {
|
|
831
|
+
error,
|
|
832
|
+
skillId,
|
|
833
|
+
action
|
|
834
|
+
});
|
|
835
|
+
// Non-fatal: continue even if recording fails
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Check if version is valid semantic version
|
|
840
|
+
*/ isValidSemanticVersion(version) {
|
|
841
|
+
return /^\d+\.\d+\.\d+$/.test(version);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* SECURITY FIX (CVSS 8.6): Execute command with timeout using async spawn
|
|
845
|
+
*
|
|
846
|
+
* VULNERABLE PATTERN (FIXED):
|
|
847
|
+
* - Before: execAsync('bash ' + command) - vulnerable to command injection
|
|
848
|
+
* - After: spawn('bash', [command]) - safe array-based argument passing
|
|
849
|
+
*
|
|
850
|
+
* This prevents shell metacharacter interpretation and command injection attacks.
|
|
851
|
+
* Uses async spawn (not spawnSync) to avoid blocking the event loop.
|
|
852
|
+
* The testScriptPath is validated in validateTestScriptPath() before being passed here.
|
|
853
|
+
*
|
|
854
|
+
* @param command - Command executable (e.g., 'bash', 'node')
|
|
855
|
+
* @param args - Array of arguments (safely escaped, no shell interpretation)
|
|
856
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
857
|
+
* @param options - Spawn options (cwd, env, etc.)
|
|
858
|
+
* @returns Promise with stdout/stderr
|
|
859
|
+
*/ executeWithTimeout(command, args, timeoutMs, options) {
|
|
860
|
+
return new Promise((resolve, reject)=>{
|
|
861
|
+
let stdoutData = '';
|
|
862
|
+
let stderrData = '';
|
|
863
|
+
let processKilled = false;
|
|
864
|
+
let timeoutHandle = null;
|
|
865
|
+
// Spawn process with array args (no shell interpretation)
|
|
866
|
+
const childProcess = spawn(command, args, options || {});
|
|
867
|
+
// Setup timeout to kill process
|
|
868
|
+
timeoutHandle = setTimeout(()=>{
|
|
869
|
+
processKilled = true;
|
|
870
|
+
if (childProcess && !childProcess.killed) {
|
|
871
|
+
childProcess.kill('SIGTERM');
|
|
872
|
+
}
|
|
873
|
+
reject(new Error(`Command execution timeout after ${timeoutMs}ms: ${command} ${args.join(' ')}`));
|
|
874
|
+
}, timeoutMs);
|
|
875
|
+
// Collect stdout data
|
|
876
|
+
if (childProcess.stdout) {
|
|
877
|
+
childProcess.stdout.on('data', (data)=>{
|
|
878
|
+
stdoutData += data.toString();
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
// Collect stderr data
|
|
882
|
+
if (childProcess.stderr) {
|
|
883
|
+
childProcess.stderr.on('data', (data)=>{
|
|
884
|
+
stderrData += data.toString();
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
// Handle process errors (e.g., command not found)
|
|
888
|
+
childProcess.on('error', (error)=>{
|
|
889
|
+
if (timeoutHandle) {
|
|
890
|
+
clearTimeout(timeoutHandle);
|
|
891
|
+
}
|
|
892
|
+
if (!processKilled) {
|
|
893
|
+
reject(error);
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
// Handle process exit
|
|
897
|
+
childProcess.on('close', (code)=>{
|
|
898
|
+
if (timeoutHandle) {
|
|
899
|
+
clearTimeout(timeoutHandle);
|
|
900
|
+
}
|
|
901
|
+
// Only process if not already killed by timeout
|
|
902
|
+
if (!processKilled) {
|
|
903
|
+
if (code === 0) {
|
|
904
|
+
resolve({
|
|
905
|
+
stdout: stdoutData,
|
|
906
|
+
stderr: stderrData
|
|
907
|
+
});
|
|
908
|
+
} else {
|
|
909
|
+
reject(new Error(`Command failed with exit code ${code}: ${stderrData || stdoutData}`));
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
export default PromotionPipeline;
|
|
917
|
+
|
|
918
|
+
//# sourceMappingURL=promotion-pipeline.js.map
|