@zq-silk/yui 0.14.0 → 0.14.2

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 (179) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +258 -125
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  5. package/dist/cli/commandCatalog.js +74 -109
  6. package/dist/cli/helpRenderer.js +1 -2
  7. package/dist/cli/interactionCandidates.js +10 -0
  8. package/dist/cli/interactionPolicy.js +76 -37
  9. package/dist/cli/roleWizard.js +5 -6
  10. package/dist/cli/updateOrchestrator.js +10 -8
  11. package/dist/cli/updatePorts.js +17 -13
  12. package/dist/cli/upgradeCommand.js +2 -5
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/durableJobCommands.js +1 -1
  18. package/dist/commands/executionAuditCommands.js +4 -9
  19. package/dist/commands/globalRoleCommands.js +17 -0
  20. package/dist/commands/profileCommands.js +200 -30
  21. package/dist/commands/sessionCommands.js +1 -1
  22. package/dist/commands/taskActor.js +29 -127
  23. package/dist/commands/taskChangeSetCommands.js +1 -3
  24. package/dist/commands/taskCommands.js +1771 -2107
  25. package/dist/commands/taskCompletionGate.js +27 -103
  26. package/dist/commands/taskContextCommand.js +47 -80
  27. package/dist/commands/taskExecutionCommands.js +9 -27
  28. package/dist/commands/taskInputCommands.js +10 -10
  29. package/dist/commands/taskIntegrationCommands.js +69 -61
  30. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  31. package/dist/commands/taskNextActionCommand.js +8 -29
  32. package/dist/commands/taskOverviewCommand.js +25 -3
  33. package/dist/commands/taskPublicationCommands.js +151 -85
  34. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  35. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  36. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  37. package/dist/commands/taskUpstreamCommands.js +79 -95
  38. package/dist/config/configCatalog.js +1 -1
  39. package/dist/context/contextSnapshot.js +17 -2
  40. package/dist/context/sessionBootstrapManifest.js +18 -1
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +1 -0
  63. package/dist/domain/agentResultTransport.js +60 -0
  64. package/dist/execution/executionHealth.js +111 -118
  65. package/dist/execution/reviewMainTurn.js +161 -0
  66. package/dist/execution/workItemExecution.js +454 -0
  67. package/dist/execution/workItemExecutionProjection.js +309 -0
  68. package/dist/execution/workItemMainTurn.js +164 -0
  69. package/dist/executor/agentExecutor.js +19 -19
  70. package/dist/executor/executorRegistry.js +7 -28
  71. package/dist/executor/fileRoleLaunchPlanner.js +51 -83
  72. package/dist/external/githubPublicationVerifier.js +114 -0
  73. package/dist/external/gitlabPublicationVerifier.js +158 -0
  74. package/dist/external/pinnedCommandRunner.js +73 -0
  75. package/dist/integration/changeSetManifest.js +0 -3
  76. package/dist/integration/deliveryObligation.js +43 -6
  77. package/dist/integration/gitIntegrationService.js +284 -101
  78. package/dist/integration/integrationAttempt.js +64 -14
  79. package/dist/integration/integrationQueueEntry.js +5 -35
  80. package/dist/integration/integrationQueueService.js +65 -194
  81. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  82. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  83. package/dist/message/message.js +16 -0
  84. package/dist/observability/executionAudit.js +4 -25
  85. package/dist/observability/faultClassification.js +58 -70
  86. package/dist/observability/orchestrationMetrics.js +8 -50
  87. package/dist/profile/agentProfile.js +60 -15
  88. package/dist/profile/agentProfileRuntime.js +100 -0
  89. package/dist/release/releaseWorkflowPorts.js +5 -68
  90. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  91. package/dist/repository/gitWorkspace.js +9 -67
  92. package/dist/repository/taskBaseFreshness.js +3 -1
  93. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  94. package/dist/repository/taskWorkspacePreparer.js +252 -411
  95. package/dist/resources/liveReferences.js +6 -55
  96. package/dist/resources/resourceDiscovery.js +1 -30
  97. package/dist/resources/resourceRegistrar.js +0 -23
  98. package/dist/review/deltaRecheck.js +8 -65
  99. package/dist/review/reviewAcceptance.js +22 -19
  100. package/dist/review/reviewConfig.js +0 -10
  101. package/dist/review/reviewDecision.js +2 -2
  102. package/dist/review/reviewRound.js +147 -273
  103. package/dist/review/reviewerAvailability.js +13 -23
  104. package/dist/role/role.js +3 -0
  105. package/dist/runtime/agentDriverObservation.js +1 -1
  106. package/dist/runtime/agentHost.js +51 -51
  107. package/dist/runtime/builtinAgentDrivers.js +9 -2
  108. package/dist/runtime/codexAppServerRuntime.js +23 -5
  109. package/dist/runtime/exactControlPlane.js +2 -267
  110. package/dist/runtime/firstProgressAdvisory.js +1 -1
  111. package/dist/runtime/index.js +1 -1
  112. package/dist/runtime/launchBroker.js +11 -11
  113. package/dist/runtime/lifecycleReservation.js +2 -2
  114. package/dist/runtime/managedCaller.js +111 -0
  115. package/dist/runtime/ports.js +16 -3
  116. package/dist/runtime/preallocatedNativeSession.js +2 -2
  117. package/dist/runtime/processExitObservation.js +3 -2
  118. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  119. package/dist/runtime/runtimeBinding.js +1 -1
  120. package/dist/runtime/runtimeObservation.js +33 -16
  121. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  122. package/dist/runtime/runtimeStopReceipt.js +11 -11
  123. package/dist/runtime/sessionLaunchRequest.js +2 -2
  124. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  125. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  126. package/dist/runtime/sessionReconciliation.js +4 -4
  127. package/dist/runtime/sessionTerminationGuard.js +9 -9
  128. package/dist/runtime/sessionTokenMetrics.js +5 -7
  129. package/dist/runtime/structuredProviderHost.js +5 -5
  130. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  131. package/dist/runtime/tmuxAdapters.js +32 -30
  132. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  133. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  134. package/dist/scheduler/roleTurnLiveness.js +6 -6
  135. package/dist/scheduler/roleTurnStall.js +8 -9
  136. package/dist/scheduler/taskExecutionProjection.js +19 -41
  137. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  138. package/dist/storage/sqliteSchema.js +1 -21
  139. package/dist/storage/sqliteStore.js +62 -47
  140. package/dist/storage/storageSchema.js +1 -8
  141. package/dist/storage/storageVersions.js +7 -4
  142. package/dist/storage/taskStore.js +8 -8
  143. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  144. package/dist/task/completionReadiness.js +17 -98
  145. package/dist/task/deliveryGuard.js +9 -11
  146. package/dist/task/draftPlan.js +142 -0
  147. package/dist/task/nextAction.js +179 -477
  148. package/dist/task/publicationVerification.js +1 -0
  149. package/dist/task/remoteDelivery.js +233 -0
  150. package/dist/task/task.js +108 -4
  151. package/dist/task/taskRecordReference.js +0 -1
  152. package/dist/turn/turn.js +109 -16
  153. package/dist/verification/verificationGateService.js +0 -26
  154. package/dist/web/assets/client/components.js +99 -121
  155. package/dist/web/assets/client/i18n.js +136 -58
  156. package/dist/web/assets/client/view.js +44 -0
  157. package/dist/web/assets/styles/cards.js +3 -15
  158. package/dist/web/assets/styles/widgets.js +4 -0
  159. package/dist/web/webSnapshot.js +29 -14
  160. package/dist/workItem/dependencyGate.js +34 -0
  161. package/dist/workItem/workItem.js +99 -266
  162. package/dist/workspace/workItemChangeSetManager.js +15 -59
  163. package/docs/task-local-identity.md +1 -1
  164. package/i18n/README.zh-CN.md +165 -59
  165. package/package.json +1 -1
  166. package/skills/yui-leader/SKILL.md +272 -782
  167. package/skills/yui-operator/SKILL.md +190 -365
  168. package/skills/yui-reviewer/SKILL.md +40 -33
  169. package/skills/yui-runtime/SKILL.md +27 -6
  170. package/skills/yui-worker/SKILL.md +19 -6
  171. package/dist/execution/candidateConvergence.js +0 -623
  172. package/dist/execution/executionGroup.js +0 -854
  173. package/dist/execution/resourceBroker.js +0 -426
  174. package/dist/executor/launchPlan.js +0 -16
  175. package/dist/review/reviewFinding.js +0 -240
  176. package/dist/review/reviewFindingLedger.js +0 -550
  177. package/dist/review/reviewOutcomeClassifier.js +0 -255
  178. package/dist/scheduler/resourceQueueProjection.js +0 -71
  179. package/dist/task/repairWave.js +0 -150
