@kbediako/codex-orchestrator 0.1.38 → 0.2.1

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.
Files changed (311) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/README.md +46 -317
  3. package/bin/codex-orchestrator.js +161 -0
  4. package/codex.orchestrator.json +149 -13
  5. package/dist/bin/codex-orchestrator.js +797 -1154
  6. package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +50 -0
  7. package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +22 -4
  8. package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +3 -3
  9. package/dist/orchestrator/src/cli/adapters/CommandTester.js +2 -2
  10. package/dist/orchestrator/src/cli/adapters/cloudFailureDiagnostics.js +295 -11
  11. package/dist/orchestrator/src/cli/coStatusAttachCliShell.js +402 -0
  12. package/dist/orchestrator/src/cli/coStatusCliShell.js +451 -0
  13. package/dist/orchestrator/src/cli/coStatusOperatorAutopilotCliShell.js +120 -0
  14. package/dist/orchestrator/src/cli/codexCliShell.js +119 -0
  15. package/dist/orchestrator/src/cli/codexDefaultsSetup.js +265 -36
  16. package/dist/orchestrator/src/cli/config/delegationConfig.js +317 -5
  17. package/dist/orchestrator/src/cli/config/repoConfigPolicy.js +2 -3
  18. package/dist/orchestrator/src/cli/config/userConfig.js +28 -13
  19. package/dist/orchestrator/src/cli/control/authenticatedControlRouteGate.js +69 -0
  20. package/dist/orchestrator/src/cli/control/authenticatedRouteComposition.js +267 -0
  21. package/dist/orchestrator/src/cli/control/authenticatedRouteController.js +5 -0
  22. package/dist/orchestrator/src/cli/control/authenticatedRouteDispatcher.js +41 -0
  23. package/dist/orchestrator/src/cli/control/compatibilityIssuePresenter.js +1035 -0
  24. package/dist/orchestrator/src/cli/control/confirmationApproveController.js +62 -0
  25. package/dist/orchestrator/src/cli/control/confirmationCreateController.js +69 -0
  26. package/dist/orchestrator/src/cli/control/confirmationIssueConsumeController.js +43 -0
  27. package/dist/orchestrator/src/cli/control/confirmationListController.js +22 -0
  28. package/dist/orchestrator/src/cli/control/confirmationValidateController.js +58 -0
  29. package/dist/orchestrator/src/cli/control/confirmations.js +25 -3
  30. package/dist/orchestrator/src/cli/control/controlActionCancelConfirmation.js +65 -0
  31. package/dist/orchestrator/src/cli/control/controlActionController.js +77 -0
  32. package/dist/orchestrator/src/cli/control/controlActionControllerSequencing.js +161 -0
  33. package/dist/orchestrator/src/cli/control/controlActionExecution.js +142 -0
  34. package/dist/orchestrator/src/cli/control/controlActionFinalization.js +43 -0
  35. package/dist/orchestrator/src/cli/control/controlActionOutcome.js +60 -0
  36. package/dist/orchestrator/src/cli/control/controlActionPreflight.js +476 -0
  37. package/dist/orchestrator/src/cli/control/controlAuthenticatedRouteHandoff.js +57 -0
  38. package/dist/orchestrator/src/cli/control/controlBootstrapAssembly.js +39 -0
  39. package/dist/orchestrator/src/cli/control/controlBootstrapMetadataPersistence.js +16 -0
  40. package/dist/orchestrator/src/cli/control/controlEventTransport.js +49 -0
  41. package/dist/orchestrator/src/cli/control/controlExpiryLifecycle.js +102 -0
  42. package/dist/orchestrator/src/cli/control/controlHostOwnership.js +480 -0
  43. package/dist/orchestrator/src/cli/control/controlHostSupervision.js +630 -0
  44. package/dist/orchestrator/src/cli/control/controlOversightFacade.js +8 -0
  45. package/dist/orchestrator/src/cli/control/controlOversightReadContract.js +1 -0
  46. package/dist/orchestrator/src/cli/control/controlOversightReadService.js +16 -0
  47. package/dist/orchestrator/src/cli/control/controlOversightUpdateContract.js +1 -0
  48. package/dist/orchestrator/src/cli/control/controlPersistenceFiles.js +6 -0
  49. package/dist/orchestrator/src/cli/control/controlQuestionChildResolution.js +18 -0
  50. package/dist/orchestrator/src/cli/control/controlRequestContext.js +42 -0
  51. package/dist/orchestrator/src/cli/control/controlRequestController.js +9 -0
  52. package/dist/orchestrator/src/cli/control/controlRequestPredispatch.js +17 -0
  53. package/dist/orchestrator/src/cli/control/controlRequestRouteDispatch.js +44 -0
  54. package/dist/orchestrator/src/cli/control/controlRuntime.js +1003 -0
  55. package/dist/orchestrator/src/cli/control/controlServer.js +23 -1456
  56. package/dist/orchestrator/src/cli/control/controlServerAuditAndErrorHelpers.js +115 -0
  57. package/dist/orchestrator/src/cli/control/controlServerAuthenticatedRouteBranch.js +29 -0
  58. package/dist/orchestrator/src/cli/control/controlServerBootstrapLifecycle.js +30 -0
  59. package/dist/orchestrator/src/cli/control/controlServerBootstrapStartSequence.js +21 -0
  60. package/dist/orchestrator/src/cli/control/controlServerOwnedRuntimeLifecycle.js +67 -0
  61. package/dist/orchestrator/src/cli/control/controlServerPublicLifecycle.js +756 -0
  62. package/dist/orchestrator/src/cli/control/controlServerPublicRouteHelpers.js +86 -0
  63. package/dist/orchestrator/src/cli/control/controlServerReadyInstanceLifecycle.js +25 -0
  64. package/dist/orchestrator/src/cli/control/controlServerReadyInstanceStartup.js +18 -0
  65. package/dist/orchestrator/src/cli/control/controlServerRequestBodyHelpers.js +37 -0
  66. package/dist/orchestrator/src/cli/control/controlServerRequestShell.js +40 -0
  67. package/dist/orchestrator/src/cli/control/controlServerRequestShellBinding.js +17 -0
  68. package/dist/orchestrator/src/cli/control/controlServerSeedLoading.js +27 -0
  69. package/dist/orchestrator/src/cli/control/controlServerSeededRuntimeAssembly.js +186 -0
  70. package/dist/orchestrator/src/cli/control/controlServerStartupInputPreparation.js +31 -0
  71. package/dist/orchestrator/src/cli/control/controlServerStartupSequence.js +49 -0
  72. package/dist/orchestrator/src/cli/control/controlState.js +233 -2
  73. package/dist/orchestrator/src/cli/control/controlStatusDashboard.js +1904 -0
  74. package/dist/orchestrator/src/cli/control/controlTelegramBridgeBootstrapLifecycle.js +22 -0
  75. package/dist/orchestrator/src/cli/control/controlTelegramBridgeLifecycle.js +67 -0
  76. package/dist/orchestrator/src/cli/control/controlTelegramBridgeOversightFacadeFactory.js +8 -0
  77. package/dist/orchestrator/src/cli/control/controlTelegramCommandController.js +49 -0
  78. package/dist/orchestrator/src/cli/control/controlTelegramDispatchRead.js +40 -0
  79. package/dist/orchestrator/src/cli/control/controlTelegramPollingController.js +89 -0
  80. package/dist/orchestrator/src/cli/control/controlTelegramProjectionNotificationController.js +29 -0
  81. package/dist/orchestrator/src/cli/control/controlTelegramPushState.js +63 -0
  82. package/dist/orchestrator/src/cli/control/controlTelegramQuestionRead.js +13 -0
  83. package/dist/orchestrator/src/cli/control/controlTelegramReadController.js +216 -0
  84. package/dist/orchestrator/src/cli/control/controlTelegramUpdateHandler.js +63 -0
  85. package/dist/orchestrator/src/cli/control/controlWatcher.js +73 -5
  86. package/dist/orchestrator/src/cli/control/delegationRegisterController.js +35 -0
  87. package/dist/orchestrator/src/cli/control/dynamicToolBridgePolicy.js +139 -0
  88. package/dist/orchestrator/src/cli/control/eventsSseController.js +12 -0
  89. package/dist/orchestrator/src/cli/control/linearBudgetState.js +1789 -0
  90. package/dist/orchestrator/src/cli/control/linearDispatchSource.js +1137 -0
  91. package/dist/orchestrator/src/cli/control/linearGraphqlClient.js +150 -0
  92. package/dist/orchestrator/src/cli/control/linearRateLimit.js +102 -0
  93. package/dist/orchestrator/src/cli/control/linearWebhookController.js +499 -0
  94. package/dist/orchestrator/src/cli/control/liveLinearAdvisoryRuntime.js +70 -0
  95. package/dist/orchestrator/src/cli/control/observabilityApiController.js +173 -0
  96. package/dist/orchestrator/src/cli/control/observabilityReadModel.js +500 -0
  97. package/dist/orchestrator/src/cli/control/observabilitySurface.js +284 -0
  98. package/dist/orchestrator/src/cli/control/observabilityUpdateNotifier.js +22 -0
  99. package/dist/orchestrator/src/cli/control/operatorDashboardPresenter.js +252 -0
  100. package/dist/orchestrator/src/cli/control/providerAgentCapacity.js +70 -0
  101. package/dist/orchestrator/src/cli/control/providerControlHostFreshnessGauge.js +1068 -0
  102. package/dist/orchestrator/src/cli/control/providerIntakeState.js +473 -0
  103. package/dist/orchestrator/src/cli/control/providerIssueHandoff.js +6811 -0
  104. package/dist/orchestrator/src/cli/control/providerIssueObservability.js +1348 -0
  105. package/dist/orchestrator/src/cli/control/providerIssueRetryQueue.js +84 -0
  106. package/dist/orchestrator/src/cli/control/providerLinearRuntimeProof.js +588 -0
  107. package/dist/orchestrator/src/cli/control/providerLinearScreenshotProof.js +473 -0
  108. package/dist/orchestrator/src/cli/control/providerLinearWorkerTruth.js +383 -0
  109. package/dist/orchestrator/src/cli/control/providerLinearWorkflowAudit.js +254 -0
  110. package/dist/orchestrator/src/cli/control/providerLinearWorkflowFacade.js +5573 -0
  111. package/dist/orchestrator/src/cli/control/providerLinearWorkflowStates.js +115 -0
  112. package/dist/orchestrator/src/cli/control/providerMergeCloseout.js +1868 -0
  113. package/dist/orchestrator/src/cli/control/providerOperatorAutopilot.js +1580 -0
  114. package/dist/orchestrator/src/cli/control/providerOperatorAutopilotLifecycle.js +154 -0
  115. package/dist/orchestrator/src/cli/control/providerOperatorAutopilotLocalRolloutExecution.js +1006 -0
  116. package/dist/orchestrator/src/cli/control/providerPollingHealth.js +435 -0
  117. package/dist/orchestrator/src/cli/control/providerTerminalCleanup.js +516 -0
  118. package/dist/orchestrator/src/cli/control/providerWorkerHosts.js +191 -0
  119. package/dist/orchestrator/src/cli/control/providerWorkflowConfigStore.js +515 -0
  120. package/dist/orchestrator/src/cli/control/questionChildResolutionAdapter.js +361 -0
  121. package/dist/orchestrator/src/cli/control/questionQueueController.js +181 -0
  122. package/dist/orchestrator/src/cli/control/questionReadRetryDeduplication.js +9 -0
  123. package/dist/orchestrator/src/cli/control/questionReadSequence.js +10 -0
  124. package/dist/orchestrator/src/cli/control/securityViolationController.js +27 -0
  125. package/dist/orchestrator/src/cli/control/selectedRunProjection.js +1885 -0
  126. package/dist/orchestrator/src/cli/control/telegramOversightApiClient.js +48 -0
  127. package/dist/orchestrator/src/cli/control/telegramOversightBridge.js +180 -0
  128. package/dist/orchestrator/src/cli/control/telegramOversightBridgeProjectionDeliveryQueue.js +25 -0
  129. package/dist/orchestrator/src/cli/control/telegramOversightBridgeRuntimeLifecycle.js +45 -0
  130. package/dist/orchestrator/src/cli/control/telegramOversightBridgeStateStore.js +77 -0
  131. package/dist/orchestrator/src/cli/control/telegramOversightControlActionApiClient.js +45 -0
  132. package/dist/orchestrator/src/cli/control/trackerDispatchPilot.js +439 -0
  133. package/dist/orchestrator/src/cli/control/uiDataController.js +34 -0
  134. package/dist/orchestrator/src/cli/control/uiSessionController.js +100 -0
  135. package/dist/orchestrator/src/cli/controlHostCliShell.js +860 -0
  136. package/dist/orchestrator/src/cli/controlHostFreshnessGaugeCliShell.js +129 -0
  137. package/dist/orchestrator/src/cli/controlHostSupervisionCliShell.js +2127 -0
  138. package/dist/orchestrator/src/cli/delegationCliShell.js +62 -0
  139. package/dist/orchestrator/src/cli/delegationServer.js +567 -678
  140. package/dist/orchestrator/src/cli/delegationServerCliShell.js +52 -0
  141. package/dist/orchestrator/src/cli/delegationServerQuestionFlowShell.js +228 -0
  142. package/dist/orchestrator/src/cli/delegationServerToolDispatchShell.js +411 -0
  143. package/dist/orchestrator/src/cli/delegationServerTransport.js +274 -0
  144. package/dist/orchestrator/src/cli/delegationSetup.js +51 -171
  145. package/dist/orchestrator/src/cli/devtoolsCliShell.js +34 -0
  146. package/dist/orchestrator/src/cli/doctor.js +678 -164
  147. package/dist/orchestrator/src/cli/doctorCliRequestShell.js +72 -0
  148. package/dist/orchestrator/src/cli/doctorCliShell.js +138 -0
  149. package/dist/orchestrator/src/cli/doctorUsage.js +119 -15
  150. package/dist/orchestrator/src/cli/exec/experience.js +16 -2
  151. package/dist/orchestrator/src/cli/exec/summary.js +3 -0
  152. package/dist/orchestrator/src/cli/execCliShell.js +51 -0
  153. package/dist/orchestrator/src/cli/flowCliRequestShell.js +44 -0
  154. package/dist/orchestrator/src/cli/flowCliShell.js +239 -0
  155. package/dist/orchestrator/src/cli/frontendTestCliRequestShell.js +80 -0
  156. package/dist/orchestrator/src/cli/frontendTestCliShell.js +41 -0
  157. package/dist/orchestrator/src/cli/init.js +95 -1
  158. package/dist/orchestrator/src/cli/initCliShell.js +50 -0
  159. package/dist/orchestrator/src/cli/linearCliShell.js +1200 -0
  160. package/dist/orchestrator/src/cli/mcpEnableCliShell.js +132 -0
  161. package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +3 -2
  162. package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +56 -0
  163. package/dist/orchestrator/src/cli/orchestrator.js +66 -1376
  164. package/dist/orchestrator/src/cli/planCliShell.js +19 -0
  165. package/dist/orchestrator/src/cli/prCliShell.js +41 -0
  166. package/dist/orchestrator/src/cli/providerLinearChildLanePhaseContract.js +204 -0
  167. package/dist/orchestrator/src/cli/providerLinearChildLaneRunner.js +1835 -0
  168. package/dist/orchestrator/src/cli/providerLinearChildLaneShell.js +2420 -0
  169. package/dist/orchestrator/src/cli/providerLinearChildStreamShell.js +385 -0
  170. package/dist/orchestrator/src/cli/providerLinearWorkerRunner.js +6834 -0
  171. package/dist/orchestrator/src/cli/resumeCliShell.js +14 -0
  172. package/dist/orchestrator/src/cli/reviewCliLaunchShell.js +72 -0
  173. package/dist/orchestrator/src/cli/rlm/alignment.js +3 -3
  174. package/dist/orchestrator/src/cli/rlm/context.js +94 -7
  175. package/dist/orchestrator/src/cli/rlm/rlmCodexRuntimeShell.js +546 -0
  176. package/dist/orchestrator/src/cli/rlm/symbolic.js +4 -2
  177. package/dist/orchestrator/src/cli/rlmCliRequestShell.js +42 -0
  178. package/dist/orchestrator/src/cli/rlmCompletionCliShell.js +46 -0
  179. package/dist/orchestrator/src/cli/rlmLaunchCliShell.js +51 -0
  180. package/dist/orchestrator/src/cli/rlmRunner.js +83 -523
  181. package/dist/orchestrator/src/cli/run/blockMemory.js +500 -0
  182. package/dist/orchestrator/src/cli/run/manifest.js +410 -73
  183. package/dist/orchestrator/src/cli/run/manifestPersister.js +45 -14
  184. package/dist/orchestrator/src/cli/run/runMemoryController.js +216 -0
  185. package/dist/orchestrator/src/cli/run/source0.js +690 -0
  186. package/dist/orchestrator/src/cli/run/workspacePath.js +101 -0
  187. package/dist/orchestrator/src/cli/runtime/mode.js +2 -1
  188. package/dist/orchestrator/src/cli/runtime/provider.js +39 -2
  189. package/dist/orchestrator/src/cli/selfCheckCliShell.js +12 -0
  190. package/dist/orchestrator/src/cli/services/commandRunner.js +698 -18
  191. package/dist/orchestrator/src/cli/services/execRuntime.js +66 -1
  192. package/dist/orchestrator/src/cli/services/orchestratorAutoScoutEvidenceRecorder.js +71 -0
  193. package/dist/orchestrator/src/cli/services/orchestratorCloudBranchResolution.js +8 -0
  194. package/dist/orchestrator/src/cli/services/orchestratorCloudEnvironmentResolution.js +22 -0
  195. package/dist/orchestrator/src/cli/services/orchestratorCloudExecutionLifecycleShell.js +39 -0
  196. package/dist/orchestrator/src/cli/services/orchestratorCloudPromptBuilder.js +37 -0
  197. package/dist/orchestrator/src/cli/services/orchestratorCloudRouteFallbackContract.js +45 -0
  198. package/dist/orchestrator/src/cli/services/orchestratorCloudRouteShell.js +36 -0
  199. package/dist/orchestrator/src/cli/services/orchestratorCloudTargetExecutor.js +277 -0
  200. package/dist/orchestrator/src/cli/services/orchestratorControlPlaneLifecycle.js +98 -0
  201. package/dist/orchestrator/src/cli/services/orchestratorControlPlaneLifecycleShell.js +54 -0
  202. package/dist/orchestrator/src/cli/services/orchestratorExecutionLifecycle.js +112 -0
  203. package/dist/orchestrator/src/cli/services/orchestratorExecutionModePolicy.js +27 -0
  204. package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteAdapterShell.js +59 -0
  205. package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteDecisionShell.js +57 -0
  206. package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteState.js +21 -0
  207. package/dist/orchestrator/src/cli/services/orchestratorExecutionRouter.js +2 -0
  208. package/dist/orchestrator/src/cli/services/orchestratorLocalPipelineExecutor.js +149 -0
  209. package/dist/orchestrator/src/cli/services/orchestratorLocalRouteShell.js +63 -0
  210. package/dist/orchestrator/src/cli/services/orchestratorPlanShell.js +54 -0
  211. package/dist/orchestrator/src/cli/services/orchestratorPlanTargetTracker.js +16 -0
  212. package/dist/orchestrator/src/cli/services/orchestratorResumePreparationShell.js +84 -0
  213. package/dist/orchestrator/src/cli/services/orchestratorResumeTokenValidation.js +15 -0
  214. package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleCompletion.js +31 -0
  215. package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleExecutionRegistration.js +37 -0
  216. package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleOrchestrationShell.js +83 -0
  217. package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleTaskManagerShell.js +37 -0
  218. package/dist/orchestrator/src/cli/services/orchestratorRuntimeManifestMutation.js +20 -0
  219. package/dist/orchestrator/src/cli/services/orchestratorStartPreparationShell.js +56 -0
  220. package/dist/orchestrator/src/cli/services/orchestratorStatusShell.js +70 -0
  221. package/dist/orchestrator/src/cli/services/pipelineResolver.js +7 -3
  222. package/dist/orchestrator/src/cli/services/plannerMemory.js +119 -0
  223. package/dist/orchestrator/src/cli/services/runPreparation.js +7 -3
  224. package/dist/orchestrator/src/cli/services/runSummaryWriter.js +9 -0
  225. package/dist/orchestrator/src/cli/setupBootstrapShell.js +114 -0
  226. package/dist/orchestrator/src/cli/setupCliShell.js +51 -0
  227. package/dist/orchestrator/src/cli/skillsCliShell.js +56 -0
  228. package/dist/orchestrator/src/cli/startCliRequestShell.js +53 -0
  229. package/dist/orchestrator/src/cli/startCliShell.js +68 -0
  230. package/dist/orchestrator/src/cli/statusCliShell.js +22 -0
  231. package/dist/orchestrator/src/cli/utils/authProvenanceFingerprint.js +27 -0
  232. package/dist/orchestrator/src/cli/utils/cloudPreflight.js +285 -7
  233. package/dist/orchestrator/src/cli/utils/codexFeatures.js +60 -0
  234. package/dist/orchestrator/src/cli/utils/delegationConfigParser.js +250 -0
  235. package/dist/orchestrator/src/cli/utils/delegationMcpHealth.js +1382 -0
  236. package/dist/orchestrator/src/cli/utils/devtools.js +2 -54
  237. package/dist/orchestrator/src/cli/utils/mcpServerEntry.js +53 -0
  238. package/dist/orchestrator/src/cli/utils/packageProgramResolver.js +151 -0
  239. package/dist/orchestrator/src/cli/utils/providerOverrideEnv.js +71 -0
  240. package/dist/orchestrator/src/cli/utils/trailingJsonObject.js +59 -0
  241. package/dist/orchestrator/src/learning/crystalizer.js +2 -2
  242. package/dist/orchestrator/src/manager.js +74 -4
  243. package/dist/orchestrator/src/persistence/ExperienceStore.js +233 -49
  244. package/dist/orchestrator/src/persistence/TaskStateStore.js +6 -6
  245. package/dist/orchestrator/src/persistence/lockFile.js +70 -4
  246. package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +39 -0
  247. package/dist/orchestrator/src/sync/createCloudSyncWorker.js +3 -2
  248. package/dist/orchestrator/src/utils/atomicWrite.js +17 -2
  249. package/dist/packages/orchestrator/src/exec/unified-exec.js +99 -6
  250. package/dist/packages/orchestrator/src/instructions/promptPacks.js +150 -19
  251. package/dist/packages/sdk-node/src/orchestrator.js +137 -13
  252. package/dist/packages/shared/config/designConfig.js +8 -1
  253. package/dist/packages/shared/streams/stdio.js +1 -1
  254. package/dist/scripts/design/pipeline/permit.js +15 -0
  255. package/dist/scripts/lib/docs-catalog.js +399 -0
  256. package/dist/scripts/lib/docs-helpers.js +87 -5
  257. package/dist/scripts/lib/pr-watch-merge.js +1088 -80
  258. package/dist/scripts/lib/provider-run-contract.js +26 -0
  259. package/dist/scripts/lib/review-command-intent-classification.js +532 -0
  260. package/dist/scripts/lib/review-command-probe-classification.js +385 -0
  261. package/dist/scripts/lib/review-execution-boundary-preflight.js +279 -0
  262. package/dist/scripts/lib/review-execution-runtime.js +753 -0
  263. package/dist/scripts/lib/review-execution-state.js +1144 -0
  264. package/dist/scripts/lib/review-execution-telemetry.js +215 -0
  265. package/dist/scripts/lib/review-inspection-target-parsing.js +78 -0
  266. package/dist/scripts/lib/review-launch-attempt.js +601 -0
  267. package/dist/scripts/lib/review-meta-surface-boundary-analysis.js +300 -0
  268. package/dist/scripts/lib/review-meta-surface-normalization.js +746 -0
  269. package/dist/scripts/lib/review-non-interactive-handoff.js +61 -0
  270. package/dist/scripts/lib/review-prompt-context.js +376 -0
  271. package/dist/scripts/lib/review-scope-advisory.js +286 -0
  272. package/dist/scripts/lib/review-scope-paths.js +123 -0
  273. package/dist/scripts/lib/review-shell-command-parser.js +389 -0
  274. package/dist/scripts/lib/review-shell-env-interpreter.js +340 -0
  275. package/dist/scripts/lib/run-manifests.js +192 -36
  276. package/dist/scripts/lib/spark-policy-classifier.js +593 -0
  277. package/dist/scripts/run-review.js +507 -1777
  278. package/docs/README.md +43 -20
  279. package/docs/book/README.md +19 -0
  280. package/docs/book/codex-cli-0124-adoption.md +68 -0
  281. package/docs/book/local-hook-impact.md +73 -0
  282. package/docs/book/operations.md +60 -0
  283. package/docs/book/public-posture.md +34 -0
  284. package/docs/book/setup.md +91 -0
  285. package/docs/book/skills.md +11 -0
  286. package/docs/guides/codex-version-policy.md +104 -0
  287. package/docs/public/downstream-setup.md +113 -0
  288. package/docs/public/provider-onboarding.md +173 -0
  289. package/package.json +23 -10
  290. package/plugins/codex-orchestrator/.codex-plugin/plugin.json +30 -0
  291. package/plugins/codex-orchestrator/.mcp.json +13 -0
  292. package/plugins/codex-orchestrator/launcher.mjs +361 -0
  293. package/schemas/manifest.json +411 -0
  294. package/skills/README.md +26 -0
  295. package/skills/collab-subagents-first/SKILL.md +1 -1
  296. package/skills/delegation-usage/DELEGATION_GUIDE.md +30 -12
  297. package/skills/delegation-usage/SKILL.md +25 -14
  298. package/skills/land/SKILL.md +77 -0
  299. package/skills/linear/SKILL.md +255 -0
  300. package/skills/release/SKILL.md +47 -3
  301. package/skills/standalone-review/SKILL.md +6 -1
  302. package/templates/README.md +4 -2
  303. package/templates/codex/.codex/agents/awaiter-high.toml +2 -2
  304. package/templates/codex/.codex/agents/worker-complex.toml +1 -1
  305. package/templates/codex/.codex/config.toml +3 -4
  306. package/templates/codex/.codex/providers/README.md +13 -0
  307. package/templates/codex/.codex/providers/control.example.json +18 -0
  308. package/templates/codex/.codex/providers/provider.env.example +15 -0
  309. package/templates/codex/AGENTS.md +15 -8
  310. package/templates/codex/mcp-client.json +5 -1
  311. package/docs/assets/setup.gif +0 -0
