@zq-silk/yui 0.11.2 → 0.12.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.
Files changed (91) hide show
  1. package/ARCHITECTURE.md +23 -4
  2. package/README.md +80 -13
  3. package/dist/cli/commandCatalog.js +30 -5
  4. package/dist/cli.js +105 -43
  5. package/dist/commands/configCommands.js +12 -45
  6. package/dist/commands/executionAuditCommands.js +1 -1
  7. package/dist/commands/jobCommands.js +4 -10
  8. package/dist/commands/projectCommands.js +581 -2
  9. package/dist/commands/taskCommands.js +159 -172
  10. package/dist/commands/taskCompletionGate.js +29 -54
  11. package/dist/commands/taskContextCommand.js +55 -11
  12. package/dist/commands/taskInputCommands.js +8 -5
  13. package/dist/commands/taskIntegrationCommands.js +2 -1
  14. package/dist/commands/taskNextActionCommand.js +56 -6
  15. package/dist/commands/taskOverviewCommand.js +17 -31
  16. package/dist/commands/taskRoleRuntimeStatus.js +24 -0
  17. package/dist/commands/taskWorkspaceCommands.js +5 -2
  18. package/dist/config/configCatalog.js +2 -2
  19. package/dist/config/yuiConfig.js +3 -7
  20. package/dist/context/wakeNotification.js +20 -5
  21. package/dist/controller/agentRuntimeObserver.js +247 -51
  22. package/dist/controller/clientRuntime.js +1 -1
  23. package/dist/controller/controller.js +8 -12
  24. package/dist/controller/fileSchedulerStoreAdapter.js +146 -198
  25. package/dist/controller/runtime.js +3 -3
  26. package/dist/controller/runtimeEventInbox.js +17 -7
  27. package/dist/controller/runtimeEventProcessor.js +12 -19
  28. package/dist/controller/runtimeObservationHook.js +45 -0
  29. package/dist/execution/resourceBroker.js +5 -4
  30. package/dist/executor/agentExecutor.js +4 -4
  31. package/dist/executor/effectiveLaunch.js +8 -48
  32. package/dist/executor/fileRoleLaunchPlanner.js +36 -16
  33. package/dist/executor/workspacePreflightClassification.js +23 -2
  34. package/dist/interaction/operatorPresentation.js +33 -89
  35. package/dist/lifecycle/exactRunTerminalization.js +1 -2
  36. package/dist/lifecycle/taskRoleSessionReset.js +5 -13
  37. package/dist/observability/orchestrationMetrics.js +8 -3
  38. package/dist/profile/agentProfile.js +1 -1
  39. package/dist/release/cliHomeReleaseFence.js +123 -0
  40. package/dist/repository/checkoutSwap.js +61 -0
  41. package/dist/repository/gitWorkspace.js +36 -0
  42. package/dist/repository/project.js +53 -3
  43. package/dist/repository/taskWorkspacePreparer.js +176 -60
  44. package/dist/resources/sqliteResourceRegistry.js +1 -1
  45. package/dist/review/deltaRecheck.js +12 -51
  46. package/dist/review/reviewAcceptance.js +26 -0
  47. package/dist/review/reviewConfig.js +0 -31
  48. package/dist/review/reviewDecision.js +113 -0
  49. package/dist/review/reviewOutcomeClassifier.js +1 -1
  50. package/dist/review/reviewRound.js +1 -1
  51. package/dist/review/reviewerAvailability.js +69 -0
  52. package/dist/runtime/agentDriverObservation.js +24 -10
  53. package/dist/runtime/builtinAgentDrivers.js +4 -3
  54. package/dist/runtime/builtinTranscriptObserver.js +301 -64
  55. package/dist/runtime/builtinTranscriptUsage.js +9 -7
  56. package/dist/runtime/index.js +1 -0
  57. package/dist/runtime/runtimeObservation.js +13 -0
  58. package/dist/runtime/runtimeProjection.js +16 -25
  59. package/dist/runtime/sessionTokenMetrics.js +181 -0
  60. package/dist/runtime/structuredProviderHost.js +7 -1
  61. package/dist/scheduler/activeRoleRunDelivery.js +4 -4
  62. package/dist/scheduler/activeTaskProgress.js +7 -6
  63. package/dist/scheduler/leaderWakeupProcessor.js +3 -5
  64. package/dist/scheduler/operatorEvent.js +34 -0
  65. package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
  66. package/dist/scheduler/roleRunStall.js +0 -14
  67. package/dist/scheduler/taskExecutionProjection.js +97 -76
  68. package/dist/scheduler/taskObservabilityProjection.js +10 -11
  69. package/dist/storage/migration/productionRegistry.js +467 -0
  70. package/dist/storage/sqliteSchema.js +18 -8
  71. package/dist/storage/sqliteStore.js +61 -21
  72. package/dist/storage/storeRpc.js +0 -1
  73. package/dist/storage/taskStore.js +47 -31
  74. package/dist/storage/upgrade/recordVersions.js +3 -2
  75. package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
  76. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
  77. package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
  78. package/dist/task/completionReadiness.js +10 -6
  79. package/dist/task/nextAction.js +82 -24
  80. package/dist/telemetry/sqliteTelemetryStore.js +1 -1
  81. package/dist/web/assets/client/components.js +13 -3
  82. package/dist/web/assets/client/i18n.js +6 -2
  83. package/dist/web/webSnapshot.js +4 -0
  84. package/i18n/README.zh-CN.md +2 -2
  85. package/package.json +1 -1
  86. package/skills/yui-leader/SKILL.md +53 -16
  87. package/skills/yui-operator/SKILL.md +9 -0
  88. package/skills/yui-reviewer/SKILL.md +28 -7
  89. package/dist/context/sessionContextBudget.js +0 -71
  90. package/dist/lifecycle/contextBudgetRollover.js +0 -81
  91. package/dist/scheduler/operatorNotification.js +0 -59