@@ -1,17 +1,15 @@
1
1
  import { createHash } from "node:crypto";
2
- import { isDeepStrictEqual } from "node:util";
3
- import { changeSetDeliverySettled, governingChangeSets } from "../integration/deliveryObligation.js";
4
- import { isAcceptedTaskReviewBaselineFromEvidence } from "../review/reviewAcceptance.js";
5
- import { classifyReviewRoundOutcome } from "../review/reviewOutcomeClassifier.js";
2
+ import { governingWorkItemDeliveries, workItemDeliverySettled } from "../integration/deliveryObligation.js";
3
+ import { isCompletedTaskReviewEvidenceFromTurns } from "../review/reviewAcceptance.js";
6
4
  import { actionableExecutionLaneRecoveries } from "../execution/executionHealth.js";
7
- import { candidateConvergenceDisagreement, candidateConvergenceEvidenceSufficient, candidateConvergenceStageResultsValid } from "../execution/candidateConvergence.js";
8
- import { executionStageSpendClosed, routeExecutionStage } from "../execution/resourceBroker.js";
9
5
  import { sameTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
10
6
  import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractResolution.js";
11
- import { currentWorkItemCandidate, currentWorkItemExecutionGroup, governingWorkItemCandidate } from "../workItem/workItem.js";
7
+ import { draftWorkItemDependencyIssue } from "./draftPlan.js";
8
+ import { currentWorkItemCandidate, governingWorkItemCandidate } from "../workItem/workItem.js";
12
9
  const OPEN_WORK_ITEM_STATUSES = new Set(["pending", "running", "awaiting_acceptance"]);
13
10
  export function projectNextAction(facts) {
14
11
  const { task } = facts;
12
+ const deliveryWorkItems = facts.workItems.filter(({ status }) => status !== "retired");
15
13
  if (task.status !== "active" && task.status !== "draft") {
16
14
  return buildAction(facts, {
17
15
  kind: "complete-task",
@@ -73,6 +71,46 @@ export function projectNextAction(facts) {
73
71
  });
74
72
  }
75
73
  if (task.status === "draft") {
74
+ const dependencyIssue = draftWorkItemDependencyIssue(facts.workItems);
75
+ if (dependencyIssue !== undefined) {
76
+ const refs = [
77
+ ref("work-item", dependencyIssue.workItemId),
78
+ ref("work-item", dependencyIssue.dependencyId)
79
+ ];
80
+ return buildAction(facts, {
81
+ kind: "repair-protocol-inconsistency",
82
+ reason: dependencyIssue.kind === "cycle"
83
+ ? `Draft Work Item dependency cycle includes ${dependencyIssue.workItemId}/${dependencyIssue.dependencyId}. Edit the Draft before activation.`
84
+ : `Draft Work Item ${dependencyIssue.workItemId} depends on ${dependencyIssue.dependencyId}, which is missing or retired. Edit the Draft before activation.`,
85
+ refs,
86
+ conflicts: refs,
87
+ preconditions: [
88
+ {
89
+ fact: `Draft dependency ${dependencyIssue.dependencyId} is valid`,
90
+ satisfied: false,
91
+ ref: refs[1]
92
+ }
93
+ ],
94
+ recommendedCommand: `yui task work edit ${task.id}/${dependencyIssue.workItemId} --clear-dependencies`
95
+ });
96
+ }
97
+ const draftWork = selectOpenWorkItem(facts.workItems);
98
+ if (draftWork.kind === "blocked") {
99
+ const refs = [
100
+ ref("work-item", draftWork.itemId),
101
+ ref("work-item", draftWork.blockedBy)
102
+ ];
103
+ return buildAction(facts, {
104
+ kind: "repair-protocol-inconsistency",
105
+ reason: `Draft Work Item ${draftWork.itemId} depends on ${draftWork.blockedBy}, which is missing, retired, or not completed. Edit the Draft before activation.`,
106
+ refs,
107
+ conflicts: refs,
108
+ preconditions: [
109
+ { fact: `Draft dependency ${draftWork.blockedBy} is valid`, satisfied: false, ref: refs[1] }
110
+ ],
111
+ recommendedCommand: `yui task work edit ${task.id}/${draftWork.itemId} --clear-dependencies`
112
+ });
113
+ }
76
114
  return buildAction(facts, {
77
115
  kind: "implement-current-work-item",
78
116
  reason: `Task ${task.id} is still a Draft; activate it before dispatching, integrating, reviewing, or completing work.`,
@@ -87,27 +125,12 @@ export function projectNextAction(facts) {
87
125
  .find((item) => item.status === "awaiting_acceptance");
88
126
  if (candidateReady !== undefined) {
89
127
  const candidate = currentWorkItemCandidate(candidateReady);
90
- if (candidate !== undefined && isEmptyDirectCandidate(candidate)) {
91
- const candidateRef = ref("candidate", `${candidateReady.id}/${candidate.id}`);
92
- return buildAction(facts, {
93
- kind: "repair-protocol-inconsistency",
94
- reason: `Candidate ${candidate.id} is a Task-main delivery with base==head (no commits); reject it and re-dispatch real work.`,
95
- refs: [ref("work-item", candidateReady.id), candidateRef],
96
- conflicts: [candidateRef],
97
- preconditions: [
98
- { fact: "Direct Candidate contains at least one commit", satisfied: false, ref: candidateRef }
99
- ],
100
- recommendedCommand: `yui task work reject ${task.id}/${candidateReady.id} --summary \"empty base==head candidate\"`
101
- });
102
- }
103
128
  const activeReview = latestActiveWorkItemReview(facts.reviewRounds, candidateReady, candidate);
104
129
  if (activeReview !== undefined) {
105
130
  const reviewRef = ref("review-round", activeReview.id);
106
131
  if (activeReview.status === "running") {
107
- if (reviewGroupAwaitingResolution(activeReview)) {
108
- return buildResolveReviewGroupAction(facts, activeReview);
109
- }
110
- if (activeReview.reviewerTurnId === undefined) {
132
+ if (activeReview.reviewerTurnId === undefined
133
+ && activeReview.executionGroup === undefined) {
111
134
  return buildAction(facts, {
112
135
  kind: "repair-protocol-inconsistency",
113
136
  reason: `ReviewRound ${activeReview.id} is running but has no Reviewer Turn.`,
@@ -120,26 +143,28 @@ export function projectNextAction(facts) {
120
143
  }
121
144
  const reviewRun = activeReviewRoundRun(activeReview, facts.activeTurns);
122
145
  if (reviewRun === undefined) {
123
- if (reviewGroupHasResourceQueue(activeReview)) {
146
+ if (reviewGroupNeedsDispatch(activeReview, facts.activeTurns)) {
124
147
  return buildAction(facts, {
125
148
  kind: "resume-review",
126
- reason: `ReviewRound ${activeReview.id} has Reviewer Lanes waiting for Resource Broker capacity.`,
149
+ reason: `ReviewRound ${activeReview.id} has Review Producer Lanes ready for dispatch or retry.`,
127
150
  refs: [reviewRef],
128
151
  preconditions: [
129
- { fact: "A Reviewer Lane is durably queued", satisfied: true, ref: reviewRef },
130
- { fact: "Resource capacity is available", satisfied: false }
152
+ { fact: "A Review Producer Lane is open", satisfied: true, ref: reviewRef }
131
153
  ],
132
154
  recommendedCommand: `yui task work review ${task.id}/${candidateReady.id}`
155
+ + reviewLaneRoleOptions(activeReview)
133
156
  });
134
157
  }
135
- const runRef = ref("turn", activeReview.reviewerTurnId);
158
+ const runRef = activeReview.reviewerTurnId === undefined
159
+ ? reviewRef
160
+ : ref("turn", activeReview.reviewerTurnId);
136
161
  return buildAction(facts, {
137
162
  kind: "repair-protocol-inconsistency",
138
- reason: `ReviewRound ${activeReview.id} references Reviewer Turn ${activeReview.reviewerTurnId}, but that Turn is not active.`,
163
+ reason: `ReviewRound ${activeReview.id} has no active Producer or main Reviewer Turn.`,
139
164
  refs: [reviewRef, runRef],
140
165
  conflicts: [reviewRef, runRef],
141
166
  preconditions: [
142
- { fact: "Reviewer Turn is active", satisfied: false, ref: runRef }
167
+ { fact: "Review execution is active or ready to dispatch", satisfied: false, ref: runRef }
143
168
  ]
144
169
  });
145
170
  }
@@ -174,6 +199,26 @@ export function projectNextAction(facts) {
174
199
  { fact: "Reviewer Turn exists", satisfied: false }
175
200
  ],
176
201
  recommendedCommand: `yui task work review ${task.id}/${candidateReady.id}`
202
+ + reviewLaneRoleOptions(activeReview)
203
+ });
204
+ }
205
+ const unintegrated = governingWorkItemDeliveries([candidateReady])
206
+ .find((delivery) => !workItemDeliverySettled(delivery, facts.integrations));
207
+ if (unintegrated !== undefined) {
208
+ return buildAction(facts, {
209
+ kind: "integrate-work-item",
210
+ reason: `Work Item ${candidateReady.id} result for Project ${unintegrated.projectId} has not passed a Leader-owned Integration.`,
211
+ refs: [ref("work-item", candidateReady.id)],
212
+ preconditions: [
213
+ {
214
+ fact: "Committed Integration records the exact WorkItem result",
215
+ satisfied: false,
216
+ ref: ref("work-item", candidateReady.id)
217
+ }
218
+ ],
219
+ recommendedCommand: `yui task integration start ${task.id} --work-item ${candidateReady.id} `
220
+ + `--project ${unintegrated.projectId} --strategy cherry-pick`,
221
+ judgmentRequired: "Leader must choose fast-forward, cherry-pick, merge, manual application, or an explicit no-op result."
177
222
  });
178
223
  }
179
224
  const refs = [
@@ -225,34 +270,15 @@ export function projectNextAction(facts) {
225
270
  const failedWork = facts.workItems.find((item) => item.status === "failed");
226
271
  if (failedWork !== undefined) {
227
272
  const failedReview = latestFailedReviewFor(facts.reviewRounds, failedWork.id);
228
- if (failedReview !== undefined) {
229
- return buildAction(facts, {
230
- kind: "route-review-findings",
231
- reason: `Work Item ${failedWork.id} failed with Review ${failedReview.id}; route its open findings into a repair wave.`,
232
- refs: [ref("work-item", failedWork.id), ref("review-round", failedReview.id)],
233
- preconditions: [
234
- { fact: "Work Item is failed", satisfied: true, ref: ref("work-item", failedWork.id) },
235
- { fact: "Review Round is failed", satisfied: true, ref: ref("review-round", failedReview.id) }
236
- ],
237
- recommendedCommand: `yui task review finding repair-wave ${task.id} --create`
238
- });
239
- }
240
- const explorationStop = exhaustedExplorationReason(failedWork, facts.executionGroups);
241
- if (explorationStop !== undefined) {
242
- return buildAction(facts, {
243
- kind: "implement-current-work-item",
244
- reason: explorationStop,
245
- refs: [ref("work-item", failedWork.id)],
246
- preconditions: [
247
- { fact: "Work Item exploration cannot continue", satisfied: true, ref: ref("work-item", failedWork.id) }
248
- ],
249
- recommendedCommand: `yui task work retire ${task.id}/${failedWork.id} --summary \"<reason>\"`
250
- });
251
- }
252
273
  return buildAction(facts, {
253
274
  kind: "implement-current-work-item",
254
- reason: `Work Item ${failedWork.id} failed without a Review verdict; retry implementation.`,
255
- refs: [ref("work-item", failedWork.id)],
275
+ reason: failedReview === undefined
276
+ ? `Work Item ${failedWork.id} failed; retry implementation after Leader diagnosis.`
277
+ : `Work Item ${failedWork.id} failed after Review ${failedReview.id}; the Leader must read its exact Reviewer Turn result and choose the repair.`,
278
+ refs: [
279
+ ref("work-item", failedWork.id),
280
+ ...(failedReview === undefined ? [] : [ref("review-round", failedReview.id)])
281
+ ],
256
282
  preconditions: [
257
283
  { fact: "Work Item is failed", satisfied: true, ref: ref("work-item", failedWork.id) }
258
284
  ],
@@ -279,9 +305,6 @@ export function projectNextAction(facts) {
279
305
  }
280
306
  if (openWork?.kind === "ready") {
281
307
  const item = openWork.item;
282
- const stageAction = buildExecutionStageAction(facts, item);
283
- if (stageAction !== null)
284
- return stageAction;
285
308
  const refs = [ref("work-item", item.id)];
286
309
  return buildAction(facts, {
287
310
  kind: "implement-current-work-item",
@@ -306,7 +329,7 @@ export function projectNextAction(facts) {
306
329
  judgmentRequired: "Leader must choose the execution path: direct execution, a native subagent, or managed Task Role dispatch."
307
330
  });
308
331
  }
309
- if (facts.workItems.length === 0
332
+ if (deliveryWorkItems.length === 0
310
333
  && !taskFinalReviewRequired(facts)
311
334
  && !facts.reviewRounds.some((round) => ((round.scope ?? "work-item") === "task"
312
335
  && (round.status === "pending" || round.status === "running")))) {
@@ -339,103 +362,70 @@ export function projectNextAction(facts) {
339
362
  : "Leader must choose the smallest useful topology from the Project-defined Task intent, then decide whether the frozen result warrants Review."
340
363
  });
341
364
  }
342
- const uncaptured = facts.workItems.find((item) => needsChangeSetCapture(facts, item));
343
- if (uncaptured !== undefined) {
344
- return buildAction(facts, {
345
- kind: "capture-change-set",
346
- reason: `Work Item ${uncaptured.id} is completed but has no ChangeSet; capture its delivery boundary.`,
347
- refs: [ref("work-item", uncaptured.id)],
348
- preconditions: [
349
- { fact: "Work Item is completed", satisfied: true, ref: ref("work-item", uncaptured.id) },
350
- { fact: "ChangeSet exists for the Work Item", satisfied: false }
351
- ],
352
- recommendedCommand: `yui task work capture ${task.id}/${uncaptured.id}`
353
- });
354
- }
355
- const unintegrated = governingChangeSets(facts.workItems, facts.changeSets)
356
- .find((changeSet) => !changeSetDeliverySettled(changeSet, facts.integrations, facts.integrationQueueEntries));
365
+ const unintegrated = governingWorkItemDeliveries(facts.workItems)
366
+ .find((delivery) => !workItemDeliverySettled(delivery, facts.integrations));
357
367
  if (unintegrated !== undefined) {
358
368
  return buildAction(facts, {
359
- kind: "integrate-change-set",
360
- reason: `ChangeSet ${unintegrated.id} has no committed Integration.`,
361
- refs: [ref("change-set", unintegrated.id)],
369
+ kind: "integrate-work-item",
370
+ reason: `Work Item ${unintegrated.workItemId} result for Project ${unintegrated.projectId} has no committed Integration.`,
371
+ refs: [ref("work-item", unintegrated.workItemId)],
362
372
  preconditions: [
363
- { fact: "ChangeSet exists", satisfied: true, ref: ref("change-set", unintegrated.id) },
364
- { fact: "Committed Integration references the ChangeSet", satisfied: false }
373
+ {
374
+ fact: "Committed Integration records the exact WorkItem result",
375
+ satisfied: false,
376
+ ref: ref("work-item", unintegrated.workItemId)
377
+ }
365
378
  ],
366
- recommendedCommand: `yui task integration start ${task.id} --project ${unintegrated.projectId} --change-set ${unintegrated.id}`
379
+ recommendedCommand: `yui task integration start ${task.id} --work-item ${unintegrated.workItemId} `
380
+ + `--project ${unintegrated.projectId} --strategy cherry-pick`,
381
+ judgmentRequired: "Leader must choose fast-forward, cherry-pick, merge, manual application, or an explicit no-op result."
367
382
  });
368
383
  }
369
384
  const finalReviewRequired = taskFinalReviewRequired(facts);
370
385
  const finalReviewContract = taskFinalReviewContract(facts);
371
386
  const failedFinal = latestTaskFinalReview(facts.reviewRounds, finalReviewContract);
372
- const failedFinalOutcome = failedFinal === undefined
373
- ? null
374
- : classifyReviewRoundOutcome(failedFinal, nextActionReviewOutcomeEvidence(facts));
375
387
  if (finalReviewRequired
376
- && failedFinal !== undefined && failedFinalOutcome?.kind === "non-semantic") {
388
+ && failedFinal?.status === "failed") {
389
+ const reviewerTurn = failedFinal.reviewerTurnId === undefined
390
+ ? undefined
391
+ : facts.reviewOutcomeEvidence?.turns.find(({ id }) => id === failedFinal.reviewerTurnId);
392
+ const sameRoundCommand = failedFinal.reviewerTurnId === undefined
393
+ ? `yui task review retry ${task.id}/${failedFinal.id}`
394
+ : reviewerTurn?.status === "failed"
395
+ ? `yui task turn retry ${task.id}/${reviewerTurn.id}`
396
+ : undefined;
377
397
  return buildAction(facts, {
378
398
  kind: "resume-review",
379
- reason: `Task-final Review ${failedFinal.id} ended before a semantic review was proven.`,
380
- refs: [ref("review-round", failedFinal.id)],
399
+ reason: `Task-final Review ${failedFinal.id} failed during execution: ${failedFinal.failure?.message ?? "unknown Core failure"}.`,
400
+ refs: [
401
+ ref("review-round", failedFinal.id),
402
+ ...(reviewerTurn?.status === "failed" ? [ref("turn", reviewerTurn.id)] : [])
403
+ ],
381
404
  preconditions: [
382
- { fact: "Task-final Review has semantic evidence", satisfied: false, ref: ref("review-round", failedFinal.id) }
405
+ {
406
+ fact: "Task-final Review has one exact completed Reviewer Turn",
407
+ satisfied: false,
408
+ ref: ref("review-round", failedFinal.id)
409
+ }
383
410
  ],
384
- recommendedCommand: `yui task review force-fresh ${task.id}/${failedFinal.id}`
411
+ ...(sameRoundCommand === undefined ? {} : { recommendedCommand: sameRoundCommand })
385
412
  });
386
413
  }
387
414
  if (finalReviewRequired
388
- && failedFinal !== undefined && failedFinalOutcome?.kind === "ambiguous") {
415
+ && failedFinal?.status === "completed"
416
+ && !isCompletedTaskReviewEvidenceFromTurns(failedFinal, facts.reviewOutcomeEvidence?.turns ?? [])) {
389
417
  return buildAction(facts, {
390
418
  kind: "repair-protocol-inconsistency",
391
- reason: `Task-final Review ${failedFinal.id} has ambiguous semantic and infrastructure evidence: ${failedFinalOutcome.reason}`,
419
+ reason: `Task-final Review ${failedFinal.id} is completed without one exact completed main Reviewer Turn.`,
392
420
  refs: [ref("review-round", failedFinal.id)],
393
421
  conflicts: [ref("review-round", failedFinal.id)],
394
422
  preconditions: [
395
- { fact: "Review outcome is unambiguously semantic or non-semantic", satisfied: false, ref: ref("review-round", failedFinal.id) }
396
- ]
397
- });
398
- }
399
- if (finalReviewRequired
400
- && failedFinal !== undefined
401
- && failedFinalOutcome?.kind === "semantic"
402
- && failedFinal.deltaRecheck?.disposition === "requires-full-review") {
403
- return buildAction(facts, {
404
- kind: "request-final-review",
405
- reason: `Delta Recheck ${failedFinal.id} could not establish equivalence; Yui recorded the result and left the next Review action to the Leader.`,
406
- refs: [ref("review-round", failedFinal.id)],
407
- preconditions: [
408
- { fact: "Current frozen head has accepting final Review evidence", satisfied: false }
409
- ],
410
- alternatives: [
411
423
  {
412
- kind: "request-full-review",
413
- reason: "Request a full Review when independent evidence is still required.",
414
- recommendedCommand: `yui task review request ${task.id} --role ${failedFinal.reviewerRoleName}`,
415
- refs: [ref("review-round", failedFinal.id)]
416
- },
417
- {
418
- kind: "continue-leader-work",
419
- reason: "Inspect directly, change the candidate, or choose another Reviewer as Task risk requires.",
420
- refs: [ref("review-round", failedFinal.id)]
424
+ fact: "ReviewRound and exact main Reviewer Turn agree",
425
+ satisfied: false,
426
+ ref: ref("review-round", failedFinal.id)
421
427
  }
422
- ],
423
- judgmentRequired: "Leader must choose full Review, another Reviewer, direct inspection, or more development; Core will not auto-escalate."
424
- });
425
- }
426
- if (finalReviewRequired
427
- && failedFinal !== undefined
428
- && failedFinalOutcome?.kind === "semantic"
429
- && ((failedFinal.checks ?? []).some(({ outcome }) => outcome === "failed")
430
- || failedFinal.deltaRecheck?.disposition === "finding")) {
431
- return buildAction(facts, {
432
- kind: "route-review-findings",
433
- reason: `Task-final Review ${failedFinal.id} delivered semantic negative evidence; route its open findings into a repair wave on one frozen head.`,
434
- refs: [ref("review-round", failedFinal.id)],
435
- preconditions: [
436
- { fact: "Task-final Review is failed", satisfied: true, ref: ref("review-round", failedFinal.id) }
437
- ],
438
- recommendedCommand: `yui task review finding repair-wave ${task.id} --create`
428
+ ]
439
429
  });
440
430
  }
441
431
  const activeFinal = latestTaskFinalReview(facts.reviewRounds, finalReviewContract);
@@ -443,10 +433,8 @@ export function projectNextAction(facts) {
443
433
  && (activeFinal.status === "pending" || activeFinal.status === "running")) {
444
434
  const reviewRef = ref("review-round", activeFinal.id);
445
435
  if (activeFinal.status === "running") {
446
- if (reviewGroupAwaitingResolution(activeFinal)) {
447
- return buildResolveReviewGroupAction(facts, activeFinal);
448
- }
449
- if (activeFinal.reviewerTurnId === undefined) {
436
+ if (activeFinal.reviewerTurnId === undefined
437
+ && activeFinal.executionGroup === undefined) {
450
438
  return buildAction(facts, {
451
439
  kind: "repair-protocol-inconsistency",
452
440
  reason: `Task-final ReviewRound ${activeFinal.id} is running but has no Reviewer Turn.`,
@@ -459,26 +447,28 @@ export function projectNextAction(facts) {
459
447
  }
460
448
  const reviewRun = activeReviewRoundRun(activeFinal, facts.activeTurns);
461
449
  if (reviewRun === undefined) {
462
- if (reviewGroupHasResourceQueue(activeFinal)) {
450
+ if (reviewGroupNeedsDispatch(activeFinal, facts.activeTurns)) {
463
451
  return buildAction(facts, {
464
452
  kind: "resume-review",
465
- reason: `Task-final ReviewRound ${activeFinal.id} has Reviewer Lanes waiting for Resource Broker capacity.`,
453
+ reason: `Task-final ReviewRound ${activeFinal.id} has Review Producer Lanes ready for dispatch or retry.`,
466
454
  refs: [reviewRef],
467
455
  preconditions: [
468
- { fact: "A Reviewer Lane is durably queued", satisfied: true, ref: reviewRef },
469
- { fact: "Resource capacity is available", satisfied: false }
456
+ { fact: "A Review Producer Lane is open", satisfied: true, ref: reviewRef }
470
457
  ],
471
458
  recommendedCommand: `yui task review request ${task.id} --role ${activeFinal.reviewerRoleName}`
459
+ + reviewLaneRoleOptions(activeFinal)
472
460
  });
473
461
  }
474
- const runRef = ref("turn", activeFinal.reviewerTurnId);
462
+ const runRef = activeFinal.reviewerTurnId === undefined
463
+ ? reviewRef
464
+ : ref("turn", activeFinal.reviewerTurnId);
475
465
  return buildAction(facts, {
476
466
  kind: "repair-protocol-inconsistency",
477
- reason: `Task-final ReviewRound ${activeFinal.id} references Reviewer Turn ${activeFinal.reviewerTurnId}, but that Turn is not active.`,
467
+ reason: `Task-final ReviewRound ${activeFinal.id} has no active Producer or main Reviewer Turn.`,
478
468
  refs: [reviewRef, runRef],
479
469
  conflicts: [reviewRef, runRef],
480
470
  preconditions: [
481
- { fact: "Reviewer Turn is active", satisfied: false, ref: runRef }
471
+ { fact: "Review execution is active or ready to dispatch", satisfied: false, ref: runRef }
482
472
  ]
483
473
  });
484
474
  }
@@ -526,7 +516,8 @@ export function projectNextAction(facts) {
526
516
  { fact: "Task-final ReviewRound is pending", satisfied: true, ref: reviewRef },
527
517
  { fact: "Reviewer Turn exists", satisfied: false }
528
518
  ],
529
- recommendedCommand: `yui task review retry ${task.id}/${activeFinal.id}`
519
+ recommendedCommand: `yui task review request ${task.id} --role ${activeFinal.reviewerRoleName}`
520
+ + reviewLaneRoleOptions(activeFinal)
530
521
  });
531
522
  }
532
523
  if (task.projectBindings.length > 0
@@ -535,40 +526,37 @@ export function projectNextAction(facts) {
535
526
  const reviewerRole = taskFinalReviewRole(facts);
536
527
  return buildAction(facts, {
537
528
  kind: "request-final-review",
538
- reason: facts.workItems.length === 0
529
+ reason: deliveryWorkItems.length === 0
539
530
  ? "This Task already owns a final-Review obligation; completion must prepare or resume a Review of its frozen Task head."
540
531
  : "All WorkItems are integrated but no valid Task-final Review attests the frozen Task result.",
541
532
  refs: [ref("task", task.id)],
542
- preconditions: facts.workItems.length === 0
533
+ preconditions: deliveryWorkItems.length === 0
543
534
  ? [{ fact: "Valid established Task-final Review at the direct head", satisfied: false }]
544
535
  : [
545
536
  { fact: "All Work Items are terminal", satisfied: true },
546
- { fact: "Every governing ChangeSet is settled", satisfied: true },
537
+ { fact: "Every governing WorkItem result is integrated", satisfied: true },
547
538
  { fact: "Valid Task-final Review at the integrated head", satisfied: false }
548
539
  ],
549
- recommendedCommand: facts.workItems.length === 0
540
+ recommendedCommand: deliveryWorkItems.length === 0
550
541
  ? `yui task complete ${task.id} --summary-file -`
551
542
  : `yui task review request ${task.id} --role ${reviewerRole ?? "<reviewer-role>"}`
552
543
  });
553
544
  }
554
545
  const finalReviewOptional = !finalReviewRequired
555
546
  && !hasValidFinalReview(facts);
556
- const unresolvedDelta = failedFinal?.deltaRecheck?.disposition === "requires-full-review";
557
547
  const optionalReviewer = facts.reviewConfig?.roleName
558
- ?? (unresolvedDelta ? failedFinal?.reviewerRoleName : undefined);
548
+ ?? failedFinal?.reviewerRoleName;
559
549
  const finalReviewAlternative = finalReviewOptional && optionalReviewer !== undefined
560
550
  ? [{
561
551
  kind: "request-final-review",
562
- reason: unresolvedDelta
563
- ? `Delta Recheck ${failedFinal.id} returned requires-full-review; request full independent evidence when the Leader judges it necessary.`
564
- : "Request an independent Task-final Review when the Leader wants extra assurance before completion.",
552
+ reason: "Request an independent Task-final Review when the Leader wants extra assurance before completion.",
565
553
  recommendedCommand: `yui task review request ${task.id} --role ${optionalReviewer}`,
566
554
  refs: [ref("task", task.id)]
567
555
  }]
568
556
  : [];
569
557
  return buildAction(facts, {
570
558
  kind: "complete-task",
571
- reason: facts.workItems.length === 0
559
+ reason: deliveryWorkItems.length === 0
572
560
  ? "The Leader-owned Task result and its established obligations are ready; complete it without creating successor work."
573
561
  : "Every independent delivery unit is integrated; complete the Task instead of creating successor work.",
574
562
  refs: [ref("task", task.id)],
@@ -576,10 +564,10 @@ export function projectNextAction(facts) {
576
564
  { fact: "All Work Items are terminal", satisfied: true },
577
565
  ...(task.projectBindings.length === 0
578
566
  ? []
579
- : facts.workItems.length === 0
567
+ : deliveryWorkItems.length === 0
580
568
  ? [{ fact: "Task main is clean, committed, and verified", satisfied: false }]
581
569
  : [
582
- { fact: "Every governing ChangeSet is settled", satisfied: true },
570
+ { fact: "Every governing WorkItem result is integrated", satisfied: true },
583
571
  ...(finalReviewRequired
584
572
  ? [{
585
573
  fact: "Valid Task-final Review at the integrated head",
@@ -592,9 +580,7 @@ export function projectNextAction(facts) {
592
580
  ...(!finalReviewOptional
593
581
  ? {}
594
582
  : {
595
- judgmentRequired: unresolvedDelta
596
- ? "Leader must route the non-accepting Delta result: full Review, another Reviewer, direct inspection, more development, or completion when policy permits."
597
- : "Leader must decide whether the frozen Task result is safe to complete or needs one optional Task-final Review."
583
+ judgmentRequired: "Leader must decide whether the frozen Task result is safe to complete or needs one optional Task-final Review."
598
584
  }),
599
585
  recommendedCommand: `yui task complete ${task.id} --summary-file -`
600
586
  });
@@ -619,30 +605,6 @@ function buildExecutionLaneRecoveryAction(facts, lane) {
619
605
  recommendedCommand: `yui task turn retry ${facts.task.id}/${lane.turnId}`
620
606
  });
621
607
  }
622
- function exhaustedExplorationReason(item, executionGroups) {
623
- const group = currentWorkItemExecutionGroup(item);
624
- if (group?.stage === undefined || group.resolution === undefined)
625
- return undefined;
626
- if (group.resolution.decision === "reject") {
627
- return `Work Item ${item.id} exploration was rejected and has no legal continuation; retire it explicitly.`;
628
- }
629
- if (group.resolution.decision === "retry"
630
- && group.stage.stage === "resolve"
631
- && group.stage.round >= group.stage.maxRounds) {
632
- return `Work Item ${item.id} exhausted its exploration round budget; retire it explicitly.`;
633
- }
634
- const resources = executionGroups?.find(({ groupId }) => groupId === group.id)?.resources;
635
- if ((group.resolution.decision === "retry" || group.resolution.decision === "blocked")
636
- && resources !== undefined
637
- && executionStageSpendClosed(resources)) {
638
- return `Work Item ${item.id} cannot retry its frozen exploration resource budget; retire it explicitly or replace it with a newly authorized delivery boundary.`;
639
- }
640
- if ((group.resolution.decision === "retry" || group.resolution.decision === "blocked")
641
- && group.stage.stageAttempt >= group.stage.budget.maxAttempts) {
642
- return `Work Item ${item.id} exhausted its ${group.stage.stage} stage attempt budget; retire it explicitly.`;
643
- }
644
- return undefined;
645
- }
646
608
  /**
647
609
  * Stable fingerprint of the durable delivery position. It changes exactly
648
610
  * when a delivery record changes, so the semantic-progress budget can compare
@@ -683,156 +645,6 @@ function buildAction(facts, input) {
683
645
  fingerprint: createHash("sha256").update(fingerprintSource).digest("hex")
684
646
  };
685
647
  }
686
- function buildExecutionStageAction(facts, item) {
687
- const group = currentWorkItemExecutionGroup(item);
688
- if (group?.stage?.resources === undefined || group.resolution !== undefined)
689
- return null;
690
- const projected = facts.executionGroups?.find(({ groupId }) => groupId === group.id);
691
- const resources = projected?.resources;
692
- if (resources === undefined)
693
- return null;
694
- const usableLaneIds = group.lanes.filter(({ status }) => status === "completed").map(({ id }) => id);
695
- const stageResultsValid = candidateConvergenceStageResultsValid(group);
696
- const disagreement = candidateConvergenceDisagreement(group);
697
- const routing = routeExecutionStage({
698
- group,
699
- resources,
700
- evidenceSufficient: candidateConvergenceEvidenceSufficient(item, group, usableLaneIds),
701
- disagreement
702
- });
703
- const refs = [ref("work-item", item.id), ref("execution-group", group.id)];
704
- const resolveCommand = (decision, suffix = "") => (`yui task work group resolve ${facts.task.id}/${item.id}`
705
- + ` --decision ${decision} --summary \"<stage decision>\"${suffix}`);
706
- if (routing.action === "blocked") {
707
- return buildAction(facts, {
708
- kind: "resolve-execution-stage",
709
- reason: `${routing.reason}; dispatch would preserve the same pending Lanes without starting them.`,
710
- refs,
711
- preconditions: [
712
- { fact: "Execution stage is unresolved", satisfied: true, ref: refs[1] },
713
- {
714
- fact: "Stage deadline or hard budget is exhausted",
715
- satisfied: executionStageSpendClosed(resources),
716
- ref: refs[1]
717
- },
718
- { fact: "Acceptance-level evidence is sufficient", satisfied: false, ref: refs[1] }
719
- ],
720
- recommendedCommand: resolveCommand("blocked"),
721
- judgmentRequired: "Leader must record the resource-blocked stage, then retire or replace the delivery boundary; the frozen budget cannot be reopened by redispatch."
722
- });
723
- }
724
- if (routing.action === "expand-parallel") {
725
- return buildAction(facts, {
726
- kind: "resolve-execution-stage",
727
- reason: routing.reason,
728
- refs,
729
- preconditions: [
730
- {
731
- fact: "Stage quorum is open or structured results show material disagreement",
732
- satisfied: !resources.quorumMet || disagreement === "high",
733
- ref: refs[1]
734
- },
735
- {
736
- fact: "Adaptive Lane capacity remains",
737
- satisfied: group.strategy.mode === "adaptive"
738
- && group.lanes.length < group.strategy.max,
739
- ref: refs[1]
740
- }
741
- ],
742
- recommendedCommand: `yui task work dispatch ${facts.task.id}/${item.id} --lane-role <independent-role>`,
743
- ...(resources.quorumMet
744
- ? {
745
- alternatives: [{
746
- kind: "deepen-sequential",
747
- reason: "Resolve the current evidence and deepen sequentially when another independent Lane has lower value.",
748
- recommendedCommand: resolveCommand("accept"),
749
- refs
750
- }]
751
- }
752
- : {}),
753
- judgmentRequired: resources.quorumMet
754
- ? "Leader must choose an unused compatible Task Role for expansion or deliberately select the sequential alternative."
755
- : "Leader must choose an unused compatible Task Role so the frozen stage can satisfy quorum."
756
- });
757
- }
758
- if (routing.action === "deepen-sequential") {
759
- const resolveRequestsNextRound = group.stage.stage === "resolve";
760
- const decision = usableLaneIds.length === 0
761
- || !stageResultsValid
762
- || !resources.quorumMet
763
- || resolveRequestsNextRound
764
- ? "retry"
765
- : "accept";
766
- return buildAction(facts, {
767
- kind: "resolve-execution-stage",
768
- reason: resolveRequestsNextRound
769
- ? "Resolve evidence does not establish a Candidate; begin another bounded exploration round."
770
- : !resources.quorumMet
771
- ? "The stage exhausted its Lane capacity before quorum; resolve it as a bounded retry."
772
- : decision === "retry"
773
- ? "The stage has no structurally usable output; resolve it as a bounded retry before redispatch."
774
- : routing.reason,
775
- refs,
776
- preconditions: [
777
- { fact: "No stage Lane remains active or queued", satisfied: true, ref: refs[1] },
778
- { fact: "Current stage has structurally usable output", satisfied: stageResultsValid, ref: refs[1] },
779
- { fact: "Stage quorum is met", satisfied: resources.quorumMet, ref: refs[1] }
780
- ],
781
- recommendedCommand: resolveCommand(decision),
782
- judgmentRequired: resolveRequestsNextRound
783
- ? "Leader must judge whether the frozen round budget permits another exploration round or the WorkItem should be retired."
784
- : decision === "retry"
785
- ? "Leader must judge whether the frozen attempt budget permits one retry or the WorkItem should be retired."
786
- : "Leader must select the usable stage evidence before advancing to the next bounded stage."
787
- });
788
- }
789
- if (routing.action === "resolve") {
790
- const earlyStop = routing.cancelPendingLaneIds.length === 0
791
- ? ""
792
- : " --early-stop <observed-marginal-value>";
793
- return buildAction(facts, {
794
- kind: "resolve-execution-stage",
795
- reason: routing.reason,
796
- refs,
797
- preconditions: [
798
- { fact: "Stage quorum is met", satisfied: resources.quorumMet, ref: refs[1] },
799
- { fact: "Acceptance-level evidence is sufficient", satisfied: true, ref: refs[1] }
800
- ],
801
- recommendedCommand: resolveCommand("accept", earlyStop),
802
- judgmentRequired: routing.cancelPendingLaneIds.length === 0
803
- ? "Leader must select and accept the evidence that satisfies the stage contract."
804
- : "Leader must record the observed marginal value before skipping never-started pending Lanes."
805
- });
806
- }
807
- if (resources.pendingLaneIds.length > 0 && resources.activeLaneIds.length === 0) {
808
- return buildAction(facts, {
809
- kind: "implement-current-work-item",
810
- reason: `ExecutionGroup ${group.id} has queued Lanes and an open resource budget; retry Broker admission after the capacity wake.`,
811
- refs,
812
- preconditions: [
813
- { fact: "At least one Lane is durably queued", satisfied: true, ref: refs[1] },
814
- {
815
- fact: "Stage deadline and hard budgets remain open",
816
- satisfied: !executionStageSpendClosed(resources),
817
- ref: refs[1]
818
- }
819
- ],
820
- recommendedCommand: `yui task work dispatch ${facts.task.id}/${item.id}`
821
- });
822
- }
823
- if (resources.activeLaneIds.length > 0) {
824
- return buildAction(facts, {
825
- kind: "repair-protocol-inconsistency",
826
- reason: `ExecutionGroup ${group.id} retains active Lanes but no delegated Turn is active.`,
827
- refs,
828
- conflicts: refs,
829
- preconditions: [
830
- { fact: "Every active Lane has an active exact Turn", satisfied: false, ref: refs[1] }
831
- ]
832
- });
833
- }
834
- return null;
835
- }
836
648
  function latestActiveWorkItemReview(rounds, item, candidate) {
837
649
  if (candidate === undefined)
838
650
  return undefined;
@@ -843,97 +655,41 @@ function latestActiveWorkItemReview(rounds, item, candidate) {
843
655
  && (round.status === "pending" || round.status === "running")));
844
656
  }
845
657
  function activeReviewRoundRun(round, activeTurns) {
846
- if (round.executionGroup !== undefined) {
847
- for (const lane of round.executionGroup.lanes) {
848
- if (lane.status !== "running"
849
- || lane.reviewRoundId !== round.id
850
- || lane.turnId === undefined) {
851
- continue;
852
- }
853
- const run = activeTurns.find((candidate) => (candidate.id === lane.turnId && candidate.roleName === lane.roleName));
854
- if (run !== undefined)
855
- return run;
856
- }
857
- return undefined;
658
+ if (round.reviewerTurnId !== undefined) {
659
+ const main = activeTurns.find((run) => (run.id === round.reviewerTurnId && run.roleName === round.reviewerRoleName));
660
+ if (main !== undefined)
661
+ return main;
858
662
  }
859
- if (round.reviewerTurnId === undefined)
860
- return undefined;
861
- return activeTurns.find((run) => (run.id === round.reviewerTurnId && run.roleName === round.reviewerRoleName));
862
- }
863
- const TERMINAL_REVIEW_LANE_STATUSES = new Set(["completed", "failed"]);
864
- function isPanelReviewRound(round) {
865
- return round.executionGroup !== undefined
866
- && (round.executionGroup.lanes.length > 1
867
- || round.executionGroup.strategy.mode === "adaptive");
663
+ for (const lane of round.executionGroup?.lanes ?? []) {
664
+ if (lane.disposition !== "open" || lane.currentTurnId === undefined)
665
+ continue;
666
+ const run = activeTurns.find((candidate) => (candidate.id === lane.currentTurnId && candidate.roleName === lane.roleName));
667
+ if (run !== undefined)
668
+ return run;
669
+ }
670
+ return undefined;
868
671
  }
869
- function reviewGroupAwaitingResolution(round) {
870
- return round.status === "running"
871
- && round.executionGroup !== undefined
872
- && round.executionGroup.resolution === undefined
873
- && isPanelReviewRound(round)
874
- && round.executionGroup.lanes.every((lane) => TERMINAL_REVIEW_LANE_STATUSES.has(lane.status));
672
+ function reviewLaneRoleOptions(round) {
673
+ return round.executionGroup?.lanes
674
+ .map(({ roleName }) => ` --lane-role ${roleName}`)
675
+ .join("") ?? "";
875
676
  }
876
- function reviewGroupHasResourceQueue(round) {
877
- return round.executionGroup !== undefined
878
- && round.executionGroup.resolution === undefined
879
- && round.executionGroup.lanes.some((lane) => (lane.status === "pending"
880
- && lane.turnId === undefined));
881
- }
882
- function buildResolveReviewGroupAction(facts, round) {
883
- const group = round.executionGroup;
884
- const refs = [
885
- ref("review-round", round.id),
886
- ref("execution-group", group.id)
887
- ];
888
- const resolveCommand = (decision) => `yui task review group resolve ${facts.task.id}/${round.id}`
889
- + ` --decision ${decision} --summary \"<decision>\"`;
890
- return buildAction(facts, {
891
- kind: "resolve-review-group",
892
- reason: `Reviewer panel ${group.id} has finished every Lane; the Leader must resolve ReviewRound ${round.id}.`,
893
- refs,
894
- preconditions: [
895
- { fact: "ReviewRound is running", satisfied: true, ref: refs[0] },
896
- { fact: "Every Reviewer Lane is terminal", satisfied: true, ref: refs[1] },
897
- { fact: "Leader has resolved the Review ExecutionGroup", satisfied: false, ref: refs[1] }
898
- ],
899
- recommendedCommand: resolveCommand("<accept|reject|blocked>"),
900
- alternatives: [
901
- {
902
- kind: "accept-review-group",
903
- reason: "Accept the usable Lane outputs when their evidence satisfies the Task objective.",
904
- recommendedCommand: resolveCommand("accept"),
905
- refs
906
- },
907
- {
908
- kind: "reject-review-group",
909
- reason: "Reject the panel evidence when it does not support the delivery.",
910
- recommendedCommand: resolveCommand("reject"),
911
- refs
912
- },
913
- {
914
- kind: "block-review-group",
915
- reason: "Block the Review when a material dependency or external fact prevents a sound decision.",
916
- recommendedCommand: resolveCommand("blocked"),
917
- refs
918
- }
919
- ],
920
- judgmentRequired: `Leader must judge Review panel ${group.id} against the Task objective, acceptance criteria, and delivery risk.`
921
- });
677
+ function reviewGroupNeedsDispatch(round, turns) {
678
+ return round.executionGroup?.lanes.some((lane) => (lane.disposition === "open"
679
+ && (lane.currentTurnId === undefined
680
+ || turns.find(({ id }) => id === lane.currentTurnId)?.status === "failed"))) === true;
922
681
  }
923
682
  function reviewRoundConflict(round, activeTurns) {
924
683
  const reviewRef = ref("review-round", round.id);
925
684
  if (round.status === "running") {
926
- if (reviewGroupAwaitingResolution(round)) {
927
- return null;
928
- }
929
- if (round.reviewerTurnId === undefined) {
685
+ if (round.reviewerTurnId === undefined && round.executionGroup === undefined) {
930
686
  return {
931
- reason: `ReviewRound ${round.id} is running but has no Reviewer Turn.`,
687
+ reason: `ReviewRound ${round.id} is running but has no execution unit.`,
932
688
  conflicts: [reviewRef]
933
689
  };
934
690
  }
935
691
  if (activeReviewRoundRun(round, activeTurns) === undefined) {
936
- if (reviewGroupHasResourceQueue(round))
692
+ if (round.reviewerTurnId === undefined && round.executionGroup !== undefined)
937
693
  return null;
938
694
  const runRef = ref("turn", round.reviewerTurnId);
939
695
  return {
@@ -944,7 +700,7 @@ function reviewRoundConflict(round, activeTurns) {
944
700
  }
945
701
  if (round.status === "pending") {
946
702
  const launchedTurnId = round.reviewerTurnId
947
- ?? round.executionGroup?.lanes.find((lane) => lane.turnId !== undefined)?.turnId;
703
+ ?? round.executionGroup?.lanes.find((lane) => lane.currentTurnId !== undefined)?.currentTurnId;
948
704
  if (launchedTurnId !== undefined) {
949
705
  const runRef = ref("turn", launchedTurnId);
950
706
  return {
@@ -962,7 +718,7 @@ function selectOpenWorkItem(workItems) {
962
718
  return { kind: "none" };
963
719
  const eligible = openItems.find((item) => (item.dependsOn.every((dependencyId) => {
964
720
  const status = byId.get(dependencyId)?.status;
965
- return status === "completed" || status === "retired";
721
+ return status === "completed";
966
722
  })));
967
723
  if (eligible !== undefined)
968
724
  return { kind: "ready", item: eligible };
@@ -975,7 +731,7 @@ function selectOpenWorkItem(workItems) {
975
731
  visited.add(current.id);
976
732
  const blockedBy = current.dependsOn.find((dependencyId) => {
977
733
  const status = byId.get(dependencyId)?.status;
978
- return status !== "completed" && status !== "retired";
734
+ return status !== "completed";
979
735
  });
980
736
  if (blockedBy === undefined)
981
737
  return { kind: "ready", item: current };
@@ -1004,12 +760,6 @@ function taskFinalReviewRole(facts) {
1004
760
  function ref(kind, id) {
1005
761
  return { kind, id };
1006
762
  }
1007
- function isEmptyDirectCandidate(candidate) {
1008
- const snapshot = candidate.taskMainSnapshot;
1009
- if (snapshot === undefined)
1010
- return false;
1011
- return snapshot.projects.every((project) => project.baseCommit === project.headCommit);
1012
- }
1013
763
  function latestFailedReviewFor(rounds, workItemId) {
1014
764
  return [...rounds]
1015
765
  .reverse()
@@ -1021,64 +771,17 @@ function latestTaskFinalReview(rounds, contract) {
1021
771
  .find((round) => ((round.scope ?? "work-item") === "task"
1022
772
  && (contract === undefined || sameTaskFinalReviewContract(round.taskFinalReviewContract, contract))));
1023
773
  }
1024
- function needsChangeSetCapture(facts, item) {
1025
- if (item.status !== "completed")
1026
- return false;
1027
- if (governingChangeSets([item], facts.changeSets).length > 0)
1028
- return false;
1029
- const candidate = item.candidates.at(-1);
1030
- if (candidate === undefined)
1031
- return false;
1032
- // A metadata-only Task-main Candidate has no WorkItem Develop
1033
- // workspace to capture; its boundary is the Task-main head itself.
1034
- if (candidate.workspace === undefined
1035
- && candidate.gitSnapshot === undefined
1036
- && candidate.taskMainSnapshot !== undefined) {
1037
- return false;
1038
- }
1039
- return candidate.workspace !== undefined || candidate.gitSnapshot !== undefined;
1040
- }
1041
774
  function hasValidFinalReview(facts) {
1042
775
  const contract = taskFinalReviewContract(facts);
1043
776
  const final = [...facts.reviewRounds]
1044
777
  .reverse()
1045
778
  .find((round) => ((round.scope ?? "work-item") === "task"
1046
779
  && (contract === undefined || sameTaskFinalReviewContract(round.taskFinalReviewContract, contract))));
1047
- if (final === undefined || !isAcceptedTaskReviewBaselineFromEvidence(final, nextActionReviewOutcomeEvidence(facts)))
1048
- return false;
1049
- if (facts.currentTaskReviewCandidate !== undefined) {
1050
- return facts.currentTaskReviewCandidate !== null
1051
- && isDeepStrictEqual(final.taskCandidate, facts.currentTaskReviewCandidate);
1052
- }
1053
- const reviewedCommits = new Set((final.taskCandidate?.projects ?? []).map((project) => project.commit));
1054
- if (reviewedCommits.size === 0)
780
+ if (final === undefined || !isCompletedTaskReviewEvidenceFromTurns(final, facts.reviewOutcomeEvidence?.turns ?? []))
1055
781
  return false;
1056
- const integratedHeads = new Set(facts.integrations
1057
- .filter((attempt) => attempt.status === "committed")
1058
- .flatMap((attempt) => facts.changeSets
1059
- .filter((changeSet) => attempt.changeSetIds.includes(changeSet.id))
1060
- .map((changeSet) => changeSet.headCommit)));
1061
- if (integratedHeads.size === 0)
1062
- return facts.workItems.length === 0;
1063
- for (const head of integratedHeads) {
1064
- if (!reviewedCommits.has(head))
1065
- return false;
1066
- }
1067
- return true;
1068
- }
1069
- /** Adapts the serializable next-action evidence bundle to the shared classifier. */
1070
- export function nextActionReviewOutcomeEvidence(facts) {
1071
- const evidence = facts.reviewOutcomeEvidence;
1072
- if (evidence === undefined)
1073
- return undefined;
1074
- return {
1075
- listTurns: () => evidence.turns,
1076
- listReviewFindings: () => evidence.reviewFindings,
1077
- listEvents: () => evidence.events
1078
- };
782
+ return final.taskCandidate !== undefined;
1079
783
  }
1080
784
  function detectProtocolInconsistency(facts) {
1081
- const changeSetIds = new Set(facts.changeSets.map((changeSet) => changeSet.id));
1082
785
  const workItemById = new Map(facts.workItems.map((item) => [item.id, item]));
1083
786
  try {
1084
787
  taskFinalReviewContractResolution(facts);
@@ -1108,14 +811,13 @@ function detectProtocolInconsistency(facts) {
1108
811
  for (const attempt of facts.integrations) {
1109
812
  if (attempt.status !== "committed")
1110
813
  continue;
1111
- const dangling = attempt.changeSetIds
1112
- .filter((id) => !changeSetIds.has(id));
1113
- if (dangling.length > 0) {
814
+ if (attempt.source.kind === "work-item"
815
+ && !workItemById.has(attempt.source.workItemId)) {
1114
816
  return {
1115
- reason: `Committed Integration ${attempt.id} references missing ChangeSet(s): ${dangling.join(", ")}.`,
817
+ reason: `Committed Integration ${attempt.id} references missing WorkItem ${attempt.source.workItemId}.`,
1116
818
  conflicts: [
1117
819
  ref("integration-attempt", attempt.id),
1118
- ...dangling.map((id) => ref("change-set", id))
820
+ ref("work-item", attempt.source.workItemId)
1119
821
  ]
1120
822
  };
1121
823
  }