agentic-orchestrator 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +15 -0
- package/CLAUDE.md +126 -0
- package/README.md +166 -25
- package/agentic/orchestrator/adapters.yaml +3 -0
- package/agentic/orchestrator/gates.yaml +47 -0
- package/agentic/orchestrator/policy.yaml +89 -0
- package/agentic/orchestrator/schemas/adapters.schema.json +12 -0
- package/agentic/orchestrator/schemas/gates.schema.json +6 -1
- package/agentic/orchestrator/schemas/index.schema.json +14 -0
- package/agentic/orchestrator/schemas/multi-project.schema.json +41 -0
- package/agentic/orchestrator/schemas/policy.schema.json +449 -52
- package/agentic/orchestrator/schemas/state.schema.json +16 -0
- package/agentic/orchestrator/tools/catalog.json +68 -0
- package/agentic/orchestrator/tools/schemas/input/cost.get.input.schema.json +10 -0
- package/agentic/orchestrator/tools/schemas/input/cost.record.input.schema.json +13 -0
- package/agentic/orchestrator/tools/schemas/input/feature.send_message.input.schema.json +11 -0
- package/agentic/orchestrator/tools/schemas/input/performance.get_analytics.input.schema.json +10 -0
- package/agentic/orchestrator/tools/schemas/input/performance.record_outcome.input.schema.json +18 -0
- package/agentic/orchestrator/tools/schemas/output/cost.get.output.schema.json +13 -0
- package/agentic/orchestrator/tools/schemas/output/cost.record.output.schema.json +13 -0
- package/agentic/orchestrator/tools/schemas/output/feature.ready_to_merge.output.schema.json +7 -0
- package/agentic/orchestrator/tools/schemas/output/feature.send_message.output.schema.json +23 -0
- package/agentic/orchestrator/tools/schemas/output/performance.get_analytics.output.schema.json +46 -0
- package/agentic/orchestrator/tools/schemas/output/performance.record_outcome.output.schema.json +10 -0
- package/agentic/orchestrator/tools.md +5 -0
- package/apps/control-plane/scripts/validate-architecture-rules.mjs +28 -2
- package/apps/control-plane/scripts/validate-docker-mcp-contract.mjs +12 -0
- package/apps/control-plane/scripts/validate-mcp-contracts.ts +92 -0
- package/apps/control-plane/src/application/adapters/adapter-registry.ts +169 -0
- package/apps/control-plane/src/application/multi-project-loader.ts +119 -0
- package/apps/control-plane/src/application/services/activity-monitor-service.ts +199 -0
- package/apps/control-plane/src/application/services/cost-tracking-service.ts +82 -0
- package/apps/control-plane/src/application/services/dependency-scheduler-service.ts +86 -0
- package/apps/control-plane/src/application/services/feature-deletion-service.ts +8 -7
- package/apps/control-plane/src/application/services/gate-interpolation-service.ts +15 -0
- package/apps/control-plane/src/application/services/gate-service.ts +38 -2
- package/apps/control-plane/src/application/services/instance-isolation-service.ts +18 -0
- package/apps/control-plane/src/application/services/issue-tracker-service.ts +469 -0
- package/apps/control-plane/src/application/services/merge-service.ts +67 -3
- package/apps/control-plane/src/application/services/notifier-service.ts +295 -0
- package/apps/control-plane/src/application/services/performance-analytics-service.ts +122 -0
- package/apps/control-plane/src/application/services/plan-service.ts +51 -0
- package/apps/control-plane/src/application/services/pr-monitor-service.ts +262 -0
- package/apps/control-plane/src/application/services/reactions-service.ts +175 -0
- package/apps/control-plane/src/application/services/reporting-service.ts +17 -2
- package/apps/control-plane/src/application/services/run-lease-service.ts +16 -38
- package/apps/control-plane/src/application/tools/tool-metadata.ts +4 -1
- package/apps/control-plane/src/cli/aop.ts +1 -1
- package/apps/control-plane/src/cli/attach-command-handler.ts +120 -0
- package/apps/control-plane/src/cli/cleanup-command-handler.ts +190 -0
- package/apps/control-plane/src/cli/cli-argument-parser.ts +69 -3
- package/apps/control-plane/src/cli/dashboard-command-handler.ts +57 -0
- package/apps/control-plane/src/cli/help-command-handler.ts +163 -0
- package/apps/control-plane/src/cli/init-command-handler.ts +609 -0
- package/apps/control-plane/src/cli/resume-command-handler.ts +1 -0
- package/apps/control-plane/src/cli/retry-command-handler.ts +138 -0
- package/apps/control-plane/src/cli/run-command-handler.ts +115 -3
- package/apps/control-plane/src/cli/send-command-handler.ts +65 -0
- package/apps/control-plane/src/cli/status-command-handler.ts +102 -2
- package/apps/control-plane/src/cli/types.ts +26 -1
- package/apps/control-plane/src/core/constants.ts +8 -2
- package/apps/control-plane/src/core/error-codes.ts +3 -1
- package/apps/control-plane/src/core/gates.ts +170 -50
- package/apps/control-plane/src/core/kernel.ts +280 -5
- package/apps/control-plane/src/core/path-layout.ts +12 -0
- package/apps/control-plane/src/core/tool-caller.ts +36 -0
- package/apps/control-plane/src/core/workspace-hooks.ts +87 -0
- package/apps/control-plane/src/interfaces/cli/bootstrap.ts +258 -9
- package/apps/control-plane/src/providers/providers.ts +235 -14
- package/apps/control-plane/src/supervisor/build-wave-executor.ts +129 -8
- package/apps/control-plane/src/supervisor/qa-wave-executor.ts +123 -5
- package/apps/control-plane/src/supervisor/run-coordinator.ts +143 -6
- package/apps/control-plane/src/supervisor/runtime.ts +135 -6
- package/apps/control-plane/src/supervisor/types.ts +12 -21
- package/apps/control-plane/src/supervisor/worker-decision-loop.ts +8 -0
- package/apps/control-plane/test/activity-monitor.spec.ts +294 -0
- package/apps/control-plane/test/adapter-registry.spec.ts +132 -0
- package/apps/control-plane/test/batch-operations.spec.ts +112 -0
- package/apps/control-plane/test/bootstrap-attach.spec.ts +102 -0
- package/apps/control-plane/test/bootstrap-edge-cases.spec.ts +252 -0
- package/apps/control-plane/test/bootstrap.spec.ts +560 -0
- package/apps/control-plane/test/cleanup-command.spec.ts +301 -0
- package/apps/control-plane/test/cli-helpers.spec.ts +404 -1
- package/apps/control-plane/test/cli.unit.spec.ts +182 -1
- package/apps/control-plane/test/collision-queue.spec.ts +104 -1
- package/apps/control-plane/test/core-utils.spec.ts +175 -2
- package/apps/control-plane/test/cost-tracking.spec.ts +143 -0
- package/apps/control-plane/test/dashboard-api.integration.spec.ts +247 -0
- package/apps/control-plane/test/dashboard-client.spec.ts +116 -0
- package/apps/control-plane/test/dashboard-command.spec.ts +103 -0
- package/apps/control-plane/test/dependency-scheduler.spec.ts +189 -0
- package/apps/control-plane/test/epoch-tracking.spec.ts +4 -4
- package/apps/control-plane/test/feature-deletion-service.spec.ts +422 -0
- package/apps/control-plane/test/feature-lifecycle.spec.ts +202 -0
- package/apps/control-plane/test/git-spawn-error.spec.ts +24 -0
- package/apps/control-plane/test/incremental-gates.spec.ts +137 -0
- package/apps/control-plane/test/init-wizard.spec.ts +506 -0
- package/apps/control-plane/test/instance-isolation.spec.ts +83 -0
- package/apps/control-plane/test/issue-tracker.spec.ts +890 -0
- package/apps/control-plane/test/kernel.coverage.spec.ts +3 -5
- package/apps/control-plane/test/kernel.coverage2.spec.ts +871 -0
- package/apps/control-plane/test/kernel.spec.ts +13 -11
- package/apps/control-plane/test/lock-service.spec.ts +508 -0
- package/apps/control-plane/test/mcp-helpers.spec.ts +176 -0
- package/apps/control-plane/test/mcp.spec.ts +50 -15
- package/apps/control-plane/test/merge-service.spec.ts +67 -4
- package/apps/control-plane/test/multi-project.spec.ts +372 -0
- package/apps/control-plane/test/notifier-service.spec.ts +388 -0
- package/apps/control-plane/test/parallel-gates.spec.ts +312 -0
- package/apps/control-plane/test/patch-service.spec.ts +253 -0
- package/apps/control-plane/test/performance-analytics.spec.ts +338 -0
- package/apps/control-plane/test/planning-wave-executor.spec.ts +168 -0
- package/apps/control-plane/test/pr-monitor.spec.ts +385 -0
- package/apps/control-plane/test/providers.spec.ts +344 -1
- package/apps/control-plane/test/reactions.spec.ts +392 -0
- package/apps/control-plane/test/resume-command.spec.ts +390 -0
- package/apps/control-plane/test/run-coordinator.spec.ts +481 -2
- package/apps/control-plane/test/schema-date-time.spec.ts +46 -0
- package/apps/control-plane/test/service-retry-paths.spec.ts +30 -0
- package/apps/control-plane/test/services.spec.ts +95 -2
- package/apps/control-plane/test/session-management.spec.ts +450 -0
- package/apps/control-plane/test/spec-ingestion.spec.ts +190 -0
- package/apps/control-plane/test/supervisor-collaborators.spec.ts +699 -2
- package/apps/control-plane/test/supervisor.spec.ts +36 -30
- package/apps/control-plane/test/supervisor.unit.spec.ts +405 -0
- package/apps/control-plane/test/worker-decision-loop.spec.ts +57 -0
- package/apps/control-plane/test/workspace-hooks.spec.ts +177 -0
- package/apps/control-plane/vitest.config.ts +21 -5
- package/dist/apps/control-plane/application/adapters/adapter-registry.d.ts +44 -0
- package/dist/apps/control-plane/application/adapters/adapter-registry.js +76 -0
- package/dist/apps/control-plane/application/adapters/adapter-registry.js.map +1 -0
- package/dist/apps/control-plane/application/multi-project-loader.d.ts +31 -0
- package/dist/apps/control-plane/application/multi-project-loader.js +82 -0
- package/dist/apps/control-plane/application/multi-project-loader.js.map +1 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +43 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.js +132 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +28 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js +48 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/dependency-scheduler-service.d.ts +26 -0
- package/dist/apps/control-plane/application/services/dependency-scheduler-service.js +75 -0
- package/dist/apps/control-plane/application/services/dependency-scheduler-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +2 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.js +6 -7
- package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/gate-interpolation-service.d.ts +7 -0
- package/dist/apps/control-plane/application/services/gate-interpolation-service.js +7 -0
- package/dist/apps/control-plane/application/services/gate-interpolation-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/gate-service.js +32 -2
- package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/instance-isolation-service.d.ts +11 -0
- package/dist/apps/control-plane/application/services/instance-isolation-service.js +17 -0
- package/dist/apps/control-plane/application/services/instance-isolation-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +65 -0
- package/dist/apps/control-plane/application/services/issue-tracker-service.js +358 -0
- package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/merge-service.d.ts +4 -0
- package/dist/apps/control-plane/application/services/merge-service.js +44 -2
- package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/notifier-service.d.ts +74 -0
- package/dist/apps/control-plane/application/services/notifier-service.js +212 -0
- package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +39 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js +75 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/plan-service.d.ts +1 -0
- package/dist/apps/control-plane/application/services/plan-service.js +53 -0
- package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +44 -0
- package/dist/apps/control-plane/application/services/pr-monitor-service.js +192 -0
- package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/reactions-service.d.ts +67 -0
- package/dist/apps/control-plane/application/services/reactions-service.js +114 -0
- package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/reporting-service.d.ts +1 -0
- package/dist/apps/control-plane/application/services/reporting-service.js +13 -2
- package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/run-lease-service.d.ts +2 -0
- package/dist/apps/control-plane/application/services/run-lease-service.js +14 -38
- package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
- package/dist/apps/control-plane/application/tools/tool-metadata.js +3 -1
- package/dist/apps/control-plane/application/tools/tool-metadata.js.map +1 -1
- package/dist/apps/control-plane/cli/aop.d.ts +1 -1
- package/dist/apps/control-plane/cli/aop.js +1 -1
- package/dist/apps/control-plane/cli/attach-command-handler.d.ts +12 -0
- package/dist/apps/control-plane/cli/attach-command-handler.js +98 -0
- package/dist/apps/control-plane/cli/attach-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/cleanup-command-handler.d.ts +12 -0
- package/dist/apps/control-plane/cli/cleanup-command-handler.js +162 -0
- package/dist/apps/control-plane/cli/cleanup-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/cli-argument-parser.js +73 -3
- package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
- package/dist/apps/control-plane/cli/dashboard-command-handler.d.ts +7 -0
- package/dist/apps/control-plane/cli/dashboard-command-handler.js +45 -0
- package/dist/apps/control-plane/cli/dashboard-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/help-command-handler.d.ts +8 -0
- package/dist/apps/control-plane/cli/help-command-handler.js +146 -0
- package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/init-command-handler.d.ts +26 -0
- package/dist/apps/control-plane/cli/init-command-handler.js +517 -0
- package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/resume-command-handler.js +1 -1
- package/dist/apps/control-plane/cli/resume-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/retry-command-handler.d.ts +8 -0
- package/dist/apps/control-plane/cli/retry-command-handler.js +111 -0
- package/dist/apps/control-plane/cli/retry-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/run-command-handler.d.ts +5 -0
- package/dist/apps/control-plane/cli/run-command-handler.js +82 -3
- package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/send-command-handler.d.ts +8 -0
- package/dist/apps/control-plane/cli/send-command-handler.js +55 -0
- package/dist/apps/control-plane/cli/send-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/status-command-handler.d.ts +12 -1
- package/dist/apps/control-plane/cli/status-command-handler.js +55 -2
- package/dist/apps/control-plane/cli/status-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/types.d.ts +26 -1
- package/dist/apps/control-plane/cli/types.js +15 -1
- package/dist/apps/control-plane/cli/types.js.map +1 -1
- package/dist/apps/control-plane/core/constants.d.ts +6 -0
- package/dist/apps/control-plane/core/constants.js +8 -2
- package/dist/apps/control-plane/core/constants.js.map +1 -1
- package/dist/apps/control-plane/core/error-codes.d.ts +2 -0
- package/dist/apps/control-plane/core/error-codes.js +3 -1
- package/dist/apps/control-plane/core/error-codes.js.map +1 -1
- package/dist/apps/control-plane/core/gates.d.ts +4 -0
- package/dist/apps/control-plane/core/gates.js +140 -43
- package/dist/apps/control-plane/core/gates.js.map +1 -1
- package/dist/apps/control-plane/core/kernel.d.ts +50 -1
- package/dist/apps/control-plane/core/kernel.js +220 -7
- package/dist/apps/control-plane/core/kernel.js.map +1 -1
- package/dist/apps/control-plane/core/path-layout.d.ts +3 -0
- package/dist/apps/control-plane/core/path-layout.js +9 -0
- package/dist/apps/control-plane/core/path-layout.js.map +1 -1
- package/dist/apps/control-plane/core/tool-caller.d.ts +32 -0
- package/dist/apps/control-plane/core/tool-caller.js +2 -0
- package/dist/apps/control-plane/core/tool-caller.js.map +1 -0
- package/dist/apps/control-plane/core/workspace-hooks.d.ts +20 -0
- package/dist/apps/control-plane/core/workspace-hooks.js +69 -0
- package/dist/apps/control-plane/core/workspace-hooks.js.map +1 -0
- package/dist/apps/control-plane/interfaces/cli/bootstrap.js +245 -9
- package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
- package/dist/apps/control-plane/providers/providers.d.ts +42 -3
- package/dist/apps/control-plane/providers/providers.js +216 -5
- package/dist/apps/control-plane/providers/providers.js.map +1 -1
- package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/build-wave-executor.js +115 -6
- package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js +109 -5
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +15 -0
- package/dist/apps/control-plane/supervisor/run-coordinator.js +132 -6
- package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
- package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/runtime.js +110 -6
- package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
- package/dist/apps/control-plane/supervisor/types.d.ts +9 -16
- package/dist/apps/control-plane/supervisor/types.js.map +1 -1
- package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/worker-decision-loop.js +5 -0
- package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
- package/eslint.config.mjs +2 -1
- package/package.json +12 -2
- package/packages/web-dashboard/next-env.d.ts +5 -0
- package/packages/web-dashboard/next.config.js +7 -0
- package/packages/web-dashboard/package.json +26 -0
- package/packages/web-dashboard/src/app/api/actions/route.ts +64 -0
- package/packages/web-dashboard/src/app/api/events/route.ts +51 -0
- package/packages/web-dashboard/src/app/api/features/[id]/checkout/route.ts +256 -0
- package/packages/web-dashboard/src/app/api/features/[id]/diff/route.ts +10 -0
- package/packages/web-dashboard/src/app/api/features/[id]/evidence/[artifact]/route.ts +25 -0
- package/packages/web-dashboard/src/app/api/features/[id]/review/route.ts +63 -0
- package/packages/web-dashboard/src/app/api/features/[id]/route.ts +16 -0
- package/packages/web-dashboard/src/app/api/projects/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/status/route.ts +15 -0
- package/packages/web-dashboard/src/app/globals.css +2 -0
- package/packages/web-dashboard/src/app/layout.tsx +15 -0
- package/packages/web-dashboard/src/app/page.tsx +393 -0
- package/packages/web-dashboard/src/lib/aop-client.ts +244 -0
- package/packages/web-dashboard/src/lib/multi-project-config.ts +116 -0
- package/packages/web-dashboard/src/lib/orchestrator-tools.ts +284 -0
- package/packages/web-dashboard/src/lib/types.ts +58 -0
- package/packages/web-dashboard/tsconfig.json +40 -0
- package/packages/web-dashboard/vitest.config.ts +6 -0
- package/spec-files/completed/agentic_orchestrator_feature_gaps_closure_spec.md +1764 -0
- package/spec-files/outstanding/agentic_orchestrator_enterprise_governance_dashboard_spec.md +348 -0
- package/spec-files/outstanding/agentic_orchestrator_knowledge_canary_spec.md +344 -0
- package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +374 -0
- package/spec-files/outstanding/agentic_orchestrator_performance_improvements_spec.md +1059 -0
- package/spec-files/outstanding/agentic_orchestrator_planning_review_quality_spec.md +466 -0
- package/spec-files/outstanding/agentic_orchestrator_quality_adoption_execution_spec.md +198 -0
- package/spec-files/outstanding/agentic_orchestrator_validator_hardening_spec.md +365 -0
- package/spec-files/progress.md +481 -52
- /package/spec-files/{agentic_orchestrator_cli_delete_command_spec.md → completed/agentic_orchestrator_cli_delete_command_spec.md} +0 -0
- /package/spec-files/{agentic_orchestrator_dot_aop_generated_artifacts_spec.md → completed/agentic_orchestrator_dot_aop_generated_artifacts_spec.md} +0 -0
- /package/spec-files/{agentic_orchestrator_mcp_formalization_spec.md → completed/agentic_orchestrator_mcp_formalization_spec.md} +0 -0
- /package/spec-files/{agentic_orchestrator_oop_refactor_spec.md → completed/agentic_orchestrator_oop_refactor_spec.md} +0 -0
- /package/spec-files/{agentic_orchestrator_single_global_orchestrator_spec.md → completed/agentic_orchestrator_single_global_orchestrator_spec.md} +0 -0
- /package/spec-files/{agentic_orchestrator_spec.md → completed/agentic_orchestrator_spec.md} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import { LockService } from '../src/application/services/lock-service.js';
|
|
6
6
|
import { ReportingService } from '../src/application/services/reporting-service.js';
|
|
7
7
|
import { RunLeaseService } from '../src/application/services/run-lease-service.js';
|
|
@@ -80,6 +80,8 @@ function makeRunLeaseHarness(initialIndex: AnyRecord = makeIndex()) {
|
|
|
80
80
|
lease_expires_at: at ?? new Date().toISOString()
|
|
81
81
|
}) as RuntimeSessionsSnapshot;
|
|
82
82
|
|
|
83
|
+
let runLease: RuntimeSessionsSnapshot = clone(initialIndex.runtime_sessions ?? makeRuntimeSessions());
|
|
84
|
+
|
|
83
85
|
const service = new RunLeaseService({
|
|
84
86
|
runLeaseTtlSeconds: () => 30,
|
|
85
87
|
withIndexLock: async <T>(operation: () => Promise<T>) => await operation(),
|
|
@@ -88,6 +90,10 @@ function makeRunLeaseHarness(initialIndex: AnyRecord = makeIndex()) {
|
|
|
88
90
|
index = clone(nextIndex);
|
|
89
91
|
writes.push(clone(nextIndex));
|
|
90
92
|
},
|
|
93
|
+
readRunLease: async () => clone(runLease),
|
|
94
|
+
writeRunLease: async (data: RuntimeSessionsSnapshot) => {
|
|
95
|
+
runLease = clone(data);
|
|
96
|
+
},
|
|
91
97
|
normalizeRuntimeSessions,
|
|
92
98
|
isRunLeaseFresh: (runtimeSessions: AnyRecord) => new Date(runtimeSessions.lease_expires_at).getTime() > Date.now(),
|
|
93
99
|
emptyRuntimeSessions
|
|
@@ -96,6 +102,7 @@ function makeRunLeaseHarness(initialIndex: AnyRecord = makeIndex()) {
|
|
|
96
102
|
return {
|
|
97
103
|
service,
|
|
98
104
|
getIndex: () => clone(index),
|
|
105
|
+
getRunLease: () => clone(runLease),
|
|
99
106
|
writes
|
|
100
107
|
};
|
|
101
108
|
}
|
|
@@ -218,6 +225,7 @@ async function makeReportingHarness() {
|
|
|
218
225
|
}),
|
|
219
226
|
readIndex: async () => clone(index),
|
|
220
227
|
statePath: (featureId: string) => path.join(tmpDir, featureId, 'state.md'),
|
|
228
|
+
featureCostPath: (featureId: string) => path.join(tmpDir, featureId, 'cost.json'),
|
|
221
229
|
readState: async (featureId: string) => ({
|
|
222
230
|
frontMatter: clone(stateMap.get(featureId) ?? {})
|
|
223
231
|
}),
|
|
@@ -379,7 +387,7 @@ describe('RunLeaseService', () => {
|
|
|
379
387
|
|
|
380
388
|
const released = await harness.service.releaseRunLease('run:owner', 'owner:owner');
|
|
381
389
|
expect(released.data.released).toBe(true);
|
|
382
|
-
expect(harness.
|
|
390
|
+
expect(harness.getRunLease().run_id).toBe('none');
|
|
383
391
|
});
|
|
384
392
|
|
|
385
393
|
it('reuses existing owner metadata and rejects not-owned lease operations', async () => {
|
|
@@ -691,3 +699,88 @@ describe('ReportingService', () => {
|
|
|
691
699
|
expect(summary.data.qa_summary.pending_required).toBe(0);
|
|
692
700
|
});
|
|
693
701
|
});
|
|
702
|
+
|
|
703
|
+
describe('ReportingService worktree_branch coverage', () => {
|
|
704
|
+
const cleanups: Array<() => Promise<void>> = [];
|
|
705
|
+
|
|
706
|
+
afterEach(async () => {
|
|
707
|
+
while (cleanups.length > 0) {
|
|
708
|
+
const cleanup = cleanups.pop();
|
|
709
|
+
if (cleanup) {await cleanup();}
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it('GIVEN_worktree_branch_string_in_frontMatter_WHEN_reportDashboard_THEN_uses_worktree_branch', async () => {
|
|
714
|
+
const harness = await makeReportingHarness();
|
|
715
|
+
cleanups.push(harness.cleanup);
|
|
716
|
+
|
|
717
|
+
await harness.ensureState('feature_a', {
|
|
718
|
+
status: STATUS.BUILDING,
|
|
719
|
+
worktree_branch: 'feature/my-branch',
|
|
720
|
+
locks: { held: [] },
|
|
721
|
+
gate_profile: 'default',
|
|
722
|
+
gates: { plan: 'pass' },
|
|
723
|
+
last_updated: '2026-01-01T00:00:00.000Z'
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
const dashboard = await harness.service.reportDashboard();
|
|
727
|
+
const feat = dashboard.data.features.find((f: AnyRecord) => f.feature_id === 'feature_a');
|
|
728
|
+
expect(feat?.branch).toBe('feature/my-branch');
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
describe('LockService sleep branch coverage', () => {
|
|
733
|
+
const cleanups: Array<() => Promise<void>> = [];
|
|
734
|
+
|
|
735
|
+
afterEach(async () => {
|
|
736
|
+
while (cleanups.length > 0) {
|
|
737
|
+
const cleanup = cleanups.pop();
|
|
738
|
+
if (cleanup) {await cleanup();}
|
|
739
|
+
}
|
|
740
|
+
vi.useRealTimers();
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it('GIVEN_lock_held_and_wait_mode_WHEN_locksAcquire_called_THEN_sleep_is_called_and_times_out', async () => {
|
|
744
|
+
vi.useFakeTimers();
|
|
745
|
+
|
|
746
|
+
const harness = await makeLockHarness({
|
|
747
|
+
policy: {
|
|
748
|
+
locks: {
|
|
749
|
+
acquire_behavior: 'wait',
|
|
750
|
+
default_wait_timeout_seconds: 10,
|
|
751
|
+
acquire_backoff: {
|
|
752
|
+
initial_ms: 0,
|
|
753
|
+
max_ms: 0,
|
|
754
|
+
multiplier: 1,
|
|
755
|
+
jitter_ms: 0
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
states: {
|
|
760
|
+
feature_a: { version: 1, status: STATUS.PLANNING, locks: { held: [] } }
|
|
761
|
+
},
|
|
762
|
+
index: makeIndex({
|
|
763
|
+
locks: { openapi: 'feature_other', db_migrations: null },
|
|
764
|
+
lock_leases: {
|
|
765
|
+
openapi: {
|
|
766
|
+
holder: 'feature_other',
|
|
767
|
+
lease_id: 'lease-sleep-test',
|
|
768
|
+
expires_at: new Date(Date.now() + 60_000).toISOString()
|
|
769
|
+
},
|
|
770
|
+
db_migrations: null
|
|
771
|
+
}
|
|
772
|
+
})
|
|
773
|
+
});
|
|
774
|
+
cleanups.push(harness.cleanup);
|
|
775
|
+
|
|
776
|
+
// Start acquisition in background - it will call sleep() on first blocked iteration
|
|
777
|
+
const acquirePromise = harness.service.locksAcquire('openapi', 'feature_a', 10);
|
|
778
|
+
|
|
779
|
+
// Advance time past the timeout to resolve the sleep and trigger timeout
|
|
780
|
+
vi.advanceTimersByTime(15_000);
|
|
781
|
+
|
|
782
|
+
await expect(acquirePromise).rejects.toMatchObject({
|
|
783
|
+
normalizedResponse: { error: { code: ERROR_CODES.LOCK_CONFLICT } }
|
|
784
|
+
});
|
|
785
|
+
});
|
|
786
|
+
});
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
4
|
+
import { AopKernel } from '../src/index.js';
|
|
5
|
+
import { SendCommandHandler } from '../src/cli/send-command-handler.js';
|
|
6
|
+
import { AttachCommandHandler } from '../src/cli/attach-command-handler.js';
|
|
7
|
+
import { ERROR_CODES } from '../src/core/error-codes.js';
|
|
8
|
+
import { TOOLS } from '../src/core/constants.js';
|
|
9
|
+
import { makeTempRepo, writeFeatureSpec } from './helpers.js';
|
|
10
|
+
import type { WorkerProvider } from '../src/index.js';
|
|
11
|
+
|
|
12
|
+
let repoRoot: string;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
repoRoot = await makeTempRepo(process.cwd());
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function writeFeatureSession(root: string, featureId: string): Promise<void> {
|
|
19
|
+
const leasePath = path.join(root, '.aop', 'runtime', 'default', 'run-lease.json');
|
|
20
|
+
await fs.mkdir(path.dirname(leasePath), { recursive: true });
|
|
21
|
+
let lease: Record<string, unknown> = {};
|
|
22
|
+
try {
|
|
23
|
+
lease = JSON.parse(await fs.readFile(leasePath, 'utf8')) as Record<string, unknown>;
|
|
24
|
+
} catch {
|
|
25
|
+
// file may not exist yet
|
|
26
|
+
}
|
|
27
|
+
lease.orchestrator_session_id = 'orch:session-1';
|
|
28
|
+
lease.feature_sessions = {
|
|
29
|
+
...((lease.feature_sessions as Record<string, unknown>) ?? {}),
|
|
30
|
+
[featureId]: {
|
|
31
|
+
planner_session_id: 'planner:1',
|
|
32
|
+
builder_session_id: 'builder:1',
|
|
33
|
+
qa_session_id: 'qa:1'
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
await fs.writeFile(leasePath, `${JSON.stringify(lease, null, 2)}\n`, 'utf8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function makeProvider(overrides: Partial<WorkerProvider> = {}): WorkerProvider {
|
|
40
|
+
return {
|
|
41
|
+
selection: { provider: 'null', model: 'null-default', provider_config_env: null, provider_config_ref: null },
|
|
42
|
+
createSession: vi.fn(),
|
|
43
|
+
reattachSession: vi.fn(),
|
|
44
|
+
closeSession: vi.fn(),
|
|
45
|
+
runWorker: vi.fn(),
|
|
46
|
+
sendMessage: vi.fn(async () => undefined),
|
|
47
|
+
...overrides
|
|
48
|
+
} as unknown as WorkerProvider;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('feature.send_message kernel handler', () => {
|
|
52
|
+
it('GIVEN active session exists WHEN send called THEN message delivered via provider.sendMessage', async () => {
|
|
53
|
+
const kernel = new AopKernel(repoRoot);
|
|
54
|
+
await kernel.ensureLoaded();
|
|
55
|
+
await writeFeatureSpec(repoRoot, 'feature_send_1');
|
|
56
|
+
await kernel.invoke('feature.init', { feature_id: 'feature_send_1' }, { actor_type: 'orchestrator', actor_id: 'test' });
|
|
57
|
+
await writeFeatureSession(repoRoot, 'feature_send_1');
|
|
58
|
+
|
|
59
|
+
const sendMessageMock = vi.fn(async () => undefined);
|
|
60
|
+
const provider = makeProvider({ sendMessage: sendMessageMock });
|
|
61
|
+
kernel.setProvider(provider);
|
|
62
|
+
|
|
63
|
+
const result = await kernel.invoke(
|
|
64
|
+
'feature.send_message',
|
|
65
|
+
{ feature_id: 'feature_send_1', message: 'Hello agent!', operation_id: 'op:send:1' },
|
|
66
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(result.ok).toBe(true);
|
|
70
|
+
expect(result.data).toMatchObject({
|
|
71
|
+
feature_id: 'feature_send_1',
|
|
72
|
+
session_id: 'planner:1',
|
|
73
|
+
target_role: 'planner',
|
|
74
|
+
delivered: true
|
|
75
|
+
});
|
|
76
|
+
expect(sendMessageMock).toHaveBeenCalledWith('planner:1', 'Hello agent!');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('GIVEN_provider_exposes_session_info_WHEN_send_called_THEN_waits_until_session_is_active_before_delivery', async () => {
|
|
80
|
+
const kernel = new AopKernel(repoRoot);
|
|
81
|
+
await kernel.ensureLoaded();
|
|
82
|
+
await writeFeatureSpec(repoRoot, 'feature_send_wait');
|
|
83
|
+
await kernel.invoke('feature.init', { feature_id: 'feature_send_wait' }, { actor_type: 'orchestrator', actor_id: 'test' });
|
|
84
|
+
await writeFeatureSession(repoRoot, 'feature_send_wait');
|
|
85
|
+
|
|
86
|
+
const getSessionInfo = vi
|
|
87
|
+
.fn()
|
|
88
|
+
.mockResolvedValueOnce({ active: false, provider: 'codex' })
|
|
89
|
+
.mockResolvedValueOnce({ active: true, provider: 'codex' });
|
|
90
|
+
const sendMessageMock = vi.fn(async () => undefined);
|
|
91
|
+
const provider = makeProvider({ sendMessage: sendMessageMock, getSessionInfo });
|
|
92
|
+
kernel.setProvider(provider);
|
|
93
|
+
|
|
94
|
+
const result = await kernel.invoke(
|
|
95
|
+
'feature.send_message',
|
|
96
|
+
{ feature_id: 'feature_send_wait', message: 'Wait for ready session', operation_id: 'op:send:wait' },
|
|
97
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(result.ok).toBe(true);
|
|
101
|
+
expect(getSessionInfo).toHaveBeenCalledTimes(2);
|
|
102
|
+
expect(sendMessageMock).toHaveBeenCalledWith('planner:1', 'Wait for ready session');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('GIVEN no active session WHEN send called THEN error session_not_found returned', async () => {
|
|
106
|
+
const kernel = new AopKernel(repoRoot);
|
|
107
|
+
await kernel.ensureLoaded();
|
|
108
|
+
await writeFeatureSpec(repoRoot, 'feature_send_nosession');
|
|
109
|
+
await kernel.invoke('feature.init', { feature_id: 'feature_send_nosession' }, { actor_type: 'orchestrator', actor_id: 'test' });
|
|
110
|
+
|
|
111
|
+
const provider = makeProvider();
|
|
112
|
+
kernel.setProvider(provider);
|
|
113
|
+
|
|
114
|
+
const result = await kernel.invoke(
|
|
115
|
+
'feature.send_message',
|
|
116
|
+
{ feature_id: 'feature_send_nosession', message: 'Hello agent!', operation_id: 'op:send:2' },
|
|
117
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
expect(result.ok).toBe(false);
|
|
121
|
+
expect(result.error).toMatchObject({
|
|
122
|
+
code: 'session_not_found',
|
|
123
|
+
message: 'No active session cluster for feature'
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('GIVEN provider does not support sendMessage WHEN send called THEN error provider_unsupported returned', async () => {
|
|
128
|
+
const kernel = new AopKernel(repoRoot);
|
|
129
|
+
await kernel.ensureLoaded();
|
|
130
|
+
await writeFeatureSpec(repoRoot, 'feature_send_noprovider');
|
|
131
|
+
await kernel.invoke('feature.init', { feature_id: 'feature_send_noprovider' }, { actor_type: 'orchestrator', actor_id: 'test' });
|
|
132
|
+
await writeFeatureSession(repoRoot, 'feature_send_noprovider');
|
|
133
|
+
|
|
134
|
+
// Provider without sendMessage
|
|
135
|
+
const provider = makeProvider({ sendMessage: undefined });
|
|
136
|
+
kernel.setProvider(provider);
|
|
137
|
+
|
|
138
|
+
const result = await kernel.invoke(
|
|
139
|
+
'feature.send_message',
|
|
140
|
+
{ feature_id: 'feature_send_noprovider', message: 'Hello agent!', operation_id: 'op:send:3' },
|
|
141
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(result.ok).toBe(false);
|
|
145
|
+
expect(result.error).toMatchObject({
|
|
146
|
+
code: 'provider_unsupported',
|
|
147
|
+
message: 'Provider does not support sendMessage'
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('GIVEN message is empty WHEN send called THEN validation error returned', async () => {
|
|
152
|
+
const kernel = new AopKernel(repoRoot);
|
|
153
|
+
await kernel.ensureLoaded();
|
|
154
|
+
await writeFeatureSpec(repoRoot, 'feature_send_empty_msg');
|
|
155
|
+
await kernel.invoke('feature.init', { feature_id: 'feature_send_empty_msg' }, { actor_type: 'orchestrator', actor_id: 'test' });
|
|
156
|
+
await writeFeatureSession(repoRoot, 'feature_send_empty_msg');
|
|
157
|
+
|
|
158
|
+
const provider = makeProvider();
|
|
159
|
+
kernel.setProvider(provider);
|
|
160
|
+
|
|
161
|
+
const result = await kernel.invoke(
|
|
162
|
+
'feature.send_message',
|
|
163
|
+
{ feature_id: 'feature_send_empty_msg', message: '', operation_id: 'op:send:4' },
|
|
164
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
expect(result.ok).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('GIVEN valid send WHEN operation_id reused THEN operation_id passes through in tool args', async () => {
|
|
171
|
+
const kernel = new AopKernel(repoRoot);
|
|
172
|
+
await kernel.ensureLoaded();
|
|
173
|
+
await writeFeatureSpec(repoRoot, 'feature_send_idem');
|
|
174
|
+
await kernel.invoke('feature.init', { feature_id: 'feature_send_idem' }, { actor_type: 'orchestrator', actor_id: 'test' });
|
|
175
|
+
await writeFeatureSession(repoRoot, 'feature_send_idem');
|
|
176
|
+
|
|
177
|
+
const sendMessageMock = vi.fn(async () => undefined);
|
|
178
|
+
const provider = makeProvider({ sendMessage: sendMessageMock });
|
|
179
|
+
kernel.setProvider(provider);
|
|
180
|
+
|
|
181
|
+
const operationId = 'op:idempotent:1';
|
|
182
|
+
const result1 = await kernel.invoke(
|
|
183
|
+
'feature.send_message',
|
|
184
|
+
{ feature_id: 'feature_send_idem', message: 'idem message', operation_id: operationId },
|
|
185
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
186
|
+
);
|
|
187
|
+
const result2 = await kernel.invoke(
|
|
188
|
+
'feature.send_message',
|
|
189
|
+
{ feature_id: 'feature_send_idem', message: 'idem message', operation_id: operationId },
|
|
190
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(result1.ok).toBe(true);
|
|
194
|
+
expect(result2.ok).toBe(true);
|
|
195
|
+
expect(result1.data.delivered).toBe(true);
|
|
196
|
+
expect(result2.data.delivered).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('SendCommandHandler', () => {
|
|
201
|
+
it('GIVEN send CLI command WHEN executed THEN tool client called with correct params', async () => {
|
|
202
|
+
const call = vi.fn(async () => ({
|
|
203
|
+
ok: true,
|
|
204
|
+
data: { feature_id: 'feature_a', session_id: 'orch:1', delivered: true }
|
|
205
|
+
}));
|
|
206
|
+
const toolClient = { call };
|
|
207
|
+
const handler = new SendCommandHandler(toolClient as never, 'run:test');
|
|
208
|
+
|
|
209
|
+
const result = await handler.execute({
|
|
210
|
+
command: 'send',
|
|
211
|
+
feature_id: 'feature_a',
|
|
212
|
+
message: 'Do something'
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(call).toHaveBeenCalledWith(
|
|
216
|
+
TOOLS.FEATURE_SEND_MESSAGE,
|
|
217
|
+
expect.objectContaining({
|
|
218
|
+
feature_id: 'feature_a',
|
|
219
|
+
message: 'Do something',
|
|
220
|
+
operation_id: expect.any(String)
|
|
221
|
+
}),
|
|
222
|
+
expect.objectContaining({
|
|
223
|
+
run_id: 'run:test',
|
|
224
|
+
actor_type: 'system'
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
expect(result).toMatchObject({
|
|
228
|
+
ok: true,
|
|
229
|
+
data: {
|
|
230
|
+
command: 'send',
|
|
231
|
+
feature_id: 'feature_a',
|
|
232
|
+
delivered: true
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('GIVEN feature_id missing WHEN send CLI called THEN validation error thrown', async () => {
|
|
238
|
+
const handler = new SendCommandHandler({ call: vi.fn() } as never, 'run:test');
|
|
239
|
+
|
|
240
|
+
await expect(
|
|
241
|
+
handler.execute({ command: 'send', message: 'Hello' })
|
|
242
|
+
).rejects.toMatchObject({
|
|
243
|
+
code: ERROR_CODES.INVALID_CLI_ARGS,
|
|
244
|
+
details: expect.objectContaining({
|
|
245
|
+
reason: '--feature-id is required for send'
|
|
246
|
+
})
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('GIVEN message missing WHEN send CLI called THEN validation error thrown', async () => {
|
|
251
|
+
const handler = new SendCommandHandler({ call: vi.fn() } as never, 'run:test');
|
|
252
|
+
|
|
253
|
+
await expect(
|
|
254
|
+
handler.execute({ command: 'send', feature_id: 'feature_a' })
|
|
255
|
+
).rejects.toMatchObject({
|
|
256
|
+
code: ERROR_CODES.INVALID_CLI_ARGS,
|
|
257
|
+
details: expect.objectContaining({
|
|
258
|
+
reason: '--message is required for send'
|
|
259
|
+
})
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('AttachCommandHandler', () => {
|
|
265
|
+
it('GIVEN_feature_in_planning_WHEN_attach_executed_THEN_attaches_to_planner_session', async () => {
|
|
266
|
+
const kernel = new AopKernel(repoRoot);
|
|
267
|
+
await kernel.ensureLoaded();
|
|
268
|
+
await writeFeatureSpec(repoRoot, 'feature_attach_planning');
|
|
269
|
+
await kernel.invoke(
|
|
270
|
+
'feature.init',
|
|
271
|
+
{ feature_id: 'feature_attach_planning' },
|
|
272
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
273
|
+
);
|
|
274
|
+
await writeFeatureSession(repoRoot, 'feature_attach_planning');
|
|
275
|
+
|
|
276
|
+
const attachToSession = vi.fn(async () => undefined);
|
|
277
|
+
const provider = makeProvider({ attachToSession, reattachSession: undefined });
|
|
278
|
+
const toolClient = {
|
|
279
|
+
call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'planning' } } }))
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
|
|
283
|
+
const result = await handler.execute({ command: 'attach', feature_id: 'feature_attach_planning' });
|
|
284
|
+
|
|
285
|
+
expect(attachToSession).toHaveBeenCalledWith('planner:1');
|
|
286
|
+
expect(result).toMatchObject({
|
|
287
|
+
ok: true,
|
|
288
|
+
data: {
|
|
289
|
+
command: 'attach',
|
|
290
|
+
feature_id: 'feature_attach_planning',
|
|
291
|
+
role: 'planner',
|
|
292
|
+
session_id: 'planner:1',
|
|
293
|
+
attached: true
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('GIVEN_provider_without_attachToSession_WHEN_attach_executed_THEN_returns_invalid_cli_args_for_unsupported_provider', async () => {
|
|
299
|
+
const kernel = new AopKernel(repoRoot);
|
|
300
|
+
await kernel.ensureLoaded();
|
|
301
|
+
await writeFeatureSpec(repoRoot, 'feature_attach_builder');
|
|
302
|
+
await kernel.invoke(
|
|
303
|
+
'feature.init',
|
|
304
|
+
{ feature_id: 'feature_attach_builder' },
|
|
305
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
306
|
+
);
|
|
307
|
+
await writeFeatureSession(repoRoot, 'feature_attach_builder');
|
|
308
|
+
|
|
309
|
+
const provider = makeProvider({
|
|
310
|
+
selection: {
|
|
311
|
+
provider: 'custom',
|
|
312
|
+
model: 'custom-default',
|
|
313
|
+
provider_config_env: null,
|
|
314
|
+
provider_config_ref: null
|
|
315
|
+
},
|
|
316
|
+
attachToSession: undefined
|
|
317
|
+
});
|
|
318
|
+
const toolClient = {
|
|
319
|
+
call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'building' } } }))
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
|
|
323
|
+
await expect(handler.execute({ command: 'attach', feature_id: 'feature_attach_builder' })).rejects.toMatchObject({
|
|
324
|
+
code: ERROR_CODES.INVALID_CLI_ARGS,
|
|
325
|
+
details: expect.objectContaining({
|
|
326
|
+
reason: expect.stringContaining('does not support interactive attach'),
|
|
327
|
+
provider: 'custom'
|
|
328
|
+
})
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('GIVEN_no_feature_session_cluster_WHEN_attach_executed_THEN_returns_invalid_cli_args', async () => {
|
|
333
|
+
const kernel = new AopKernel(repoRoot);
|
|
334
|
+
await kernel.ensureLoaded();
|
|
335
|
+
await writeFeatureSpec(repoRoot, 'feature_attach_missing');
|
|
336
|
+
await kernel.invoke(
|
|
337
|
+
'feature.init',
|
|
338
|
+
{ feature_id: 'feature_attach_missing' },
|
|
339
|
+
{ actor_type: 'orchestrator', actor_id: 'test' }
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const provider = makeProvider();
|
|
343
|
+
const toolClient = {
|
|
344
|
+
call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'planning' } } }))
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
|
|
348
|
+
await expect(handler.execute({ command: 'attach', feature_id: 'feature_attach_missing' })).rejects.toMatchObject({
|
|
349
|
+
code: ERROR_CODES.INVALID_CLI_ARGS,
|
|
350
|
+
details: expect.objectContaining({
|
|
351
|
+
reason: expect.stringContaining('No active session cluster found')
|
|
352
|
+
})
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('AttachCommandHandler branch coverage', () => {
|
|
358
|
+
it('GIVEN_missing_feature_id_WHEN_attach_executed_THEN_throws_invalid_cli_args', async () => {
|
|
359
|
+
const kernel = { getRuntimeSessions: vi.fn() } as never;
|
|
360
|
+
const provider = makeProvider();
|
|
361
|
+
const toolClient = { call: vi.fn() };
|
|
362
|
+
const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
|
|
363
|
+
await expect(handler.execute({ command: 'attach' })).rejects.toMatchObject({
|
|
364
|
+
code: ERROR_CODES.INVALID_CLI_ARGS
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('GIVEN_invalid_feature_id_WHEN_attach_executed_THEN_throws_invalid_cli_args', async () => {
|
|
369
|
+
const kernel = { getRuntimeSessions: vi.fn() } as never;
|
|
370
|
+
const provider = makeProvider();
|
|
371
|
+
const toolClient = { call: vi.fn() };
|
|
372
|
+
const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
|
|
373
|
+
await expect(handler.execute({ command: 'attach', feature_id: 'INVALID Feature ID' })).rejects.toMatchObject({
|
|
374
|
+
code: ERROR_CODES.INVALID_CLI_ARGS
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('GIVEN_feature_in_qa_status_WHEN_attach_executed_THEN_attaches_to_qa_session', async () => {
|
|
379
|
+
const kernel = new AopKernel(repoRoot);
|
|
380
|
+
await kernel.ensureLoaded();
|
|
381
|
+
await writeFeatureSpec(repoRoot, 'feature_attach_qa');
|
|
382
|
+
await kernel.invoke('feature.init', { feature_id: 'feature_attach_qa' }, { actor_type: 'orchestrator', actor_id: 'test' });
|
|
383
|
+
await writeFeatureSession(repoRoot, 'feature_attach_qa');
|
|
384
|
+
|
|
385
|
+
const attachToSession = vi.fn(async () => undefined);
|
|
386
|
+
const provider = makeProvider({ attachToSession });
|
|
387
|
+
const toolClient = {
|
|
388
|
+
call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'qa' } } }))
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
|
|
392
|
+
const result = await handler.execute({ command: 'attach', feature_id: 'feature_attach_qa' });
|
|
393
|
+
const data = (result as Record<string, unknown>).data as Record<string, unknown>;
|
|
394
|
+
expect(data.role).toBe('qa');
|
|
395
|
+
expect(attachToSession).toHaveBeenCalledWith('qa:1');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('GIVEN_feature_in_building_status_WHEN_attach_executed_THEN_attaches_to_builder_session', async () => {
|
|
399
|
+
const kernel = new AopKernel(repoRoot);
|
|
400
|
+
await kernel.ensureLoaded();
|
|
401
|
+
await writeFeatureSpec(repoRoot, 'feature_attach_building');
|
|
402
|
+
await kernel.invoke('feature.init', { feature_id: 'feature_attach_building' }, { actor_type: 'orchestrator', actor_id: 'test' });
|
|
403
|
+
await writeFeatureSession(repoRoot, 'feature_attach_building');
|
|
404
|
+
|
|
405
|
+
const attachToSession = vi.fn(async () => undefined);
|
|
406
|
+
const provider = makeProvider({ attachToSession });
|
|
407
|
+
const toolClient = {
|
|
408
|
+
call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'building' } } }))
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
|
|
412
|
+
const result = await handler.execute({ command: 'attach', feature_id: 'feature_attach_building' });
|
|
413
|
+
const data = (result as Record<string, unknown>).data as Record<string, unknown>;
|
|
414
|
+
expect(data.role).toBe('builder');
|
|
415
|
+
expect(attachToSession).toHaveBeenCalledWith('builder:1');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('GIVEN_feature_session_with_unassigned_specific_role_WHEN_attach_executed_THEN_falls_back_to_orchestrator_session', async () => {
|
|
419
|
+
const kernel = new AopKernel(repoRoot);
|
|
420
|
+
await kernel.ensureLoaded();
|
|
421
|
+
await writeFeatureSpec(repoRoot, 'feature_attach_fallback');
|
|
422
|
+
await kernel.invoke('feature.init', { feature_id: 'feature_attach_fallback' }, { actor_type: 'orchestrator', actor_id: 'test' });
|
|
423
|
+
|
|
424
|
+
// Write session with unassigned planner_session_id but valid orchestrator session
|
|
425
|
+
const leasePath = path.join(repoRoot, '.aop', 'runtime', 'default', 'run-lease.json');
|
|
426
|
+
await fs.mkdir(path.dirname(leasePath), { recursive: true });
|
|
427
|
+
await fs.writeFile(leasePath, JSON.stringify({
|
|
428
|
+
orchestrator_session_id: 'orch:fallback-1',
|
|
429
|
+
feature_sessions: {
|
|
430
|
+
feature_attach_fallback: {
|
|
431
|
+
planner_session_id: 'unassigned',
|
|
432
|
+
builder_session_id: 'unassigned',
|
|
433
|
+
qa_session_id: 'unassigned'
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}), 'utf8');
|
|
437
|
+
|
|
438
|
+
const attachToSession = vi.fn(async () => undefined);
|
|
439
|
+
const provider = makeProvider({ attachToSession });
|
|
440
|
+
const toolClient = {
|
|
441
|
+
call: vi.fn(async () => ({ ok: true, data: { front_matter: { status: 'planning' } } }))
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const handler = new AttachCommandHandler(toolClient as never, 'run:test', kernel, provider);
|
|
445
|
+
const result = await handler.execute({ command: 'attach', feature_id: 'feature_attach_fallback' });
|
|
446
|
+
const data = (result as Record<string, unknown>).data as Record<string, unknown>;
|
|
447
|
+
expect(data.session_id).toBe('orch:fallback-1');
|
|
448
|
+
expect(attachToSession).toHaveBeenCalledWith('orch:fallback-1');
|
|
449
|
+
});
|
|
450
|
+
});
|