package/ARCHITECTURE.md CHANGED
@@ -19,6 +19,16 @@ units. For a feature, the Leader judges whether the whole result is small
19
19
  enough to own in the same way or large enough to need independently owned
20
20
  delivery units.
21
21
 
22
+ Operator updates have one durable path. Domain and runtime transitions first
23
+ append an immutable TaskEvent; user-owned questions append an InputRequest.
24
+ Only those record references enter the global Operator mailbox. The Controller
25
+ batches pending references into one receipt-backed synthetic user message for
26
+ the existing interactive Operator and defers the complete batch while that
27
+ Operator is busy or unavailable. The message carries CLI read pointers rather
28
+ than copied Task or Provider narrative. No lower layer calls the Operator, no
29
+ mutable notification projection duplicates the event history, and no Goal or
30
+ polling protocol is required to follow work.
31
+
22
32
  `WorkItem` means one substantial, independently acceptable requirement with a
23
33
  clear owner. Create multiple WorkItems only when multiple Workers can own and
24
34
  advance those requirements independently, normally in parallel. Internal
@@ -128,10 +138,14 @@ late results until explicitly reopened.
128
138
 
129
139
  Stable Project checkouts are read-only references. Task identity follows one
130
140
  bounded outcome rather than Project count. A Task binds zero or more Projects,