@@ -0,0 +1,1580 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { access, appendFile, mkdir, readFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import process from 'node:process';
5
+ import { isoTimestamp } from '../utils/time.js';
6
+ import { transitionProviderLinearIssueState } from './providerLinearWorkflowFacade.js';
7
+ import { isLiveLinearTrackedIssueOwnedByCurrentViewerOrUnassigned, sortLiveLinearTrackedIssuesForDispatch } from './linearDispatchSource.js';
8
+ import { classifyProviderLinearWorkflowState, isProviderLinearTrackedIssueMutable, normalizeProviderLinearWorkflowState, providerLinearTodoBlockedByNonTerminal } from './providerLinearWorkflowStates.js';
9
+ import { cloneProviderOperatorAutopilotLifecycleRecord } from './providerOperatorAutopilotLifecycle.js';
10
+ import { cloneLocalRolloutExecutionAttempt, executeProviderOperatorAutopilotLocalRolloutActions, resolveEnabledLocalRolloutExecutionActionIds, resolveProviderOperatorAutopilotLocalRolloutExecutionConfig } from './providerOperatorAutopilotLocalRolloutExecution.js';
11
+ export const PROVIDER_OPERATOR_AUTOPILOT_AUDIT_FILENAME = 'provider-operator-autopilot.jsonl';
12
+ export const DEFAULT_PROVIDER_OPERATOR_AUTOPILOT_PENDING_SUMMARY = 'Merge closeout completed; local rollout follow-up may still be required.';
13
+ const DEFAULT_BACKLOG_STATE_NAME = 'Backlog';
14
+ const DEFAULT_READY_STATE_NAME = 'Ready';
15
+ const DEFAULT_REWORK_STATE_NAME = 'Rework';
16
+ const BLOCKED_STATE_NAME = 'blocked';
17
+ const CANONICAL_OWNER_MARKER_PREFIX = 'codex-orchestrator:canonical-owner-key=';
18
+ const SUPERSEDED_CANONICAL_OWNER_MARKER_PREFIX = 'codex-orchestrator:superseded-canonical-owner-key=';
19
+ const TERMINAL_BLOCKER_ADVISORY_CANONICAL_OWNER_KEY = 'blocked-terminal-blocker-cleanup-advisory';
20
+ const TERMINAL_BLOCKER_ADVISORY_CANONICAL_OWNER_MARKERS = new Set([
21
+ `${CANONICAL_OWNER_MARKER_PREFIX}${TERMINAL_BLOCKER_ADVISORY_CANONICAL_OWNER_KEY}`,
22
+ `${SUPERSEDED_CANONICAL_OWNER_MARKER_PREFIX}${TERMINAL_BLOCKER_ADVISORY_CANONICAL_OWNER_KEY}`
23
+ ]);
24
+ const DEFAULT_BACKLOG_PROMOTION_SNAPSHOT_MAX_UNTRACKED_CYCLES = 3;
25
+ const DEFAULT_BACKLOG_PROMOTION_SNAPSHOT_TERMINAL_STATE_TYPES = [
26
+ 'completed',
27
+ 'canceled'
28
+ ];
29
+ const DEFAULT_REWORK_EXCLUDED_ACTION_REQUIRED_REASONS = [
30
+ 'draft',
31
+ 'label:do-not-merge',
32
+ 'review=REVIEW_REQUIRED',
33
+ 'required_checks_query_failed'
34
+ ];
35
+ const BACKLOG_PROMOTION_BLOCKING_CLAIM_STATES = new Set([
36
+ 'accepted',
37
+ 'starting',
38
+ 'running',
39
+ 'resuming',
40
+ 'resumable'
41
+ ]);
42
+ const FOLLOW_UP_PACKET_PREFIX_PATTERN = /Follow-up packet prefix:\s*`?(linear-[a-z0-9-]+)/iu;
43
+ const REVIEW_HANDOFF_REWORK_ELIGIBLE_CLAIM_STATES = new Set(['handoff_failed']);
44
+ export function resolveProviderOperatorAutopilotAuditPath(runDir) {
45
+ return join(runDir, PROVIDER_OPERATOR_AUTOPILOT_AUDIT_FILENAME);
46
+ }
47
+ export function resolveProviderOperatorAutopilotConfig(value) {
48
+ const record = asRecord(value);
49
+ const operatorAutopilot = asRecord(record?.operator_autopilot ?? record?.operatorAutopilot);
50
+ const enabled = readBoolean(operatorAutopilot, 'enabled') ?? false;
51
+ const backlogPromotion = asRecord(operatorAutopilot?.backlog_promotion ?? operatorAutopilot?.backlogPromotion);
52
+ const snapshotRetention = asRecord(backlogPromotion?.snapshot_retention ?? backlogPromotion?.snapshotRetention);
53
+ const reviewHandoffRework = asRecord(operatorAutopilot?.review_handoff_rework ?? operatorAutopilot?.reviewHandoffRework);
54
+ const postMergeRollout = asRecord(operatorAutopilot?.post_merge_rollout ?? operatorAutopilot?.postMergeRollout);
55
+ return {
56
+ enabled,
57
+ backlog_promotion: {
58
+ enabled: readBoolean(backlogPromotion, 'enabled') ?? enabled,
59
+ state_name: readNonEmptyString(backlogPromotion, 'state_name', 'stateName') ??
60
+ DEFAULT_BACKLOG_STATE_NAME,
61
+ target_state_name: readNonEmptyString(backlogPromotion, 'target_state_name', 'targetStateName') ??
62
+ DEFAULT_READY_STATE_NAME,
63
+ snapshot_retention: {
64
+ // Keep at least one missing-page cycle before pruning so CO-216 manual
65
+ // demotion suppression cannot be erased by a single temporary omission.
66
+ max_untracked_cycles: Math.max(2, readPositiveInteger(snapshotRetention, 'max_untracked_cycles', 'maxUntrackedCycles') ?? DEFAULT_BACKLOG_PROMOTION_SNAPSHOT_MAX_UNTRACKED_CYCLES),
67
+ terminal_state_types: normalizeStringArray(readStringArray(snapshotRetention, 'terminal_state_types', 'terminalStateTypes') ?? [...DEFAULT_BACKLOG_PROMOTION_SNAPSHOT_TERMINAL_STATE_TYPES])
68
+ }
69
+ },
70
+ review_handoff_rework: {
71
+ enabled: readBoolean(reviewHandoffRework, 'enabled') ?? enabled,
72
+ target_state_name: readNonEmptyString(reviewHandoffRework, 'target_state_name', 'targetStateName') ??
73
+ DEFAULT_REWORK_STATE_NAME,
74
+ excluded_action_required_reasons: readStringArray(reviewHandoffRework, 'excluded_action_required_reasons', 'excludedActionRequiredReasons') ?? [...DEFAULT_REWORK_EXCLUDED_ACTION_REQUIRED_REASONS]
75
+ },
76
+ post_merge_rollout: {
77
+ enabled: readBoolean(postMergeRollout, 'enabled') ?? enabled,
78
+ summary: readNonEmptyString(postMergeRollout, 'summary') ??
79
+ DEFAULT_PROVIDER_OPERATOR_AUTOPILOT_PENDING_SUMMARY,
80
+ execution: resolveProviderOperatorAutopilotLocalRolloutExecutionConfig(postMergeRollout)
81
+ }
82
+ };
83
+ }
84
+ export async function appendProviderOperatorAutopilotAuditResult(auditPath, result) {
85
+ await mkdir(dirname(auditPath), { recursive: true });
86
+ await appendFile(auditPath, `${JSON.stringify(result)}\n`, 'utf8');
87
+ }
88
+ export function areProviderOperatorAutopilotResultsMeaningfullyEqual(left, right) {
89
+ return JSON.stringify(normalizeComparableResult(left)) === JSON.stringify(normalizeComparableResult(right));
90
+ }
91
+ export async function runProviderOperatorAutopilot(input, deps = {}) {
92
+ const now = deps.now ?? isoTimestamp;
93
+ const transitionIssueState = deps.transition_issue_state ?? transitionProviderLinearIssueState;
94
+ const recordedAt = now();
95
+ if (!input.config.enabled) {
96
+ return {
97
+ recorded_at: recordedAt,
98
+ status: 'disabled',
99
+ summary: 'Operator autopilot is disabled.',
100
+ error: null,
101
+ actions: [],
102
+ holds: [],
103
+ pending_actions: [],
104
+ terminal_blocker_advisories: [],
105
+ resolved_actions: [],
106
+ lifecycle_records: [],
107
+ local_rollout_execution_attempts: [],
108
+ backlog_promotion_snapshots: [],
109
+ backlog_promotion_snapshot_retention_records: []
110
+ };
111
+ }
112
+ const sortedTrackedIssues = sortLiveLinearTrackedIssuesForDispatch(input.tracked_issues);
113
+ const trackedIssuesById = new Map(sortedTrackedIssues.map((issue) => [issue.id, issue]));
114
+ const orderByIssueId = new Map(sortedTrackedIssues.map((issue, index) => [issue.id, index]));
115
+ const claimsByIssueId = new Map(input.claims.map((claim) => [claim.issue_id, claim]));
116
+ const actions = [];
117
+ const holds = [];
118
+ const terminalBlockerAdvisories = collectTerminalBlockerAdvisories(sortedTrackedIssues);
119
+ const pendingActions = collectPendingActions({
120
+ claims: input.claims,
121
+ config: input.config
122
+ });
123
+ const lifecycleRecords = (input.lifecycle_records ?? []).map(cloneProviderOperatorAutopilotLifecycleRecord);
124
+ const resolveBacklogPromotionSnapshotState = () => resolveNextBacklogPromotionSnapshots({
125
+ previousResult: input.previous_result ?? null,
126
+ trackedIssuesById,
127
+ actions,
128
+ holds,
129
+ targetStateName: input.config.backlog_promotion.target_state_name,
130
+ backlogStateName: input.config.backlog_promotion.state_name,
131
+ retentionConfig: input.config.backlog_promotion.snapshot_retention,
132
+ evaluatedAt: recordedAt
133
+ });
134
+ const buildResultWithBacklogPromotionSnapshotState = (result) => {
135
+ const snapshotState = resolveBacklogPromotionSnapshotState();
136
+ return {
137
+ ...result,
138
+ backlog_promotion_snapshots: snapshotState.snapshots,
139
+ backlog_promotion_snapshot_retention_records: snapshotState.retention_records
140
+ };
141
+ };
142
+ const reviewHandoffOutcome = await maybeRunReviewHandoffRework({
143
+ claims: input.claims,
144
+ trackedIssuesById,
145
+ orderByIssueId,
146
+ config: input.config,
147
+ recordedAt,
148
+ sourceSetup: input.source_setup ?? null,
149
+ env: input.env ?? process.env,
150
+ transitionIssueState
151
+ });
152
+ if (reviewHandoffOutcome.failed) {
153
+ const effectiveLocalRolloutActions = resolveEffectiveLocalRolloutActions({
154
+ pendingActions,
155
+ postMergeRolloutEnabled: input.config.post_merge_rollout.enabled,
156
+ lifecycleRecords
157
+ });
158
+ return buildResultWithBacklogPromotionSnapshotState({
159
+ recorded_at: recordedAt,
160
+ status: 'failed',
161
+ summary: reviewHandoffOutcome.summary,
162
+ error: reviewHandoffOutcome.error,
163
+ actions: [],
164
+ holds,
165
+ pending_actions: effectiveLocalRolloutActions.pending_actions,
166
+ terminal_blocker_advisories: terminalBlockerAdvisories,
167
+ resolved_actions: effectiveLocalRolloutActions.resolved_actions,
168
+ lifecycle_records: effectiveLocalRolloutActions.lifecycle_records,
169
+ local_rollout_execution_attempts: cloneLocalRolloutExecutionAttempts(input.local_rollout_execution_attempts)
170
+ });
171
+ }
172
+ if (reviewHandoffOutcome.action) {
173
+ actions.push(reviewHandoffOutcome.action);
174
+ }
175
+ if (reviewHandoffOutcome.hold) {
176
+ holds.push(reviewHandoffOutcome.hold);
177
+ }
178
+ if (actions.length === 0) {
179
+ const backlogOutcome = await maybeRunBacklogPromotion({
180
+ sortedTrackedIssues,
181
+ claimsByIssueId,
182
+ config: input.config,
183
+ recordedAt,
184
+ previousResult: input.previous_result ?? null,
185
+ sourceSetup: input.source_setup ?? null,
186
+ env: input.env ?? process.env,
187
+ repoRoot: input.repo_root ?? process.cwd(),
188
+ transitionIssueState
189
+ });
190
+ if (backlogOutcome.failed) {
191
+ const effectiveLocalRolloutActions = resolveEffectiveLocalRolloutActions({
192
+ pendingActions,
193
+ postMergeRolloutEnabled: input.config.post_merge_rollout.enabled,
194
+ lifecycleRecords
195
+ });
196
+ return buildResultWithBacklogPromotionSnapshotState({
197
+ recorded_at: recordedAt,
198
+ status: 'failed',
199
+ summary: backlogOutcome.summary,
200
+ error: backlogOutcome.error,
201
+ actions,
202
+ holds,
203
+ pending_actions: effectiveLocalRolloutActions.pending_actions,
204
+ terminal_blocker_advisories: terminalBlockerAdvisories,
205
+ resolved_actions: effectiveLocalRolloutActions.resolved_actions,
206
+ lifecycle_records: effectiveLocalRolloutActions.lifecycle_records,
207
+ local_rollout_execution_attempts: cloneLocalRolloutExecutionAttempts(input.local_rollout_execution_attempts)
208
+ });
209
+ }
210
+ if (backlogOutcome.action) {
211
+ actions.push(backlogOutcome.action);
212
+ }
213
+ if (backlogOutcome.hold) {
214
+ holds.push(backlogOutcome.hold);
215
+ }
216
+ }
217
+ let effectiveLocalRolloutActions = resolveEffectiveLocalRolloutActions({
218
+ pendingActions,
219
+ postMergeRolloutEnabled: input.config.post_merge_rollout.enabled,
220
+ lifecycleRecords
221
+ });
222
+ let localRolloutExecutionAttempts = cloneLocalRolloutExecutionAttempts(input.local_rollout_execution_attempts);
223
+ if (input.config.post_merge_rollout.execution.enabled &&
224
+ effectiveLocalRolloutActions.pending_actions.length > 0) {
225
+ if (!input.repo_root) {
226
+ return buildResultWithBacklogPromotionSnapshotState({
227
+ recorded_at: recordedAt,
228
+ status: 'failed',
229
+ summary: 'Local rollout execution is enabled but repo_root was not provided.',
230
+ error: 'missing_repo_root',
231
+ actions,
232
+ holds,
233
+ pending_actions: effectiveLocalRolloutActions.pending_actions,
234
+ terminal_blocker_advisories: terminalBlockerAdvisories,
235
+ resolved_actions: effectiveLocalRolloutActions.resolved_actions,
236
+ lifecycle_records: effectiveLocalRolloutActions.lifecycle_records,
237
+ local_rollout_execution_attempts: localRolloutExecutionAttempts
238
+ });
239
+ }
240
+ const executionOutcome = await executeProviderOperatorAutopilotLocalRolloutActions({
241
+ pendingActions: effectiveLocalRolloutActions.pending_actions,
242
+ config: input.config.post_merge_rollout.execution,
243
+ repoRoot: input.repo_root,
244
+ priorAttempts: localRolloutExecutionAttempts
245
+ }, {
246
+ now,
247
+ runCommand: deps.run_local_rollout_command,
248
+ appendExecutionAttempt: deps.append_local_rollout_execution_attempt,
249
+ appendLifecycleRecord: deps.append_local_rollout_lifecycle_record
250
+ });
251
+ localRolloutExecutionAttempts = executionOutcome.attempts.map(cloneLocalRolloutExecutionAttempt);
252
+ if (executionOutcome.lifecycle_records.length > 0) {
253
+ lifecycleRecords.push(...executionOutcome.lifecycle_records.map(cloneProviderOperatorAutopilotLifecycleRecord));
254
+ effectiveLocalRolloutActions = resolveEffectiveLocalRolloutActions({
255
+ pendingActions,
256
+ postMergeRolloutEnabled: input.config.post_merge_rollout.enabled,
257
+ lifecycleRecords
258
+ });
259
+ }
260
+ }
261
+ const hasTransitionedAction = actions.some((action) => action.transition.status === 'transitioned');
262
+ const status = hasTransitionedAction ||
263
+ effectiveLocalRolloutActions.pending_actions.length > 0 ||
264
+ effectiveLocalRolloutActions.resolved_actions.length > 0 ||
265
+ terminalBlockerAdvisories.length > 0
266
+ ? 'acted'
267
+ : 'noop';
268
+ return buildResultWithBacklogPromotionSnapshotState({
269
+ recorded_at: recordedAt,
270
+ status,
271
+ summary: summarizeOperatorAutopilotResult({
272
+ actions,
273
+ holds,
274
+ pendingActions: effectiveLocalRolloutActions.pending_actions,
275
+ resolvedActions: effectiveLocalRolloutActions.resolved_actions,
276
+ terminalBlockerAdvisories
277
+ }),
278
+ error: null,
279
+ actions,
280
+ holds,
281
+ pending_actions: effectiveLocalRolloutActions.pending_actions,
282
+ terminal_blocker_advisories: terminalBlockerAdvisories,
283
+ resolved_actions: effectiveLocalRolloutActions.resolved_actions,
284
+ lifecycle_records: effectiveLocalRolloutActions.lifecycle_records,
285
+ local_rollout_execution_attempts: localRolloutExecutionAttempts
286
+ });
287
+ }
288
+ async function maybeRunBacklogPromotion(input) {
289
+ if (!input.config.backlog_promotion.enabled) {
290
+ return { action: null, hold: null, failed: false, summary: '', error: null };
291
+ }
292
+ const backlogState = normalizeProviderLinearWorkflowState(input.config.backlog_promotion.state_name);
293
+ const candidateIndex = input.sortedTrackedIssues.findIndex((issue) => normalizeProviderLinearWorkflowState(issue.state) === backlogState &&
294
+ isProviderLinearTrackedIssueMutable(issue));
295
+ const candidate = candidateIndex >= 0 ? input.sortedTrackedIssues[candidateIndex] : null;
296
+ if (!candidate) {
297
+ return { action: null, hold: null, failed: false, summary: '', error: null };
298
+ }
299
+ const higherRankedBlockedQueueLane = candidateIndex > 0
300
+ ? input.sortedTrackedIssues.slice(0, candidateIndex).find((issue) => {
301
+ const workflowState = classifyProviderLinearWorkflowState(issue);
302
+ return (isProviderLinearTrackedIssueMutable(issue) &&
303
+ workflowState.isTodo &&
304
+ providerLinearTodoBlockedByNonTerminal(issue.blocked_by));
305
+ }) ?? null
306
+ : null;
307
+ if (higherRankedBlockedQueueLane) {
308
+ return {
309
+ action: null,
310
+ hold: buildAutopilotHoldRecord({
311
+ kind: 'backlog_promotion',
312
+ issue: candidate,
313
+ reason: 'backlog_head_blocked_by_higher_ranked_lane',
314
+ summary: `Backlog head ${candidate.identifier} remains parked because higher-ranked queue lane ${higherRankedBlockedQueueLane.identifier} is still blocked by non-terminal work: ${formatBlockedBy(higherRankedBlockedQueueLane.blocked_by)}.`,
315
+ actionRequiredReasons: []
316
+ }),
317
+ failed: false,
318
+ summary: '',
319
+ error: null
320
+ };
321
+ }
322
+ const existingClaim = input.claimsByIssueId.get(candidate.id) ?? null;
323
+ if (existingClaim && isBacklogPromotionBlockedByExistingClaimState(existingClaim.state)) {
324
+ return {
325
+ action: null,
326
+ hold: buildAutopilotHoldRecord({
327
+ kind: 'backlog_promotion',
328
+ issue: candidate,
329
+ reason: 'backlog_head_already_claimed',
330
+ summary: `Backlog head ${candidate.identifier} remains parked because an intake claim is already present (${existingClaim.state}).`,
331
+ actionRequiredReasons: []
332
+ }),
333
+ failed: false,
334
+ summary: '',
335
+ error: null
336
+ };
337
+ }
338
+ if (!isLiveLinearTrackedIssueOwnedByCurrentViewerOrUnassigned(candidate)) {
339
+ return {
340
+ action: null,
341
+ hold: buildAutopilotHoldRecord({
342
+ kind: 'backlog_promotion',
343
+ issue: candidate,
344
+ reason: 'backlog_head_owned_by_other_operator',
345
+ summary: `Backlog head ${candidate.identifier} remains parked because it is assigned to another operator.`,
346
+ actionRequiredReasons: []
347
+ }),
348
+ failed: false,
349
+ summary: '',
350
+ error: null
351
+ };
352
+ }
353
+ if (providerLinearTodoBlockedByNonTerminal(candidate.blocked_by)) {
354
+ return {
355
+ action: null,
356
+ hold: buildAutopilotHoldRecord({
357
+ kind: 'backlog_promotion',
358
+ issue: candidate,
359
+ reason: 'backlog_head_blocked_by_non_terminal',
360
+ summary: `Backlog head ${candidate.identifier} remains parked because it is blocked by non-terminal work: ${formatBlockedBy(candidate.blocked_by)}.`,
361
+ actionRequiredReasons: []
362
+ }),
363
+ failed: false,
364
+ summary: '',
365
+ error: null
366
+ };
367
+ }
368
+ if (await isTraceabilityPendingFollowUpIssue(candidate, input.repoRoot)) {
369
+ return {
370
+ action: null,
371
+ hold: buildAutopilotHoldRecord({
372
+ kind: 'backlog_promotion',
373
+ issue: candidate,
374
+ reason: 'backlog_head_follow_up_traceability_pending',
375
+ summary: `Backlog head ${candidate.identifier} remains parked because follow-up traceability requires packet and registry mirror setup before leaving Backlog.`,
376
+ actionRequiredReasons: []
377
+ }),
378
+ failed: false,
379
+ summary: '',
380
+ error: null
381
+ };
382
+ }
383
+ const previousBacklogPromotion = resolvePreviousBacklogPromotionSnapshot({
384
+ previousResult: input.previousResult,
385
+ issueId: candidate.id,
386
+ targetStateName: input.config.backlog_promotion.target_state_name
387
+ });
388
+ const explicitBacklogDemotion = resolveExplicitBacklogDemotionHold({
389
+ candidate,
390
+ previousBacklogPromotion,
391
+ backlogStateName: input.config.backlog_promotion.state_name,
392
+ targetStateName: input.config.backlog_promotion.target_state_name
393
+ });
394
+ if (explicitBacklogDemotion) {
395
+ return {
396
+ action: null,
397
+ hold: buildAutopilotHoldRecord({
398
+ kind: 'backlog_promotion',
399
+ issue: candidate,
400
+ reason: 'backlog_head_manual_demotion_unacknowledged',
401
+ summary: explicitBacklogDemotion.summary,
402
+ promotionAttemptedAt: explicitBacklogDemotion.promotion_attempted_at,
403
+ promotionIssueUpdatedAt: explicitBacklogDemotion.promotion_issue_updated_at,
404
+ forcePathUsed: explicitBacklogDemotion.force_path_used,
405
+ actionRequiredReasons: []
406
+ }),
407
+ failed: false,
408
+ summary: '',
409
+ error: null
410
+ };
411
+ }
412
+ const transition = await input.transitionIssueState({
413
+ issueId: candidate.id,
414
+ stateName: input.config.backlog_promotion.target_state_name,
415
+ expectedStateName: candidate.state,
416
+ expectedStateType: candidate.state_type,
417
+ expectedUpdatedAt: candidate.updated_at,
418
+ sourceSetup: input.sourceSetup,
419
+ env: input.env
420
+ });
421
+ const transitionRecord = mapTransitionRecord({
422
+ transition,
423
+ attemptedAt: input.recordedAt,
424
+ previousState: candidate.state,
425
+ previousStateType: candidate.state_type,
426
+ previousUpdatedAt: candidate.updated_at,
427
+ targetStateName: input.config.backlog_promotion.target_state_name
428
+ });
429
+ if (!transition.ok) {
430
+ return {
431
+ action: null,
432
+ hold: null,
433
+ failed: true,
434
+ summary: `Backlog head ${candidate.identifier} could not transition to ${input.config.backlog_promotion.target_state_name}.`,
435
+ error: transitionRecord.error
436
+ };
437
+ }
438
+ if (transition.action === 'noop') {
439
+ return {
440
+ action: {
441
+ kind: 'backlog_promotion',
442
+ issue_id: candidate.id,
443
+ issue_identifier: candidate.identifier,
444
+ reason: 'backlog_head_already_promoted',
445
+ summary: `Backlog head ${candidate.identifier} already reflected ${input.config.backlog_promotion.target_state_name} when autopilot evaluated it.`,
446
+ transition: transitionRecord,
447
+ action_required_reasons: []
448
+ },
449
+ hold: null,
450
+ failed: false,
451
+ summary: '',
452
+ error: null
453
+ };
454
+ }
455
+ return {
456
+ action: {
457
+ kind: 'backlog_promotion',
458
+ issue_id: candidate.id,
459
+ issue_identifier: candidate.identifier,
460
+ reason: 'backlog_head_promoted',
461
+ summary: `Promoted backlog head ${candidate.identifier} to ${input.config.backlog_promotion.target_state_name}.`,
462
+ transition: transitionRecord,
463
+ action_required_reasons: []
464
+ },
465
+ hold: null,
466
+ failed: false,
467
+ summary: '',
468
+ error: null
469
+ };
470
+ }
471
+ async function maybeRunReviewHandoffRework(input) {
472
+ if (!input.config.review_handoff_rework.enabled) {
473
+ return { action: null, hold: null, failed: false, summary: '', error: null };
474
+ }
475
+ const candidates = input.claims
476
+ .flatMap((claim) => {
477
+ const trackedIssue = input.trackedIssuesById.get(claim.issue_id) ?? null;
478
+ if (!trackedIssue || !classifyProviderLinearWorkflowState(trackedIssue).isHandoff) {
479
+ return [];
480
+ }
481
+ if (!isReviewHandoffReworkClaimEligible({ claim, trackedIssue })) {
482
+ return [];
483
+ }
484
+ const reviewPromotion = claim.review_promotion ?? null;
485
+ if (reviewPromotion?.status !== 'action_required') {
486
+ return [];
487
+ }
488
+ const actionRequiredReasons = resolveReviewHandoffActionRequiredReasons(reviewPromotion);
489
+ const authorActionReasons = actionRequiredReasons.filter((reason) => isAuthorActionRequiredReason(reason, input.config.review_handoff_rework.excluded_action_required_reasons));
490
+ return [{
491
+ claim,
492
+ trackedIssue,
493
+ sortOrder: input.orderByIssueId.get(claim.issue_id) ?? Number.MAX_SAFE_INTEGER,
494
+ actionRequiredReasons,
495
+ authorActionReasons
496
+ }];
497
+ })
498
+ .sort((left, right) => {
499
+ if (left.sortOrder !== right.sortOrder) {
500
+ return left.sortOrder - right.sortOrder;
501
+ }
502
+ const leftKey = left.trackedIssue.identifier ?? left.claim.issue_id;
503
+ const rightKey = right.trackedIssue.identifier ?? right.claim.issue_id;
504
+ return leftKey.localeCompare(rightKey);
505
+ });
506
+ const parkedCandidate = candidates[0] ?? null;
507
+ if (!parkedCandidate) {
508
+ return { action: null, hold: null, failed: false, summary: '', error: null };
509
+ }
510
+ const candidate = candidates.find((candidateEntry) => candidateEntry.authorActionReasons.length > 0) ??
511
+ parkedCandidate;
512
+ const actionRequiredReasons = [...candidate.actionRequiredReasons];
513
+ const authorActionReasons = [...candidate.authorActionReasons];
514
+ if (authorActionReasons.length === 0) {
515
+ return {
516
+ action: null,
517
+ hold: buildAutopilotHoldRecord({
518
+ kind: 'review_handoff_rework',
519
+ issue: candidate.trackedIssue,
520
+ reason: 'review_handoff_non_author_action_required',
521
+ summary: `Review handoff ${candidate.trackedIssue.identifier} remains parked because the current blockers are not author-action-required: ${formatReasonList(actionRequiredReasons)}.`,
522
+ actionRequiredReasons: [...actionRequiredReasons]
523
+ }),
524
+ failed: false,
525
+ summary: '',
526
+ error: null
527
+ };
528
+ }
529
+ const transition = await input.transitionIssueState({
530
+ issueId: candidate.trackedIssue.id,
531
+ stateName: input.config.review_handoff_rework.target_state_name,
532
+ expectedStateName: candidate.trackedIssue.state,
533
+ expectedStateType: candidate.trackedIssue.state_type,
534
+ expectedUpdatedAt: candidate.trackedIssue.updated_at,
535
+ sourceSetup: input.sourceSetup,
536
+ env: input.env
537
+ });
538
+ const transitionRecord = mapTransitionRecord({
539
+ transition,
540
+ attemptedAt: input.recordedAt,
541
+ previousState: candidate.trackedIssue.state,
542
+ previousStateType: candidate.trackedIssue.state_type,
543
+ previousUpdatedAt: candidate.trackedIssue.updated_at,
544
+ targetStateName: input.config.review_handoff_rework.target_state_name
545
+ });
546
+ if (!transition.ok) {
547
+ return {
548
+ action: null,
549
+ hold: null,
550
+ failed: true,
551
+ summary: `Review handoff ${candidate.trackedIssue.identifier} could not transition to ${input.config.review_handoff_rework.target_state_name}.`,
552
+ error: transitionRecord.error
553
+ };
554
+ }
555
+ if (transition.action === 'noop') {
556
+ return {
557
+ action: {
558
+ kind: 'review_handoff_rework',
559
+ issue_id: candidate.trackedIssue.id,
560
+ issue_identifier: candidate.trackedIssue.identifier,
561
+ reason: 'author_action_required_rework_already_applied',
562
+ summary: `Review handoff ${candidate.trackedIssue.identifier} already reflected ${input.config.review_handoff_rework.target_state_name} when autopilot evaluated author-action-required blockers: ${authorActionReasons.join(', ')}.`,
563
+ transition: transitionRecord,
564
+ action_required_reasons: [...authorActionReasons]
565
+ },
566
+ hold: null,
567
+ failed: false,
568
+ summary: '',
569
+ error: null
570
+ };
571
+ }
572
+ return {
573
+ action: {
574
+ kind: 'review_handoff_rework',
575
+ issue_id: candidate.trackedIssue.id,
576
+ issue_identifier: candidate.trackedIssue.identifier,
577
+ reason: 'author_action_required_rework',
578
+ summary: `Moved review handoff ${candidate.trackedIssue.identifier} to ${input.config.review_handoff_rework.target_state_name} because author action is required: ${authorActionReasons.join(', ')}.`,
579
+ transition: transitionRecord,
580
+ action_required_reasons: [...authorActionReasons]
581
+ },
582
+ hold: null,
583
+ failed: false,
584
+ summary: '',
585
+ error: null
586
+ };
587
+ }
588
+ function isReviewHandoffReworkClaimEligible(input) {
589
+ return (REVIEW_HANDOFF_REWORK_ELIGIBLE_CLAIM_STATES.has(input.claim.state) &&
590
+ isLiveLinearTrackedIssueOwnedByCurrentViewerOrUnassigned(input.trackedIssue));
591
+ }
592
+ function collectPendingActions(input) {
593
+ if (!input.config.post_merge_rollout.enabled) {
594
+ return [];
595
+ }
596
+ const pendingByIssueId = new Map();
597
+ for (const claim of input.claims) {
598
+ const mergeCloseout = claim.merge_closeout ?? null;
599
+ if (!mergeCloseout || mergeCloseout.status !== 'merged') {
600
+ continue;
601
+ }
602
+ const nextAction = buildLocalRolloutPendingAction({
603
+ claim,
604
+ summary: input.config.post_merge_rollout.summary,
605
+ executableActionIds: resolveEnabledLocalRolloutExecutionActionIds(input.config.post_merge_rollout.execution)
606
+ });
607
+ const existingAction = pendingByIssueId.get(claim.issue_id) ?? null;
608
+ if (existingAction &&
609
+ compareNullableIsoTimestamp(existingAction.merge_closeout_recorded_at, nextAction.merge_closeout_recorded_at) >= 0) {
610
+ continue;
611
+ }
612
+ pendingByIssueId.set(claim.issue_id, nextAction);
613
+ }
614
+ return [...pendingByIssueId.values()].sort((left, right) => (left.issue_identifier ?? left.issue_id).localeCompare(right.issue_identifier ?? right.issue_id));
615
+ }
616
+ function buildLocalRolloutPendingAction(input) {
617
+ const mergeCloseout = input.claim.merge_closeout;
618
+ if (!mergeCloseout || mergeCloseout.status !== 'merged') {
619
+ throw new Error('Cannot build local rollout pending action without merged closeout truth.');
620
+ }
621
+ const issueIdentifier = input.claim.issue_identifier ?? mergeCloseout.issue_identifier ?? null;
622
+ return {
623
+ kind: 'local_rollout',
624
+ action_instance_id: buildLocalRolloutActionInstanceId({
625
+ claim: input.claim,
626
+ mergeCloseout
627
+ }),
628
+ issue_id: input.claim.issue_id,
629
+ issue_identifier: issueIdentifier,
630
+ summary: `${input.summary} Merge closeout reason=${mergeCloseout.reason}; shared_root=${mergeCloseout.shared_root?.status ?? 'unknown'}; linear_transition=${mergeCloseout.linear_transition?.status ?? 'unknown'}.`,
631
+ merge_closeout_recorded_at: mergeCloseout.recorded_at,
632
+ merge_closeout_reason: mergeCloseout.reason,
633
+ shared_root_status: mergeCloseout.shared_root?.status ?? null,
634
+ linear_transition_status: mergeCloseout.linear_transition?.status ?? null,
635
+ executable_action_ids: [...input.executableActionIds],
636
+ lifecycle_state: 'pending',
637
+ lifecycle_actor: null,
638
+ lifecycle_reason: null,
639
+ lifecycle_recorded_at: null
640
+ };
641
+ }
642
+ function buildLocalRolloutActionInstanceId(input) {
643
+ const identity = {
644
+ kind: 'local_rollout',
645
+ issue_id: input.claim.issue_id,
646
+ pr_url: input.mergeCloseout.pr?.url ?? null,
647
+ pr_number: input.mergeCloseout.pr?.number ?? null,
648
+ snapshot_merged_at: input.mergeCloseout.snapshot?.merged_at ?? null,
649
+ snapshot_head_oid: input.mergeCloseout.snapshot?.head_oid ?? null
650
+ };
651
+ const digest = createHash('sha256')
652
+ .update(JSON.stringify(identity))
653
+ .digest('hex')
654
+ .slice(0, 24);
655
+ return `local_rollout:${digest}`;
656
+ }
657
+ function groupLifecycleRecordsByActionInstanceId(records) {
658
+ const grouped = new Map();
659
+ for (const record of records) {
660
+ const current = grouped.get(record.action_instance_id) ?? [];
661
+ current.push(cloneProviderOperatorAutopilotLifecycleRecord(record));
662
+ grouped.set(record.action_instance_id, current);
663
+ }
664
+ for (const [actionInstanceId, actionRecords] of grouped) {
665
+ grouped.set(actionInstanceId, actionRecords.sort((left, right) => compareNullableIsoTimestamp(left.recorded_at, right.recorded_at)));
666
+ }
667
+ return grouped;
668
+ }
669
+ function compareNullableIsoTimestamp(left, right) {
670
+ if (left === right) {
671
+ return 0;
672
+ }
673
+ if (!left) {
674
+ return -1;
675
+ }
676
+ if (!right) {
677
+ return 1;
678
+ }
679
+ const leftTime = Date.parse(left);
680
+ const rightTime = Date.parse(right);
681
+ if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
682
+ return leftTime - rightTime;
683
+ }
684
+ return left.localeCompare(right);
685
+ }
686
+ export function resolveEffectiveLocalRolloutActions(input) {
687
+ if (!input.postMergeRolloutEnabled) {
688
+ return { pending_actions: [], resolved_actions: [], lifecycle_records: [] };
689
+ }
690
+ const recordsByActionInstanceId = groupLifecycleRecordsByActionInstanceId(input.lifecycleRecords);
691
+ const pendingActionInstanceIds = new Set(input.pendingActions.map((action) => action.action_instance_id));
692
+ const pendingActions = [];
693
+ const resolvedActions = [];
694
+ for (const action of input.pendingActions) {
695
+ const lifecycleRecords = recordsByActionInstanceId.get(action.action_instance_id)?.map(cloneProviderOperatorAutopilotLifecycleRecord) ?? [];
696
+ const latestTerminalRecord = lifecycleRecords.filter(isTerminalLifecycleRecord).at(-1) ?? null;
697
+ if (latestTerminalRecord) {
698
+ resolvedActions.push({
699
+ ...clonePendingActionRecord(action),
700
+ lifecycle_state: latestTerminalRecord.state,
701
+ lifecycle_actor: latestTerminalRecord.actor,
702
+ lifecycle_reason: latestTerminalRecord.reason,
703
+ lifecycle_recorded_at: latestTerminalRecord.recorded_at
704
+ });
705
+ continue;
706
+ }
707
+ const latestRecord = lifecycleRecords.at(-1) ?? null;
708
+ if (latestRecord?.state === 'acknowledged') {
709
+ pendingActions.push({
710
+ ...clonePendingActionRecord(action),
711
+ lifecycle_state: 'acknowledged',
712
+ lifecycle_actor: latestRecord.actor,
713
+ lifecycle_reason: latestRecord.reason,
714
+ lifecycle_recorded_at: latestRecord.recorded_at
715
+ });
716
+ continue;
717
+ }
718
+ pendingActions.push(clonePendingActionRecord(action));
719
+ }
720
+ const matchedLifecycleRecords = input.lifecycleRecords
721
+ .filter((record) => pendingActionInstanceIds.has(record.action_instance_id))
722
+ .map(cloneProviderOperatorAutopilotLifecycleRecord);
723
+ return {
724
+ pending_actions: pendingActions,
725
+ resolved_actions: resolvedActions,
726
+ lifecycle_records: matchedLifecycleRecords
727
+ };
728
+ }
729
+ function isTerminalLifecycleRecord(record) {
730
+ return record.state === 'cleared' || record.state === 'dismissed';
731
+ }
732
+ function collectTerminalBlockerAdvisories(trackedIssues) {
733
+ return trackedIssues
734
+ .flatMap((issue) => {
735
+ if (normalizeProviderLinearWorkflowState(issue.state) !== BLOCKED_STATE_NAME) {
736
+ return [];
737
+ }
738
+ if (!isProviderLinearTrackedIssueMutable(issue)) {
739
+ return [];
740
+ }
741
+ if (issue.blocked_by_truncated === true) {
742
+ return [];
743
+ }
744
+ const blockers = issue.blocked_by ?? [];
745
+ if (blockers.length === 0 || !blockers.every(isTerminalBlocker)) {
746
+ return [];
747
+ }
748
+ const canonicalOwnerHints = resolveCanonicalOwnerHints(issue);
749
+ const duplicateHints = resolveDuplicateHints(issue);
750
+ if (canonicalOwnerHints.length === 0 &&
751
+ duplicateHints.length === 0 &&
752
+ hasExternalPrBlockerHint(issue)) {
753
+ return [];
754
+ }
755
+ const recommendedAction = duplicateHints.length > 0 || canonicalOwnerHints.length > 0
756
+ ? 'duplicate_cleanup'
757
+ : 'ready_to_unblock';
758
+ const advisory = {
759
+ kind: 'terminal_blocker_cleanup',
760
+ issue_id: issue.id,
761
+ issue_identifier: issue.identifier,
762
+ issue_state: issue.state,
763
+ issue_state_type: issue.state_type,
764
+ issue_updated_at: issue.updated_at,
765
+ blockers: blockers.map((blocker) => ({
766
+ id: blocker.id,
767
+ identifier: blocker.identifier,
768
+ state: blocker.state,
769
+ state_type: blocker.state_type
770
+ })),
771
+ canonical_owner_hints: canonicalOwnerHints,
772
+ duplicate_hints: duplicateHints,
773
+ recommended_action: recommendedAction,
774
+ summary: buildTerminalBlockerAdvisorySummary({
775
+ issue,
776
+ blockers,
777
+ canonicalOwnerHints,
778
+ duplicateHints,
779
+ recommendedAction,
780
+ relationsTruncated: issue.relations_truncated === true
781
+ })
782
+ };
783
+ return [advisory];
784
+ })
785
+ .sort((left, right) => (left.issue_identifier ?? left.issue_id).localeCompare(right.issue_identifier ?? right.issue_id));
786
+ }
787
+ function isTerminalBlocker(blocker) {
788
+ return !providerLinearTodoBlockedByNonTerminal([blocker]);
789
+ }
790
+ function hasExternalPrBlockerHint(issue) {
791
+ const descriptionHint = classifyCurrentExternalPrBlockerText(normalizeOptionalString(issue.description) ?? '');
792
+ if (descriptionHint) {
793
+ return descriptionHint === 'blocked';
794
+ }
795
+ const activityHint = resolveLatestTrackedActivityExternalPrHint(issue.recent_activity ?? []);
796
+ return activityHint === 'blocked';
797
+ }
798
+ function resolveLatestTrackedActivityExternalPrHint(recentActivity) {
799
+ let latestHint = null;
800
+ let latestCreatedAtMs = Number.NEGATIVE_INFINITY;
801
+ let latestOrder = -1;
802
+ recentActivity.forEach((entry, order) => {
803
+ const summary = normalizeOptionalString(entry.summary);
804
+ if (!summary) {
805
+ return;
806
+ }
807
+ const hint = classifyCurrentExternalPrBlockerText(summary);
808
+ if (!hint) {
809
+ return;
810
+ }
811
+ const candidateMs = Date.parse(entry.created_at ?? '');
812
+ const createdAtMs = Number.isFinite(candidateMs) ? candidateMs : Number.NEGATIVE_INFINITY;
813
+ if (!latestHint ||
814
+ createdAtMs > latestCreatedAtMs ||
815
+ (createdAtMs === latestCreatedAtMs && order >= latestOrder)) {
816
+ latestHint = hint;
817
+ latestCreatedAtMs = createdAtMs;
818
+ latestOrder = order;
819
+ }
820
+ });
821
+ return latestHint;
822
+ }
823
+ function classifyCurrentExternalPrBlockerText(value) {
824
+ const segments = splitExternalPrHintSegments(value);
825
+ let latestHint = null;
826
+ for (let index = 0; index < segments.length; index += 1) {
827
+ if (!EXTERNAL_PR_REFERENCE_PATTERN.test(segments[index] ?? '')) {
828
+ continue;
829
+ }
830
+ let endIndex = index + 1;
831
+ while (endIndex < segments.length &&
832
+ !EXTERNAL_PR_REFERENCE_PATTERN.test(segments[endIndex] ?? '')) {
833
+ endIndex += 1;
834
+ }
835
+ const hint = classifyExternalPrHintBlock(segments.slice(index, endIndex).join(' '));
836
+ if (hint) {
837
+ latestHint = hint;
838
+ }
839
+ index = endIndex - 1;
840
+ }
841
+ return latestHint;
842
+ }
843
+ const MARKDOWN_LINK_DOT_PLACEHOLDER = '__co_pr_link_dot__';
844
+ function splitExternalPrHintSegments(value) {
845
+ return value
846
+ .replace(/\]\(([^)]*)\)/gu, (_match, target) => `](${target.replaceAll('.', MARKDOWN_LINK_DOT_PLACEHOLDER)})`)
847
+ .split(/[\n.;]+/u)
848
+ .map((segment) => segment.replaceAll(MARKDOWN_LINK_DOT_PLACEHOLDER, '.').trim())
849
+ .filter((segment) => segment.length > 0);
850
+ }
851
+ const EXTERNAL_PR_REFERENCE_PATTERN = /\b(?:pr|pull request)\b(?:\s|`|\[|\]|\(|\))*#?\d+\b/iu;
852
+ const EXTERNAL_PR_REFERENCE_PATTERN_GLOBAL = /\b(?:pr|pull request)\b(?:\s|`|\[|\]|\(|\))*#?\d+\b/giu;
853
+ const EXTERNAL_PR_MERGE_BLOCKER_PATTERN = /\bclosed\s+unmerged\b|\bunmerged\b|\bnot\s+(?:yet\s+)?merged\b|\bneeds?\s+(?:to\s+)?be\s+merged\b|\bmerge\s+pending\b|\bpending\s+merge\b/giu;
854
+ function classifyExternalPrHintBlock(segment) {
855
+ let sawResolved = false;
856
+ for (const prSegment of splitExternalPrReferenceSegments(segment)) {
857
+ const hint = classifyExternalPrHintSegment(prSegment);
858
+ if (hint === 'blocked') {
859
+ return 'blocked';
860
+ }
861
+ if (hint === 'resolved') {
862
+ sawResolved = true;
863
+ }
864
+ }
865
+ return sawResolved ? 'resolved' : null;
866
+ }
867
+ function splitExternalPrReferenceSegments(segment) {
868
+ const matches = [...segment.matchAll(EXTERNAL_PR_REFERENCE_PATTERN_GLOBAL)];
869
+ if (matches.length <= 1) {
870
+ return [segment];
871
+ }
872
+ return matches
873
+ .map((match, index) => {
874
+ const start = index === 0 ? 0 : (match.index ?? 0);
875
+ const end = matches[index + 1]?.index ?? segment.length;
876
+ return segment.slice(start, end).trim();
877
+ })
878
+ .filter((entry) => entry.length > 0);
879
+ }
880
+ function classifyExternalPrHintSegment(segment) {
881
+ if (!EXTERNAL_PR_REFERENCE_PATTERN.test(segment)) {
882
+ return null;
883
+ }
884
+ const resolvedSignals = collectExternalPrHintSignals(segment, /\b(?:no longer|not|isn't|is not)\s+(?:block(?:ed|er|ing)?|pending|failing|dirty|draft)\b|\bchecks?\s+(?:passed|passing|green|clean)\b|\bunblocked\b|\bclosed\b(?!\s+unmerged\b)/giu, 'resolved');
885
+ const mergeBlockerSignals = collectExternalPrHintSignals(segment, EXTERNAL_PR_MERGE_BLOCKER_PATTERN, 'blocked', resolvedSignals);
886
+ if (mergeBlockerSignals.length > 0) {
887
+ return 'blocked';
888
+ }
889
+ const blockedSignals = collectExternalPrHintSignals(segment, /\b(?:block(?:ed|er|ing)?|wait(?:ing)?\s+(?:on|for)|pending|draft|dirty|fail(?:ed|ing)?|checks?\s+(?:fail(?:ed|ing)?|pending|red)|red\s+checks?)\b/giu, 'blocked', resolvedSignals);
890
+ const latestSignal = [...resolvedSignals, ...blockedSignals].sort((left, right) => left.index === right.index ? left.priority - right.priority : left.index - right.index).at(-1);
891
+ return latestSignal?.kind ?? null;
892
+ }
893
+ function collectExternalPrHintSignals(segment, pattern, kind, ignoredSpans = []) {
894
+ const signals = [];
895
+ for (const match of segment.matchAll(pattern)) {
896
+ const matched = match[0];
897
+ const start = match.index ?? -1;
898
+ if (start < 0 || matched.length === 0) {
899
+ continue;
900
+ }
901
+ const end = start + matched.length;
902
+ if (ignoredSpans.some((span) => start < span.end && end > span.start)) {
903
+ continue;
904
+ }
905
+ signals.push({
906
+ kind,
907
+ index: start,
908
+ priority: kind === 'blocked' ? 1 : 0,
909
+ start,
910
+ end
911
+ });
912
+ }
913
+ return signals;
914
+ }
915
+ function resolveCanonicalOwnerHints(issue) {
916
+ const description = normalizeOptionalString(issue.description);
917
+ if (!description) {
918
+ return [];
919
+ }
920
+ const hints = new Set();
921
+ for (const markerPrefix of [
922
+ CANONICAL_OWNER_MARKER_PREFIX,
923
+ SUPERSEDED_CANONICAL_OWNER_MARKER_PREFIX
924
+ ]) {
925
+ let cursor = 0;
926
+ while (cursor < description.length) {
927
+ const markerIndex = description.indexOf(markerPrefix, cursor);
928
+ if (markerIndex < 0) {
929
+ break;
930
+ }
931
+ const markerStart = markerIndex;
932
+ let markerEnd = markerIndex + markerPrefix.length;
933
+ while (markerEnd < description.length &&
934
+ !/[\s`'")\]}]/u.test(description[markerEnd])) {
935
+ markerEnd += 1;
936
+ }
937
+ const marker = description.slice(markerStart, markerEnd);
938
+ if (TERMINAL_BLOCKER_ADVISORY_CANONICAL_OWNER_MARKERS.has(marker)) {
939
+ hints.add(marker);
940
+ }
941
+ cursor = markerEnd + 1;
942
+ }
943
+ }
944
+ return [...hints].sort();
945
+ }
946
+ function resolveDuplicateHints(issue) {
947
+ const hints = new Set();
948
+ for (const relation of issue.relations ?? []) {
949
+ const normalizedType = normalizeProviderLinearWorkflowState(relation.type);
950
+ if (normalizedType !== 'duplicate' &&
951
+ normalizedType !== 'duplicates' &&
952
+ normalizedType !== 'duplicated by' &&
953
+ normalizedType !== 'duplicate of') {
954
+ continue;
955
+ }
956
+ const identifier = relation.issue.identifier ?? relation.issue.id ?? 'unknown';
957
+ const state = relation.issue.state ?? relation.issue.state_type ?? 'unknown';
958
+ hints.add(`${relation.direction}:${relation.type ?? 'unknown'}:${identifier}:${state}`);
959
+ }
960
+ return [...hints].sort();
961
+ }
962
+ function buildTerminalBlockerAdvisorySummary(input) {
963
+ const issueIdentifier = input.issue.identifier ?? input.issue.id;
964
+ const action = input.recommendedAction === 'duplicate_cleanup'
965
+ ? 'duplicate-cleanup candidate'
966
+ : 'ready-to-unblock candidate';
967
+ const hintParts = [
968
+ input.duplicateHints.length > 0
969
+ ? `duplicate hints=${input.duplicateHints.join(', ')}`
970
+ : null,
971
+ input.canonicalOwnerHints.length > 0
972
+ ? `canonical owner hints=${input.canonicalOwnerHints.join(', ')}`
973
+ : null,
974
+ input.relationsTruncated
975
+ ? 'relation evidence may be truncated before duplicate hints are exhausted'
976
+ : null
977
+ ].filter((part) => part !== null);
978
+ const hintSuffix = hintParts.length > 0 ? `; ${hintParts.join('; ')}` : '';
979
+ return `Blocked issue ${issueIdentifier} has only terminal blockers (${formatBlockedBy(input.blockers)}); recommend ${action}${hintSuffix}.`;
980
+ }
981
+ function summarizeOperatorAutopilotResult(input) {
982
+ const parts = [];
983
+ if (input.actions.length > 0) {
984
+ parts.push(input.actions.map((action) => action.summary).join(' '));
985
+ }
986
+ if (input.holds.length > 0) {
987
+ parts.push(input.holds.map((hold) => hold.summary).join(' '));
988
+ }
989
+ if (input.pendingActions.length > 0) {
990
+ const acknowledgedCount = input.pendingActions.filter((pendingAction) => pendingAction.lifecycle_state === 'acknowledged').length;
991
+ parts.push(input.pendingActions.length === 1
992
+ ? `Surfaced 1 ${acknowledgedCount === 1 ? 'acknowledged ' : ''}pending local rollout action (${input.pendingActions[0].issue_identifier ?? input.pendingActions[0].issue_id}).`
993
+ : `Surfaced ${input.pendingActions.length} pending local rollout actions.`);
994
+ }
995
+ if (input.resolvedActions.length > 0) {
996
+ parts.push(input.resolvedActions.length === 1
997
+ ? `Suppressed 1 ${input.resolvedActions[0].lifecycle_state} local rollout action (${input.resolvedActions[0].issue_identifier ?? input.resolvedActions[0].issue_id}).`
998
+ : `Suppressed ${input.resolvedActions.length} cleared or dismissed local rollout actions.`);
999
+ }
1000
+ if (input.terminalBlockerAdvisories.length > 0) {
1001
+ const duplicateCleanupCount = input.terminalBlockerAdvisories.filter((advisory) => advisory.recommended_action === 'duplicate_cleanup').length;
1002
+ const readyToUnblockCount = input.terminalBlockerAdvisories.length - duplicateCleanupCount;
1003
+ const issueList = input.terminalBlockerAdvisories
1004
+ .map((advisory) => advisory.issue_identifier ?? advisory.issue_id)
1005
+ .join(', ');
1006
+ parts.push(`Surfaced ${input.terminalBlockerAdvisories.length} Blocked terminal-blocker advisory candidate(s): ${duplicateCleanupCount} duplicate-cleanup, ${readyToUnblockCount} ready-to-unblock (${issueList}).`);
1007
+ }
1008
+ if (parts.length === 0) {
1009
+ return 'Operator autopilot evaluated the current queue and found no bounded action to take.';
1010
+ }
1011
+ return parts.join(' ');
1012
+ }
1013
+ function mapTransitionRecord(input) {
1014
+ if (!input.transition.ok) {
1015
+ return {
1016
+ status: 'failed',
1017
+ attempted_at: input.attemptedAt,
1018
+ previous_state: input.previousState,
1019
+ target_state: input.targetStateName,
1020
+ issue_state: input.previousState,
1021
+ issue_state_type: input.previousStateType,
1022
+ issue_updated_at: input.previousUpdatedAt,
1023
+ force_path_used: false,
1024
+ error: `${input.transition.error.code}: ${input.transition.error.message}`
1025
+ };
1026
+ }
1027
+ return {
1028
+ status: input.transition.action === 'noop' ? 'noop' : 'transitioned',
1029
+ attempted_at: input.attemptedAt,
1030
+ previous_state: input.transition.previous_state?.name ?? input.previousState,
1031
+ target_state: input.transition.target_state.name,
1032
+ issue_state: input.transition.issue.state?.name ?? input.previousState,
1033
+ issue_state_type: input.transition.issue.state?.type ?? input.previousStateType,
1034
+ issue_updated_at: input.transition.issue.updated_at ?? input.previousUpdatedAt,
1035
+ force_path_used: input.transition.transition_guard?.force ?? false,
1036
+ error: null
1037
+ };
1038
+ }
1039
+ function buildAutopilotHoldRecord(input) {
1040
+ return {
1041
+ kind: input.kind,
1042
+ issue_id: input.issue?.id ?? null,
1043
+ issue_identifier: input.issue?.identifier ?? null,
1044
+ issue_state: input.issue?.state ?? null,
1045
+ issue_state_type: input.issue?.state_type ?? null,
1046
+ issue_updated_at: input.issue?.updated_at ?? null,
1047
+ promotion_attempted_at: input.promotionAttemptedAt ?? null,
1048
+ promotion_issue_updated_at: input.promotionIssueUpdatedAt ?? null,
1049
+ force_path_used: input.forcePathUsed ?? false,
1050
+ reason: input.reason,
1051
+ summary: input.summary,
1052
+ action_required_reasons: [...input.actionRequiredReasons]
1053
+ };
1054
+ }
1055
+ function resolvePreviousBacklogPromotionSnapshot(input) {
1056
+ const snapshot = collectBacklogPromotionSnapshotsFromResult(input.previousResult, input.targetStateName).find((candidate) => candidate.issue_id === input.issueId);
1057
+ if (!snapshot) {
1058
+ return null;
1059
+ }
1060
+ return {
1061
+ attempted_at: snapshot.attempted_at,
1062
+ issue_updated_at: snapshot.issue_updated_at,
1063
+ force_path_used: snapshot.force_path_used
1064
+ };
1065
+ }
1066
+ function resolveNextBacklogPromotionSnapshots(input) {
1067
+ const normalizedBacklogState = normalizeProviderLinearWorkflowState(input.backlogStateName);
1068
+ const normalizedTargetState = normalizeProviderLinearWorkflowState(input.targetStateName);
1069
+ const snapshotsByIssueId = new Map();
1070
+ const retentionRecords = [];
1071
+ const prunedPreviousSnapshotIssueIds = new Set();
1072
+ // Repeat the minimum defensively for persisted/legacy configs loaded before
1073
+ // resolveProviderOperatorAutopilotConfig normalized snapshot_retention.
1074
+ const maxUntrackedCycles = Math.max(2, input.retentionConfig.max_untracked_cycles);
1075
+ for (const snapshot of collectBacklogPromotionSnapshotsFromResult(input.previousResult, input.targetStateName)) {
1076
+ const issue = input.trackedIssuesById.get(snapshot.issue_id) ?? null;
1077
+ const currentUntrackedCycles = normalizeNonNegativeInteger(snapshot.untracked_cycles);
1078
+ if (!issue) {
1079
+ const nextUntrackedCycles = currentUntrackedCycles + 1;
1080
+ const shouldPrune = nextUntrackedCycles >= maxUntrackedCycles;
1081
+ retentionRecords.push(buildBacklogPromotionSnapshotRetentionRecord({
1082
+ snapshot,
1083
+ evaluatedAt: input.evaluatedAt,
1084
+ decision: shouldPrune ? 'pruned' : 'retained',
1085
+ reason: shouldPrune
1086
+ ? 'stale_untracked_cycle_limit'
1087
+ : 'temporarily_untracked',
1088
+ ageMs: calculateSnapshotAgeMs(input.evaluatedAt, snapshot.attempted_at),
1089
+ untrackedCycles: nextUntrackedCycles,
1090
+ maxUntrackedCycles,
1091
+ issue: null,
1092
+ terminalStateEvidence: false
1093
+ }));
1094
+ if (!shouldPrune) {
1095
+ snapshotsByIssueId.set(snapshot.issue_id, {
1096
+ ...snapshot,
1097
+ untracked_cycles: nextUntrackedCycles
1098
+ });
1099
+ }
1100
+ continue;
1101
+ }
1102
+ const issueState = normalizeProviderLinearWorkflowState(issue?.state);
1103
+ const terminalStateEvidence = isBacklogPromotionSnapshotTerminalIssueState(issue, input.retentionConfig.terminal_state_types);
1104
+ if (terminalStateEvidence) {
1105
+ retentionRecords.push(buildBacklogPromotionSnapshotRetentionRecord({
1106
+ snapshot,
1107
+ evaluatedAt: input.evaluatedAt,
1108
+ decision: 'pruned',
1109
+ reason: 'terminal_state',
1110
+ ageMs: calculateSnapshotAgeMs(input.evaluatedAt, snapshot.attempted_at),
1111
+ untrackedCycles: 0,
1112
+ maxUntrackedCycles,
1113
+ issue,
1114
+ terminalStateEvidence: true
1115
+ }));
1116
+ prunedPreviousSnapshotIssueIds.add(snapshot.issue_id);
1117
+ continue;
1118
+ }
1119
+ if (!isProviderLinearTrackedIssueMutable(issue)) {
1120
+ retentionRecords.push(buildBacklogPromotionSnapshotRetentionRecord({
1121
+ snapshot,
1122
+ evaluatedAt: input.evaluatedAt,
1123
+ decision: 'pruned',
1124
+ reason: 'tracked_archived_or_trashed',
1125
+ ageMs: calculateSnapshotAgeMs(input.evaluatedAt, snapshot.attempted_at),
1126
+ untrackedCycles: 0,
1127
+ maxUntrackedCycles,
1128
+ issue,
1129
+ terminalStateEvidence: false
1130
+ }));
1131
+ prunedPreviousSnapshotIssueIds.add(snapshot.issue_id);
1132
+ continue;
1133
+ }
1134
+ if ((issueState === normalizedBacklogState || issueState === normalizedTargetState)) {
1135
+ const nextSnapshot = {
1136
+ ...snapshot,
1137
+ issue_identifier: issue.identifier ?? snapshot.issue_identifier,
1138
+ untracked_cycles: 0
1139
+ };
1140
+ if (currentUntrackedCycles > 0) {
1141
+ retentionRecords.push(buildBacklogPromotionSnapshotRetentionRecord({
1142
+ snapshot: nextSnapshot,
1143
+ evaluatedAt: input.evaluatedAt,
1144
+ decision: 'retained',
1145
+ reason: 'tracked_state_reset_untracked_cycles',
1146
+ ageMs: calculateSnapshotAgeMs(input.evaluatedAt, snapshot.attempted_at),
1147
+ untrackedCycles: 0,
1148
+ maxUntrackedCycles,
1149
+ issue,
1150
+ terminalStateEvidence: false
1151
+ }));
1152
+ }
1153
+ snapshotsByIssueId.set(snapshot.issue_id, nextSnapshot);
1154
+ continue;
1155
+ }
1156
+ retentionRecords.push(buildBacklogPromotionSnapshotRetentionRecord({
1157
+ snapshot,
1158
+ evaluatedAt: input.evaluatedAt,
1159
+ decision: 'pruned',
1160
+ reason: 'tracked_non_backlog_non_target_state',
1161
+ ageMs: calculateSnapshotAgeMs(input.evaluatedAt, snapshot.attempted_at),
1162
+ untrackedCycles: 0,
1163
+ maxUntrackedCycles,
1164
+ issue,
1165
+ terminalStateEvidence: false
1166
+ }));
1167
+ prunedPreviousSnapshotIssueIds.add(snapshot.issue_id);
1168
+ }
1169
+ for (const action of input.actions) {
1170
+ const snapshot = buildBacklogPromotionSnapshotFromAction(action, input.targetStateName);
1171
+ if (snapshot) {
1172
+ prunedPreviousSnapshotIssueIds.delete(snapshot.issue_id);
1173
+ snapshotsByIssueId.set(snapshot.issue_id, snapshot);
1174
+ }
1175
+ }
1176
+ for (const hold of input.holds) {
1177
+ const snapshot = buildBacklogPromotionSnapshotFromHold(hold, input.targetStateName);
1178
+ if (snapshot && !prunedPreviousSnapshotIssueIds.has(snapshot.issue_id)) {
1179
+ snapshotsByIssueId.set(snapshot.issue_id, snapshot);
1180
+ }
1181
+ }
1182
+ return {
1183
+ snapshots: sortBacklogPromotionSnapshots([...snapshotsByIssueId.values()]),
1184
+ retention_records: sortBacklogPromotionSnapshotRetentionRecords(retentionRecords)
1185
+ };
1186
+ }
1187
+ function buildBacklogPromotionSnapshotRetentionRecord(input) {
1188
+ return {
1189
+ issue_id: input.snapshot.issue_id,
1190
+ issue_identifier: input.issue?.identifier ?? input.snapshot.issue_identifier,
1191
+ target_state: input.snapshot.target_state,
1192
+ attempted_at: input.snapshot.attempted_at,
1193
+ issue_updated_at: input.snapshot.issue_updated_at,
1194
+ evaluated_at: input.evaluatedAt,
1195
+ decision: input.decision,
1196
+ reason: input.reason,
1197
+ age_ms: input.ageMs,
1198
+ untracked_cycles: input.untrackedCycles,
1199
+ max_untracked_cycles: input.maxUntrackedCycles,
1200
+ issue_state: input.issue?.state ?? null,
1201
+ issue_state_type: input.issue?.state_type ?? null,
1202
+ issue_archived_at: input.issue?.archived_at ?? null,
1203
+ issue_trashed: input.issue?.trashed ?? null,
1204
+ issue_observed_updated_at: input.issue?.updated_at ?? null,
1205
+ terminal_state_evidence: input.terminalStateEvidence,
1206
+ force_path_used: input.snapshot.force_path_used ?? false
1207
+ };
1208
+ }
1209
+ function isBacklogPromotionSnapshotTerminalIssueState(issue, terminalStateTypes) {
1210
+ const normalizedTerminalStateTypes = terminalStateTypes
1211
+ .map((stateType) => normalizeProviderLinearWorkflowState(stateType))
1212
+ .filter((stateType) => stateType !== null);
1213
+ const normalizedType = normalizeProviderLinearWorkflowState(issue.state_type);
1214
+ if (normalizedType !== null && normalizedTerminalStateTypes.includes(normalizedType)) {
1215
+ return true;
1216
+ }
1217
+ const normalizedState = normalizeProviderLinearWorkflowState(issue.state);
1218
+ if (normalizedState !== null && normalizedTerminalStateTypes.includes(normalizedState)) {
1219
+ return true;
1220
+ }
1221
+ return classifyProviderLinearWorkflowState(issue).isTerminal;
1222
+ }
1223
+ function calculateSnapshotAgeMs(evaluatedAt, attemptedAt) {
1224
+ const evaluatedAtMs = Date.parse(evaluatedAt);
1225
+ const attemptedAtMs = Date.parse(attemptedAt);
1226
+ if (!Number.isFinite(evaluatedAtMs) || !Number.isFinite(attemptedAtMs)) {
1227
+ return null;
1228
+ }
1229
+ return Math.max(0, evaluatedAtMs - attemptedAtMs);
1230
+ }
1231
+ function sortBacklogPromotionSnapshots(snapshots) {
1232
+ return [...snapshots].sort((left, right) => (left.issue_identifier ?? left.issue_id).localeCompare(right.issue_identifier ?? right.issue_id));
1233
+ }
1234
+ function sortBacklogPromotionSnapshotRetentionRecords(records) {
1235
+ return [...records].sort((left, right) => (left.issue_identifier ?? left.issue_id).localeCompare(right.issue_identifier ?? right.issue_id));
1236
+ }
1237
+ function collectBacklogPromotionSnapshotsFromResult(result, targetStateName) {
1238
+ if (!result) {
1239
+ return [];
1240
+ }
1241
+ const snapshotsByIssueId = new Map();
1242
+ const prunedSnapshotKeys = collectPrunedBacklogPromotionSnapshotKeysFromResult(result, targetStateName);
1243
+ const normalizedTarget = normalizeProviderLinearWorkflowState(targetStateName);
1244
+ for (const snapshot of result.backlog_promotion_snapshots ?? []) {
1245
+ if (normalizedTarget !== null &&
1246
+ normalizeProviderLinearWorkflowState(snapshot.target_state) === normalizedTarget &&
1247
+ normalizeOptionalString(snapshot.issue_id) &&
1248
+ normalizeOptionalString(snapshot.attempted_at)) {
1249
+ snapshotsByIssueId.set(snapshot.issue_id, {
1250
+ issue_id: snapshot.issue_id,
1251
+ issue_identifier: snapshot.issue_identifier ?? null,
1252
+ target_state: snapshot.target_state,
1253
+ attempted_at: snapshot.attempted_at,
1254
+ issue_updated_at: snapshot.issue_updated_at ?? null,
1255
+ force_path_used: snapshot.force_path_used ?? false,
1256
+ untracked_cycles: normalizeNonNegativeInteger(snapshot.untracked_cycles)
1257
+ });
1258
+ }
1259
+ }
1260
+ for (const action of result.actions) {
1261
+ const snapshot = buildBacklogPromotionSnapshotFromAction(action, targetStateName);
1262
+ if (snapshot && !prunedSnapshotKeys.has(buildBacklogPromotionSnapshotKey(snapshot))) {
1263
+ snapshotsByIssueId.set(snapshot.issue_id, snapshot);
1264
+ }
1265
+ }
1266
+ for (const hold of result.holds) {
1267
+ const snapshot = buildBacklogPromotionSnapshotFromHold(hold, targetStateName);
1268
+ if (snapshot && !prunedSnapshotKeys.has(buildBacklogPromotionSnapshotKey(snapshot))) {
1269
+ snapshotsByIssueId.set(snapshot.issue_id, snapshot);
1270
+ }
1271
+ }
1272
+ return [...snapshotsByIssueId.values()];
1273
+ }
1274
+ function collectPrunedBacklogPromotionSnapshotKeysFromResult(result, targetStateName) {
1275
+ const keys = new Set();
1276
+ const normalizedTarget = normalizeProviderLinearWorkflowState(targetStateName);
1277
+ for (const record of result.backlog_promotion_snapshot_retention_records ?? []) {
1278
+ if (record.decision === 'pruned' &&
1279
+ normalizedTarget !== null &&
1280
+ normalizeProviderLinearWorkflowState(record.target_state) === normalizedTarget &&
1281
+ normalizeOptionalString(record.issue_id) &&
1282
+ normalizeOptionalString(record.attempted_at)) {
1283
+ keys.add(buildBacklogPromotionSnapshotKey({
1284
+ issue_id: record.issue_id,
1285
+ target_state: record.target_state,
1286
+ attempted_at: record.attempted_at
1287
+ }));
1288
+ }
1289
+ }
1290
+ return keys;
1291
+ }
1292
+ function buildBacklogPromotionSnapshotKey(input) {
1293
+ const normalizedTarget = normalizeProviderLinearWorkflowState(input.target_state) ?? input.target_state;
1294
+ return `${input.issue_id}\u0000${normalizedTarget}\u0000${input.attempted_at}`;
1295
+ }
1296
+ function buildBacklogPromotionSnapshotFromAction(action, targetStateName) {
1297
+ const normalizedTarget = normalizeProviderLinearWorkflowState(targetStateName);
1298
+ if (action.kind !== 'backlog_promotion' ||
1299
+ action.reason !== 'backlog_head_promoted' ||
1300
+ action.transition.status !== 'transitioned' ||
1301
+ normalizedTarget === null ||
1302
+ normalizeProviderLinearWorkflowState(action.transition.target_state) !== normalizedTarget) {
1303
+ return null;
1304
+ }
1305
+ return {
1306
+ issue_id: action.issue_id,
1307
+ issue_identifier: action.issue_identifier,
1308
+ target_state: action.transition.target_state,
1309
+ attempted_at: action.transition.attempted_at,
1310
+ issue_updated_at: action.transition.issue_updated_at,
1311
+ force_path_used: action.transition.force_path_used ?? false,
1312
+ untracked_cycles: 0
1313
+ };
1314
+ }
1315
+ function buildBacklogPromotionSnapshotFromHold(hold, targetStateName) {
1316
+ if (hold.kind !== 'backlog_promotion' ||
1317
+ hold.reason !== 'backlog_head_manual_demotion_unacknowledged' ||
1318
+ !hold.issue_id ||
1319
+ !hold.promotion_attempted_at) {
1320
+ return null;
1321
+ }
1322
+ return {
1323
+ issue_id: hold.issue_id,
1324
+ issue_identifier: hold.issue_identifier,
1325
+ target_state: targetStateName,
1326
+ attempted_at: hold.promotion_attempted_at,
1327
+ issue_updated_at: hold.promotion_issue_updated_at ?? null,
1328
+ force_path_used: hold.force_path_used ?? false,
1329
+ untracked_cycles: 0
1330
+ };
1331
+ }
1332
+ function resolveExplicitBacklogDemotionHold(input) {
1333
+ if (!input.previousBacklogPromotion) {
1334
+ return null;
1335
+ }
1336
+ const latestActivity = resolveMostRecentTrackedActivity(input.candidate.recent_activity);
1337
+ if (!latestActivity) {
1338
+ return null;
1339
+ }
1340
+ const stateTransition = parseTrackedIssueStateTransitionSummary(latestActivity.summary);
1341
+ const normalizedBacklogState = normalizeProviderLinearWorkflowState(input.backlogStateName);
1342
+ const normalizedTargetState = normalizeProviderLinearWorkflowState(input.targetStateName);
1343
+ if (!stateTransition ||
1344
+ normalizedBacklogState === null ||
1345
+ normalizedTargetState === null ||
1346
+ normalizeProviderLinearWorkflowState(stateTransition.fromState) !== normalizedTargetState ||
1347
+ normalizeProviderLinearWorkflowState(stateTransition.toState) !== normalizedBacklogState) {
1348
+ return null;
1349
+ }
1350
+ const previousPromotionTimestamp = input.previousBacklogPromotion.issue_updated_at ?? input.previousBacklogPromotion.attempted_at;
1351
+ const demotionTimestamp = latestActivity.created_at ?? input.candidate.updated_at;
1352
+ if (compareNullableIsoTimestamp(demotionTimestamp, previousPromotionTimestamp) <= 0) {
1353
+ return null;
1354
+ }
1355
+ const actorFragment = latestActivity.actor_name ? ` by ${latestActivity.actor_name}` : '';
1356
+ const timestampFragment = latestActivity.created_at ? ` at ${latestActivity.created_at}` : '';
1357
+ return {
1358
+ summary: `Backlog head ${input.candidate.identifier} remains parked because autopilot last promoted it at ${previousPromotionTimestamp} and the latest issue activity is an explicit ${stateTransition.fromState} -> ${stateTransition.toState} demotion${actorFragment}${timestampFragment}; wait for a newer acknowledgement update before re-promoting.`,
1359
+ promotion_attempted_at: input.previousBacklogPromotion.attempted_at,
1360
+ promotion_issue_updated_at: input.previousBacklogPromotion.issue_updated_at,
1361
+ force_path_used: input.previousBacklogPromotion.force_path_used
1362
+ };
1363
+ }
1364
+ function resolveMostRecentTrackedActivity(recentActivity) {
1365
+ if (recentActivity.length === 0) {
1366
+ return null;
1367
+ }
1368
+ return [...recentActivity].sort((left, right) => compareNullableIsoTimestamp(right.created_at, left.created_at))[0] ?? null;
1369
+ }
1370
+ function parseTrackedIssueStateTransitionSummary(summary) {
1371
+ const normalized = normalizeOptionalString(summary);
1372
+ if (!normalized || !normalized.startsWith('State ') || !normalized.includes(' -> ')) {
1373
+ return null;
1374
+ }
1375
+ const transition = normalized.slice('State '.length);
1376
+ const separatorIndex = transition.indexOf(' -> ');
1377
+ if (separatorIndex < 0) {
1378
+ return null;
1379
+ }
1380
+ const fromState = normalizeOptionalString(transition.slice(0, separatorIndex));
1381
+ const toState = normalizeOptionalString(transition.slice(separatorIndex + ' -> '.length));
1382
+ if (!fromState && !toState) {
1383
+ return null;
1384
+ }
1385
+ return {
1386
+ fromState,
1387
+ toState
1388
+ };
1389
+ }
1390
+ function isAuthorActionRequiredReason(reason, excludedReasons) {
1391
+ if (excludedReasons.includes(reason)) {
1392
+ return false;
1393
+ }
1394
+ return (reason === 'pr_closed_unmerged' ||
1395
+ reason.startsWith('review=') ||
1396
+ reason.startsWith('merge_state=') ||
1397
+ reason.startsWith('unresolved_threads=') ||
1398
+ reason.startsWith('unacknowledged_bot_feedback=') ||
1399
+ reason.startsWith('required_checks_failed=') ||
1400
+ reason.startsWith('checks_failed='));
1401
+ }
1402
+ function resolveReviewHandoffActionRequiredReasons(reviewPromotion) {
1403
+ const snapshotReasons = reviewPromotion?.snapshot?.action_required_reasons ?? [];
1404
+ if (snapshotReasons.length > 0) {
1405
+ return [...snapshotReasons];
1406
+ }
1407
+ const fallbackReason = normalizeOptionalString(reviewPromotion?.reason);
1408
+ return fallbackReason ? [fallbackReason] : [];
1409
+ }
1410
+ function normalizeComparableResult(result) {
1411
+ if (!result) {
1412
+ return null;
1413
+ }
1414
+ return {
1415
+ status: result.status,
1416
+ summary: result.summary,
1417
+ error: result.error,
1418
+ actions: result.actions,
1419
+ holds: result.holds,
1420
+ pending_actions: result.pending_actions,
1421
+ terminal_blocker_advisories: result.terminal_blocker_advisories ?? [],
1422
+ resolved_actions: result.resolved_actions ?? [],
1423
+ lifecycle_records: result.lifecycle_records ?? [],
1424
+ local_rollout_execution_attempts: result.local_rollout_execution_attempts ?? [],
1425
+ backlog_promotion_snapshots: result.backlog_promotion_snapshots ?? [],
1426
+ backlog_promotion_snapshot_retention_records: result.backlog_promotion_snapshot_retention_records ?? []
1427
+ };
1428
+ }
1429
+ function cloneLocalRolloutExecutionAttempts(attempts) {
1430
+ return (attempts ?? []).map(cloneLocalRolloutExecutionAttempt);
1431
+ }
1432
+ function clonePendingActionRecord(record) {
1433
+ return {
1434
+ kind: record.kind,
1435
+ action_instance_id: record.action_instance_id,
1436
+ issue_id: record.issue_id,
1437
+ issue_identifier: record.issue_identifier,
1438
+ summary: record.summary,
1439
+ merge_closeout_recorded_at: record.merge_closeout_recorded_at,
1440
+ merge_closeout_reason: record.merge_closeout_reason,
1441
+ shared_root_status: record.shared_root_status,
1442
+ linear_transition_status: record.linear_transition_status,
1443
+ executable_action_ids: [...(record.executable_action_ids ?? [])],
1444
+ lifecycle_state: record.lifecycle_state,
1445
+ lifecycle_actor: record.lifecycle_actor,
1446
+ lifecycle_reason: record.lifecycle_reason,
1447
+ lifecycle_recorded_at: record.lifecycle_recorded_at
1448
+ };
1449
+ }
1450
+ function formatBlockedBy(blockedBy) {
1451
+ const blockers = (blockedBy ?? []).map((blocker) => {
1452
+ const identifier = normalizeOptionalString(blocker.identifier) ?? 'unknown';
1453
+ const state = normalizeOptionalString(blocker.state) ?? blocker.state_type ?? 'unknown';
1454
+ return `${identifier}:${state}`;
1455
+ });
1456
+ return blockers.length > 0 ? blockers.join(', ') : 'unknown blockers';
1457
+ }
1458
+ function formatReasonList(reasons) {
1459
+ return reasons.length > 0 ? reasons.join(', ') : 'unclassified action_required';
1460
+ }
1461
+ function isBacklogPromotionBlockedByExistingClaimState(state) {
1462
+ return typeof state === 'string' && BACKLOG_PROMOTION_BLOCKING_CLAIM_STATES.has(state);
1463
+ }
1464
+ async function isTraceabilityPendingFollowUpIssue(issue, repoRoot) {
1465
+ const description = typeof issue.description === 'string' ? issue.description : '';
1466
+ const followUpTaskId = description.match(FOLLOW_UP_PACKET_PREFIX_PATTERN)?.[1] ?? null;
1467
+ if (!followUpTaskId) {
1468
+ return false;
1469
+ }
1470
+ const packetPaths = [
1471
+ `docs/PRD-${followUpTaskId}.md`,
1472
+ `docs/TECH_SPEC-${followUpTaskId}.md`,
1473
+ `docs/ACTION_PLAN-${followUpTaskId}.md`,
1474
+ `tasks/specs/${followUpTaskId}.md`,
1475
+ `tasks/tasks-${followUpTaskId}.md`,
1476
+ `.agent/task/${followUpTaskId}.md`
1477
+ ];
1478
+ for (const path of packetPaths) {
1479
+ if (!await fileExists(join(repoRoot, path))) {
1480
+ return true;
1481
+ }
1482
+ }
1483
+ const registryMirrorPaths = [
1484
+ 'tasks/index.json',
1485
+ 'docs/TASKS.md',
1486
+ 'docs/docs-freshness-registry.json'
1487
+ ];
1488
+ for (const path of registryMirrorPaths) {
1489
+ const content = await readTextFileIfPresent(join(repoRoot, path));
1490
+ if (!content?.includes(followUpTaskId)) {
1491
+ return true;
1492
+ }
1493
+ }
1494
+ return false;
1495
+ }
1496
+ function asRecord(value) {
1497
+ return value && typeof value === 'object' ? value : null;
1498
+ }
1499
+ async function fileExists(path) {
1500
+ try {
1501
+ await access(path);
1502
+ return true;
1503
+ }
1504
+ catch {
1505
+ return false;
1506
+ }
1507
+ }
1508
+ async function readTextFileIfPresent(path) {
1509
+ try {
1510
+ return await readFile(path, 'utf8');
1511
+ }
1512
+ catch {
1513
+ return null;
1514
+ }
1515
+ }
1516
+ function readBoolean(record, ...keys) {
1517
+ for (const key of keys) {
1518
+ const value = record?.[key];
1519
+ if (typeof value === 'boolean') {
1520
+ return value;
1521
+ }
1522
+ }
1523
+ return null;
1524
+ }
1525
+ function readNonEmptyString(record, ...keys) {
1526
+ for (const key of keys) {
1527
+ const value = record?.[key];
1528
+ if (typeof value === 'string') {
1529
+ const trimmed = value.trim();
1530
+ if (trimmed.length > 0) {
1531
+ return trimmed;
1532
+ }
1533
+ }
1534
+ }
1535
+ return null;
1536
+ }
1537
+ function readStringArray(record, ...keys) {
1538
+ for (const key of keys) {
1539
+ const value = record?.[key];
1540
+ if (!Array.isArray(value)) {
1541
+ continue;
1542
+ }
1543
+ const normalized = value
1544
+ .flatMap((item) => (typeof item === 'string' ? [item.trim()] : []))
1545
+ .filter((item) => item.length > 0);
1546
+ return normalized;
1547
+ }
1548
+ return null;
1549
+ }
1550
+ function readPositiveInteger(record, ...keys) {
1551
+ for (const key of keys) {
1552
+ const value = record?.[key];
1553
+ if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
1554
+ return value;
1555
+ }
1556
+ }
1557
+ return null;
1558
+ }
1559
+ function normalizeNonNegativeInteger(value) {
1560
+ return typeof value === 'number' && Number.isInteger(value) && value >= 0
1561
+ ? value
1562
+ : 0;
1563
+ }
1564
+ function normalizeStringArray(values) {
1565
+ const normalized = values
1566
+ .map((value) => value.trim())
1567
+ .filter((value) => value.length > 0);
1568
+ // Empty terminal_state_types is treated as unset; disabling terminal-state
1569
+ // pruning is not supported because terminal evidence is the safest prune path.
1570
+ return normalized.length > 0
1571
+ ? normalized
1572
+ : [...DEFAULT_BACKLOG_PROMOTION_SNAPSHOT_TERMINAL_STATE_TYPES];
1573
+ }
1574
+ function normalizeOptionalString(value) {
1575
+ if (typeof value !== 'string') {
1576
+ return null;
1577
+ }
1578
+ const trimmed = value.trim();
1579
+ return trimmed.length > 0 ? trimmed : null;
1580
+ }