claude-flow-novice 2.15.5 → 2.15.6
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/.gs-api-quota.json +16 -0
- package/.claude/cfn-extras/.gs-progress-state.json +22 -0
- package/.claude/cfn-extras/GOOGLE_SHEETS_IMPLEMENTATION_SUMMARY.md +414 -0
- package/.claude/cfn-extras/agents/google-sheets/README.md +114 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-advanced-analytics-specialist.md +288 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-api-integrator.md +127 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-automation-scripting-specialist.md +195 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-business-validator.md +179 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-collaboration-security-specialist.md +240 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-coordinator.md +214 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-data-transformer.md +127 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-data-validation-quality-specialist.md +177 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-data-validator.md +119 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-data-visualization-specialist.md +135 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-design-layout-specialist.md +109 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-formula-engineer.md +127 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-formula-engineering-specialist.md +138 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-formula-validator.md +128 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-generalist.md +645 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-integration-api-specialist.md +258 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-performance-analyst.md +125 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-performance-optimization-specialist.md +211 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-schema-designer.md +130 -0
- package/.claude/cfn-extras/agents/google-sheets/google-sheets-template-architecture-specialist.md +259 -0
- package/.claude/cfn-extras/docs/GOOGLE_SHEETS_CFN_LOOP.md +617 -0
- package/.claude/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +453 -0
- package/.claude/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +272 -0
- package/.claude/cfn-extras/skills/google-sheets-api-coordinator/api-call.sh +254 -0
- package/.claude/cfn-extras/skills/google-sheets-api-coordinator/test.sh +174 -0
- package/.claude/cfn-extras/skills/google-sheets-api-coordinator/validate.sh +98 -0
- package/.claude/cfn-extras/skills/google-sheets-decomposition/SKILL.md +269 -0
- package/.claude/cfn-extras/skills/google-sheets-decomposition/decompose.sh +313 -0
- package/.claude/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +237 -0
- package/.claude/cfn-extras/skills/google-sheets-formula-builder/build-formula.sh +220 -0
- package/.claude/cfn-extras/skills/google-sheets-formula-builder/test.sh +172 -0
- package/.claude/cfn-extras/skills/google-sheets-formula-builder/validate.sh +98 -0
- package/.claude/cfn-extras/skills/google-sheets-progress/SKILL.md +287 -0
- package/.claude/cfn-extras/skills/google-sheets-progress/test.sh +385 -0
- package/.claude/cfn-extras/skills/google-sheets-progress/track-progress.sh +516 -0
- package/.claude/cfn-extras/skills/google-sheets-progress/validate.sh +119 -0
- package/.claude/cfn-extras/skills/google-sheets-sprint-order/SKILL.md +277 -0
- package/.claude/cfn-extras/skills/google-sheets-sprint-order/order-sprints.sh +233 -0
- package/.claude/cfn-extras/skills/google-sheets-validation/SKILL.md +352 -0
- package/.claude/cfn-extras/skills/google-sheets-validation/test.sh +355 -0
- package/.claude/cfn-extras/skills/google-sheets-validation/validate-state.sh +374 -0
- package/.claude/cfn-extras/skills/google-sheets-validation/validate.sh +128 -0
- package/.claude/commands/cfn-context.md +10 -0
- package/.claude/commands/cfn-loop-cli.md +36 -15
- package/.claude/commands/google-sheets/google-sheets-loop.md +289 -0
- package/.claude/skills/cfn-agent-selector/SKILL.md +143 -0
- package/.claude/skills/cfn-agent-selector/select-agents.sh +94 -0
- package/.claude/skills/cfn-agent-spawning/get-agent-provider-env.sh +22 -2
- package/.claude/skills/cfn-docker-agent-spawning/spawn-agent.sh +21 -2
- package/.claude/skills/cfn-docker-loop-orchestration/orchestrate.sh +11 -5
- package/.claude/skills/cfn-docker-redis-coordination/MIGRATION_SUMMARY.md +348 -0
- package/.claude/skills/cfn-docker-redis-coordination/README.md +294 -0
- package/.claude/skills/cfn-docker-redis-coordination/jest.config.js +37 -0
- package/.claude/skills/cfn-docker-redis-coordination/package-lock.json +5259 -0
- package/.claude/skills/cfn-docker-redis-coordination/package.json +40 -0
- package/.claude/skills/cfn-docker-redis-coordination/src/coordinator.ts +801 -0
- package/.claude/skills/cfn-docker-redis-coordination/src/index.ts +42 -0
- package/.claude/skills/cfn-docker-redis-coordination/src/types.ts +351 -0
- package/.claude/skills/cfn-docker-redis-coordination/tests/coordinator.test.ts +1464 -0
- package/.claude/skills/cfn-docker-redis-coordination/tsconfig.json +30 -0
- package/.claude/skills/cfn-loop-orchestration/.eslintrc.js +56 -0
- package/.claude/skills/cfn-loop-orchestration/.prettierrc.json +18 -0
- package/.claude/skills/cfn-loop-orchestration/README.md +149 -41
- package/.claude/skills/cfn-loop-orchestration/jest.config.js +67 -0
- package/.claude/skills/cfn-loop-orchestration/orchestrate-wrapper.sh +268 -0
- package/.claude/skills/cfn-loop-orchestration/orchestrate.sh +91 -8
- package/.claude/skills/cfn-loop-orchestration/package-lock.json +5470 -0
- package/.claude/skills/cfn-loop-orchestration/package.json +49 -0
- package/.claude/skills/cfn-loop-orchestration/src/agent-spawner/agent-spawner.ts +34 -0
- package/.claude/skills/cfn-loop-orchestration/src/gate-checker/gate-checker.ts +36 -0
- package/.claude/skills/cfn-loop-orchestration/src/index.ts +14 -0
- package/.claude/skills/cfn-loop-orchestration/src/orchestrator/orchestrator.ts +31 -0
- package/.claude/skills/cfn-loop-orchestration/src/redis/redis-coordinator.ts +72 -0
- package/.claude/skills/cfn-loop-orchestration/src/types.ts +188 -0
- package/.claude/skills/cfn-loop-orchestration/src/utils/logger.ts +32 -0
- package/.claude/skills/cfn-loop-orchestration/tests/setup.ts +22 -0
- package/.claude/skills/cfn-loop-orchestration/tests/types.test.ts +132 -0
- package/.claude/skills/cfn-loop-orchestration/tsconfig.json +54 -0
- package/.claude/skills/cfn-redis-coordination/bash-wrappers/store-context.sh +23 -0
- package/.claude/skills/cfn-redis-coordination/dist/agent-logger.d.ts +92 -0
- package/.claude/skills/cfn-redis-coordination/dist/agent-logger.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/agent-logger.js +329 -0
- package/.claude/skills/cfn-redis-coordination/dist/agent-logger.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/agent-recovery.d.ts +75 -0
- package/.claude/skills/cfn-redis-coordination/dist/agent-recovery.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/agent-recovery.js +302 -0
- package/.claude/skills/cfn-redis-coordination/dist/agent-recovery.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/completion-reporter.d.ts +58 -0
- package/.claude/skills/cfn-redis-coordination/dist/completion-reporter.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/completion-reporter.js +237 -0
- package/.claude/skills/cfn-redis-coordination/dist/completion-reporter.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/context-manager.d.ts +63 -0
- package/.claude/skills/cfn-redis-coordination/dist/context-manager.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/context-manager.js +230 -0
- package/.claude/skills/cfn-redis-coordination/dist/context-manager.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/index.d.ts +45 -0
- package/.claude/skills/cfn-redis-coordination/dist/index.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/index.js +114 -0
- package/.claude/skills/cfn-redis-coordination/dist/index.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/mode-detector.d.ts +31 -0
- package/.claude/skills/cfn-redis-coordination/dist/mode-detector.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/mode-detector.js +185 -0
- package/.claude/skills/cfn-redis-coordination/dist/mode-detector.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/redis-client.d.ts +191 -0
- package/.claude/skills/cfn-redis-coordination/dist/redis-client.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/redis-client.js +509 -0
- package/.claude/skills/cfn-redis-coordination/dist/redis-client.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/result-collector.d.ts +75 -0
- package/.claude/skills/cfn-redis-coordination/dist/result-collector.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/result-collector.js +281 -0
- package/.claude/skills/cfn-redis-coordination/dist/result-collector.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/swarm-manager.d.ts +75 -0
- package/.claude/skills/cfn-redis-coordination/dist/swarm-manager.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/swarm-manager.js +354 -0
- package/.claude/skills/cfn-redis-coordination/dist/swarm-manager.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/task-analyzer.d.ts +62 -0
- package/.claude/skills/cfn-redis-coordination/dist/task-analyzer.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/task-analyzer.js +305 -0
- package/.claude/skills/cfn-redis-coordination/dist/task-analyzer.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/task-executor.d.ts +97 -0
- package/.claude/skills/cfn-redis-coordination/dist/task-executor.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/task-executor.js +283 -0
- package/.claude/skills/cfn-redis-coordination/dist/task-executor.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/types.d.ts +176 -0
- package/.claude/skills/cfn-redis-coordination/dist/types.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/types.js +81 -0
- package/.claude/skills/cfn-redis-coordination/dist/types.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/waiting-coordinator.d.ts +86 -0
- package/.claude/skills/cfn-redis-coordination/dist/waiting-coordinator.d.ts.map +1 -0
- package/.claude/skills/cfn-redis-coordination/dist/waiting-coordinator.js +419 -0
- package/.claude/skills/cfn-redis-coordination/dist/waiting-coordinator.js.map +1 -0
- package/.claude/skills/cfn-redis-coordination/docs/migration/PHASE_3_REDIS_COORDINATION_COMPLETION_REPORT.md +553 -0
- package/.claude/skills/cfn-redis-coordination/jest.config.js +23 -0
- package/.claude/skills/cfn-redis-coordination/package-lock.json +5272 -0
- package/.claude/skills/cfn-redis-coordination/package.json +45 -0
- package/.claude/skills/cfn-redis-coordination/redis-cli-wrapper.sh +21 -8
- package/.claude/skills/cfn-redis-coordination/src/agent-logger.ts +446 -0
- package/.claude/skills/cfn-redis-coordination/src/agent-recovery.ts +454 -0
- package/.claude/skills/cfn-redis-coordination/src/completion-reporter.ts +396 -0
- package/.claude/skills/cfn-redis-coordination/src/context-manager.ts +327 -0
- package/.claude/skills/cfn-redis-coordination/src/index.ts +82 -0
- package/.claude/skills/cfn-redis-coordination/src/mode-detector.ts +155 -0
- package/.claude/skills/cfn-redis-coordination/src/redis/redis-client.ts +305 -0
- package/.claude/skills/cfn-redis-coordination/src/redis/redis-functions.ts +283 -0
- package/.claude/skills/cfn-redis-coordination/src/redis-client.ts +654 -0
- package/.claude/skills/cfn-redis-coordination/src/result-collector.ts +437 -0
- package/.claude/skills/cfn-redis-coordination/src/swarm-manager.ts +494 -0
- package/.claude/skills/cfn-redis-coordination/src/task-analyzer.ts +404 -0
- package/.claude/skills/cfn-redis-coordination/src/task-executor.ts +423 -0
- package/.claude/skills/cfn-redis-coordination/src/types.ts +235 -0
- package/.claude/skills/cfn-redis-coordination/src/waiting-coordinator.ts +587 -0
- package/.claude/skills/cfn-redis-coordination/test-connection-attempts.js +70 -0
- package/.claude/skills/cfn-redis-coordination/test-mode-simple.js +121 -0
- package/.claude/skills/cfn-redis-coordination/test-redis-check.js +84 -0
- package/.claude/skills/cfn-redis-coordination/test-task-mode-redis.cjs +391 -0
- package/.claude/skills/cfn-redis-coordination/tests/coordination.test.ts +779 -0
- package/.claude/skills/cfn-redis-coordination/tsconfig.json +31 -0
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +172 -2
- package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +1 -1
- package/claude-assets/agents/custom/cfn-redis-operations.md +530 -0
- package/claude-assets/agents/custom/cfn-system-expert.md +77 -0
- package/claude-assets/cfn-extras/.gs-api-quota.json +16 -0
- package/claude-assets/cfn-extras/.gs-progress-state.json +22 -0
- package/claude-assets/cfn-extras/GOOGLE_SHEETS_IMPLEMENTATION_SUMMARY.md +414 -0
- package/claude-assets/cfn-extras/agents/google-sheets/README.md +114 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-advanced-analytics-specialist.md +288 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-api-integrator.md +127 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-automation-scripting-specialist.md +195 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-business-validator.md +179 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-collaboration-security-specialist.md +240 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-coordinator.md +214 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-data-transformer.md +127 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-data-validation-quality-specialist.md +177 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-data-validator.md +119 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-data-visualization-specialist.md +135 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-design-layout-specialist.md +109 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-formula-engineer.md +127 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-formula-engineering-specialist.md +138 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-formula-validator.md +128 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-generalist.md +645 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-integration-api-specialist.md +258 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-performance-analyst.md +125 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-performance-optimization-specialist.md +211 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-schema-designer.md +130 -0
- package/claude-assets/cfn-extras/agents/google-sheets/google-sheets-template-architecture-specialist.md +259 -0
- package/claude-assets/cfn-extras/docs/GOOGLE_SHEETS_CFN_LOOP.md +617 -0
- package/claude-assets/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +453 -0
- package/claude-assets/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +272 -0
- package/claude-assets/cfn-extras/skills/google-sheets-api-coordinator/api-call.sh +254 -0
- package/claude-assets/cfn-extras/skills/google-sheets-api-coordinator/test.sh +174 -0
- package/claude-assets/cfn-extras/skills/google-sheets-api-coordinator/validate.sh +98 -0
- package/claude-assets/cfn-extras/skills/google-sheets-decomposition/SKILL.md +269 -0
- package/claude-assets/cfn-extras/skills/google-sheets-decomposition/decompose.sh +313 -0
- package/claude-assets/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +237 -0
- package/claude-assets/cfn-extras/skills/google-sheets-formula-builder/build-formula.sh +220 -0
- package/claude-assets/cfn-extras/skills/google-sheets-formula-builder/test.sh +172 -0
- package/claude-assets/cfn-extras/skills/google-sheets-formula-builder/validate.sh +98 -0
- package/claude-assets/cfn-extras/skills/google-sheets-progress/SKILL.md +287 -0
- package/claude-assets/cfn-extras/skills/google-sheets-progress/test.sh +385 -0
- package/claude-assets/cfn-extras/skills/google-sheets-progress/track-progress.sh +516 -0
- package/claude-assets/cfn-extras/skills/google-sheets-progress/validate.sh +119 -0
- package/claude-assets/cfn-extras/skills/google-sheets-sprint-order/SKILL.md +277 -0
- package/claude-assets/cfn-extras/skills/google-sheets-sprint-order/order-sprints.sh +233 -0
- package/claude-assets/cfn-extras/skills/google-sheets-validation/SKILL.md +352 -0
- package/claude-assets/cfn-extras/skills/google-sheets-validation/test.sh +355 -0
- package/claude-assets/cfn-extras/skills/google-sheets-validation/validate-state.sh +374 -0
- package/claude-assets/cfn-extras/skills/google-sheets-validation/validate.sh +128 -0
- package/claude-assets/commands/cfn-context.md +10 -0
- package/claude-assets/commands/cfn-loop-cli.md +36 -15
- package/claude-assets/commands/google-sheets/google-sheets-loop.md +289 -0
- package/claude-assets/hooks/cfn-pre-execution/SESSION_START_README.md +87 -0
- package/claude-assets/hooks/cfn-pre-execution/TEST_SESSION_START.md +128 -0
- package/claude-assets/hooks/cfn-pre-execution/session-start-context.sh +111 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/INTEGRATION_EXAMPLE.md +209 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/README.md +130 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/SKILL.md +243 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/agent-mappings.json +142 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/select-agents.sh +173 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/task-classifier.sh +71 -0
- package/claude-assets/skills/cfn-agent-selection-with-fallback/test-agent-selection.sh +282 -0
- package/claude-assets/skills/cfn-agent-selector/SKILL.md +143 -0
- package/claude-assets/skills/cfn-agent-selector/select-agents.sh +94 -0
- package/claude-assets/skills/cfn-agent-spawning/get-agent-provider-env.sh +22 -2
- package/claude-assets/skills/cfn-docker-agent-spawning/spawn-agent.sh +21 -2
- package/claude-assets/skills/cfn-docker-coordination/.eslintrc.json +33 -0
- package/claude-assets/skills/cfn-docker-coordination/README.md +349 -0
- package/claude-assets/skills/cfn-docker-coordination/docker-helpers.sh +433 -0
- package/claude-assets/skills/cfn-docker-coordination/jest.config.js +25 -0
- package/claude-assets/skills/cfn-docker-coordination/package-lock.json +6827 -0
- package/claude-assets/skills/cfn-docker-coordination/package.json +38 -0
- package/claude-assets/skills/cfn-docker-coordination/src/agent-container.ts +471 -0
- package/claude-assets/skills/cfn-docker-coordination/src/docker-client.ts +483 -0
- package/claude-assets/skills/cfn-docker-coordination/src/health-checker.ts +418 -0
- package/claude-assets/skills/cfn-docker-coordination/src/index.ts +45 -0
- package/claude-assets/skills/cfn-docker-coordination/src/network-manager.ts +377 -0
- package/claude-assets/skills/cfn-docker-coordination/src/types.ts +412 -0
- package/claude-assets/skills/cfn-docker-coordination/src/volume-manager.ts +389 -0
- package/claude-assets/skills/cfn-docker-coordination/tests/agent-container.test.ts +379 -0
- package/claude-assets/skills/cfn-docker-coordination/tests/docker-client.test.ts +345 -0
- package/claude-assets/skills/cfn-docker-coordination/tests/health-checker.test.ts +535 -0
- package/claude-assets/skills/cfn-docker-coordination/tests/integration.test.ts +193 -0
- package/claude-assets/skills/cfn-docker-coordination/tests/network-manager.test.ts +352 -0
- package/claude-assets/skills/cfn-docker-coordination/tests/setup.ts +36 -0
- package/claude-assets/skills/cfn-docker-coordination/tsconfig.json +29 -0
- package/claude-assets/skills/cfn-docker-logging/INTEGRATION.md +268 -0
- package/claude-assets/skills/cfn-docker-logging/SAMPLE_OUTPUTS.md +237 -0
- package/claude-assets/skills/cfn-docker-logging/SKILL.md +442 -0
- package/claude-assets/skills/cfn-docker-logging/capture-container-logs.sh +120 -0
- package/claude-assets/skills/cfn-docker-logging/enable-logging.sh +430 -0
- package/claude-assets/skills/cfn-docker-logging/init-hybrid-logging.sh +210 -0
- package/claude-assets/skills/cfn-docker-logging/queries/analytics-summary.sh +87 -0
- package/claude-assets/skills/cfn-docker-logging/queries/query-agent-timeline.sh +51 -0
- package/claude-assets/skills/cfn-docker-logging/queries/query-consensus-history.sh +56 -0
- package/claude-assets/skills/cfn-docker-logging/queries/query-coordination-timeline.sh +39 -0
- package/claude-assets/skills/cfn-docker-logging/queries/query-failed-containers.sh +40 -0
- package/claude-assets/skills/cfn-docker-logging/queries/query-gate-checks.sh +39 -0
- package/claude-assets/skills/cfn-docker-logging/schema.sql +111 -0
- package/claude-assets/skills/cfn-docker-logging/sqlite-helpers.sh +240 -0
- package/claude-assets/skills/cfn-docker-logging/test-hybrid-logging.sh +331 -0
- package/claude-assets/skills/cfn-docker-loop-orchestration/orchestrate.sh +11 -5
- package/claude-assets/skills/cfn-docker-redis-coordination/MIGRATION_SUMMARY.md +348 -0
- package/claude-assets/skills/cfn-docker-redis-coordination/README.md +294 -0
- package/claude-assets/skills/cfn-docker-redis-coordination/jest.config.js +37 -0
- package/claude-assets/skills/cfn-docker-redis-coordination/package-lock.json +5259 -0
- package/claude-assets/skills/cfn-docker-redis-coordination/package.json +40 -0
- package/claude-assets/skills/cfn-docker-redis-coordination/src/coordinator.ts +801 -0
- package/claude-assets/skills/cfn-docker-redis-coordination/src/index.ts +42 -0
- package/claude-assets/skills/cfn-docker-redis-coordination/src/types.ts +351 -0
- package/claude-assets/skills/cfn-docker-redis-coordination/tests/coordinator.test.ts +1464 -0
- package/claude-assets/skills/cfn-docker-redis-coordination/tsconfig.json +30 -0
- package/claude-assets/skills/cfn-error-logging/.eslintrc.json +57 -0
- package/claude-assets/skills/cfn-error-logging/.prettierrc.json +10 -0
- package/claude-assets/skills/cfn-error-logging/MIGRATION_SUMMARY.md +485 -0
- package/claude-assets/skills/cfn-error-logging/package.json +47 -0
- package/claude-assets/skills/cfn-error-logging/src/error-logger.ts +1042 -0
- package/claude-assets/skills/cfn-error-logging/src/index.ts +12 -0
- package/claude-assets/skills/cfn-error-logging/src/types.ts +456 -0
- package/claude-assets/skills/cfn-error-logging/tests/error-logger.test.ts +1302 -0
- package/claude-assets/skills/cfn-error-logging/tsconfig.json +38 -0
- package/claude-assets/skills/cfn-loop-orchestration/.eslintrc.js +56 -0
- package/claude-assets/skills/cfn-loop-orchestration/.prettierrc.json +18 -0
- package/claude-assets/skills/cfn-loop-orchestration/README.md +149 -41
- package/claude-assets/skills/cfn-loop-orchestration/jest.config.js +67 -0
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate-wrapper.sh +268 -0
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh +91 -8
- package/claude-assets/skills/cfn-loop-orchestration/package-lock.json +5470 -0
- package/claude-assets/skills/cfn-loop-orchestration/package.json +49 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/agent-spawner/agent-spawner.ts +34 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/gate-checker/gate-checker.ts +36 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/index.ts +14 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/orchestrator/orchestrator.ts +31 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/redis/redis-coordinator.ts +72 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/types.ts +188 -0
- package/claude-assets/skills/cfn-loop-orchestration/src/utils/logger.ts +32 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/setup.ts +22 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/types.test.ts +132 -0
- package/claude-assets/skills/cfn-loop-orchestration/tsconfig.json +54 -0
- package/claude-assets/skills/cfn-redis-coordination/bash-wrappers/store-context.sh +23 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/agent-logger.d.ts +92 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/agent-logger.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/agent-logger.js +329 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/agent-logger.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/agent-recovery.d.ts +75 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/agent-recovery.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/agent-recovery.js +302 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/agent-recovery.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/completion-reporter.d.ts +58 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/completion-reporter.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/completion-reporter.js +237 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/completion-reporter.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/context-manager.d.ts +63 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/context-manager.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/context-manager.js +230 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/context-manager.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/index.d.ts +45 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/index.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/index.js +114 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/index.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/mode-detector.d.ts +31 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/mode-detector.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/mode-detector.js +185 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/mode-detector.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/redis-client.d.ts +191 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/redis-client.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/redis-client.js +509 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/redis-client.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/result-collector.d.ts +75 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/result-collector.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/result-collector.js +281 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/result-collector.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/swarm-manager.d.ts +75 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/swarm-manager.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/swarm-manager.js +354 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/swarm-manager.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/task-analyzer.d.ts +62 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/task-analyzer.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/task-analyzer.js +305 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/task-analyzer.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/task-executor.d.ts +97 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/task-executor.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/task-executor.js +283 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/task-executor.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/types.d.ts +176 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/types.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/types.js +81 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/types.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/waiting-coordinator.d.ts +86 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/waiting-coordinator.d.ts.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/waiting-coordinator.js +419 -0
- package/claude-assets/skills/cfn-redis-coordination/dist/waiting-coordinator.js.map +1 -0
- package/claude-assets/skills/cfn-redis-coordination/docs/migration/PHASE_3_REDIS_COORDINATION_COMPLETION_REPORT.md +553 -0
- package/claude-assets/skills/cfn-redis-coordination/jest.config.js +23 -0
- package/claude-assets/skills/cfn-redis-coordination/package-lock.json +5272 -0
- package/claude-assets/skills/cfn-redis-coordination/package.json +45 -0
- package/claude-assets/skills/cfn-redis-coordination/redis-cli-wrapper.sh +21 -8
- package/claude-assets/skills/cfn-redis-coordination/src/agent-logger.ts +446 -0
- package/claude-assets/skills/cfn-redis-coordination/src/agent-recovery.ts +454 -0
- package/claude-assets/skills/cfn-redis-coordination/src/completion-reporter.ts +396 -0
- package/claude-assets/skills/cfn-redis-coordination/src/context-manager.ts +327 -0
- package/claude-assets/skills/cfn-redis-coordination/src/index.ts +82 -0
- package/claude-assets/skills/cfn-redis-coordination/src/mode-detector.ts +155 -0
- package/claude-assets/skills/cfn-redis-coordination/src/redis/redis-client.ts +305 -0
- package/claude-assets/skills/cfn-redis-coordination/src/redis/redis-functions.ts +283 -0
- package/claude-assets/skills/cfn-redis-coordination/src/redis-client.ts +654 -0
- package/claude-assets/skills/cfn-redis-coordination/src/result-collector.ts +437 -0
- package/claude-assets/skills/cfn-redis-coordination/src/swarm-manager.ts +494 -0
- package/claude-assets/skills/cfn-redis-coordination/src/task-analyzer.ts +404 -0
- package/claude-assets/skills/cfn-redis-coordination/src/task-executor.ts +423 -0
- package/claude-assets/skills/cfn-redis-coordination/src/types.ts +235 -0
- package/claude-assets/skills/cfn-redis-coordination/src/waiting-coordinator.ts +587 -0
- package/claude-assets/skills/cfn-redis-coordination/test-connection-attempts.js +70 -0
- package/claude-assets/skills/cfn-redis-coordination/test-mode-simple.js +121 -0
- package/claude-assets/skills/cfn-redis-coordination/test-redis-check.js +84 -0
- package/claude-assets/skills/cfn-redis-coordination/test-task-mode-redis.cjs +391 -0
- package/claude-assets/skills/cfn-redis-coordination/tests/coordination.test.ts +779 -0
- package/claude-assets/skills/cfn-redis-coordination/tsconfig.json +31 -0
- package/claude-assets/skills/cfn-skill-propagation/README.md +233 -0
- package/claude-assets/skills/cfn-skill-propagation/package-lock.json +5174 -0
- package/claude-assets/skills/cfn-skill-propagation/package.json +52 -0
- package/claude-assets/skills/cfn-skill-propagation/propagate-skill-update.sh +32 -0
- package/claude-assets/skills/cfn-skill-propagation/src/cli.ts +75 -0
- package/claude-assets/skills/cfn-skill-propagation/src/database-adapter.ts +239 -0
- package/claude-assets/skills/cfn-skill-propagation/src/file-system-adapter.ts +113 -0
- package/claude-assets/skills/cfn-skill-propagation/src/index.ts +72 -0
- package/claude-assets/skills/cfn-skill-propagation/src/logger.ts +43 -0
- package/claude-assets/skills/cfn-skill-propagation/src/metadata-parser.ts +154 -0
- package/claude-assets/skills/cfn-skill-propagation/src/skill-propagator.ts +274 -0
- package/claude-assets/skills/cfn-skill-propagation/src/skill-validator.ts +179 -0
- package/claude-assets/skills/cfn-skill-propagation/src/types.ts +143 -0
- package/claude-assets/skills/cfn-skill-propagation/src/version-manager.ts +118 -0
- package/claude-assets/skills/cfn-skill-propagation/tests/file-system-adapter.test.ts +91 -0
- package/claude-assets/skills/cfn-skill-propagation/tests/metadata-parser.test.ts +176 -0
- package/claude-assets/skills/cfn-skill-propagation/tests/skill-propagator.test.ts +209 -0
- package/claude-assets/skills/cfn-skill-propagation/tests/skill-validator.test.ts +203 -0
- package/claude-assets/skills/cfn-skill-propagation/tests/version-manager.test.ts +115 -0
- package/claude-assets/skills/cfn-skill-propagation/tsconfig.json +34 -0
- package/claude-assets/skills/task-classifier/SKILL.md +81 -0
- package/claude-assets/skills/task-classifier/classify-task.sh +62 -0
- package/claude-assets/skills/workflow-codification/package-lock.json +5170 -0
- package/claude-assets/skills/workflow-codification/package.json +30 -0
- package/claude-assets/skills/workflow-codification/src/index.ts +24 -0
- package/claude-assets/skills/workflow-codification/src/pattern-analyzer.ts +537 -0
- package/claude-assets/skills/workflow-codification/src/types.ts +180 -0
- package/claude-assets/skills/workflow-codification/tests/pattern-analyzer.test.ts +960 -0
- package/claude-assets/skills/workflow-codification/tsconfig.json +34 -0
- package/claude-assets/skills/workflow-codification/workflow-codification.db +0 -0
- package/dist/agent-spawner/agent-spawner.js +448 -0
- package/dist/agent-spawner/agent-spawner.js.map +1 -0
- package/dist/agent-spawner/index.js +10 -0
- package/dist/agent-spawner/index.js.map +1 -0
- package/dist/agent-spawner/types.js +14 -0
- package/dist/agent-spawner/types.js.map +1 -0
- package/dist/agents/agent-loader.js +146 -165
- package/dist/agents/agent-loader.js.map +1 -1
- package/dist/cli/agent-executor.js +47 -1
- package/dist/cli/agent-executor.js.map +1 -1
- package/dist/cli/agent-spawn.js +4 -1
- package/dist/cli/agent-spawn.js.map +1 -1
- package/dist/cli/tool-executor.js +3 -1
- package/dist/cli/tool-executor.js.map +1 -1
- package/dist/gate-checker/gate-checker.js +292 -0
- package/dist/gate-checker/gate-checker.js.map +1 -0
- package/dist/gate-checker/types.js +94 -0
- package/dist/gate-checker/types.js.map +1 -0
- package/dist/lib/database-service/connection-pool-manager.js +2 -1
- package/dist/lib/database-service/connection-pool-manager.js.map +1 -1
- package/dist/orchestrator/index.js +10 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/orchestrate.js +496 -0
- package/dist/orchestrator/orchestrate.js.map +1 -0
- package/dist/orchestrator/types.js +58 -0
- package/dist/orchestrator/types.js.map +1 -0
- package/package.json +1 -1
- package/scripts/switch-api.sh +142 -4
- package/scripts/verify-no-secrets.sh +6 -13
- package/tests/README.md +175 -58
|
@@ -0,0 +1,1464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Coordinator Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive tests for RedisCoordinator with 90%+ code coverage
|
|
5
|
+
* Tests include:
|
|
6
|
+
* - Initialization and configuration
|
|
7
|
+
* - Agent registration and status management
|
|
8
|
+
* - Confidence tracking and completion signaling
|
|
9
|
+
* - Loop waiting and timeout handling
|
|
10
|
+
* - Consensus collection
|
|
11
|
+
* - Error handling and validation
|
|
12
|
+
* - Redis connection failures
|
|
13
|
+
* - Security validation
|
|
14
|
+
*
|
|
15
|
+
* @module cfn-docker-redis-coordination/tests/coordinator.test.ts
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { RedisCoordinator } from '../src/coordinator';
|
|
19
|
+
import {
|
|
20
|
+
CoordinatorConfig,
|
|
21
|
+
IRedisClient,
|
|
22
|
+
ILogger,
|
|
23
|
+
ValidationError,
|
|
24
|
+
SecurityError,
|
|
25
|
+
TimeoutError,
|
|
26
|
+
RedisConnectionError,
|
|
27
|
+
} from '../src/types';
|
|
28
|
+
|
|
29
|
+
// Mock Redis Client
|
|
30
|
+
class MockRedisClient implements IRedisClient {
|
|
31
|
+
private data: Record<string, Record<string, string>> = {};
|
|
32
|
+
private lists: Record<string, string[]> = {};
|
|
33
|
+
private sets: Record<string, Set<string>> = {};
|
|
34
|
+
private expirations: Map<string, number> = new Map();
|
|
35
|
+
|
|
36
|
+
async exists(key: string): Promise<boolean> {
|
|
37
|
+
return key in this.data || key in this.lists || key in this.sets;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async del(key: string): Promise<number> {
|
|
41
|
+
let deleted = 0;
|
|
42
|
+
if (key in this.data) {
|
|
43
|
+
delete this.data[key];
|
|
44
|
+
deleted++;
|
|
45
|
+
}
|
|
46
|
+
if (key in this.lists) {
|
|
47
|
+
delete this.lists[key];
|
|
48
|
+
deleted++;
|
|
49
|
+
}
|
|
50
|
+
if (key in this.sets) {
|
|
51
|
+
delete this.sets[key];
|
|
52
|
+
deleted++;
|
|
53
|
+
}
|
|
54
|
+
this.expirations.delete(key);
|
|
55
|
+
return deleted;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async keys(pattern: string): Promise<string[]> {
|
|
59
|
+
const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`);
|
|
60
|
+
const allKeys = [
|
|
61
|
+
...Object.keys(this.data),
|
|
62
|
+
...Object.keys(this.lists),
|
|
63
|
+
...Object.keys(this.sets),
|
|
64
|
+
];
|
|
65
|
+
return allKeys.filter((key) => regex.test(key));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async dbsize(): Promise<number> {
|
|
69
|
+
const allKeys = new Set([
|
|
70
|
+
...Object.keys(this.data),
|
|
71
|
+
...Object.keys(this.lists),
|
|
72
|
+
...Object.keys(this.sets),
|
|
73
|
+
]);
|
|
74
|
+
return allKeys.size;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async flushdb(): Promise<string> {
|
|
78
|
+
this.data = {};
|
|
79
|
+
this.lists = {};
|
|
80
|
+
this.sets = {};
|
|
81
|
+
this.expirations.clear();
|
|
82
|
+
return 'OK';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async get(key: string): Promise<string | null> {
|
|
86
|
+
// Get first value from hash (simplified)
|
|
87
|
+
const hash = this.data[key];
|
|
88
|
+
return hash ? Object.values(hash)[0] || null : null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async set(key: string, value: string): Promise<string> {
|
|
92
|
+
if (!this.data[key]) {
|
|
93
|
+
this.data[key] = {};
|
|
94
|
+
}
|
|
95
|
+
this.data[key]['value'] = value;
|
|
96
|
+
return 'OK';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async setex(
|
|
100
|
+
key: string,
|
|
101
|
+
seconds: number,
|
|
102
|
+
value: string
|
|
103
|
+
): Promise<string> {
|
|
104
|
+
await this.set(key, value);
|
|
105
|
+
await this.expire(key, seconds);
|
|
106
|
+
return 'OK';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async hget(key: string, field: string): Promise<string | null> {
|
|
110
|
+
const hash = this.data[key];
|
|
111
|
+
return hash ? (hash[field] || null) : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async hset(
|
|
115
|
+
key: string,
|
|
116
|
+
fields: Record<string, string | number | boolean>
|
|
117
|
+
): Promise<number> {
|
|
118
|
+
if (!this.data[key]) {
|
|
119
|
+
this.data[key] = {};
|
|
120
|
+
}
|
|
121
|
+
const hash = this.data[key];
|
|
122
|
+
let count = 0;
|
|
123
|
+
for (const [field, value] of Object.entries(fields)) {
|
|
124
|
+
hash[field] = String(value);
|
|
125
|
+
count++;
|
|
126
|
+
}
|
|
127
|
+
return count;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async hmset(
|
|
131
|
+
key: string,
|
|
132
|
+
fields: Record<string, string | number | boolean>
|
|
133
|
+
): Promise<string> {
|
|
134
|
+
await this.hset(key, fields);
|
|
135
|
+
return 'OK';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async hgetall(key: string): Promise<Record<string, string>> {
|
|
139
|
+
return this.data[key] || {};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async hkeys(key: string): Promise<string[]> {
|
|
143
|
+
const hash = this.data[key];
|
|
144
|
+
return hash ? Object.keys(hash) : [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async hvals(key: string): Promise<string[]> {
|
|
148
|
+
const hash = this.data[key];
|
|
149
|
+
return hash ? Object.values(hash) : [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async lpush(key: string, values: string[]): Promise<number> {
|
|
153
|
+
if (!this.lists[key]) {
|
|
154
|
+
this.lists[key] = [];
|
|
155
|
+
}
|
|
156
|
+
this.lists[key].unshift(...values);
|
|
157
|
+
return this.lists[key].length;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async rpush(key: string, values: string[]): Promise<number> {
|
|
161
|
+
if (!this.lists[key]) {
|
|
162
|
+
this.lists[key] = [];
|
|
163
|
+
}
|
|
164
|
+
this.lists[key].push(...values);
|
|
165
|
+
return this.lists[key].length;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async blpop(
|
|
169
|
+
keys: string[],
|
|
170
|
+
timeout: number
|
|
171
|
+
): Promise<[string, string] | null> {
|
|
172
|
+
for (const key of keys) {
|
|
173
|
+
const list = this.lists[key];
|
|
174
|
+
if (list && list.length > 0) {
|
|
175
|
+
const value = list.pop();
|
|
176
|
+
if (value) {
|
|
177
|
+
return [key, value];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async lrange(key: string, start: number, stop: number): Promise<string[]> {
|
|
185
|
+
const list = this.lists[key];
|
|
186
|
+
if (!list) return [];
|
|
187
|
+
// Handle negative indices
|
|
188
|
+
const len = list.length;
|
|
189
|
+
let actualStart = start < 0 ? Math.max(0, len + start) : start;
|
|
190
|
+
let actualStop = stop < 0 ? Math.max(-1, len + stop) : stop;
|
|
191
|
+
|
|
192
|
+
actualStart = Math.max(0, Math.min(actualStart, len - 1));
|
|
193
|
+
actualStop = Math.max(-1, Math.min(actualStop, len - 1));
|
|
194
|
+
|
|
195
|
+
if (actualStart > actualStop) return [];
|
|
196
|
+
return list.slice(actualStart, actualStop + 1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async sadd(key: string, members: string[]): Promise<number> {
|
|
200
|
+
if (!this.sets[key]) {
|
|
201
|
+
this.sets[key] = new Set();
|
|
202
|
+
}
|
|
203
|
+
let count = 0;
|
|
204
|
+
for (const member of members) {
|
|
205
|
+
if (!this.sets[key].has(member)) {
|
|
206
|
+
this.sets[key].add(member);
|
|
207
|
+
count++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return count;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async smembers(key: string): Promise<string[]> {
|
|
214
|
+
const set = this.sets[key];
|
|
215
|
+
return set ? Array.from(set) : [];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async scard(key: string): Promise<number> {
|
|
219
|
+
const set = this.sets[key];
|
|
220
|
+
return set ? set.size : 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async expire(key: string, seconds: number): Promise<number> {
|
|
224
|
+
if (await this.exists(key)) {
|
|
225
|
+
this.expirations.set(key, Date.now() + seconds * 1000);
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async pexpire(key: string, milliseconds: number): Promise<number> {
|
|
232
|
+
if (await this.exists(key)) {
|
|
233
|
+
this.expirations.set(key, Date.now() + milliseconds);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async ttl(key: string): Promise<number> {
|
|
240
|
+
const expiration = this.expirations.get(key);
|
|
241
|
+
if (!expiration) return -1;
|
|
242
|
+
const remaining = Math.floor((expiration - Date.now()) / 1000);
|
|
243
|
+
return remaining > 0 ? remaining : -2;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async ping(): Promise<string> {
|
|
247
|
+
return 'PONG';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async info(section?: string): Promise<string> {
|
|
251
|
+
if (section === 'memory') {
|
|
252
|
+
return '# Memory\nused_memory_human:1M\n';
|
|
253
|
+
}
|
|
254
|
+
return '# Server\nredis_version:6.0.0\n';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async quit(): Promise<void> {
|
|
258
|
+
this.data = {};
|
|
259
|
+
this.lists = {};
|
|
260
|
+
this.sets = {};
|
|
261
|
+
this.expirations.clear();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Mock Logger
|
|
266
|
+
class MockLogger implements ILogger {
|
|
267
|
+
public logs: Array<{ level: string; message: string }> = [];
|
|
268
|
+
|
|
269
|
+
log(message: string): void {
|
|
270
|
+
this.logs.push({ level: 'log', message });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
info(message: string): void {
|
|
274
|
+
this.logs.push({ level: 'info', message });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
warn(message: string): void {
|
|
278
|
+
this.logs.push({ level: 'warn', message });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
error(message: string): void {
|
|
282
|
+
this.logs.push({ level: 'error', message });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
debug(message: string): void {
|
|
286
|
+
this.logs.push({ level: 'debug', message });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
clear(): void {
|
|
290
|
+
this.logs = [];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
describe('RedisCoordinator', () => {
|
|
295
|
+
let coordinator: RedisCoordinator;
|
|
296
|
+
let mockRedis: MockRedisClient;
|
|
297
|
+
let mockLogger: MockLogger;
|
|
298
|
+
let config: CoordinatorConfig;
|
|
299
|
+
|
|
300
|
+
beforeEach(async () => {
|
|
301
|
+
mockRedis = new MockRedisClient();
|
|
302
|
+
mockLogger = new MockLogger();
|
|
303
|
+
config = {
|
|
304
|
+
redis: {
|
|
305
|
+
host: 'localhost',
|
|
306
|
+
port: 6379,
|
|
307
|
+
db: 0,
|
|
308
|
+
},
|
|
309
|
+
taskId: 'test-task-001',
|
|
310
|
+
defaultTimeout: 30,
|
|
311
|
+
defaultTTL: 3600,
|
|
312
|
+
mode: 'standard',
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
coordinator = new RedisCoordinator(config, mockLogger, mockRedis);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
afterEach(async () => {
|
|
319
|
+
await mockRedis.flushdb();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe('Constructor', () => {
|
|
323
|
+
it('should create coordinator with valid config', () => {
|
|
324
|
+
expect(coordinator).toBeDefined();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should throw ValidationError for invalid task ID', () => {
|
|
328
|
+
const invalidConfig = {
|
|
329
|
+
...config,
|
|
330
|
+
taskId: 'invalid-task-with-special-chars!@#',
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
expect(
|
|
334
|
+
() => new RedisCoordinator(invalidConfig, mockLogger, mockRedis)
|
|
335
|
+
).toThrow(ValidationError);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should throw ValidationError for empty task ID', () => {
|
|
339
|
+
const invalidConfig = {
|
|
340
|
+
...config,
|
|
341
|
+
taskId: '',
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
expect(
|
|
345
|
+
() => new RedisCoordinator(invalidConfig, mockLogger, mockRedis)
|
|
346
|
+
).toThrow(ValidationError);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should throw ValidationError for task ID exceeding max length', () => {
|
|
350
|
+
const invalidConfig = {
|
|
351
|
+
...config,
|
|
352
|
+
taskId: 'a'.repeat(257),
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
expect(
|
|
356
|
+
() => new RedisCoordinator(invalidConfig, mockLogger, mockRedis)
|
|
357
|
+
).toThrow(ValidationError);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe('initTask', () => {
|
|
362
|
+
it('should initialize task without context', async () => {
|
|
363
|
+
await coordinator.initTask();
|
|
364
|
+
|
|
365
|
+
const exists = await mockRedis.exists('cfn_docker:task:test-task-001:meta');
|
|
366
|
+
expect(exists).toBe(true);
|
|
367
|
+
|
|
368
|
+
const meta = await mockRedis.hgetall('cfn_docker:task:test-task-001:meta');
|
|
369
|
+
expect(meta.created_by).toBe('cfn-docker-redis-coordination');
|
|
370
|
+
expect(meta.mode).toBe('standard');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should initialize task with context', async () => {
|
|
374
|
+
const context = {
|
|
375
|
+
branch: 'main',
|
|
376
|
+
iteration: 1,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
await coordinator.initTask(context);
|
|
380
|
+
|
|
381
|
+
const contextData = await mockRedis.hgetall('cfn_docker:task:test-task-001:context');
|
|
382
|
+
expect(contextData.branch).toBe('main');
|
|
383
|
+
expect(contextData.iteration).toBe('1');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should set TTL on task metadata', async () => {
|
|
387
|
+
await coordinator.initTask();
|
|
388
|
+
|
|
389
|
+
const ttl = await mockRedis.ttl('cfn_docker:task:test-task-001:meta');
|
|
390
|
+
expect(ttl).toBeGreaterThan(0);
|
|
391
|
+
expect(ttl).toBeLessThanOrEqual(3600);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should throw SecurityError for oversized context value', async () => {
|
|
395
|
+
const coordinator2 = new RedisCoordinator(config, mockLogger, mockRedis);
|
|
396
|
+
|
|
397
|
+
const largeContext = {
|
|
398
|
+
oversized: 'x'.repeat(1024 * 1024 + 1), // Exceeds 1MB limit
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
await expect(coordinator2.initTask(largeContext)).rejects.toThrow(
|
|
402
|
+
SecurityError
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
describe('storeContext', () => {
|
|
408
|
+
it('should store context successfully', async () => {
|
|
409
|
+
const context = { key1: 'value1', key2: 'value2' };
|
|
410
|
+
|
|
411
|
+
await coordinator.storeContext(context);
|
|
412
|
+
|
|
413
|
+
const stored = await mockRedis.hgetall('cfn_docker:task:test-task-001:context');
|
|
414
|
+
expect(stored.key1).toBe('value1');
|
|
415
|
+
expect(stored.key2).toBe('value2');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('should set TTL on context', async () => {
|
|
419
|
+
await coordinator.storeContext({ test: 'value' });
|
|
420
|
+
|
|
421
|
+
const ttl = await mockRedis.ttl('cfn_docker:task:test-task-001:context');
|
|
422
|
+
expect(ttl).toBeGreaterThan(0);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('getContext', () => {
|
|
427
|
+
it('should retrieve stored context', async () => {
|
|
428
|
+
const context = { branch: 'develop', revision: '1a2b3c' };
|
|
429
|
+
await coordinator.storeContext(context);
|
|
430
|
+
|
|
431
|
+
const retrieved = await coordinator.getContext();
|
|
432
|
+
|
|
433
|
+
expect(retrieved.branch).toBe('develop');
|
|
434
|
+
expect(retrieved.revision).toBe('1a2b3c');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('should throw ValidationError if context not found', async () => {
|
|
438
|
+
await expect(coordinator.getContext()).rejects.toThrow(ValidationError);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe('registerAgent', () => {
|
|
443
|
+
it('should register agent successfully', async () => {
|
|
444
|
+
await coordinator.registerAgent('agent-001', 'backend-developer', 'container-123');
|
|
445
|
+
|
|
446
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
447
|
+
expect(agentData.agent_id).toBe('agent-001');
|
|
448
|
+
expect(agentData.agent_type).toBe('backend-developer');
|
|
449
|
+
expect(agentData.container_id).toBe('container-123');
|
|
450
|
+
expect(agentData.status).toBe('spawning');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('should register agent without container ID', async () => {
|
|
454
|
+
await coordinator.registerAgent('agent-002', 'frontend-developer');
|
|
455
|
+
|
|
456
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-002');
|
|
457
|
+
expect(agentData.agent_id).toBe('agent-002');
|
|
458
|
+
expect(agentData.container_id).toBe('');
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should add agent to status history', async () => {
|
|
462
|
+
await coordinator.registerAgent('agent-003', 'tester');
|
|
463
|
+
|
|
464
|
+
const history = await mockRedis.lrange(
|
|
465
|
+
'cfn_docker:agent:agent-003:status_history',
|
|
466
|
+
0,
|
|
467
|
+
-1
|
|
468
|
+
);
|
|
469
|
+
expect(history.length).toBeGreaterThan(0);
|
|
470
|
+
|
|
471
|
+
const entry = JSON.parse(history[0]);
|
|
472
|
+
expect(entry.status).toBe('spawning');
|
|
473
|
+
expect(entry.timestamp).toBeDefined();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('should throw ValidationError for invalid agent ID', async () => {
|
|
477
|
+
await expect(
|
|
478
|
+
coordinator.registerAgent('invalid@agent!', 'developer')
|
|
479
|
+
).rejects.toThrow(ValidationError);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('should throw ValidationError for empty agent ID', async () => {
|
|
483
|
+
await expect(coordinator.registerAgent('', 'developer')).rejects.toThrow(
|
|
484
|
+
ValidationError
|
|
485
|
+
);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('should set TTL on agent data', async () => {
|
|
489
|
+
await coordinator.registerAgent('agent-004', 'developer');
|
|
490
|
+
|
|
491
|
+
const ttl = await mockRedis.ttl('cfn_docker:agent:agent-004');
|
|
492
|
+
expect(ttl).toBeGreaterThan(0);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
describe('updateStatus', () => {
|
|
497
|
+
beforeEach(async () => {
|
|
498
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('should update agent status to running', async () => {
|
|
502
|
+
await coordinator.updateStatus('agent-001', 'running', 1);
|
|
503
|
+
|
|
504
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
505
|
+
expect(agentData.status).toBe('running');
|
|
506
|
+
expect(agentData.iteration).toBe('1');
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('should update agent status to completed', async () => {
|
|
510
|
+
await coordinator.updateStatus('agent-001', 'completed', 2);
|
|
511
|
+
|
|
512
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
513
|
+
expect(agentData.status).toBe('completed');
|
|
514
|
+
expect(agentData.iteration).toBe('2');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should add status to history', async () => {
|
|
518
|
+
await coordinator.updateStatus('agent-001', 'running');
|
|
519
|
+
|
|
520
|
+
const history = await mockRedis.lrange(
|
|
521
|
+
'cfn_docker:agent:agent-001:status_history',
|
|
522
|
+
0,
|
|
523
|
+
-1
|
|
524
|
+
);
|
|
525
|
+
expect(history.length).toBeGreaterThanOrEqual(2); // Initial spawning + running
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('should throw ValidationError for invalid agent ID', async () => {
|
|
529
|
+
await expect(
|
|
530
|
+
coordinator.updateStatus('invalid@id', 'running')
|
|
531
|
+
).rejects.toThrow(ValidationError);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('should throw ValidationError for invalid status', async () => {
|
|
535
|
+
await expect(
|
|
536
|
+
coordinator.updateStatus('agent-001', 'invalid-status' as any)
|
|
537
|
+
).rejects.toThrow(ValidationError);
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe('signalComplete', () => {
|
|
542
|
+
beforeEach(async () => {
|
|
543
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('should signal completion with confidence', async () => {
|
|
547
|
+
await coordinator.signalComplete('agent-001', 0.85, 1);
|
|
548
|
+
|
|
549
|
+
const statusKey = 'cfn_docker:task:test-task-001:agent:agent-001:done';
|
|
550
|
+
const exists = await mockRedis.exists(statusKey);
|
|
551
|
+
expect(exists).toBe(true);
|
|
552
|
+
|
|
553
|
+
const confidenceKey =
|
|
554
|
+
'cfn_docker:task:test-task-001:confidence:agent-001';
|
|
555
|
+
const confidenceData = await mockRedis.hgetall(confidenceKey);
|
|
556
|
+
expect(confidenceData.confidence).toBe('0.85');
|
|
557
|
+
expect(confidenceData.iteration).toBe('1');
|
|
558
|
+
expect(confidenceData.agent_type).toBe('developer');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('should signal completion with confidence 1.0', async () => {
|
|
562
|
+
await coordinator.signalComplete('agent-001', 1.0, 1);
|
|
563
|
+
|
|
564
|
+
const confidenceKey =
|
|
565
|
+
'cfn_docker:task:test-task-001:confidence:agent-001';
|
|
566
|
+
const confidenceData = await mockRedis.hgetall(confidenceKey);
|
|
567
|
+
expect(confidenceData.confidence).toBe('1');
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('should signal completion with confidence 0.0', async () => {
|
|
571
|
+
await coordinator.signalComplete('agent-001', 0.0, 1);
|
|
572
|
+
|
|
573
|
+
const confidenceKey =
|
|
574
|
+
'cfn_docker:task:test-task-001:confidence:agent-001';
|
|
575
|
+
const confidenceData = await mockRedis.hgetall(confidenceKey);
|
|
576
|
+
expect(confidenceData.confidence).toBe('0');
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should update agent status to completed', async () => {
|
|
580
|
+
await coordinator.signalComplete('agent-001', 0.85, 1);
|
|
581
|
+
|
|
582
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
583
|
+
expect(agentData.status).toBe('completed');
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('should throw ValidationError for invalid confidence < 0', async () => {
|
|
587
|
+
await expect(
|
|
588
|
+
coordinator.signalComplete('agent-001', -0.1)
|
|
589
|
+
).rejects.toThrow(ValidationError);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should throw ValidationError for invalid confidence > 1', async () => {
|
|
593
|
+
await expect(
|
|
594
|
+
coordinator.signalComplete('agent-001', 1.1)
|
|
595
|
+
).rejects.toThrow(ValidationError);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should throw ValidationError for invalid agent ID', async () => {
|
|
599
|
+
await expect(
|
|
600
|
+
coordinator.signalComplete('invalid@id', 0.85)
|
|
601
|
+
).rejects.toThrow(ValidationError);
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
describe('waitLoop', () => {
|
|
606
|
+
beforeEach(async () => {
|
|
607
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
608
|
+
await coordinator.registerAgent('agent-002', 'developer');
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('should wait for loop completion when agents complete', async () => {
|
|
612
|
+
// Simulate agent completion
|
|
613
|
+
await coordinator.signalComplete('agent-001', 0.85);
|
|
614
|
+
await coordinator.signalComplete('agent-002', 0.90);
|
|
615
|
+
|
|
616
|
+
const result = await coordinator.waitLoop({
|
|
617
|
+
taskId: 'test-task-001',
|
|
618
|
+
loopNumber: 3,
|
|
619
|
+
agentCount: 2,
|
|
620
|
+
timeout: 5,
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
expect(result.success).toBe(true);
|
|
624
|
+
expect(result.completedAgents).toBe(2);
|
|
625
|
+
expect(result.expectedAgents).toBe(2);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it('should timeout when not all agents complete', async () => {
|
|
629
|
+
await coordinator.signalComplete('agent-001', 0.85);
|
|
630
|
+
|
|
631
|
+
const result = await coordinator.waitLoop({
|
|
632
|
+
taskId: 'test-task-001',
|
|
633
|
+
loopNumber: 3,
|
|
634
|
+
agentCount: 2,
|
|
635
|
+
timeout: 1,
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
expect(result.success).toBe(false);
|
|
639
|
+
expect(result.completedAgents).toBeLessThan(2);
|
|
640
|
+
expect(result.message).toContain('timeout');
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
it('should throw ValidationError for invalid task ID', async () => {
|
|
644
|
+
await expect(
|
|
645
|
+
coordinator.waitLoop({
|
|
646
|
+
taskId: 'invalid@task',
|
|
647
|
+
loopNumber: 3,
|
|
648
|
+
agentCount: 2,
|
|
649
|
+
})
|
|
650
|
+
).rejects.toThrow(ValidationError);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it('should throw ValidationError for invalid loop number < 1', async () => {
|
|
654
|
+
await expect(
|
|
655
|
+
coordinator.waitLoop({
|
|
656
|
+
taskId: 'test-task-001',
|
|
657
|
+
loopNumber: 0,
|
|
658
|
+
agentCount: 2,
|
|
659
|
+
})
|
|
660
|
+
).rejects.toThrow(ValidationError);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it('should throw ValidationError for invalid loop number > 4', async () => {
|
|
664
|
+
await expect(
|
|
665
|
+
coordinator.waitLoop({
|
|
666
|
+
taskId: 'test-task-001',
|
|
667
|
+
loopNumber: 5,
|
|
668
|
+
agentCount: 2,
|
|
669
|
+
})
|
|
670
|
+
).rejects.toThrow(ValidationError);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('should throw ValidationError for invalid agent count < 1', async () => {
|
|
674
|
+
await expect(
|
|
675
|
+
coordinator.waitLoop({
|
|
676
|
+
taskId: 'test-task-001',
|
|
677
|
+
loopNumber: 3,
|
|
678
|
+
agentCount: 0,
|
|
679
|
+
})
|
|
680
|
+
).rejects.toThrow(ValidationError);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('should throw ValidationError for timeout out of range', async () => {
|
|
684
|
+
await expect(
|
|
685
|
+
coordinator.waitLoop({
|
|
686
|
+
taskId: 'test-task-001',
|
|
687
|
+
loopNumber: 3,
|
|
688
|
+
agentCount: 2,
|
|
689
|
+
timeout: 0,
|
|
690
|
+
})
|
|
691
|
+
).rejects.toThrow(ValidationError);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('should throw ValidationError for timeout exceeding max', async () => {
|
|
695
|
+
await expect(
|
|
696
|
+
coordinator.waitLoop({
|
|
697
|
+
taskId: 'test-task-001',
|
|
698
|
+
loopNumber: 3,
|
|
699
|
+
agentCount: 2,
|
|
700
|
+
timeout: 4000,
|
|
701
|
+
})
|
|
702
|
+
).rejects.toThrow(ValidationError);
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
describe('collectConsensus', () => {
|
|
707
|
+
beforeEach(async () => {
|
|
708
|
+
// Register validators and simulate completion
|
|
709
|
+
for (let i = 1; i <= 3; i++) {
|
|
710
|
+
await coordinator.registerAgent(`validator-${i}`, 'validator');
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('should collect consensus when threshold met', async () => {
|
|
715
|
+
await coordinator.signalComplete('validator-1', 0.95, 1);
|
|
716
|
+
await coordinator.signalComplete('validator-2', 0.90, 1);
|
|
717
|
+
await coordinator.signalComplete('validator-3', 0.92, 1);
|
|
718
|
+
|
|
719
|
+
const result = await coordinator.collectConsensus({
|
|
720
|
+
taskId: 'test-task-001',
|
|
721
|
+
loopNumber: 2,
|
|
722
|
+
requiredConsensus: 0.90,
|
|
723
|
+
timeout: 5,
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
expect(result.success).toBe(true);
|
|
727
|
+
expect(result.responsesReceived).toBe(3);
|
|
728
|
+
expect(result.averageConfidence).toBeGreaterThanOrEqual(0.90);
|
|
729
|
+
expect(result.consensusReached).toBe(true);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('should return PROCEED decision for high confidence', async () => {
|
|
733
|
+
await coordinator.signalComplete('validator-1', 0.92, 1);
|
|
734
|
+
await coordinator.signalComplete('validator-2', 0.91, 1);
|
|
735
|
+
|
|
736
|
+
const result = await coordinator.collectConsensus({
|
|
737
|
+
taskId: 'test-task-001',
|
|
738
|
+
loopNumber: 2,
|
|
739
|
+
requiredConsensus: 0.90,
|
|
740
|
+
timeout: 5,
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
expect(result.decision).toBe('PROCEED');
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it('should return COMPLETE decision for very high confidence', async () => {
|
|
747
|
+
await coordinator.signalComplete('validator-1', 0.98, 1);
|
|
748
|
+
await coordinator.signalComplete('validator-2', 0.96, 1);
|
|
749
|
+
|
|
750
|
+
const result = await coordinator.collectConsensus({
|
|
751
|
+
taskId: 'test-task-001',
|
|
752
|
+
loopNumber: 2,
|
|
753
|
+
requiredConsensus: 0.90,
|
|
754
|
+
timeout: 5,
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
expect(result.decision).toBe('COMPLETE');
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it('should timeout when consensus not reached', async () => {
|
|
761
|
+
await coordinator.signalComplete('validator-1', 0.70, 1);
|
|
762
|
+
|
|
763
|
+
const result = await coordinator.collectConsensus({
|
|
764
|
+
taskId: 'test-task-001',
|
|
765
|
+
loopNumber: 2,
|
|
766
|
+
requiredConsensus: 0.90,
|
|
767
|
+
timeout: 1,
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
expect(result.success).toBe(false);
|
|
771
|
+
expect(result.consensusReached).toBe(false);
|
|
772
|
+
expect(result.decision).toBe('ABORT');
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('should throw ValidationError for invalid task ID', async () => {
|
|
776
|
+
await expect(
|
|
777
|
+
coordinator.collectConsensus({
|
|
778
|
+
taskId: 'invalid@task',
|
|
779
|
+
loopNumber: 2,
|
|
780
|
+
requiredConsensus: 0.90,
|
|
781
|
+
})
|
|
782
|
+
).rejects.toThrow(ValidationError);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('should throw ValidationError for invalid loop number', async () => {
|
|
786
|
+
await expect(
|
|
787
|
+
coordinator.collectConsensus({
|
|
788
|
+
taskId: 'test-task-001',
|
|
789
|
+
loopNumber: 0,
|
|
790
|
+
requiredConsensus: 0.90,
|
|
791
|
+
})
|
|
792
|
+
).rejects.toThrow(ValidationError);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('should throw ValidationError for invalid consensus < 0', async () => {
|
|
796
|
+
await expect(
|
|
797
|
+
coordinator.collectConsensus({
|
|
798
|
+
taskId: 'test-task-001',
|
|
799
|
+
loopNumber: 2,
|
|
800
|
+
requiredConsensus: -0.1,
|
|
801
|
+
})
|
|
802
|
+
).rejects.toThrow(ValidationError);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it('should throw ValidationError for invalid consensus > 1', async () => {
|
|
806
|
+
await expect(
|
|
807
|
+
coordinator.collectConsensus({
|
|
808
|
+
taskId: 'test-task-001',
|
|
809
|
+
loopNumber: 2,
|
|
810
|
+
requiredConsensus: 1.1,
|
|
811
|
+
})
|
|
812
|
+
).rejects.toThrow(ValidationError);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('should throw ValidationError for timeout out of range', async () => {
|
|
816
|
+
await expect(
|
|
817
|
+
coordinator.collectConsensus({
|
|
818
|
+
taskId: 'test-task-001',
|
|
819
|
+
loopNumber: 2,
|
|
820
|
+
requiredConsensus: 0.90,
|
|
821
|
+
timeout: 0,
|
|
822
|
+
})
|
|
823
|
+
).rejects.toThrow(ValidationError);
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
describe('healthCheck', () => {
|
|
828
|
+
it('should pass health check', async () => {
|
|
829
|
+
await expect(coordinator.healthCheck()).resolves.not.toThrow();
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
it('should log health check information', async () => {
|
|
833
|
+
mockLogger.clear();
|
|
834
|
+
await coordinator.healthCheck();
|
|
835
|
+
|
|
836
|
+
const logMessages = mockLogger.logs.map((l) => l.message);
|
|
837
|
+
expect(logMessages.some((msg) => msg.includes('health check'))).toBe(true);
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
describe('cleanup', () => {
|
|
842
|
+
it('should clean up task data', async () => {
|
|
843
|
+
await coordinator.initTask({ test: 'value' });
|
|
844
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
845
|
+
|
|
846
|
+
await coordinator.cleanup();
|
|
847
|
+
|
|
848
|
+
const taskExists = await mockRedis.exists(
|
|
849
|
+
'cfn_docker:task:test-task-001:meta'
|
|
850
|
+
);
|
|
851
|
+
expect(taskExists).toBe(false);
|
|
852
|
+
});
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
describe('disconnect', () => {
|
|
856
|
+
it('should disconnect from Redis', async () => {
|
|
857
|
+
await expect(coordinator.disconnect()).resolves.not.toThrow();
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
describe('Error handling', () => {
|
|
862
|
+
it('should handle Redis connection errors gracefully', async () => {
|
|
863
|
+
const failingRedis: IRedisClient = {
|
|
864
|
+
...mockRedis,
|
|
865
|
+
exists: jest.fn().mockRejectedValue(new Error('Connection failed')),
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const failingCoordinator = new RedisCoordinator(
|
|
869
|
+
config,
|
|
870
|
+
mockLogger,
|
|
871
|
+
failingRedis
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
await expect(failingCoordinator.getContext()).rejects.toThrow();
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
it('should catch and log errors in initTask', async () => {
|
|
878
|
+
const failingRedis: IRedisClient = {
|
|
879
|
+
...mockRedis,
|
|
880
|
+
hset: jest.fn().mockRejectedValue(new Error('Storage failed')),
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
const failingCoordinator = new RedisCoordinator(
|
|
884
|
+
config,
|
|
885
|
+
mockLogger,
|
|
886
|
+
failingRedis
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
mockLogger.clear();
|
|
890
|
+
await expect(failingCoordinator.initTask()).rejects.toThrow();
|
|
891
|
+
expect(
|
|
892
|
+
mockLogger.logs.some((l) => l.level === 'error')
|
|
893
|
+
).toBe(true);
|
|
894
|
+
});
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
describe('Confidence validation', () => {
|
|
898
|
+
beforeEach(async () => {
|
|
899
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
it('should accept confidence of 0.0', async () => {
|
|
903
|
+
await expect(
|
|
904
|
+
coordinator.signalComplete('agent-001', 0.0)
|
|
905
|
+
).resolves.not.toThrow();
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
it('should accept confidence of 1.0', async () => {
|
|
909
|
+
await expect(
|
|
910
|
+
coordinator.signalComplete('agent-001', 1.0)
|
|
911
|
+
).resolves.not.toThrow();
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it('should accept confidence of 0.5', async () => {
|
|
915
|
+
await expect(
|
|
916
|
+
coordinator.signalComplete('agent-001', 0.5)
|
|
917
|
+
).resolves.not.toThrow();
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
it('should reject NaN confidence', async () => {
|
|
921
|
+
await expect(
|
|
922
|
+
coordinator.signalComplete('agent-001', NaN)
|
|
923
|
+
).rejects.toThrow(ValidationError);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
it('should reject Infinity confidence', async () => {
|
|
927
|
+
await expect(
|
|
928
|
+
coordinator.signalComplete('agent-001', Infinity)
|
|
929
|
+
).rejects.toThrow(ValidationError);
|
|
930
|
+
});
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
describe('Agent ID validation', () => {
|
|
934
|
+
it('should accept alphanumeric agent IDs', async () => {
|
|
935
|
+
await expect(
|
|
936
|
+
coordinator.registerAgent('agent123', 'developer')
|
|
937
|
+
).resolves.not.toThrow();
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
it('should accept agent IDs with hyphens', async () => {
|
|
941
|
+
await expect(
|
|
942
|
+
coordinator.registerAgent('agent-001-backend', 'developer')
|
|
943
|
+
).resolves.not.toThrow();
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
it('should accept agent IDs with underscores', async () => {
|
|
947
|
+
await expect(
|
|
948
|
+
coordinator.registerAgent('agent_001_backend', 'developer')
|
|
949
|
+
).resolves.not.toThrow();
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
it('should reject agent IDs with special characters', async () => {
|
|
953
|
+
await expect(
|
|
954
|
+
coordinator.registerAgent('agent@001!', 'developer')
|
|
955
|
+
).rejects.toThrow(ValidationError);
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
it('should reject agent IDs with spaces', async () => {
|
|
959
|
+
await expect(
|
|
960
|
+
coordinator.registerAgent('agent 001', 'developer')
|
|
961
|
+
).rejects.toThrow(ValidationError);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
it('should reject overly long agent IDs', async () => {
|
|
965
|
+
const longId = 'a'.repeat(257);
|
|
966
|
+
await expect(
|
|
967
|
+
coordinator.registerAgent(longId, 'developer')
|
|
968
|
+
).rejects.toThrow(ValidationError);
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
describe('Mode-specific behavior', () => {
|
|
973
|
+
it('should initialize with standard mode', () => {
|
|
974
|
+
const stdConfig = { ...config, mode: 'standard' as const };
|
|
975
|
+
const stdCoordinator = new RedisCoordinator(
|
|
976
|
+
stdConfig,
|
|
977
|
+
mockLogger,
|
|
978
|
+
mockRedis
|
|
979
|
+
);
|
|
980
|
+
expect(stdCoordinator).toBeDefined();
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
it('should initialize with MVP mode', () => {
|
|
984
|
+
const mvpConfig = { ...config, mode: 'mvp' as const };
|
|
985
|
+
const mvpCoordinator = new RedisCoordinator(
|
|
986
|
+
mvpConfig,
|
|
987
|
+
mockLogger,
|
|
988
|
+
mockRedis
|
|
989
|
+
);
|
|
990
|
+
expect(mvpCoordinator).toBeDefined();
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
it('should initialize with enterprise mode', () => {
|
|
994
|
+
const enterpriseConfig = { ...config, mode: 'enterprise' as const };
|
|
995
|
+
const enterpriseCoordinator = new RedisCoordinator(
|
|
996
|
+
enterpriseConfig,
|
|
997
|
+
mockLogger,
|
|
998
|
+
mockRedis
|
|
999
|
+
);
|
|
1000
|
+
expect(enterpriseCoordinator).toBeDefined();
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
describe('State persistence', () => {
|
|
1005
|
+
it('should persist agent registration across operations', async () => {
|
|
1006
|
+
await coordinator.registerAgent('agent-001', 'developer', 'container-123');
|
|
1007
|
+
await coordinator.updateStatus('agent-001', 'running');
|
|
1008
|
+
|
|
1009
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
1010
|
+
expect(agentData.agent_id).toBe('agent-001');
|
|
1011
|
+
expect(agentData.status).toBe('running');
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
it('should maintain confidence history', async () => {
|
|
1015
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1016
|
+
await coordinator.signalComplete('agent-001', 0.85);
|
|
1017
|
+
await coordinator.updateStatus('agent-001', 'running');
|
|
1018
|
+
await coordinator.signalComplete('agent-001', 0.90);
|
|
1019
|
+
|
|
1020
|
+
const history = await mockRedis.lrange(
|
|
1021
|
+
'cfn_docker:agent:agent-001:status_history',
|
|
1022
|
+
0,
|
|
1023
|
+
-1
|
|
1024
|
+
);
|
|
1025
|
+
expect(history.length).toBeGreaterThanOrEqual(3);
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
describe('Additional coverage tests', () => {
|
|
1030
|
+
it('should handle empty agent list in waitLoop', async () => {
|
|
1031
|
+
const result = await coordinator.waitLoop({
|
|
1032
|
+
taskId: 'test-task-001',
|
|
1033
|
+
loopNumber: 3,
|
|
1034
|
+
agentCount: 1,
|
|
1035
|
+
timeout: 1,
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
expect(result.success).toBe(false);
|
|
1039
|
+
expect(result.completedAgents).toBe(0);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it('should handle agents with special types', async () => {
|
|
1043
|
+
await coordinator.registerAgent('agent-001', 'tester');
|
|
1044
|
+
await coordinator.registerAgent('agent-002', 'validator');
|
|
1045
|
+
await coordinator.registerAgent('agent-003', 'product-owner');
|
|
1046
|
+
|
|
1047
|
+
await coordinator.signalComplete('agent-001', 0.88);
|
|
1048
|
+
await coordinator.signalComplete('agent-002', 0.91);
|
|
1049
|
+
await coordinator.signalComplete('agent-003', 0.87);
|
|
1050
|
+
|
|
1051
|
+
const agent1 = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
1052
|
+
expect(agent1.agent_type).toBe('tester');
|
|
1053
|
+
|
|
1054
|
+
const agent2 = await mockRedis.hgetall('cfn_docker:agent:agent-002');
|
|
1055
|
+
expect(agent2.agent_type).toBe('validator');
|
|
1056
|
+
|
|
1057
|
+
const agent3 = await mockRedis.hgetall('cfn_docker:agent:agent-003');
|
|
1058
|
+
expect(agent3.agent_type).toBe('product-owner');
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
it('should handle multiple status updates in sequence', async () => {
|
|
1062
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1063
|
+
|
|
1064
|
+
const statuses: AgentStatus[] = ['running', 'working', 'completed'];
|
|
1065
|
+
for (let i = 0; i < statuses.length; i++) {
|
|
1066
|
+
await coordinator.updateStatus('agent-001', statuses[i], i + 1);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
1070
|
+
expect(agentData.status).toBe('completed');
|
|
1071
|
+
expect(agentData.iteration).toBe('3');
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
it('should correctly store and retrieve consensus with metadata', async () => {
|
|
1075
|
+
for (let i = 1; i <= 5; i++) {
|
|
1076
|
+
await coordinator.registerAgent(`validator-${i}`, 'validator');
|
|
1077
|
+
const confidence = 0.85 + i * 0.02;
|
|
1078
|
+
await coordinator.signalComplete(`validator-${i}`, confidence);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const result = await coordinator.collectConsensus({
|
|
1082
|
+
taskId: 'test-task-001',
|
|
1083
|
+
loopNumber: 2,
|
|
1084
|
+
requiredConsensus: 0.88,
|
|
1085
|
+
timeout: 5,
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
expect(result.success).toBe(true);
|
|
1089
|
+
expect(result.responsesReceived).toBeGreaterThan(0);
|
|
1090
|
+
expect(result.averageConfidence).toBeGreaterThanOrEqual(0.88);
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
it('should handle zero iteration in updateStatus', async () => {
|
|
1094
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1095
|
+
await coordinator.updateStatus('agent-001', 'running', 0);
|
|
1096
|
+
|
|
1097
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
1098
|
+
expect(agentData.iteration).toBe('0');
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
it('should handle large iteration numbers', async () => {
|
|
1102
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1103
|
+
const largeIteration = 99;
|
|
1104
|
+
await coordinator.updateStatus('agent-001', 'completed', largeIteration);
|
|
1105
|
+
|
|
1106
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
1107
|
+
expect(agentData.iteration).toBe('99');
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
it('should handle context with numeric and boolean values', async () => {
|
|
1111
|
+
const context = {
|
|
1112
|
+
branch: 'main',
|
|
1113
|
+
iteration: 5,
|
|
1114
|
+
success: true,
|
|
1115
|
+
failed: false,
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
await coordinator.storeContext(context);
|
|
1119
|
+
const retrieved = await coordinator.getContext();
|
|
1120
|
+
|
|
1121
|
+
expect(retrieved.branch).toBe('main');
|
|
1122
|
+
expect(retrieved.iteration).toBe('5');
|
|
1123
|
+
expect(retrieved.success).toBe('true');
|
|
1124
|
+
expect(retrieved.failed).toBe('false');
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
it('should handle consensus with single validator', async () => {
|
|
1128
|
+
await coordinator.registerAgent('validator-1', 'validator');
|
|
1129
|
+
await coordinator.signalComplete('validator-1', 0.95);
|
|
1130
|
+
|
|
1131
|
+
const result = await coordinator.collectConsensus({
|
|
1132
|
+
taskId: 'test-task-001',
|
|
1133
|
+
loopNumber: 2,
|
|
1134
|
+
requiredConsensus: 0.90,
|
|
1135
|
+
timeout: 5,
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
expect(result.success).toBe(true);
|
|
1139
|
+
expect(result.responsesReceived).toBe(1);
|
|
1140
|
+
expect(result.averageConfidence).toBe(0.95);
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
it('should handle partial consensus collection', async () => {
|
|
1144
|
+
await coordinator.registerAgent('validator-1', 'validator');
|
|
1145
|
+
await coordinator.registerAgent('validator-2', 'validator');
|
|
1146
|
+
|
|
1147
|
+
await coordinator.signalComplete('validator-1', 0.92);
|
|
1148
|
+
|
|
1149
|
+
const result = await coordinator.collectConsensus({
|
|
1150
|
+
taskId: 'test-task-001',
|
|
1151
|
+
loopNumber: 2,
|
|
1152
|
+
requiredConsensus: 0.85,
|
|
1153
|
+
timeout: 5,
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
expect(result.success).toBe(true);
|
|
1157
|
+
expect(result.responsesReceived).toBe(1);
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
it('should handle consensus below threshold', async () => {
|
|
1161
|
+
await coordinator.registerAgent('validator-1', 'validator');
|
|
1162
|
+
await coordinator.registerAgent('validator-2', 'validator');
|
|
1163
|
+
|
|
1164
|
+
await coordinator.signalComplete('validator-1', 0.70);
|
|
1165
|
+
await coordinator.signalComplete('validator-2', 0.72);
|
|
1166
|
+
|
|
1167
|
+
const result = await coordinator.collectConsensus({
|
|
1168
|
+
taskId: 'test-task-001',
|
|
1169
|
+
loopNumber: 2,
|
|
1170
|
+
requiredConsensus: 0.90,
|
|
1171
|
+
timeout: 1,
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
expect(result.success).toBe(false);
|
|
1175
|
+
expect(result.consensusReached).toBe(false);
|
|
1176
|
+
expect(result.decision).toBe('ABORT');
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
it('should log operations in verbose mode', async () => {
|
|
1180
|
+
const verboseConfig = { ...config, verbose: true };
|
|
1181
|
+
const verboseCoordinator = new RedisCoordinator(
|
|
1182
|
+
verboseConfig,
|
|
1183
|
+
mockLogger,
|
|
1184
|
+
mockRedis
|
|
1185
|
+
);
|
|
1186
|
+
|
|
1187
|
+
mockLogger.clear();
|
|
1188
|
+
await verboseCoordinator.registerAgent('agent-001', 'developer');
|
|
1189
|
+
expect(
|
|
1190
|
+
mockLogger.logs.some((l) => l.message.includes('Agent registered'))
|
|
1191
|
+
).toBe(true);
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
it('should handle agent registration without container ID', async () => {
|
|
1195
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1196
|
+
|
|
1197
|
+
const agentData = await mockRedis.hgetall('cfn_docker:agent:agent-001');
|
|
1198
|
+
expect(agentData.container_id).toBe('');
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
it('should handle default timeout values', async () => {
|
|
1202
|
+
const defaultConfig: CoordinatorConfig = {
|
|
1203
|
+
redis: {
|
|
1204
|
+
host: 'localhost',
|
|
1205
|
+
port: 6379,
|
|
1206
|
+
db: 0,
|
|
1207
|
+
},
|
|
1208
|
+
taskId: 'test-task-default',
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
const defaultCoordinator = new RedisCoordinator(
|
|
1212
|
+
defaultConfig,
|
|
1213
|
+
mockLogger,
|
|
1214
|
+
mockRedis
|
|
1215
|
+
);
|
|
1216
|
+
|
|
1217
|
+
await defaultCoordinator.registerAgent('agent-001', 'developer');
|
|
1218
|
+
expect(defaultCoordinator).toBeDefined();
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
it('should handle confidence edge cases', async () => {
|
|
1222
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1223
|
+
await coordinator.registerAgent('agent-002', 'developer');
|
|
1224
|
+
await coordinator.registerAgent('agent-003', 'developer');
|
|
1225
|
+
|
|
1226
|
+
// Test edge confidence values
|
|
1227
|
+
await coordinator.signalComplete('agent-001', 0.0);
|
|
1228
|
+
await coordinator.signalComplete('agent-002', 0.5);
|
|
1229
|
+
await coordinator.signalComplete('agent-003', 1.0);
|
|
1230
|
+
|
|
1231
|
+
const result = await coordinator.collectConsensus({
|
|
1232
|
+
taskId: 'test-task-001',
|
|
1233
|
+
loopNumber: 2,
|
|
1234
|
+
requiredConsensus: 0.5,
|
|
1235
|
+
timeout: 5,
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
expect(result.success).toBe(true);
|
|
1239
|
+
expect(result.averageConfidence).toBeCloseTo(0.5, 1);
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
it('should correctly use mode configuration', async () => {
|
|
1243
|
+
const modes: ExecutionMode[] = ['mvp', 'standard', 'enterprise'];
|
|
1244
|
+
|
|
1245
|
+
for (const mode of modes) {
|
|
1246
|
+
const modeConfig: CoordinatorConfig = {
|
|
1247
|
+
redis: {
|
|
1248
|
+
host: 'localhost',
|
|
1249
|
+
port: 6379,
|
|
1250
|
+
db: 0,
|
|
1251
|
+
},
|
|
1252
|
+
taskId: `test-task-${mode}`,
|
|
1253
|
+
mode,
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
const modeCoordinator = new RedisCoordinator(
|
|
1257
|
+
modeConfig,
|
|
1258
|
+
mockLogger,
|
|
1259
|
+
mockRedis
|
|
1260
|
+
);
|
|
1261
|
+
|
|
1262
|
+
await modeCoordinator.initTask();
|
|
1263
|
+
const meta = await mockRedis.hgetall(
|
|
1264
|
+
`cfn_docker:task:test-task-${mode}:meta`
|
|
1265
|
+
);
|
|
1266
|
+
expect(meta.mode).toBe(mode);
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
it('should handle initTask errors gracefully', async () => {
|
|
1271
|
+
const failingRedis: IRedisClient = {
|
|
1272
|
+
...mockRedis,
|
|
1273
|
+
hset: jest.fn().mockRejectedValue(new Error('Storage error')),
|
|
1274
|
+
expire: jest.fn().mockResolvedValue(1),
|
|
1275
|
+
exists: jest.fn().mockResolvedValue(false),
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
const errorCoordinator = new RedisCoordinator(config, mockLogger, failingRedis);
|
|
1279
|
+
|
|
1280
|
+
await expect(errorCoordinator.initTask()).rejects.toThrow();
|
|
1281
|
+
expect(
|
|
1282
|
+
mockLogger.logs.some((l) => l.level === 'error')
|
|
1283
|
+
).toBe(true);
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
it('should handle storeContext errors gracefully', async () => {
|
|
1287
|
+
const failingRedis: IRedisClient = {
|
|
1288
|
+
...mockRedis,
|
|
1289
|
+
hset: jest.fn().mockRejectedValue(new Error('Storage error')),
|
|
1290
|
+
expire: jest.fn().mockResolvedValue(1),
|
|
1291
|
+
exists: jest.fn().mockResolvedValue(false),
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
const errorCoordinator = new RedisCoordinator(config, mockLogger, failingRedis);
|
|
1295
|
+
|
|
1296
|
+
await expect(
|
|
1297
|
+
errorCoordinator.storeContext({ test: 'value' })
|
|
1298
|
+
).rejects.toThrow();
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
it('should handle registerAgent errors gracefully', async () => {
|
|
1302
|
+
const failingRedis: IRedisClient = {
|
|
1303
|
+
...mockRedis,
|
|
1304
|
+
hset: jest.fn().mockRejectedValue(new Error('Registration error')),
|
|
1305
|
+
lpush: jest.fn().mockResolvedValue(1),
|
|
1306
|
+
expire: jest.fn().mockResolvedValue(1),
|
|
1307
|
+
exists: jest.fn().mockResolvedValue(false),
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
const errorCoordinator = new RedisCoordinator(config, mockLogger, failingRedis);
|
|
1311
|
+
|
|
1312
|
+
await expect(
|
|
1313
|
+
errorCoordinator.registerAgent('agent-001', 'developer')
|
|
1314
|
+
).rejects.toThrow();
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
it('should handle updateStatus errors gracefully', async () => {
|
|
1318
|
+
const failingRedis: IRedisClient = {
|
|
1319
|
+
...mockRedis,
|
|
1320
|
+
hset: jest.fn().mockRejectedValue(new Error('Status update error')),
|
|
1321
|
+
lpush: jest.fn().mockResolvedValue(1),
|
|
1322
|
+
exists: jest.fn().mockResolvedValue(false),
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
const errorCoordinator = new RedisCoordinator(config, mockLogger, failingRedis);
|
|
1326
|
+
|
|
1327
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1328
|
+
await expect(
|
|
1329
|
+
errorCoordinator.updateStatus('agent-001', 'running')
|
|
1330
|
+
).rejects.toThrow();
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
it('should handle signalComplete errors gracefully', async () => {
|
|
1334
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1335
|
+
|
|
1336
|
+
const failingRedis: IRedisClient = {
|
|
1337
|
+
...mockRedis,
|
|
1338
|
+
lpush: jest.fn().mockRejectedValue(new Error('Signal error')),
|
|
1339
|
+
hget: jest.fn().mockResolvedValue('developer'),
|
|
1340
|
+
hset: jest.fn().mockResolvedValue(1),
|
|
1341
|
+
expire: jest.fn().mockResolvedValue(1),
|
|
1342
|
+
exists: jest.fn().mockResolvedValue(true),
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
const errorCoordinator = new RedisCoordinator(config, mockLogger, failingRedis);
|
|
1346
|
+
|
|
1347
|
+
await expect(
|
|
1348
|
+
errorCoordinator.signalComplete('agent-001', 0.85)
|
|
1349
|
+
).rejects.toThrow();
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
it('should handle storeContext with very large values', async () => {
|
|
1353
|
+
const largeValue = 'x'.repeat(1024 * 1024 + 1); // 1MB + 1 byte
|
|
1354
|
+
const context = { large: largeValue };
|
|
1355
|
+
|
|
1356
|
+
await expect(coordinator.storeContext(context)).rejects.toThrow(
|
|
1357
|
+
SecurityError
|
|
1358
|
+
);
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
it('should handle negative confidence values', async () => {
|
|
1362
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1363
|
+
|
|
1364
|
+
await expect(
|
|
1365
|
+
coordinator.signalComplete('agent-001', -0.001)
|
|
1366
|
+
).rejects.toThrow(ValidationError);
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
it('should handle consensus timeout with no responses', async () => {
|
|
1370
|
+
const result = await coordinator.collectConsensus({
|
|
1371
|
+
taskId: 'test-task-001',
|
|
1372
|
+
loopNumber: 2,
|
|
1373
|
+
requiredConsensus: 0.90,
|
|
1374
|
+
timeout: 1,
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
expect(result.success).toBe(false);
|
|
1378
|
+
expect(result.responsesReceived).toBe(0);
|
|
1379
|
+
expect(result.decision).toBe('ABORT');
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
it('should handle cleanup with non-existent task', async () => {
|
|
1383
|
+
const nonExistentConfig: CoordinatorConfig = {
|
|
1384
|
+
redis: {
|
|
1385
|
+
host: 'localhost',
|
|
1386
|
+
port: 6379,
|
|
1387
|
+
db: 0,
|
|
1388
|
+
},
|
|
1389
|
+
taskId: 'non-existent-task',
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
const cleanupCoordinator = new RedisCoordinator(
|
|
1393
|
+
nonExistentConfig,
|
|
1394
|
+
mockLogger,
|
|
1395
|
+
mockRedis
|
|
1396
|
+
);
|
|
1397
|
+
|
|
1398
|
+
await cleanupCoordinator.cleanup();
|
|
1399
|
+
expect(
|
|
1400
|
+
mockLogger.logs.some((l) => l.message.includes('Cleanup completed'))
|
|
1401
|
+
).toBe(true);
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
it('should handle healthCheck with mocked Redis', async () => {
|
|
1405
|
+
mockLogger.clear();
|
|
1406
|
+
await coordinator.healthCheck();
|
|
1407
|
+
|
|
1408
|
+
const healthLogs = mockLogger.logs.filter((l) =>
|
|
1409
|
+
l.message.includes('health')
|
|
1410
|
+
);
|
|
1411
|
+
expect(healthLogs.length).toBeGreaterThan(0);
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
it('should validate agent status types', async () => {
|
|
1415
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1416
|
+
|
|
1417
|
+
const validStatuses: AgentStatus[] = [
|
|
1418
|
+
'spawning',
|
|
1419
|
+
'running',
|
|
1420
|
+
'working',
|
|
1421
|
+
'completed',
|
|
1422
|
+
'failed',
|
|
1423
|
+
'timeout',
|
|
1424
|
+
];
|
|
1425
|
+
|
|
1426
|
+
for (const status of validStatuses) {
|
|
1427
|
+
await expect(
|
|
1428
|
+
coordinator.updateStatus('agent-001', status)
|
|
1429
|
+
).resolves.not.toThrow();
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
it('should handle consensus with fractional values', async () => {
|
|
1434
|
+
await coordinator.registerAgent('validator-1', 'validator');
|
|
1435
|
+
await coordinator.registerAgent('validator-2', 'validator');
|
|
1436
|
+
|
|
1437
|
+
await coordinator.signalComplete('validator-1', 0.333);
|
|
1438
|
+
await coordinator.signalComplete('validator-2', 0.667);
|
|
1439
|
+
|
|
1440
|
+
const result = await coordinator.collectConsensus({
|
|
1441
|
+
taskId: 'test-task-001',
|
|
1442
|
+
loopNumber: 2,
|
|
1443
|
+
requiredConsensus: 0.5,
|
|
1444
|
+
timeout: 5,
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
expect(result.success).toBe(true);
|
|
1448
|
+
expect(result.averageConfidence).toBeCloseTo(0.5, 0);
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
it('should handle very small timeout values', async () => {
|
|
1452
|
+
await coordinator.registerAgent('agent-001', 'developer');
|
|
1453
|
+
|
|
1454
|
+
const result = await coordinator.waitLoop({
|
|
1455
|
+
taskId: 'test-task-001',
|
|
1456
|
+
loopNumber: 3,
|
|
1457
|
+
agentCount: 1,
|
|
1458
|
+
timeout: 1,
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
expect(result).toBeDefined();
|
|
1462
|
+
});
|
|
1463
|
+
});
|
|
1464
|
+
});
|