@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.
- package/ARCHITECTURE.md +64 -35
- package/README.md +258 -125
- package/dist/agent/argumentPolicy.js +2 -1
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateOrchestrator.js +10 -8
- package/dist/cli/updatePorts.js +17 -13
- package/dist/cli/upgradeCommand.js +2 -5
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sessionBootstrapManifest.js +18 -1
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +1 -0
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +51 -83
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +2 -267
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/sqliteSchema.js +1 -21
- package/dist/storage/sqliteStore.js +62 -47
- package/dist/storage/storageSchema.js +1 -8
- package/dist/storage/storageVersions.js +7 -4
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +1 -1
- package/i18n/README.zh-CN.md +165 -59
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +27 -6
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/task/repairWave.js +0 -150
package/dist/task/nextAction.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 {
|
|
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 (
|
|
108
|
-
|
|
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 (
|
|
146
|
+
if (reviewGroupNeedsDispatch(activeReview, facts.activeTurns)) {
|
|
124
147
|
return buildAction(facts, {
|
|
125
148
|
kind: "resume-review",
|
|
126
|
-
reason: `ReviewRound ${activeReview.id} has
|
|
149
|
+
reason: `ReviewRound ${activeReview.id} has Review Producer Lanes ready for dispatch or retry.`,
|
|
127
150
|
refs: [reviewRef],
|
|
128
151
|
preconditions: [
|
|
129
|
-
{ fact: "A
|
|
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 =
|
|
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}
|
|
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: "
|
|
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:
|
|
255
|
-
|
|
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 (
|
|
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
|
|
343
|
-
|
|
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-
|
|
360
|
-
reason: `
|
|
361
|
-
refs: [ref("
|
|
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
|
-
{
|
|
364
|
-
|
|
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} --
|
|
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
|
|
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}
|
|
380
|
-
refs: [
|
|
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
|
-
{
|
|
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
|
-
|
|
411
|
+
...(sameRoundCommand === undefined ? {} : { recommendedCommand: sameRoundCommand })
|
|
385
412
|
});
|
|
386
413
|
}
|
|
387
414
|
if (finalReviewRequired
|
|
388
|
-
&& failedFinal
|
|
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}
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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 (
|
|
447
|
-
|
|
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 (
|
|
450
|
+
if (reviewGroupNeedsDispatch(activeFinal, facts.activeTurns)) {
|
|
463
451
|
return buildAction(facts, {
|
|
464
452
|
kind: "resume-review",
|
|
465
|
-
reason: `Task-final ReviewRound ${activeFinal.id} has
|
|
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
|
|
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 =
|
|
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}
|
|
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: "
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
??
|
|
548
|
+
?? failedFinal?.reviewerRoleName;
|
|
559
549
|
const finalReviewAlternative = finalReviewOptional && optionalReviewer !== undefined
|
|
560
550
|
? [{
|
|
561
551
|
kind: "request-final-review",
|
|
562
|
-
reason:
|
|
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:
|
|
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
|
-
:
|
|
567
|
+
: deliveryWorkItems.length === 0
|
|
580
568
|
? [{ fact: "Task main is clean, committed, and verified", satisfied: false }]
|
|
581
569
|
: [
|
|
582
|
-
{ fact: "Every governing
|
|
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:
|
|
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.
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
|
870
|
-
return round.
|
|
871
|
-
|
|
872
|
-
|
|
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
|
|
877
|
-
return round.executionGroup
|
|
878
|
-
&&
|
|
879
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
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.
|
|
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"
|
|
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"
|
|
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 || !
|
|
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
|
-
|
|
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
|
-
|
|
1112
|
-
|
|
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
|
|
817
|
+
reason: `Committed Integration ${attempt.id} references missing WorkItem ${attempt.source.workItemId}.`,
|
|
1116
818
|
conflicts: [
|
|
1117
819
|
ref("integration-attempt", attempt.id),
|
|
1118
|
-
|
|
820
|
+
ref("work-item", attempt.source.workItemId)
|
|
1119
821
|
]
|
|
1120
822
|
};
|
|
1121
823
|
}
|