131
- records an independent base ref for each binding, and owns one workspace root
132
- containing a managed main worktree for each binding. The
133
- `<workspace>/tasks/<task>/main` root is a logical multi-Project container, not a
134
- Git repository. Each Project child (for example
141
+ records an independent base ref for each binding, and adopts one workspace root
142
+ only when it becomes active. A Draft owns planning state and Project bindings,
143
+ not a writable Workspace. Activation prepares physical worktrees first and then
144
+ commits status, workspace identity, cwd, and durable Workspace ownership in one
145
+ TaskStore transaction; failure discards unadopted resources and leaves the Task
146
+ Draft. The active Workspace contains a managed main worktree for each binding.
147
+ The `<workspace>/tasks/<task>/main` root is a logical multi-Project container,
148
+ not a Git repository. Each Project child (for example
135
149
  `<workspace>/tasks/<task>/main/yui`) is the supported Git cwd and points to
136
150
  `<workspace>/worktree/<project>/<task>/main`; Git commands run in that child.
137
151
  For a single-Project workspace, the native Agent starts in that Project's
@@ -142,6 +156,11 @@ additional-directory mechanism. The active Leader may append a Project when the
142
156
  same outcome expands; replacing an existing binding is not a scope-repair
143
157
  mechanism.
144
158
 
159
+ Before a Role launch, Yui verifies that every physical Project HEAD still
160
+ descends from its Workspace's recorded base. Normal committed progress is
161
+ allowed; a reset or repoint outside that lineage is reported as
162
+ `physical-drift` and fails closed before Provider launch.
163
+
145
164
  A WorkItem can read the full Task workspace but has an explicit Project write
146
165
  scope. Isolation creates a second root with independent worktrees for writable
147
166
  Projects and Task-main context for the rest. The managed dispatch and
package/README.md CHANGED
@@ -274,6 +274,12 @@ yui task context <task-id>
274
274
  yui task activate <task-id>
275
275
  ```
276
276
 
277
+ A Draft stores planning state and Project bindings only; it does not adopt a
278
+ writable managed Workspace. `task activate` prepares every bound Project first,
279
+ then commits the Task's `active` status and Task-owned Workspace together. A
280
+ preparation or consistency failure leaves the Task Draft and reports the
281
+ workspace diagnosis instead of exposing a partially adopted execution root.
282
+
277
283
  Task type describes intent rather than selecting an execution protocol.
278
284
  Software Projects use `bugfix` or `feature`: a bugfix is Leader-owned; if it
279
285
  grows into independently owned delivery requirements, reclassify it as a
@@ -294,6 +300,49 @@ files as dirty, preserves ignored files, and refuses missing remotes or refs and
294
300
  When the configured branch is `HEAD`, refresh resolves the remote's symbolic default branch for that
295
301
  operation and requires the checkout to be on that branch; detached or mismatched checkouts fail.
296
302
 
303
+ ### Project lifecycle
304
+
305
+ Divergence and end-of-life are explicit, Operator-authority operations with fail-closed gates.
306
+ Every destructive command refuses a managed Task Session (run it from an Operator or user
307
+ terminal), an active Task binding, a dirty checkout, and an unreachable or unverified remote.
308
+
309
+ ```sh
310
+ yui project diagnose app
311
+ yui project reset app
312
+ yui project reset app --discard-local
313
+ yui project replace app --discard-local
314
+ yui project retire app --reason "superseded by app-ng"
315
+ yui project delete app --confirm app
316
+ yui project delete app --checkout --confirm app
317
+ ```
318
+
319
+ `project reset` handles the divergence `project refresh` refuses. Without `--discard-local` it is
320
+ a dry run: it fetches and verifies the remote baseline, and when the checkout has diverged it
321
+ refuses while listing the exact local commits that would be discarded. With `--discard-local` it
322
+ hard-resets the clean checkout to the verified remote commit (a plain fast-forward when the
323
+ checkout is merely behind). `project replace` goes further for Home-managed checkouts: it clones
324
+ the remote into a staging directory, verifies both branches, copies the Yui-local refs
325
+ (`refs/heads/yui/`, `refs/yui/archive/`) so historical evidence keeps resolving, then swaps the
326
+ checkout on disk while the catalog record keeps its path. Replace refuses linked worktrees (Task
327
+ or Integration workspaces) and dirty checkouts, and requires `--discard-local`. The swap is
328
+ recoverable: the previous checkout is parked at a backup path and restored on any failure, a
329
+ catalog refusal rolls the swap back, and a crash mid-swap is healed on the next run (a crash
330
+ before the swap leaves only a removable staging clone).
331
+
332
+ `project retire` is the auditable soft deprecation: it records who retired the Project, when, and
333
+ why, while retaining the catalog record, checkout, and every historical
334
+ Task/Run/Review/Integration/Publication reference. A retired Project cannot be refreshed,
335
+ updated, migrated, reset, replaced, maintained through Knowledge writes (add/retire/propose/
336
+ accept/reject), or bound to new Tasks, WorkItems, or Integrations; Knowledge reads (`list`,
337
+ `show`, `proposals list/show`) stay open so the evidence stays auditable.
338
+ `project delete` is the separate hard-removal decision: it requires a retired Project, an exact
339
+ `--confirm <project-id>` acknowledgment, and fails closed while any Task record references the
340
+ Project. `--checkout` additionally removes the Home-managed checkout (external checkouts are
341
+ user-owned and must be removed manually): it first refuses linked worktrees and dirty checkouts,
342
+ then moves the checkout to a tombstone before removing the catalog record, restoring it on any
343
+ failure so the catalog and checkout never disagree unrecoverably. `project show` and
344
+ `project list` display the lifecycle status and retirement record.
345
+
297
346
  Use `task context` as the first detailed read of an existing Task. It combines the Task, Brief, active Decisions, recent Milestones, Roles, current and recent WorkItems with their Runs, recent Messages, open and resolved InputRequests, and recent Events. Terminal output keeps histories and long text compact; `yui --json task context <task-id>` returns the complete records in the top-level `data` field.
298
347
 
299
348
  Leader wakeups stay deliberately small: the wake envelope carries only the
@@ -347,14 +396,16 @@ acceptance so the Leader can accept it directly or run
347
396
  Leader-managed candidates awaiting a decision instead of marking them done.
348
397
  `final` does not create WorkItem ReviewRounds or decide Task topology. The
349
398
  Leader explicitly requests a Task-scoped Review, unless an immutable Task
350
- contract requires one. The Round freezes Task main directly, so even a
351
- Leader-owned Task with no WorkItem can be reviewed. A changed frozen head needs
352
- a new semantic Round; the same compatible Reviewer Session may continue in its
399
+ contract requires one. The Round snapshots the exact Task-main Project heads
400
+ directly, so even a Leader-owned Task with no WorkItem can be reviewed without
401
+ locking the mutable Task workspace. A changed frozen head needs a new semantic
402
+ Round; the same Reviewer Session continues in its
353
403
  stable workspace, while every Run remains bound to its exact Round and head.
354
404
  The Reviewer follows Project Policy/Knowledge and reports reachable, material,
355
405
  actionable findings across the complete Task.
356
- A ReviewRound freezes the Candidate's exact Git commit and creates a fresh,
357
- ReviewRound-owned writable worktree on a unique branch. Its AgentRun may edit,
406
+ A ReviewRound freezes the Candidate's exact Git commit and updates the
407
+ Reviewer Role's stable writable workspace to that head while recording exact
408
+ Round-owned workspace evidence. Its AgentRun may edit,
358
409
  test, and optionally commit diagnostic evidence there, but never changes the
359
410
  Candidate or Worker workspace and never creates another WorkItem, Candidate,
360
411
  ChangeSet, or recursive review. The result wakes the Leader, who decides whether
@@ -362,6 +413,17 @@ to route evidence to the original Worker, accept, reject and redispatch that
362
413
  Worker in its existing Session, review again, or request user input.
363
414
  A failed review remains visible evidence and wakes the Leader, but does not
364
415
  take that decision away from the Leader.
416
+ Task context and next-action expose direct Review facts: every frozen Project
417
+ commit, its relation to the current candidate, the active Run, and the Reviewer
418
+ workspace. A request that fails after Round creation retains the ReviewRound
419
+ and reports its exact reason; the Leader opens that Round and decides whether
420
+ to retry, inspect or clean the workspace, use another Reviewer, or continue
421
+ other work.
422
+ An active Task-final Review freezes only its own candidate; it does not prevent
423
+ the Leader from processing new input or advancing a later candidate. Delta
424
+ Recheck is always available when Yui can prove an accepted contiguous baseline
425
+ and exact diff. Yui does not select a mode from generic size thresholds;
426
+ `requires-full-review` returns to the Leader without creating another Round.
365
427
  Candidate history, every ReviewRound, and the Leader decision remain grouped
366
428
  under the original WorkItem. A rejected result creates a new Candidate on the
367
429
  next dispatch while reusing the original execution Role, Session, and
@@ -551,11 +613,14 @@ Every stage is a new immutable ExecutionGroup. Its ContextSnapshot and selected
551
613
  parent Lane results are durable references; `retry` repeats a stage within its
552
614
  attempt budget, while `retry` at Resolve begins the next bounded round.
553
615
 
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
616
+ Each new stage also freezes one Resource Broker contract: behavioral tool-call
617
+ and wall-clock budgets; a display-only token threshold; quorum and deadline; a
618
+ straggler window; and the minimum marginal value for more Lane spend. Omitted
619
+ values reuse the existing context budget and runtime-health windows; stage
620
+ retries share the original cumulative observations and absolute deadline.
621
+ Observed token totals and the configured token threshold are cost context only:
622
+ they never close spend, block admission, suppress scheduling, or stop a Lane.
623
+ Execution, Lane retry, and Reviewer-panel
559
624
  admission all count active Lanes at Home, Task, WorkItem, Group, Provider,
560
625
  Agent, and model scopes. Capacity pressure keeps the excess Lane durably
561
626
  pending instead of failing the Group. Capacity release or deadline arrival
@@ -570,8 +635,9 @@ The Leader may add `--early-stop <0-100>` to an accepting Group resolution.
570
635
  Yui permits it only after quorum and T5's passed Verify/Resolve evidence prove
571
636
  sufficiency. It may skip Lanes that never started; active stragglers are
572
637
  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.
638
+ insufficient, behavioral tool-call/wall-clock budget or deadline exhaustion
639
+ blocks the stage for Leader judgment instead of turning thin evidence into
640
+ success.
575
641
 
576
642
  New exploration histories also freeze the structured candidate-convergence
577
643
  contract. Yui appends the exact stage-local JSON shape to every Lane assignment
@@ -749,7 +815,7 @@ yui task input request <task-id> --question "Which format should be the default?
749
815
 
750
816
  The recommendation is shown to the user. If no answer arrives, the nearest-deadline timer wakes the Controller to atomically apply that exact choice and queue the fixed Leader session to resume. Free-text and user-required requests never auto-resolve.
751
817
 
752
- `task input list` is the authoritative global open-input Inbox; add a Task ID to scope it, or `--all` to include answered and cancelled requests. The Controller also makes one receipt-backed, best-effort delivery to an already-running Operator process. It never starts or interrupts an Operator for this notification; unavailable process state or a changed pane fence falls back to the durable Inbox and is reconsidered on a later Controller pass. It does not inspect or classify Agent terminal text. Answers may be submitted by the user or Operator. An open request prevents unrelated pending wakes and Task completion or archival. The originating Leader may instead run `yui task input cancel <task-id> <input-id> --reason "..."`; cancellation queues that fixed Leader session to resume.
818
+ `task input list` is the authoritative global open-input Inbox; add a Task ID to scope it, or `--all` to include answered and cancelled requests. Task completion, retirement, Leader attention, stalls, and open input are queued to the global Operator mailbox only as immutable TaskEvent or InputRequest references. The Controller merges one pending mailbox batch into one receipt-backed `[Yui updates]` user message for an existing ready Operator; the Operator reads the referenced records through the CLI and decides what is worth presenting. A running or unavailable Operator is never started or interrupted: the whole batch remains durable and is retried after native turn completion or a later Controller pass. This path is a user message, not a tool call, and it never inspects or classifies Agent terminal text. Answers may be submitted by the user or Operator. An open request prevents unrelated pending wakes and Task completion or archival. The originating Leader may instead run `yui task input cancel <task-id> <input-id> --reason "..."`; cancellation queues that fixed Leader session to resume.
753
819
 
754
820
  Inspect the result:
755
821
 
@@ -1011,6 +1077,7 @@ yui config completion [bash|zsh|fish]
1011
1077
  yui session enter|record|replace|reconcile
1012
1078
  yui session stop --all
1013
1079
  yui project add|clone|refresh|update|discover|list|show|knowledge
1080
+ yui project reset|replace|retire|delete
1014
1081
  ```
1015
1082
 
1016
1083
  `yui update` stages the newly published package **side by side** — it never
@@ -59,8 +59,7 @@ function durableConfigDomainNode(domain) {
59
59
  const values = CONFIG_KEY_VALUES.filter(({ name }) => keys.includes(name));
60
60
  const options = domain === "workflow"
61
61
  ? [
62
- "--soft-tokens", "--hard-tokens", "--role", "--trigger", "--finding-ledger",
63
- "--delta-recheck", "--delta-recheck-max-lines", "--delta-recheck-max-files"
62
+ "--soft-tokens", "--hard-tokens", "--role", "--trigger", "--finding-ledger"
64
63
  ]
65
64
  : domain === "runtime"
66
65
  ? ["--quiet-after-seconds", "--diagnostic-after-seconds", "--stall-after-seconds"]
@@ -86,8 +85,7 @@ function durableConfigDomainNode(domain) {
86
85
  optionValues: domain === "workflow"
87
86
  ? {
88
87
  "--trigger": ["always", "leader", "final"],
89
- "--finding-ledger": ["shadow", "enforce"],
90
- "--delta-recheck": ["enabled", "disabled"]
88
+ "--finding-ledger": ["shadow", "enforce"]
91
89
  }
92
90
  : {}
93
91
  },
@@ -1260,7 +1258,10 @@ export const ROOT_COMMAND = buildNode({
1260
1258
  {
1261
1259
  name: "project",
1262
1260
  summary: "Manage Projects, stable checkouts, branches, and Yui knowledge.",
1263
- sections: [{ id: "manage", title: "Commands", entries: ["add", "clone", "refresh", "diagnose", "migrate", "update", "discover", "list", "show", "knowledge"] }],
1261
+ sections: [
1262
+ { id: "manage", title: "Commands", entries: ["add", "clone", "refresh", "diagnose", "migrate", "update", "discover", "list", "show", "knowledge"] },
1263
+ { id: "lifecycle", title: "Lifecycle (Operator authority)", entries: ["reset", "replace", "retire", "delete"] }
1264
+ ],
1264
1265
  children: [
1265
1266
  {
1266
1267
  name: "add",
@@ -1291,6 +1292,30 @@ export const ROOT_COMMAND = buildNode({
1291
1292
  usage: "yui project migrate <project> [--preflight]",
1292
1293
  options: ["--preflight"]
1293
1294
  },
1295
+ {
1296
+ name: "reset",
1297
+ summary: "Hard-reset a canonical checkout to its verified remote baseline (Operator authority).",
1298
+ usage: "yui project reset <project> [--discard-local]",
1299
+ options: ["--discard-local"]
1300
+ },
1301
+ {
1302
+ name: "replace",
1303
+ summary: "Re-clone a Home-managed checkout from its remote, preserving Yui refs (Operator authority).",
1304
+ usage: "yui project replace <project> --discard-local",
1305
+ options: ["--discard-local"]
1306
+ },
1307
+ {
1308
+ name: "retire",
1309
+ summary: "Soft-deprecate a Project; record and evidence are retained (Operator authority).",
1310
+ usage: "yui project retire <project> --reason <text>",
1311
+ options: ["--reason"]
1312
+ },
1313
+ {
1314
+ name: "delete",
1315
+ summary: "Remove a retired Project's catalog record and optionally its checkout (Operator authority).",
1316
+ usage: "yui project delete <project> [--checkout] --confirm <project-id>",
1317
+ options: ["--checkout", "--confirm"]
1318
+ },
1294
1319
  {
1295
1320
  name: "update",
1296
1321
  summary: "Update a bound Project's aliases, remote, or branch refs.",
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, resolvedExecutionStageRetryGroup, validateTaskArchiveRequest } from "./commands/taskCommands.js";
40
+ import { dispatchPreparedReviewRound, failPendingReviewRound, 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";
@@ -49,6 +49,7 @@ import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
49
49
  import { createUpdatePorts } from "./cli/updatePorts.js";
50
50
  import { createReleaseWorkflowPorts } from "./release/releaseWorkflowPorts.js";
51
51
  import { acquireHandoverLock, readRuntimeIdentity } from "./release/runtimeRelease.js";
52
+ import { assertCliHomeReleaseFence, describeCliHomeInvocation } from "./release/cliHomeReleaseFence.js";
52
53
  import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
53
54
  import { reconcileTaskRemoteBaselines, verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
54
55
  import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
@@ -68,7 +69,7 @@ import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliE
68
69
  import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
69
70
  import { AGENT_HOST_CONTROL_PROTOCOL, runAgentHost, sendAgentHostAuthorityControl } from "./runtime/agentHost.js";
70
71
  import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
71
- import { FileTaskWorkspacePreparer, ReviewRoundWorkspaceEvidenceError } from "./repository/taskWorkspacePreparer.js";
72
+ import { FileTaskWorkspacePreparer } from "./repository/taskWorkspacePreparer.js";
72
73
  import { inspectStorageSchema } from "./storage/storageSchema.js";
73
74
  import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth, resolveStatusIdentityEnabled } from "./observability/runtimeIdentity.js";
74
75
  import { resolveYuiHome } from "./storage/taskStore.js";
@@ -83,13 +84,14 @@ import { AgentConfigurationCatalogService } from "./executor/agentConfigurationC
83
84
  import { TmuxWebTerminalService } from "./web/tmuxWebTerminal.js";
84
85
  import { listOperatorSessions, operatorSessionRef } from "./operator/operatorSessionHistory.js";
85
86
  import { YUI_VERSION, yuiVersionIdentity } from "./version.js";
87
+ import { SqliteSchemaMigrationError } from "./storage/sqliteSchema.js";
86
88
  import { YUI_CONTROL_PLANE_DESCRIPTOR, YUI_TASK_RUNTIME_DESCRIPTOR, assertCompatibleControlPlanePreflight, assertExactControlPlanePreflight, assertExactTaskRuntimeEnvironment, assertExactTaskRuntimeState, exactControlPlaneDigest, extractExactControlArgument, createExactControlPlaneDescriptor, parseExactControlPlaneDescriptor } from "./runtime/exactControlPlane.js";
87
89
  import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
88
90
  import { builtinAgentDriverRegistry } from "./runtime/builtinAgentDrivers.js";
89
91
  import { createTaskFinalReviewContract, extractTaskFinalReviewRequest } from "./review/taskFinalReviewContract.js";
90
92
  import { prepareTaskFinalReviewContractRebindProof, resolveRecordedTaskFinalReviewContract } from "./review/taskFinalReviewContractRebind.js";
91
93
  import { assessDeltaRecheck } from "./review/deltaRecheck.js";
92
- import { deltaRecheckEnabled } from "./review/reviewConfig.js";
94
+ import { isAcceptedTaskReviewBaseline } from "./review/reviewAcceptance.js";
93
95
  import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
94
96
  import { currentWorkItemExecutionGroup, workItemExecutionGroupById } from "./workItem/workItem.js";
95
97
  const VERSION = YUI_VERSION;
@@ -107,13 +109,47 @@ void main().catch((error) => {
107
109
  process.exitCode = error.exitCode;
108
110
  return;
109
111
  }
110
- const message = error instanceof Error ? error.message : String(error);
112
+ const message = runtimeFailureMessage(error);
111
113
  process.stderr.write(`${jsonOutput
112
114
  ? JSON.stringify({ ok: false, code: "RUNTIME_ERROR", message, details: {} })
113
115
  : `RUNTIME_ERROR: ${message}`}\n`);
114
116
  process.exitCode = 5;
115
117
  });
118
+ function runtimeFailureMessage(error) {
119
+ const message = error instanceof Error ? error.message : String(error);
120
+ if (!(error instanceof SqliteSchemaMigrationError))
121
+ return message;
122
+ try {
123
+ return `${message}\n${describeCliHomeInvocation({
124
+ home: resolveYuiHome(process.env),
125
+ packageRoot: fileURLToPath(new URL("../", import.meta.url)),
126
+ entryPath: fileURLToPath(import.meta.url)
127
+ })}`;
128
+ }
129
+ catch {
130
+ return message;
131
+ }
132
+ }
116
133
  export async function main() {
134
+ const home = resolveYuiHome(process.env);
135
+ const routedForFence = args.length === 0 ? undefined : routeInvocation(args);
136
+ const managedInvocation = process.env.YUI_SESSION_SCOPE === "task"
137
+ || process.env.YUI_SESSION_SCOPE === "global"
138
+ || process.env[YUI_CONTROL_PLANE_DESCRIPTOR] !== undefined
139
+ || process.env[YUI_TASK_RUNTIME_DESCRIPTOR] !== undefined;
140
+ const homeFreeInvocation = args.length === 0
141
+ || (args[0] === "version" && args.length === 1)
142
+ || routedForFence?.kind === "help"
143
+ || routedForFence?.kind === "path-error"
144
+ || routedForFence?.kind === "incomplete";
145
+ if (managedInvocation || !homeFreeInvocation) {
146
+ assertCliHomeReleaseFence({
147
+ home,
148
+ packageRoot: fileURLToPath(new URL("../", import.meta.url)),
149
+ entryPath: fileURLToPath(import.meta.url),
150
+ args
151
+ });
152
+ }
117
153
  const delegatedDriver = explicitReleaseActivationDriver();
118
154
  if (delegatedDriver !== null) {
119
155
  const delegated = spawnSync(process.execPath, [delegatedDriver, ...process.argv.slice(2)], {
@@ -160,7 +196,6 @@ export async function main() {
160
196
  process.exitCode = runUpdateCommand();
161
197
  return;
162
198
  }
163
- const home = resolveYuiHome(process.env);
164
199
  if (args[0] === "release") {
165
200
  const subcommand = args[1];
166
201
  if (subcommand === "install" && args.length === 3) {
@@ -1123,14 +1158,16 @@ export async function main() {
1123
1158
  const laneSnapshotPreflight = executionLaneGitSnapshot === undefined
1124
1159
  ? undefined
1125
1160
  : 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") {
1161
+ // Physical preparation may precede the durable write, but Task status,
1162
+ // workspace identity/cwd, and ManagedWorkspace ownership are adopted by
1163
+ // one transaction. A failed attempt therefore leaves the Task Draft and
1164
+ // owning no writable workspace.
1165
+ let taskWorkspaceActivation;
1166
+ if (resolved[1] === "activate" && resolved.length === 3) {
1130
1167
  const taskId = resolved[2];
1131
1168
  const task = taskId === undefined ? null : store.getTask(taskId);
1132
1169
  if (task !== null && task.status === "draft") {
1133
- await workspacePreparer.prepareTaskWorkspace(task.id);
1170
+ taskWorkspaceActivation = await workspacePreparer.activateTaskWorkspace(task.id);
1134
1171
  }
1135
1172
  }
1136
1173
  const result = runTaskCommand(resolved.slice(1), store, {
@@ -1153,6 +1190,7 @@ export async function main() {
1153
1190
  ? {}
1154
1191
  : { candidateWorkspace: candidateMaterialization.workspace ?? null }),
1155
1192
  ...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
1193
+ ...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
1156
1194
  ...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
1157
1195
  ...(directTaskMainSnapshot === undefined ? {} : { directTaskMainSnapshot }),
1158
1196
  ...(actualTaskReviewCandidate === undefined
@@ -1219,31 +1257,54 @@ export async function main() {
1219
1257
  ...(storedRound?.deltaRecheck === undefined
1220
1258
  || deltaRecheckPreflight === undefined
1221
1259
  ? {}
1222
- : { deltaRecheckDiff: deltaRecheckPreflight.diffByProject })
1260
+ : {
1261
+ deltaRecheckDiff: deltaRecheckPreflight.diffByProject
1262
+ })
1223
1263
  });
1224
1264
  reviewOutput = run === null
1225
1265
  ? `Review ${requestedRound.id} retained pending by Resource Broker\n`
1226
1266
  : `Review queued as ${requestedRound.id} (${run.id})\n`;
1227
1267
  reviewData = {
1268
+ reviewRequest: run === null
1269
+ ? {
1270
+ kind: "busy",
1271
+ reviewerRoleName: requestedRound.reviewerRoleName,
1272
+ phase: "resource-broker",
1273
+ activeReviewRoundId: requestedRound.id,
1274
+ retryable: true,
1275
+ retryAfterSeconds: 5
1276
+ }
1277
+ : {
1278
+ kind: "started",
1279
+ reviewerRoleName: requestedRound.reviewerRoleName,
1280
+ reviewRoundId: requestedRound.id,
1281
+ runId: run.id
1282
+ },
1228
1283
  reviewRound: store.getReviewRound(requestedRound.taskId, requestedRound.id),
1229
1284
  ...(run === null ? {} : { reviewRun: run }),
1230
1285
  workspace
1231
1286
  };
1232
1287
  }
1233
1288
  catch (error) {
1234
- const currentRound = store.getReviewRound(requestedRound.taskId, requestedRound.id);
1235
- if (requestedRound.resumedPendingFinalReview
1236
- && !requestedRound.terminalizedLeaderRun
1237
- && (error instanceof ReviewRoundWorkspaceEvidenceError
1238
- || error instanceof TaskFinalReviewDispatchDriftError)
1239
- && (currentRound?.scope ?? "work-item") === "task") {
1240
- throw error;
1241
- }
1242
1289
  const message = error instanceof Error ? error.message : String(error);
1243
1290
  await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
1244
- const failed = failPendingReviewRound(requestedRound.taskId, requestedRound.id, message, store, { runtime, environment: process.env, yuiHome: home });
1245
- reviewOutput = `Review could not start: ${message}\n`;
1246
- reviewData = { reviewRound: failed };
1291
+ const failed = failPendingReviewRound(requestedRound.taskId, requestedRound.id, message, store, {
1292
+ runtime,
1293
+ environment: process.env,
1294
+ yuiHome: home
1295
+ });
1296
+ reviewOutput = `Review could not start: ${message}\n`
1297
+ + "The failed ReviewRound was retained for Leader routing.\n";
1298
+ reviewData = {
1299
+ reviewRequest: {
1300
+ kind: "unavailable",
1301
+ reviewerRoleName: requestedRound.reviewerRoleName,
1302
+ reviewRoundId: failed.id,
1303
+ reason: message,
1304
+ retryable: true
1305
+ },
1306
+ reviewRound: failed
1307
+ };
1247
1308
  }
1248
1309
  }
1249
1310
  if (resolved[1] === "project" && resolved[2] === "add") {
@@ -1906,6 +1967,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
1906
1967
  if (args[0] !== "task")
1907
1968
  return undefined;
1908
1969
  let taskId;
1970
+ let decisionSupportRead = false;
1909
1971
  if (args[1] === "complete" && args[2] !== undefined) {
1910
1972
  const task = store.getTask(args[2]);
1911
1973
  if (task === null || task.status !== "active" || task.projectBindings.length === 0) {
@@ -1963,9 +2025,21 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
1963
2025
  taskId = reference.taskId;
1964
2026
  }
1965
2027
  }
2028
+ else if ((args[1] === "context" || args[1] === "next-action")
2029
+ && args[2] !== undefined) {
2030
+ taskId = store.getTask(args[2])?.id;
2031
+ decisionSupportRead = true;
2032
+ }
1966
2033
  if (taskId === undefined || store.getTask(taskId)?.status !== "active")
1967
2034
  return undefined;
1968
- return snapshotActualTaskReviewCandidate(taskId, store, preparer);
2035
+ try {
2036
+ return await snapshotActualTaskReviewCandidate(taskId, store, preparer);
2037
+ }
2038
+ catch (error) {
2039
+ if (decisionSupportRead && error instanceof CliError)
2040
+ return undefined;
2041
+ throw error;
2042
+ }
1969
2043
  }
1970
2044
  async function snapshotActualTaskReviewCandidate(taskId, store, preparer) {
1971
2045
  const task = store.getTask(taskId);
@@ -2002,9 +2076,8 @@ async function snapshotActualTaskReviewCandidate(taskId, store, preparer) {
2002
2076
  /**
2003
2077
  * Issue 07: computes the delta-recheck assessment for `task review request
2004
2078
  * --delta-recheck`. Runs only for that exact command; every other command
2005
- * returns undefined. The assessment's deterministic gates (contiguous base,
2006
- * attempt thresholds, evidence-file overlap) fail closed here; the Reviewer
2007
- * still owns the semantic equivalence decision.
2079
+ * returns undefined. Technical evidence boundaries fail closed here; semantic
2080
+ * risk remains a Leader and Project-policy decision.
2008
2081
  */
2009
2082
  async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReviewCandidate) {
2010
2083
  if (args[0] !== "task" || args[1] !== "review" || args[2] !== "request") {
@@ -2018,23 +2091,13 @@ async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReview
2018
2091
  const task = store.getTask(taskId);
2019
2092
  if (task === null || task.status !== "active")
2020
2093
  return undefined;
2021
- const config = store.getReviewConfig();
2022
- if (!deltaRecheckEnabled(config)) {
2023
- throw usageError("Delta-recheck is not enabled for this Project's review policy. "
2024
- + "Set `yui config workflow set review --role <role> --trigger final --delta-recheck enabled` "
2025
- + "or request a full Review.");
2026
- }
2027
- const reviewConfig = config;
2028
2094
  if (actualTaskReviewCandidate === undefined)
2029
2095
  return undefined;
2030
2096
  // The previous Round is the latest completed Task-final Round that accepted
2031
2097
  // a head (a full Review or an equivalent-and-accepted delta). A
2032
2098
  // non-accepted delta cannot be the base for a new delta.
2033
2099
  const previous = [...store.listReviewRounds(task.id)]
2034
- .filter((round) => ((round.scope ?? "work-item") === "task"
2035
- && round.status === "completed"
2036
- && (round.deltaRecheck === undefined
2037
- || round.deltaRecheck.disposition === "equivalent-and-accepted")))
2100
+ .filter((round) => isAcceptedTaskReviewBaseline(store, round))
2038
2101
  .sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }))
2039
2102
  .at(-1);
2040
2103
  if (previous === undefined) {
@@ -2052,11 +2115,10 @@ async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReview
2052
2115
  repositoryPaths,
2053
2116
  previousRound: previous,
2054
2117
  candidate: actualTaskReviewCandidate,
2055
- git: new NodeGitWorkspace(),
2056
- config: reviewConfig
2118
+ git: new NodeGitWorkspace()
2057
2119
  });
2058
2120
  if (assessment.kind === "ineligible") {
2059
- throw usageError(`Delta-recheck is not allowed: ${assessment.reason} Request a full Review instead.`);
2121
+ throw usageError(`Delta-recheck is technically unavailable: ${assessment.reason}`);
2060
2122
  }
2061
2123
  return assessment.preflight;
2062
2124
  }
@@ -2115,13 +2177,13 @@ function reviewRoundFromCommandData(data) {
2115
2177
  const value = round;
2116
2178
  return typeof value.id === "string"
2117
2179
  && typeof value.taskId === "string"
2180
+ && typeof value.reviewerRoleName === "string"
2118
2181
  && typeof value.status === "string"
2119
2182
  ? {
2120
2183
  id: value.id,
2121
2184
  taskId: value.taskId,
2122
- status: value.status,
2123
- resumedPendingFinalReview: data[RESUMED_PENDING_FINAL_REVIEW] === true,
2124
- terminalizedLeaderRun: data[TERMINALIZED_LEADER_BEFORE_FINAL_REVIEW] === true
2185
+ reviewerRoleName: value.reviewerRoleName,
2186
+ status: value.status
2125
2187
  }
2126
2188
  : undefined;
2127
2189
  }