@zq-silk/yui 0.10.1 → 0.11.0
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/README.md +53 -0
- package/dist/cli/commandCatalog.js +28 -7
- package/dist/cli.js +50 -47
- package/dist/commands/projectCommands.js +69 -6
- package/dist/commands/taskCommands.js +639 -89
- package/dist/commands/taskContextCommand.js +78 -27
- package/dist/commands/taskNextActionCommand.js +13 -2
- package/dist/commands/taskOverviewCommand.js +21 -5
- package/dist/commands/taskUpstreamCommands.js +136 -0
- package/dist/context/runContextPack.js +184 -17
- package/dist/controller/agentRuntimeObserver.js +31 -20
- package/dist/controller/fileSchedulerStoreAdapter.js +7 -13
- package/dist/execution/candidateConvergence.js +623 -0
- package/dist/execution/executionGroup.js +255 -13
- package/dist/execution/executionHealth.js +324 -0
- package/dist/execution/resourceBroker.js +425 -0
- package/dist/executor/fileRoleLaunchPlanner.js +6 -9
- package/dist/executor/workspacePreflightClassification.js +117 -0
- package/dist/lifecycle/exactRunTerminalization.js +13 -2
- package/dist/lifecycle/taskRoleSessionReset.js +4 -2
- package/dist/repository/taskBaseFreshness.js +26 -1
- package/dist/repository/taskWorkspacePreparer.js +17 -1
- package/dist/review/reviewRound.js +27 -6
- package/dist/run/agentRun.js +2 -2
- package/dist/run/recoveryProjection.js +15 -0
- package/dist/runtime/runtimeContinuationProjection.js +7 -0
- package/dist/scheduler/actionability.js +169 -3
- package/dist/scheduler/activeTaskProgress.js +15 -10
- package/dist/scheduler/leaderWakeupProcessor.js +17 -1
- package/dist/scheduler/taskExecutionProjection.js +105 -8
- package/dist/scheduler/taskObservabilityProjection.js +282 -0
- package/dist/storage/migration/productionRegistry.js +14 -0
- package/dist/storage/sqliteStore.js +12 -0
- package/dist/storage/taskStore.js +1 -1
- package/dist/task/completionReadiness.js +1 -1
- package/dist/task/nextAction.js +314 -2
- package/dist/web/assets/client/components.js +116 -0
- package/dist/web/assets/client/i18n.js +66 -0
- package/dist/web/assets/client/view.js +15 -0
- package/dist/web/assets/styles/cards.js +23 -0
- package/dist/web/assets/styles/responsive.js +2 -0
- package/dist/web/webSnapshot.js +8 -2
- package/dist/workItem/workItem.js +262 -5
- package/i18n/README.zh-CN.md +42 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -532,6 +532,59 @@ yui task work isolate <task-id>/<work-item-id>
|
|
|
532
532
|
yui task work dispatch <task-id>/<work-item-id> --input "Implement and run focused tests"
|
|
533
533
|
```
|
|
534
534
|
|
|
535
|
+
Dispatch remains `single` by default. A Leader can explicitly enable bounded
|
|
536
|
+
multi-route exploration on a fresh WorkItem; each accepted stage advances
|
|
537
|
+
`Plan → Generate → Compare → Synthesize → Verify → Resolve`, and only the
|
|
538
|
+
accepted Resolve stage materializes the existing single Candidate:
|
|
539
|
+
|
|
540
|
+
```sh
|
|
541
|
+
yui task work dispatch <task-id>/<work-item-id> \
|
|
542
|
+
--mode parallel-diverse --max-rounds 2 --stage-max-attempts 2 \
|
|
543
|
+
--strategy fixed:2 --lane-role critic \
|
|
544
|
+
--stage-max-tokens 240000 --stage-max-tool-calls 200 \
|
|
545
|
+
--stage-max-seconds 1800 --stage-quorum 2
|
|
546
|
+
yui task work group resolve <task-id>/<work-item-id> \
|
|
547
|
+
--decision accept --summary "Plan evidence is sufficient"
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Every stage is a new immutable ExecutionGroup. Its ContextSnapshot and selected
|
|
551
|
+
parent Lane results are durable references; `retry` repeats a stage within its
|
|
552
|
+
attempt budget, while `retry` at Resolve begins the next bounded round.
|
|
553
|
+
|
|
554
|
+
Each new stage also freezes one Resource Broker contract: token, tool-call and
|
|
555
|
+
wall-clock budgets; quorum and deadline; a straggler window; and the minimum
|
|
556
|
+
marginal value for more Lane spend. Omitted values reuse the existing context
|
|
557
|
+
budget and runtime-health windows; stage retries share the original cumulative
|
|
558
|
+
spend and absolute deadline. Execution, Lane retry, and Reviewer-panel
|
|
559
|
+
admission all count active Lanes at Home, Task, WorkItem, Group, Provider,
|
|
560
|
+
Agent, and model scopes. Capacity pressure keeps the excess Lane durably
|
|
561
|
+
pending instead of failing the Group. Capacity release or deadline arrival
|
|
562
|
+
wakes the Leader through the existing actionability path; rerunning the same
|
|
563
|
+
dispatch resumes the frozen input. Released capacity is reserved for the
|
|
564
|
+
oldest currently admissible waiter, while a Provider- or Agent-blocked queue
|
|
565
|
+
head does not prevent independent scopes from making progress. Provider rate
|
|
566
|
+
limits still use the existing in-place retry window and therefore never fan
|
|
567
|
+
out into sibling failures.
|
|
568
|
+
|
|
569
|
+
The Leader may add `--early-stop <0-100>` to an accepting Group resolution.
|
|
570
|
+
Yui permits it only after quorum and T5's passed Verify/Resolve evidence prove
|
|
571
|
+
sufficiency. It may skip Lanes that never started; active stragglers are
|
|
572
|
+
reported and retained, never killed automatically for cost. If evidence is
|
|
573
|
+
insufficient, budget or deadline exhaustion blocks the stage for Leader
|
|
574
|
+
judgment instead of turning thin evidence into success.
|
|
575
|
+
|
|
576
|
+
New exploration histories also freeze the structured candidate-convergence
|
|
577
|
+
contract. Yui appends the exact stage-local JSON shape to every Lane assignment
|
|
578
|
+
and validates selected reports before the Leader can advance: Compare must
|
|
579
|
+
partition duplicate clusters and justify each selected route with a direct
|
|
580
|
+
source, executable check, or frozen artifact; Synthesize uses a claim/evidence
|
|
581
|
+
table for research, a decision matrix for architecture, and one frozen Git
|
|
582
|
+
snapshot for code. Verify must use a Role independent from the selected
|
|
583
|
+
Synthesize author. Only complete criterion evidence can produce `passed` and
|
|
584
|
+
an accepted Resolve Candidate; explicit gaps produce `next-round` and can only
|
|
585
|
+
continue through bounded Resolve `retry`. Votes and derived analysis remain
|
|
586
|
+
reportable context, but never substitute for direct evidence.
|
|
587
|
+
|
|
535
588
|
Permission is one adapter-specific enum configuration on each Agent binding:
|
|
536
589
|
`default` follows the provider, `bypass` compiles the provider's supported
|
|
537
590
|
bypass flag, and `configured` retains whichever native options are explicitly
|
|
@@ -343,6 +343,19 @@ const taskChildren = [
|
|
|
343
343
|
usage: "yui task rebuild <task> [--latest]",
|
|
344
344
|
options: ["--latest"]
|
|
345
345
|
},
|
|
346
|
+
{
|
|
347
|
+
name: "upstream",
|
|
348
|
+
summary: "Integrate upstream changes into an Active Task workspace.",
|
|
349
|
+
sections: [{ id: "manage", title: "Commands", entries: ["integrate"] }],
|
|
350
|
+
children: [
|
|
351
|
+
{
|
|
352
|
+
name: "integrate",
|
|
353
|
+
summary: "Merge the remote development head into the Task workspace.",
|
|
354
|
+
usage: "yui task upstream integrate <task> [--latest] [--project <project>]",
|
|
355
|
+
options: ["--latest", "--project"]
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
},
|
|
346
359
|
{
|
|
347
360
|
name: "history",
|
|
348
361
|
summary: "Inspect and archive legacy Task refs in the Home repository.",
|
|
@@ -619,8 +632,11 @@ const taskChildren = [
|
|
|
619
632
|
{
|
|
620
633
|
name: "dispatch",
|
|
621
634
|
summary: "Dispatch a work item to its Role.",
|
|
622
|
-
usage: "yui task work dispatch <task>/<work> [--input <text>] [--strategy fixed:<count>|adaptive:<max>] [--lane-role <role> ...]",
|
|
623
|
-
options: ["--input", "--strategy", "--lane-role"]
|
|
635
|
+
usage: "yui task work dispatch <task>/<work> [--input <text>] [--strategy fixed:<count>|adaptive:<max>] [--lane-role <role> ...] [--mode <single|parallel-diverse|ensemble-replicated|adversarial|adaptive-exploration>] [--max-rounds <count>] [--stage-max-attempts <count>] [--stage-max-tokens <count>] [--stage-max-tool-calls <count>] [--stage-max-seconds <count>] [--stage-quorum <count>] [--stage-straggler-seconds <count>] [--stage-min-marginal-value <0-100>]",
|
|
636
|
+
options: ["--input", "--strategy", "--lane-role", "--mode", "--max-rounds", "--stage-max-attempts", "--stage-max-tokens", "--stage-max-tool-calls", "--stage-max-seconds", "--stage-quorum", "--stage-straggler-seconds", "--stage-min-marginal-value"],
|
|
637
|
+
optionValues: {
|
|
638
|
+
"--mode": ["single", "parallel-diverse", "ensemble-replicated", "adversarial", "adaptive-exploration"]
|
|
639
|
+
}
|
|
624
640
|
},
|
|
625
641
|
{
|
|
626
642
|
name: "group",
|
|
@@ -630,9 +646,9 @@ const taskChildren = [
|
|
|
630
646
|
children: [{
|
|
631
647
|
name: "resolve",
|
|
632
648
|
summary: "Select Lane outputs and resolve the Worker group.",
|
|
633
|
-
usage: "yui task work group resolve <task>/<work> --decision <accept|reject|blocked> --summary <text> [--lane <lane-id> ...]",
|
|
634
|
-
options: ["--decision", "--summary", "--lane"],
|
|
635
|
-
optionValues: { "--decision": ["accept", "reject", "blocked"] }
|
|
649
|
+
usage: "yui task work group resolve <task>/<work> --decision <accept|reject|retry|blocked> --summary <text> [--lane <lane-id> ...] [--early-stop <marginal-value-percent>]",
|
|
650
|
+
options: ["--decision", "--summary", "--lane", "--early-stop"],
|
|
651
|
+
optionValues: { "--decision": ["accept", "reject", "retry", "blocked"] }
|
|
636
652
|
}]
|
|
637
653
|
},
|
|
638
654
|
{
|
|
@@ -1244,7 +1260,7 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1244
1260
|
{
|
|
1245
1261
|
name: "project",
|
|
1246
1262
|
summary: "Manage Projects, stable checkouts, branches, and Yui knowledge.",
|
|
1247
|
-
sections: [{ id: "manage", title: "Commands", entries: ["add", "clone", "refresh", "migrate", "update", "discover", "list", "show", "knowledge"] }],
|
|
1263
|
+
sections: [{ id: "manage", title: "Commands", entries: ["add", "clone", "refresh", "diagnose", "migrate", "update", "discover", "list", "show", "knowledge"] }],
|
|
1248
1264
|
children: [
|
|
1249
1265
|
{
|
|
1250
1266
|
name: "add",
|
|
@@ -1264,6 +1280,11 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1264
1280
|
summary: "Fast-forward a clean stable checkout from its configured remote.",
|
|
1265
1281
|
usage: "yui project refresh <project>"
|
|
1266
1282
|
},
|
|
1283
|
+
{
|
|
1284
|
+
name: "diagnose",
|
|
1285
|
+
summary: "Show canonical HEAD vs remote head without mutating the checkout.",
|
|
1286
|
+
usage: "yui project diagnose <project>"
|
|
1287
|
+
},
|
|
1267
1288
|
{
|
|
1268
1289
|
name: "migrate",
|
|
1269
1290
|
summary: "Move an external Project into a Home-managed repository.",
|
|
@@ -1381,7 +1402,7 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1381
1402
|
name: "task",
|
|
1382
1403
|
summary: "Manage Tasks, WorkItems, Agent Runs, and integration.",
|
|
1383
1404
|
sections: [
|
|
1384
|
-
{ id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "base", "update", "activate", "complete", "reopen", "retire", "list", "show", "context", "next-action", "archive", "rebuild", "history", "replace", "reconcile"] },
|
|
1405
|
+
{ id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "base", "update", "activate", "complete", "reopen", "retire", "list", "show", "context", "next-action", "archive", "rebuild", "history", "replace", "reconcile", "upstream"] },
|
|
1385
1406
|
{ id: "collaboration", title: "Collaboration", entries: ["message", "input", "grant", "workflow", "publication", "work", "run", "review", "integration", "role", "overlap", "change-set"] },
|
|
1386
1407
|
{ id: "knowledge", title: "Task Knowledge", entries: ["brief", "decision", "milestone", "event", "continuation", "wake"] }
|
|
1387
1408
|
],
|
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ import { runResourcesCommand } from "./commands/resourcesCommands.js";
|
|
|
37
37
|
import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
|
|
38
38
|
import { runProjectCommand } from "./commands/projectCommands.js";
|
|
39
39
|
import { runProfileCommand } from "./commands/profileCommands.js";
|
|
40
|
-
import { dispatchPreparedReviewRound, failPendingReviewRound, RESUMED_PENDING_FINAL_REVIEW, TERMINALIZED_LEADER_BEFORE_FINAL_REVIEW, TaskFinalReviewDispatchDriftError, preserveReviewRoundWorkspace, parseTaskCompletionRequest, parseTaskFinalReviewContractRebindRequest, preflightTaskCompletion, runTaskCommand, normalizedExecutionLanePlan, validateTaskArchiveRequest } from "./commands/taskCommands.js";
|
|
40
|
+
import { dispatchPreparedReviewRound, failPendingReviewRound, RESUMED_PENDING_FINAL_REVIEW, TERMINALIZED_LEADER_BEFORE_FINAL_REVIEW, TaskFinalReviewDispatchDriftError, preserveReviewRoundWorkspace, parseTaskCompletionRequest, parseTaskFinalReviewContractRebindRequest, preflightTaskCompletion, runTaskCommand, normalizedExecutionLanePlan, resolvedExecutionStageRetryGroup, validateTaskArchiveRequest } from "./commands/taskCommands.js";
|
|
41
41
|
import { taskActor } from "./commands/taskActor.js";
|
|
42
42
|
import { isCurrentGlobalOperator } from "./commands/taskInputCommands.js";
|
|
43
43
|
import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
|
|
@@ -52,6 +52,7 @@ import { acquireHandoverLock, readRuntimeIdentity } from "./release/runtimeRelea
|
|
|
52
52
|
import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
|
|
53
53
|
import { reconcileTaskRemoteBaselines, verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
|
|
54
54
|
import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
|
|
55
|
+
import { runTaskUpstreamCommand } from "./commands/taskUpstreamCommands.js";
|
|
55
56
|
import { assertTaskBaseFreshnessForCompletion, inspectTaskBaseFreshness } from "./repository/taskBaseFreshness.js";
|
|
56
57
|
import { FileCompletionManager, resolveCliIdentity } from "./completion/fileCompletionManager.js";
|
|
57
58
|
import { assertFileTaskControllerStorageCompatible, ensureFileTaskController, FileTaskWorkflowRuntime, refreshRunningFileTaskControllerConfiguration, refreshRunningFileTaskControllerEnvironment, restartFileTaskController, stopFileTaskController } from "./controller/clientRuntime.js";
|
|
@@ -1005,6 +1006,11 @@ export async function main() {
|
|
|
1005
1006
|
emit(result.output, false, result.data);
|
|
1006
1007
|
return;
|
|
1007
1008
|
}
|
|
1009
|
+
if (resolved[1] === "upstream") {
|
|
1010
|
+
const result = await runTaskUpstreamCommand(resolved.slice(2), store);
|
|
1011
|
+
emit(result.output, false, result.data);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1008
1014
|
if (resolved[1] === "complete" && resolved[2] !== undefined) {
|
|
1009
1015
|
const completionRequest = parseTaskCompletionRequest(resolved.slice(2));
|
|
1010
1016
|
completionSummary = completionRequest.summary;
|
|
@@ -1117,6 +1123,16 @@ export async function main() {
|
|
|
1117
1123
|
const laneSnapshotPreflight = executionLaneGitSnapshot === undefined
|
|
1118
1124
|
? undefined
|
|
1119
1125
|
: executionLaneGitSnapshot;
|
|
1126
|
+
// RFC Phase 3: prepare the Task workspace before activation so a
|
|
1127
|
+
// preparation failure keeps the Task in Draft instead of leaving it
|
|
1128
|
+
// Active without a workspace.
|
|
1129
|
+
if (resolved[1] === "activate") {
|
|
1130
|
+
const taskId = resolved[2];
|
|
1131
|
+
const task = taskId === undefined ? null : store.getTask(taskId);
|
|
1132
|
+
if (task !== null && task.status === "draft") {
|
|
1133
|
+
await workspacePreparer.prepareTaskWorkspace(task.id);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1120
1136
|
const result = runTaskCommand(resolved.slice(1), store, {
|
|
1121
1137
|
runtime,
|
|
1122
1138
|
environment: process.env,
|
|
@@ -1166,10 +1182,12 @@ export async function main() {
|
|
|
1166
1182
|
: store.getReviewRound(requestedRound.taskId, requestedRound.id);
|
|
1167
1183
|
let reviewOutput = "";
|
|
1168
1184
|
let reviewData;
|
|
1185
|
+
const resumesReviewDispatch = (resolved[1] === "review" && resolved[2] === "request")
|
|
1186
|
+
|| (resolved[1] === "work" && resolved[2] === "review")
|
|
1187
|
+
|| (resolved[1] === "run" && resolved[2] === "retry");
|
|
1169
1188
|
const reviewDispatchNeeded = requestedRound?.status === "pending"
|
|
1170
1189
|
|| (requestedRound?.status === "running"
|
|
1171
|
-
&&
|
|
1172
|
-
&& resolved[2] === "request"
|
|
1190
|
+
&& resumesReviewDispatch
|
|
1173
1191
|
&& persistedRequestedRound?.executionGroup?.lanes.some((lane) => (lane.status === "pending" && lane.runId === undefined)) === true);
|
|
1174
1192
|
if (reviewDispatchNeeded) {
|
|
1175
1193
|
try {
|
|
@@ -1203,10 +1221,12 @@ export async function main() {
|
|
|
1203
1221
|
? {}
|
|
1204
1222
|
: { deltaRecheckDiff: deltaRecheckPreflight.diffByProject })
|
|
1205
1223
|
});
|
|
1206
|
-
reviewOutput =
|
|
1224
|
+
reviewOutput = run === null
|
|
1225
|
+
? `Review ${requestedRound.id} retained pending by Resource Broker\n`
|
|
1226
|
+
: `Review queued as ${requestedRound.id} (${run.id})\n`;
|
|
1207
1227
|
reviewData = {
|
|
1208
1228
|
reviewRound: store.getReviewRound(requestedRound.taskId, requestedRound.id),
|
|
1209
|
-
reviewRun: run,
|
|
1229
|
+
...(run === null ? {} : { reviewRun: run }),
|
|
1210
1230
|
workspace
|
|
1211
1231
|
};
|
|
1212
1232
|
}
|
|
@@ -1226,45 +1246,6 @@ export async function main() {
|
|
|
1226
1246
|
reviewData = { reviewRound: failed };
|
|
1227
1247
|
}
|
|
1228
1248
|
}
|
|
1229
|
-
if (resolved[1] === "create") {
|
|
1230
|
-
const created = result.data;
|
|
1231
|
-
if (created?.task?.id !== undefined) {
|
|
1232
|
-
let workspace;
|
|
1233
|
-
try {
|
|
1234
|
-
workspace = await workspacePreparer.prepareTaskWorkspace(created.task.id);
|
|
1235
|
-
}
|
|
1236
|
-
catch (error) {
|
|
1237
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1238
|
-
workspace = {
|
|
1239
|
-
taskId: created.task.id,
|
|
1240
|
-
status: "failed",
|
|
1241
|
-
error: message
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
const latest = store.getTask(created.task.id);
|
|
1245
|
-
const leader = store.getRole(created.task.id, "leader");
|
|
1246
|
-
if (latest !== null && leader !== null) {
|
|
1247
|
-
const warning = workspace.status === "failed"
|
|
1248
|
-
? `Main worktree is not ready: ${workspace.error ?? "unknown error"}.\n`
|
|
1249
|
-
+ `After correcting the Git problem, run yui task reconcile ${created.task.id}.\n`
|
|
1250
|
-
: "";
|
|
1251
|
-
emit(`${result.output}${warning}`, false, {
|
|
1252
|
-
...created,
|
|
1253
|
-
task: latest,
|
|
1254
|
-
leader,
|
|
1255
|
-
workspace
|
|
1256
|
-
});
|
|
1257
|
-
return;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
if (resolved[1] === "activate") {
|
|
1262
|
-
const taskId = resolved[2];
|
|
1263
|
-
const task = taskId === undefined ? null : store.getTask(taskId);
|
|
1264
|
-
if (task?.status === "active") {
|
|
1265
|
-
await workspacePreparer.prepareTaskWorkspace(task.id);
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
1249
|
if (resolved[1] === "project" && resolved[2] === "add") {
|
|
1269
1250
|
const taskId = resolved[3];
|
|
1270
1251
|
const task = taskId === undefined ? null : store.getTask(taskId);
|
|
@@ -1683,6 +1664,7 @@ async function candidateSnapshotForTaskCommand(args, store, preparer, environmen
|
|
|
1683
1664
|
const fixedSingleLane = group?.strategy.mode === "fixed"
|
|
1684
1665
|
&& group.strategy.count === 1
|
|
1685
1666
|
&& group.lanes.length === 1
|
|
1667
|
+
&& group.stage === undefined
|
|
1686
1668
|
&& run.workspace.owner.type === "work-item";
|
|
1687
1669
|
if (fixedSingleLane)
|
|
1688
1670
|
return preparer.snapshotCandidateWorkspace(run.workspace);
|
|
@@ -1725,9 +1707,28 @@ async function candidateMaterializationForTaskCommand(args, store, preparer, env
|
|
|
1725
1707
|
: currentWorkItemExecutionGroup(item);
|
|
1726
1708
|
if (item === null || item === undefined || group === undefined)
|
|
1727
1709
|
return undefined;
|
|
1710
|
+
if (group.stage !== undefined && group.stage.stage !== "resolve")
|
|
1711
|
+
return undefined;
|
|
1712
|
+
// Early termination first stops never-started spend. Active stragglers are
|
|
1713
|
+
// deliberately retained, so the command records that stop and leaves the
|
|
1714
|
+
// group unresolved. Do not merge Lane output into the WorkItem Candidate
|
|
1715
|
+
// until those active Lanes settle and the Leader resolves the group again.
|
|
1716
|
+
if (args.includes("--early-stop")
|
|
1717
|
+
&& group.lanes.some(({ status }) => status === "running"))
|
|
1718
|
+
return undefined;
|
|
1728
1719
|
const selected = args.flatMap((value, index) => value === "--lane" && args[index + 1] !== undefined ? [args[index + 1]] : []);
|
|
1720
|
+
const materializedLaneIds = selected.length === 0
|
|
1721
|
+
? group.lanes
|
|
1722
|
+
.filter((lane) => lane.status === "yielded" || lane.status === "completed")
|
|
1723
|
+
.map(({ id }) => id)
|
|
1724
|
+
: selected;
|
|
1725
|
+
if (group.stage?.convergence !== undefined
|
|
1726
|
+
&& group.stage.stage === "resolve"
|
|
1727
|
+
&& materializedLaneIds.length !== 1) {
|
|
1728
|
+
throw usageError("Candidate convergence Resolve must select exactly one Lane before materialization.");
|
|
1729
|
+
}
|
|
1729
1730
|
try {
|
|
1730
|
-
return await preparer.materializeExecutionGroupCandidate(item.taskId, item.id, group.id,
|
|
1731
|
+
return await preparer.materializeExecutionGroupCandidate(item.taskId, item.id, group.id, materializedLaneIds);
|
|
1731
1732
|
}
|
|
1732
1733
|
catch (error) {
|
|
1733
1734
|
throw usageError(error instanceof Error ? error.message : String(error));
|
|
@@ -1781,6 +1782,9 @@ async function prepareExecutionLaneWorkspacesForCommand(args, store, preparer, e
|
|
|
1781
1782
|
requestedRoles: roles,
|
|
1782
1783
|
requestedStrategy,
|
|
1783
1784
|
existingGroup: group === undefined ? undefined : group,
|
|
1785
|
+
resolvedRetryGroup: isDispatch
|
|
1786
|
+
? resolvedExecutionStageRetryGroup(currentGroup)
|
|
1787
|
+
: undefined,
|
|
1784
1788
|
status: item.status,
|
|
1785
1789
|
nextGroupId: `execution-group-${store.peekNextAgentRunId(item.taskId)}`,
|
|
1786
1790
|
retryLaneId,
|
|
@@ -1799,8 +1803,7 @@ async function prepareExecutionLaneWorkspacesForCommand(args, store, preparer, e
|
|
|
1799
1803
|
throw usageError(`ExecutionGroup strategy is frozen: ${group.id}.`);
|
|
1800
1804
|
}
|
|
1801
1805
|
const laneCount = plan.requestedCount;
|
|
1802
|
-
const
|
|
1803
|
-
const adaptive = strategyArg?.startsWith("adaptive:") === true || group?.strategy.mode === "adaptive";
|
|
1806
|
+
const adaptive = plan.strategy.mode === "adaptive";
|
|
1804
1807
|
const needsIsolation = adaptive || laneCount > 1 || (group?.lanes.length ?? 0) > 1;
|
|
1805
1808
|
if (!needsIsolation)
|
|
1806
1809
|
return undefined;
|
|
@@ -27,6 +27,9 @@ export async function runProjectCommand(args, store, options = {}) {
|
|
|
27
27
|
data: refreshed
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
if (command === "diagnose") {
|
|
31
|
+
return diagnoseProject(rest, store, options);
|
|
32
|
+
}
|
|
30
33
|
if (command === "migrate") {
|
|
31
34
|
const migrated = await migrateProject(rest, store, options);
|
|
32
35
|
return {
|
|
@@ -80,12 +83,72 @@ async function refreshProject(args, store, options) {
|
|
|
80
83
|
if (project.stableBranch !== project.developmentBranch) {
|
|
81
84
|
throw usageError(`Project refresh requires matching stable and development branches: ${project.id}.`);
|
|
82
85
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
// RFC Phase 1: hold the per-Project maintenance fence for the whole refresh
|
|
87
|
+
// so Task workspace preparation and other maintenance cannot interleave
|
|
88
|
+
// with the canonical branch/working-tree move.
|
|
89
|
+
const releaseMaintenance = acquireProjectMaintenanceLock(store.rootDirectory(), project.id);
|
|
90
|
+
try {
|
|
91
|
+
const refreshed = await (options.git ?? new NodeGitWorkspace()).refresh({
|
|
92
|
+
repositoryPath: project.path,
|
|
93
|
+
remoteUrl: project.remoteUrl,
|
|
94
|
+
stableRef: project.stableBranch
|
|
95
|
+
});
|
|
96
|
+
return { project, ...refreshed };
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
releaseMaintenance();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* RFC Phase 1: diagnose a managed Project's divergence without mutating it.
|
|
104
|
+
* Shows the canonical HEAD, remote advertised SHA, and whether the checkout
|
|
105
|
+
* can be fast-forwarded. Does not auto-reset or rebase.
|
|
106
|
+
*/
|
|
107
|
+
async function diagnoseProject(args, store, options) {
|
|
108
|
+
if (args.length !== 1) {
|
|
109
|
+
throw usageError("Project diagnose usage: yui project diagnose <project>.");
|
|
110
|
+
}
|
|
111
|
+
const project = requireProject(store, args[0]);
|
|
112
|
+
if (project.remoteUrl === undefined) {
|
|
113
|
+
return {
|
|
114
|
+
output: `Project ${project.id} has no remote URL; nothing to diagnose.\n`,
|
|
115
|
+
data: { project, diagnosis: "no-remote" }
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const git = options.git ?? new NodeGitWorkspace();
|
|
119
|
+
const releaseMaintenance = acquireProjectMaintenanceLock(store.rootDirectory(), project.id);
|
|
120
|
+
try {
|
|
121
|
+
const current = await git.inspect(project.path, "HEAD");
|
|
122
|
+
const remote = await git.resolveRemoteBaseline({
|
|
123
|
+
repositoryPath: project.path,
|
|
124
|
+
remoteUrl: project.remoteUrl,
|
|
125
|
+
developmentRef: project.developmentBranch
|
|
126
|
+
});
|
|
127
|
+
const isAncestor = await git.isAncestor(project.path, current.baseCommit, remote.commit);
|
|
128
|
+
const diverged = !isAncestor && current.baseCommit !== remote.commit;
|
|
129
|
+
const lines = [
|
|
130
|
+
`Project: ${project.id}`,
|
|
131
|
+
` canonical HEAD: ${current.baseCommit}`,
|
|
132
|
+
` remote ${project.developmentBranch}: ${remote.commit}`,
|
|
133
|
+
` status: ${current.baseCommit === remote.commit ? "up-to-date" : diverged ? "diverged" : "behind"}`,
|
|
134
|
+
...(diverged
|
|
135
|
+
? [" The canonical checkout has local commits not on the remote.",
|
|
136
|
+
" Use `yui project refresh` only after resolving the divergence manually."]
|
|
137
|
+
: [])
|
|
138
|
+
];
|
|
139
|
+
return {
|
|
140
|
+
output: lines.join("\n") + "\n",
|
|
141
|
+
data: {
|
|
142
|
+
project,
|
|
143
|
+
diagnosis: diverged ? "diverged" : current.baseCommit === remote.commit ? "up-to-date" : "behind",
|
|
144
|
+
canonicalHead: current.baseCommit,
|
|
145
|
+
remoteHead: remote.commit
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
releaseMaintenance();
|
|
151
|
+
}
|
|
89
152
|
}
|
|
90
153
|
async function discoverProjects(args, store, options) {
|
|
91
154
|
if (args.length > 1) {
|