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
|
@@ -146,6 +146,8 @@ export interface FeatureDeletionServicePort {
|
|
|
146
146
|
withIndexLock<T>(operation: () => Promise<T>): Promise<T>;
|
|
147
147
|
readIndex(): Promise<AnyRecord>;
|
|
148
148
|
writeIndex(index: AnyRecord): Promise<void>;
|
|
149
|
+
readRunLease(): Promise<RuntimeSessionsSnapshot>;
|
|
150
|
+
writeRunLease(data: RuntimeSessionsSnapshot): Promise<void>;
|
|
149
151
|
normalizeRuntimeSessions(value: unknown, at?: string): RuntimeSessionsSnapshot;
|
|
150
152
|
isRunLeaseFresh(runtimeSessions: RuntimeSessionsSnapshot): boolean;
|
|
151
153
|
locksRelease(resource: string, featureId: string): Promise<unknown>;
|
|
@@ -222,7 +224,7 @@ export class FeatureDeletionService {
|
|
|
222
224
|
const stateHeldLocks = readHeldLocksFromState(stateFrontMatter);
|
|
223
225
|
|
|
224
226
|
const indexSnapshot = await this.port.readIndex();
|
|
225
|
-
const runtimeSessions = this.port.
|
|
227
|
+
const runtimeSessions = await this.port.readRunLease();
|
|
226
228
|
if (this.hasFreshRunLease(runtimeSessions)) {
|
|
227
229
|
throw {
|
|
228
230
|
normalizedResponse: fail(ERROR_CODES.RUN_ALREADY_ACTIVE, 'Cannot delete feature while a run lease is active', {
|
|
@@ -293,7 +295,6 @@ export class FeatureDeletionService {
|
|
|
293
295
|
const indexUpdate = await this.port.withIndexLock(async () => {
|
|
294
296
|
const index = await this.port.readIndex();
|
|
295
297
|
let changed = false;
|
|
296
|
-
let blockedQueueRemoved: number;
|
|
297
298
|
let runtimeSessionRemoved = false;
|
|
298
299
|
|
|
299
300
|
const active = asStringArray(index.active);
|
|
@@ -322,7 +323,7 @@ export class FeatureDeletionService {
|
|
|
322
323
|
const record = asRecord(entry);
|
|
323
324
|
return record.feature_id !== featureId;
|
|
324
325
|
});
|
|
325
|
-
blockedQueueRemoved = blockedQueue.length - nextBlockedQueue.length;
|
|
326
|
+
const blockedQueueRemoved = blockedQueue.length - nextBlockedQueue.length;
|
|
326
327
|
if (blockedQueueRemoved > 0) {
|
|
327
328
|
index.blocked_queue = nextBlockedQueue;
|
|
328
329
|
changed = true;
|
|
@@ -347,15 +348,15 @@ export class FeatureDeletionService {
|
|
|
347
348
|
}
|
|
348
349
|
index.lock_leases = lockLeases;
|
|
349
350
|
|
|
350
|
-
const
|
|
351
|
-
const featureSessions = normalizeFeatureSessions(
|
|
351
|
+
const currentRunLease = await this.port.readRunLease();
|
|
352
|
+
const featureSessions = normalizeFeatureSessions(currentRunLease);
|
|
352
353
|
if (Object.prototype.hasOwnProperty.call(featureSessions, featureId)) {
|
|
353
354
|
delete featureSessions[featureId];
|
|
354
|
-
|
|
355
|
+
currentRunLease.feature_sessions = featureSessions as RuntimeSessionsSnapshot['feature_sessions'];
|
|
356
|
+
await this.port.writeRunLease(currentRunLease);
|
|
355
357
|
runtimeSessionRemoved = true;
|
|
356
358
|
changed = true;
|
|
357
359
|
}
|
|
358
|
-
index.runtime_sessions = nextRuntimeSessions;
|
|
359
360
|
|
|
360
361
|
if (changed) {
|
|
361
362
|
const version = typeof index.version === 'number' ? index.version : 0;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface InterpolationContext {
|
|
2
|
+
base_branch: string;
|
|
3
|
+
feature_id: string;
|
|
4
|
+
worktree_path?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function interpolateGateCommands(commands: string[], context: InterpolationContext): string[] {
|
|
8
|
+
return commands.map((token) =>
|
|
9
|
+
token.replaceAll('{base_branch}', context.base_branch).replaceAll('{feature_id}', context.feature_id)
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isIncrementalMode(mode: string): boolean {
|
|
14
|
+
return mode === 'fast';
|
|
15
|
+
}
|
|
@@ -2,9 +2,11 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { pathExists } from '../../core/fs.js';
|
|
4
4
|
import { runGateMode } from '../../core/gates.js';
|
|
5
|
+
import type { GateProfile, GateStep } from '../../core/gates.js';
|
|
5
6
|
import { ERROR_CODES } from '../../core/error-codes.js';
|
|
6
7
|
import { fail } from '../../core/response.js';
|
|
7
8
|
import { GATE_RESULT, STATUS } from '../../core/constants.js';
|
|
9
|
+
import { interpolateGateCommands, isIncrementalMode } from './gate-interpolation-service.js';
|
|
8
10
|
|
|
9
11
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
12
|
type AnyRecord = Record<string, any>;
|
|
@@ -118,12 +120,37 @@ export class GateService {
|
|
|
118
120
|
const profile = this.gateProfileAndMode(effectiveProfileName, mode);
|
|
119
121
|
const repoRoot = this.port.getRepoRoot();
|
|
120
122
|
|
|
123
|
+
const policySnapshot = this.port.getPolicySnapshot();
|
|
124
|
+
const worktreeConfig = policySnapshot['worktree'] as { base_branch?: string } | undefined;
|
|
125
|
+
const baseBranch = worktreeConfig?.base_branch ?? 'main';
|
|
126
|
+
|
|
127
|
+
const profileAsGate = profile as unknown as GateProfile;
|
|
128
|
+
const originalModeSteps = profileAsGate.modes?.[mode] ?? [];
|
|
129
|
+
const hasBaseBranchTemplate =
|
|
130
|
+
isIncrementalMode(mode) &&
|
|
131
|
+
originalModeSteps.some((step) => step.cmd.some((token) => token.includes('{base_branch}')));
|
|
132
|
+
|
|
133
|
+
let effectiveProfile: AnyRecord = profile;
|
|
134
|
+
if (isIncrementalMode(mode)) {
|
|
135
|
+
const interpolatedSteps: GateStep[] = originalModeSteps.map((step) => ({
|
|
136
|
+
...step,
|
|
137
|
+
cmd: interpolateGateCommands(step.cmd, { base_branch: baseBranch, feature_id: featureId })
|
|
138
|
+
}));
|
|
139
|
+
effectiveProfile = {
|
|
140
|
+
...profile,
|
|
141
|
+
modes: {
|
|
142
|
+
...(profileAsGate.modes ?? {}),
|
|
143
|
+
[mode]: interpolatedSteps
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
121
148
|
const runResult = (await runGateMode({
|
|
122
149
|
featureId,
|
|
123
150
|
mode,
|
|
124
151
|
profileName: effectiveProfileName,
|
|
125
|
-
profile,
|
|
126
|
-
policy:
|
|
152
|
+
profile: effectiveProfile,
|
|
153
|
+
policy: policySnapshot as Parameters<typeof runGateMode>[0]['policy'],
|
|
127
154
|
worktreePath: this.port.worktreePath(featureId),
|
|
128
155
|
logDirectory: this.port.logsPath(featureId),
|
|
129
156
|
evidenceDirectory: this.port.evidencePath(featureId)
|
|
@@ -150,6 +177,15 @@ export class GateService {
|
|
|
150
177
|
})
|
|
151
178
|
);
|
|
152
179
|
|
|
180
|
+
if (hasBaseBranchTemplate) {
|
|
181
|
+
const skippedInfo = { skipped_reason: 'incremental_mode' as const, base_branch: baseBranch };
|
|
182
|
+
for (const evidenceFile of [runResult.evidence_path, runResult.latest_path]) {
|
|
183
|
+
const raw = await fs.readFile(evidenceFile, 'utf8');
|
|
184
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
185
|
+
await fs.writeFile(evidenceFile, `${JSON.stringify({ ...parsed, ...skippedInfo }, null, 2)}\n`, 'utf8');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
153
189
|
if (runResult.overall === 'fail' && runResult.coverage_status === 'fail') {
|
|
154
190
|
throw {
|
|
155
191
|
normalizedResponse: fail(
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { stableHash } from '../../core/fs.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Computes a deterministic 12-character instance ID from a config file path.
|
|
5
|
+
* This ensures two orchestrator checkouts with different config paths get
|
|
6
|
+
* independent run-lease files and dashboard ports.
|
|
7
|
+
*/
|
|
8
|
+
export function computeInstanceId(configPath: string): string {
|
|
9
|
+
return stableHash(configPath).substring(0, 12);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns the canonical config path for the default single-instance case.
|
|
14
|
+
* Used when no explicit --config flag is provided.
|
|
15
|
+
*/
|
|
16
|
+
export function defaultConfigPath(repoRoot: string): string {
|
|
17
|
+
return `${repoRoot}/agentic/orchestrator/policy.yaml`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
|
|
6
|
+
export interface Issue {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
body: string;
|
|
10
|
+
status: string;
|
|
11
|
+
url: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IssueTracker {
|
|
15
|
+
getIssue(issueId: string): Promise<Issue>;
|
|
16
|
+
updateIssueStatus(issueId: string, status: string): Promise<void>;
|
|
17
|
+
addComment(issueId: string, comment: string): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface IssueTrackerConfig {
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
type: string;
|
|
23
|
+
config?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// --- GitHub adapter (via gh CLI) ---
|
|
27
|
+
|
|
28
|
+
export type GhRunner = (args: string[]) => Promise<{ stdout: string; exitCode: number }>;
|
|
29
|
+
export type HttpRunner = (url: string, init: RequestInit) => Promise<{ status: number; ok: boolean; body: string }>;
|
|
30
|
+
|
|
31
|
+
export function createGhRunner(fn?: GhRunner): GhRunner {
|
|
32
|
+
if (fn !== undefined) {
|
|
33
|
+
return fn;
|
|
34
|
+
}
|
|
35
|
+
return async (args: string[]) => {
|
|
36
|
+
try {
|
|
37
|
+
const { stdout } = await execFileAsync('gh', args, { timeout: 15_000 });
|
|
38
|
+
return { stdout, exitCode: 0 };
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
const e = err as Record<string, unknown>;
|
|
41
|
+
if (e['code'] === 'ENOENT' || e['code'] === 127) {
|
|
42
|
+
return { stdout: '', exitCode: 127 };
|
|
43
|
+
}
|
|
44
|
+
const exitCode = typeof e['code'] === 'number' ? e['code'] : 1;
|
|
45
|
+
return { stdout: '', exitCode };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createHttpRunner(fn?: HttpRunner): HttpRunner {
|
|
51
|
+
if (fn !== undefined) {
|
|
52
|
+
return fn;
|
|
53
|
+
}
|
|
54
|
+
return async (url: string, init: RequestInit) => {
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(url, init);
|
|
57
|
+
return {
|
|
58
|
+
status: response.status,
|
|
59
|
+
ok: response.ok,
|
|
60
|
+
body: await response.text()
|
|
61
|
+
};
|
|
62
|
+
} catch {
|
|
63
|
+
return { status: 0, ok: false, body: '' };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function tryParseJson(value: string): Record<string, unknown> | null {
|
|
69
|
+
if (!value) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(value) as unknown;
|
|
74
|
+
if (parsed && typeof parsed === 'object') {
|
|
75
|
+
return parsed as Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class GitHubIssueTracker implements IssueTracker {
|
|
84
|
+
private readonly repo: string | undefined;
|
|
85
|
+
private readonly ghRunner: GhRunner;
|
|
86
|
+
|
|
87
|
+
constructor(config: Record<string, string> = {}, ghRunner?: GhRunner) {
|
|
88
|
+
this.repo = config['repo'];
|
|
89
|
+
this.ghRunner = ghRunner ?? createGhRunner();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getIssue(issueId: string): Promise<Issue> {
|
|
93
|
+
const repoArgs = this.repo ? ['--repo', this.repo] : [];
|
|
94
|
+
const result = await this.ghRunner([
|
|
95
|
+
'issue', 'view', issueId,
|
|
96
|
+
...repoArgs,
|
|
97
|
+
'--json', 'number,title,body,state,url'
|
|
98
|
+
]);
|
|
99
|
+
if (result.exitCode !== 0 || !result.stdout) {
|
|
100
|
+
return { id: issueId, title: '', body: '', status: 'unknown', url: '' };
|
|
101
|
+
}
|
|
102
|
+
const parsed = JSON.parse(result.stdout) as {
|
|
103
|
+
number: number;
|
|
104
|
+
title: string;
|
|
105
|
+
body: string;
|
|
106
|
+
state: string;
|
|
107
|
+
url: string;
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
id: String(parsed.number),
|
|
111
|
+
title: parsed.title,
|
|
112
|
+
body: parsed.body,
|
|
113
|
+
status: parsed.state.toLowerCase(),
|
|
114
|
+
url: parsed.url
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async updateIssueStatus(issueId: string, status: string): Promise<void> {
|
|
119
|
+
const repoArgs = this.repo ? ['--repo', this.repo] : [];
|
|
120
|
+
const stateArg = status === 'closed' || status === 'merged' ? 'closed' : 'open';
|
|
121
|
+
await this.ghRunner(['issue', 'edit', issueId, ...repoArgs, '--state', stateArg]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async addComment(issueId: string, comment: string): Promise<void> {
|
|
125
|
+
const repoArgs = this.repo ? ['--repo', this.repo] : [];
|
|
126
|
+
await this.ghRunner(['issue', 'comment', issueId, ...repoArgs, '--body', comment]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface LinearIssueNode {
|
|
131
|
+
id: string;
|
|
132
|
+
identifier: string;
|
|
133
|
+
title: string;
|
|
134
|
+
description: string;
|
|
135
|
+
url: string;
|
|
136
|
+
state: {
|
|
137
|
+
name: string;
|
|
138
|
+
} | null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readString(value: unknown): string {
|
|
142
|
+
return typeof value === 'string' ? value : '';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function lowerCaseStatus(value: string): string {
|
|
146
|
+
return value ? value.toLowerCase() : 'unknown';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizeAopStatus(status: string): string {
|
|
150
|
+
return status.trim().toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function mapDefaultLinearStatus(status: string): string | null {
|
|
154
|
+
const normalized = normalizeAopStatus(status);
|
|
155
|
+
if (normalized === 'merged' || normalized === 'closed') {
|
|
156
|
+
return 'done';
|
|
157
|
+
}
|
|
158
|
+
if (normalized === 'blocked' || normalized === 'failed') {
|
|
159
|
+
return 'blocked';
|
|
160
|
+
}
|
|
161
|
+
if (normalized === 'planning') {
|
|
162
|
+
return 'backlog';
|
|
163
|
+
}
|
|
164
|
+
if (normalized === 'qa' || normalized === 'ready_to_merge') {
|
|
165
|
+
return 'in_review';
|
|
166
|
+
}
|
|
167
|
+
if (normalized === 'building') {
|
|
168
|
+
return 'in_progress';
|
|
169
|
+
}
|
|
170
|
+
return 'in_progress';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function mapDefaultJiraTransition(status: string): string {
|
|
174
|
+
const normalized = normalizeAopStatus(status);
|
|
175
|
+
if (normalized === 'merged' || normalized === 'closed') {
|
|
176
|
+
return 'done';
|
|
177
|
+
}
|
|
178
|
+
if (normalized === 'planning') {
|
|
179
|
+
return 'to do';
|
|
180
|
+
}
|
|
181
|
+
if (normalized === 'blocked' || normalized === 'failed') {
|
|
182
|
+
return 'blocked';
|
|
183
|
+
}
|
|
184
|
+
return 'in progress';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// --- Linear adapter (GraphQL HTTP) ---
|
|
188
|
+
export class LinearIssueTracker implements IssueTracker {
|
|
189
|
+
private readonly token: string;
|
|
190
|
+
private readonly baseUrl: string;
|
|
191
|
+
private readonly config: Record<string, string>;
|
|
192
|
+
private readonly httpRunner: HttpRunner;
|
|
193
|
+
|
|
194
|
+
constructor(config: Record<string, string> = {}, httpRunner?: HttpRunner) {
|
|
195
|
+
this.token = config['token'] ?? '';
|
|
196
|
+
this.baseUrl = config['base_url'] ?? 'https://api.linear.app/graphql';
|
|
197
|
+
this.config = config;
|
|
198
|
+
this.httpRunner = createHttpRunner(httpRunner);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private async graphQl<TData>(query: string, variables: Record<string, unknown>): Promise<TData | null> {
|
|
202
|
+
const headers: Record<string, string> = {
|
|
203
|
+
'Content-Type': 'application/json'
|
|
204
|
+
};
|
|
205
|
+
if (this.token) {
|
|
206
|
+
headers['Authorization'] = `Bearer ${this.token}`;
|
|
207
|
+
}
|
|
208
|
+
const result = await this.httpRunner(this.baseUrl, {
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers,
|
|
211
|
+
body: JSON.stringify({ query, variables })
|
|
212
|
+
});
|
|
213
|
+
if (!result.ok || !result.body) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
const parsed = tryParseJson(result.body);
|
|
217
|
+
if (!parsed) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
if (Array.isArray(parsed['errors']) && parsed['errors'].length > 0) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const data = parsed['data'];
|
|
224
|
+
if (data && typeof data === 'object') {
|
|
225
|
+
return data as TData;
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private static parseIssueNode(value: unknown): LinearIssueNode | null {
|
|
231
|
+
if (!value || typeof value !== 'object') {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const raw = value as Record<string, unknown>;
|
|
235
|
+
const stateRaw = raw['state'];
|
|
236
|
+
return {
|
|
237
|
+
id: readString(raw['id']),
|
|
238
|
+
identifier: readString(raw['identifier']),
|
|
239
|
+
title: readString(raw['title']),
|
|
240
|
+
description: readString(raw['description']),
|
|
241
|
+
url: readString(raw['url']),
|
|
242
|
+
state:
|
|
243
|
+
stateRaw && typeof stateRaw === 'object'
|
|
244
|
+
? {
|
|
245
|
+
name: readString((stateRaw as Record<string, unknown>)['name'])
|
|
246
|
+
}
|
|
247
|
+
: null
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private async resolveIssueNode(issueId: string): Promise<LinearIssueNode | null> {
|
|
252
|
+
const byIdentifier = await this.graphQl<{ issueByIdentifier?: unknown }>(
|
|
253
|
+
'query AopIssueByIdentifier($identifier: String!) { issueByIdentifier(identifier: $identifier) { id identifier title description url state { name } } }',
|
|
254
|
+
{ identifier: issueId }
|
|
255
|
+
);
|
|
256
|
+
const identifierIssue = LinearIssueTracker.parseIssueNode(byIdentifier?.issueByIdentifier);
|
|
257
|
+
if (identifierIssue) {
|
|
258
|
+
return identifierIssue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const byId = await this.graphQl<{ issue?: unknown }>(
|
|
262
|
+
'query AopIssueById($id: String!) { issue(id: $id) { id identifier title description url state { name } } }',
|
|
263
|
+
{ id: issueId }
|
|
264
|
+
);
|
|
265
|
+
return LinearIssueTracker.parseIssueNode(byId?.issue);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private resolveStateId(status: string): string | null {
|
|
269
|
+
const normalized = normalizeAopStatus(status);
|
|
270
|
+
const explicit = this.config[`state_id_${normalized}`];
|
|
271
|
+
if (explicit && explicit.length > 0) {
|
|
272
|
+
return explicit;
|
|
273
|
+
}
|
|
274
|
+
const defaultKey = mapDefaultLinearStatus(status);
|
|
275
|
+
if (!defaultKey) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
const defaultMapped = this.config[`state_id_${defaultKey}`];
|
|
279
|
+
return defaultMapped && defaultMapped.length > 0 ? defaultMapped : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async getIssue(issueId: string): Promise<Issue> {
|
|
283
|
+
const issue = await this.resolveIssueNode(issueId).catch(() => null);
|
|
284
|
+
if (!issue) {
|
|
285
|
+
return { id: issueId, title: '', body: '', status: 'unknown', url: '' };
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
id: issue.identifier || issue.id || issueId,
|
|
289
|
+
title: issue.title,
|
|
290
|
+
body: issue.description,
|
|
291
|
+
status: lowerCaseStatus(issue.state?.name ?? ''),
|
|
292
|
+
url: issue.url
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async updateIssueStatus(issueId: string, status: string): Promise<void> {
|
|
297
|
+
const issue = await this.resolveIssueNode(issueId).catch(() => null);
|
|
298
|
+
if (!issue?.id) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const stateId = this.resolveStateId(status);
|
|
302
|
+
if (!stateId) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
await this.graphQl(
|
|
306
|
+
'mutation AopIssueUpdate($id: String!, $stateId: String!) { issueUpdate(id: $id, input: { stateId: $stateId }) { success } }',
|
|
307
|
+
{ id: issue.id, stateId }
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async addComment(issueId: string, comment: string): Promise<void> {
|
|
312
|
+
const issue = await this.resolveIssueNode(issueId).catch(() => null);
|
|
313
|
+
if (!issue?.id) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
await this.graphQl(
|
|
317
|
+
'mutation AopCommentCreate($issueId: String!, $body: String!) { commentCreate(input: { issueId: $issueId, body: $body }) { success } }',
|
|
318
|
+
{ issueId: issue.id, body: comment }
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function toJiraDescription(value: unknown): string {
|
|
324
|
+
if (typeof value === 'string') {
|
|
325
|
+
return value;
|
|
326
|
+
}
|
|
327
|
+
if (!value || typeof value !== 'object') {
|
|
328
|
+
return '';
|
|
329
|
+
}
|
|
330
|
+
return JSON.stringify(value);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// --- Jira adapter (REST HTTP) ---
|
|
334
|
+
export class JiraIssueTracker implements IssueTracker {
|
|
335
|
+
private readonly baseUrl: string;
|
|
336
|
+
private readonly token: string;
|
|
337
|
+
private readonly email: string;
|
|
338
|
+
private readonly config: Record<string, string>;
|
|
339
|
+
private readonly httpRunner: HttpRunner;
|
|
340
|
+
|
|
341
|
+
constructor(config: Record<string, string> = {}, httpRunner?: HttpRunner) {
|
|
342
|
+
this.baseUrl = (config['base_url'] ?? config['url'] ?? '').replace(/\/+$/, '');
|
|
343
|
+
this.token = config['token'] ?? '';
|
|
344
|
+
this.email = config['email'] ?? config['user'] ?? '';
|
|
345
|
+
this.config = config;
|
|
346
|
+
this.httpRunner = createHttpRunner(httpRunner);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private authHeader(): string | null {
|
|
350
|
+
if (this.email && this.token) {
|
|
351
|
+
return `Basic ${Buffer.from(`${this.email}:${this.token}`, 'utf8').toString('base64')}`;
|
|
352
|
+
}
|
|
353
|
+
if (this.token) {
|
|
354
|
+
return `Bearer ${this.token}`;
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private async requestJson<T>(
|
|
360
|
+
endpoint: string,
|
|
361
|
+
method: 'GET' | 'POST',
|
|
362
|
+
body?: Record<string, unknown>
|
|
363
|
+
): Promise<T | null> {
|
|
364
|
+
if (!this.baseUrl) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
const headers: Record<string, string> = {
|
|
368
|
+
Accept: 'application/json'
|
|
369
|
+
};
|
|
370
|
+
const authHeader = this.authHeader();
|
|
371
|
+
if (authHeader) {
|
|
372
|
+
headers['Authorization'] = authHeader;
|
|
373
|
+
}
|
|
374
|
+
if (body !== undefined) {
|
|
375
|
+
headers['Content-Type'] = 'application/json';
|
|
376
|
+
}
|
|
377
|
+
const result = await this.httpRunner(`${this.baseUrl}${endpoint}`, {
|
|
378
|
+
method,
|
|
379
|
+
headers,
|
|
380
|
+
body: body === undefined ? undefined : JSON.stringify(body)
|
|
381
|
+
});
|
|
382
|
+
if (!result.ok || !result.body) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
const parsed = tryParseJson(result.body);
|
|
386
|
+
return parsed as T | null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private resolveTransitionName(status: string): string {
|
|
390
|
+
const normalized = normalizeAopStatus(status);
|
|
391
|
+
const explicit = this.config[`transition_${normalized}`];
|
|
392
|
+
if (explicit && explicit.length > 0) {
|
|
393
|
+
return explicit.toLowerCase();
|
|
394
|
+
}
|
|
395
|
+
return mapDefaultJiraTransition(status);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async getIssue(issueId: string): Promise<Issue> {
|
|
399
|
+
const payload = await this.requestJson<{
|
|
400
|
+
key?: string;
|
|
401
|
+
fields?: {
|
|
402
|
+
summary?: unknown;
|
|
403
|
+
description?: unknown;
|
|
404
|
+
status?: {
|
|
405
|
+
name?: unknown;
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
}>(`/rest/api/2/issue/${encodeURIComponent(issueId)}?fields=summary,description,status`, 'GET');
|
|
409
|
+
if (!payload) {
|
|
410
|
+
return { id: issueId, title: '', body: '', status: 'unknown', url: '' };
|
|
411
|
+
}
|
|
412
|
+
const fields = payload.fields ?? {};
|
|
413
|
+
return {
|
|
414
|
+
id: readString(payload.key) || issueId,
|
|
415
|
+
title: readString(fields.summary),
|
|
416
|
+
body: toJiraDescription(fields.description),
|
|
417
|
+
status: lowerCaseStatus(readString(fields.status?.name)),
|
|
418
|
+
url: this.baseUrl ? `${this.baseUrl}/browse/${issueId}` : ''
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async updateIssueStatus(issueId: string, status: string): Promise<void> {
|
|
423
|
+
const transitionsPayload = await this.requestJson<{ transitions?: Array<{ id?: string; name?: string }> }>(
|
|
424
|
+
`/rest/api/2/issue/${encodeURIComponent(issueId)}/transitions`,
|
|
425
|
+
'GET'
|
|
426
|
+
);
|
|
427
|
+
const transitions = transitionsPayload?.transitions ?? [];
|
|
428
|
+
const targetName = this.resolveTransitionName(status);
|
|
429
|
+
const transition = transitions.find(
|
|
430
|
+
(item) => typeof item.name === 'string' && item.name.toLowerCase() === targetName
|
|
431
|
+
);
|
|
432
|
+
if (!transition?.id) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
await this.requestJson(
|
|
436
|
+
`/rest/api/2/issue/${encodeURIComponent(issueId)}/transitions`,
|
|
437
|
+
'POST',
|
|
438
|
+
{ transition: { id: transition.id } }
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async addComment(issueId: string, comment: string): Promise<void> {
|
|
443
|
+
await this.requestJson(
|
|
444
|
+
`/rest/api/2/issue/${encodeURIComponent(issueId)}/comment`,
|
|
445
|
+
'POST',
|
|
446
|
+
{ body: comment }
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// --- Factory ---
|
|
452
|
+
export function createIssueTracker(
|
|
453
|
+
config: IssueTrackerConfig | undefined,
|
|
454
|
+
ghRunner?: GhRunner,
|
|
455
|
+
httpRunner?: HttpRunner
|
|
456
|
+
): IssueTracker | undefined {
|
|
457
|
+
if (!config) {return undefined;}
|
|
458
|
+
if (config.enabled === false) {return undefined;}
|
|
459
|
+
switch (config.type) {
|
|
460
|
+
case 'github':
|
|
461
|
+
return new GitHubIssueTracker(config.config ?? {}, ghRunner);
|
|
462
|
+
case 'linear':
|
|
463
|
+
return new LinearIssueTracker(config.config ?? {}, httpRunner);
|
|
464
|
+
case 'jira':
|
|
465
|
+
return new JiraIssueTracker(config.config ?? {}, httpRunner);
|
|
466
|
+
default:
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
}
|