@zq-silk/yui 0.8.9 → 0.9.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/ARCHITECTURE.md +48 -46
- package/README.md +33 -31
- package/dist/cli/commandCatalog.js +7 -7
- package/dist/cli.js +1 -31
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/taskCommands.js +110 -307
- package/dist/commands/taskCompletionGate.js +15 -12
- package/dist/commands/taskContextCommand.js +18 -11
- package/dist/commands/taskNextActionCommand.js +4 -5
- package/dist/commands/taskWorkspaceCommands.js +2 -2
- package/dist/execution/executionGroup.js +0 -3
- package/dist/executor/agentExecutor.js +4 -2
- package/dist/executor/effectiveLaunch.js +33 -3
- package/dist/integration/gitIntegrationService.js +1 -1
- package/dist/lifecycle/exactRunTerminalization.js +12 -8
- package/dist/observability/orchestrationMetrics.js +5 -19
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/taskWorkspaceCoordinator.js +2 -0
- package/dist/repository/taskWorkspacePreparer.js +173 -26
- package/dist/review/reviewRound.js +41 -24
- package/dist/storage/migration/productionRegistry.js +138 -0
- package/dist/storage/sqliteStore.js +1 -1
- package/dist/storage/taskStore.js +26 -27
- package/dist/task/completionReadiness.js +24 -22
- package/dist/task/nextAction.js +47 -61
- package/dist/task/task.js +12 -19
- package/dist/web/assets/client/components.js +3 -1
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/webSnapshot.js +0 -3
- package/i18n/README.zh-CN.md +18 -19
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +47 -43
- package/skills/yui-operator/SKILL.md +24 -33
- package/skills/yui-reviewer/SKILL.md +18 -12
- package/skills/yui-worker/SKILL.md +5 -3
|
@@ -2,14 +2,13 @@ import { usageError } from "../errors/cliError.js";
|
|
|
2
2
|
import { GitIntegrationService } from "../integration/gitIntegrationService.js";
|
|
3
3
|
import { createIntegrationAttempt } from "../integration/integrationAttempt.js";
|
|
4
4
|
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
5
|
-
import { taskDeliveryPath } from "../task/task.js";
|
|
6
5
|
import { publicationExternalKey } from "../task/publicationReference.js";
|
|
7
6
|
import { isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
|
|
8
7
|
import { workspaceProjectEntry } from "../worktree/managedWorkspace.js";
|
|
9
8
|
/**
|
|
10
9
|
* Verify the one supported ancestry waiver before `task complete` mutates any
|
|
11
10
|
* durable state. The explicit Publication must be the current verified merged
|
|
12
|
-
* record, bind the exact physical Task head (and,
|
|
11
|
+
* record, bind the exact physical Task head (and, when WorkItems exist, its
|
|
13
12
|
* completed final Review), and name an ancestry-divergent commit with the
|
|
14
13
|
* exact same Git tree.
|
|
15
14
|
*/
|
|
@@ -30,12 +29,14 @@ export async function verifyTaskCompletionPublishedTree(taskId, publicationId, s
|
|
|
30
29
|
throw usageError(`Publication ${publication.id} must record exact local and remote commits.`);
|
|
31
30
|
}
|
|
32
31
|
const workspace = requireTaskWorkspace(store, task);
|
|
33
|
-
const
|
|
34
|
-
const latestReview =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
const latestRecordedReview = latestTaskFinalReview(store, task.id);
|
|
33
|
+
const latestReview = latestRecordedReview !== undefined
|
|
34
|
+
&& isSemanticReviewRound(latestRecordedReview, store)
|
|
35
|
+
&& latestRecordedReview.taskCandidate !== undefined
|
|
36
|
+
? latestRecordedReview
|
|
37
|
+
: undefined;
|
|
38
|
+
if (latestRecordedReview !== undefined && latestReview === undefined) {
|
|
39
|
+
throw usageError(`Task ${task.id} has an unsettled Task-final Review obligation before accepting a published tree.`);
|
|
39
40
|
}
|
|
40
41
|
const git = options.git ?? new NodeGitWorkspace();
|
|
41
42
|
const actualHeads = new Map();
|
|
@@ -122,7 +123,7 @@ export function assertTaskCompletionPublishedTreeProof(store, task, publicationI
|
|
|
122
123
|
|| publication.remoteCommit !== proof.remoteCommit) {
|
|
123
124
|
throw usageError(`Publication evidence changed before Task completion: ${publication.id}.`);
|
|
124
125
|
}
|
|
125
|
-
if (
|
|
126
|
+
if (proof.reviewRoundId !== undefined) {
|
|
126
127
|
const latestReview = latestTaskFinalReview(store, task.id);
|
|
127
128
|
if (latestReview === undefined
|
|
128
129
|
|| latestReview.id !== proof.reviewRoundId
|
|
@@ -132,8 +133,8 @@ export function assertTaskCompletionPublishedTreeProof(store, task, publicationI
|
|
|
132
133
|
throw usageError(`Task-final Review evidence changed before published-tree completion: ${task.id}.`);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
|
-
else if (
|
|
136
|
-
throw usageError(`
|
|
136
|
+
else if (latestTaskFinalReview(store, task.id) !== undefined) {
|
|
137
|
+
throw usageError(`Published-tree proof omitted the Task-final Review: ${task.id}.`);
|
|
137
138
|
}
|
|
138
139
|
const actualCommit = actualCandidate.projects.find(({ projectId }) => (projectId === proof.projectId))?.commit;
|
|
139
140
|
if (actualCommit !== proof.localCommit) {
|
|
@@ -183,8 +184,10 @@ export async function reconcileTaskRemoteBaselines(taskId, store, home, options
|
|
|
183
184
|
// The delivery contract opts Project-backed Tasks into committed
|
|
184
185
|
// Integration evidence. Metadata-only Tasks retain their existing local
|
|
185
186
|
// completion semantics and have no remote baseline to reconcile here.
|
|
186
|
-
if (task.projectBindings.length === 0
|
|
187
|
+
if (task.projectBindings.length === 0
|
|
188
|
+
|| !store.listIntegrationAttempts(task.id).some(({ status }) => status === "committed")) {
|
|
187
189
|
return [];
|
|
190
|
+
}
|
|
188
191
|
const workspace = requireTaskWorkspace(store, task);
|
|
189
192
|
const git = options.git ?? new NodeGitWorkspace();
|
|
190
193
|
if (git.fetchRemoteHeadIntoWorktree === undefined
|
|
@@ -4,7 +4,6 @@ import { formatTimestamp } from "../output/timePresentation.js";
|
|
|
4
4
|
import { isRoleRunStalled, latestStallProgressAt } from "../scheduler/roleRunStall.js";
|
|
5
5
|
import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
|
|
6
6
|
import { projectNextAction } from "../task/nextAction.js";
|
|
7
|
-
import { taskDeliveryPath } from "../task/task.js";
|
|
8
7
|
import { inspectTaskRoleSessionRecovery } from "./taskRoleRuntimeStatus.js";
|
|
9
8
|
import { summarizeExecutionGroup } from "../execution/executionGroup.js";
|
|
10
9
|
import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
@@ -54,7 +53,6 @@ export function runTaskContextCommand(args, store) {
|
|
|
54
53
|
}
|
|
55
54
|
return {
|
|
56
55
|
task,
|
|
57
|
-
deliveryPath: taskDeliveryPath(task),
|
|
58
56
|
execution,
|
|
59
57
|
reviewConfig: reader.getReviewConfig(),
|
|
60
58
|
brief: reader.getTaskBrief(task.id),
|
|
@@ -141,12 +139,15 @@ export function runTaskContextCommand(args, store) {
|
|
|
141
139
|
...(managedWorkspaces.length === 0
|
|
142
140
|
? [" None."]
|
|
143
141
|
: managedWorkspaces.map((workspace) => (` ${managedWorkspaceLabel(workspace)}: ${workspace.root} (${workspace.entries.filter(({ access }) => access === "write").length} writable / ${workspace.entries.length} Projects)`))),
|
|
144
|
-
`
|
|
145
|
-
`
|
|
146
|
-
? "
|
|
147
|
-
:
|
|
142
|
+
`Type: ${task.type ?? "unspecified"}`,
|
|
143
|
+
`Execution topology: ${workItems.length === 0
|
|
144
|
+
? "Leader-owned Task main"
|
|
145
|
+
: `${workItems.length} independently owned WorkItem(s), integrated on Task main`}`,
|
|
146
|
+
`Completion evidence: ${task.projectBindings.length === 0
|
|
147
|
+
? "no Project evidence required"
|
|
148
|
+
: workItems.length === 0
|
|
148
149
|
? "clean committed Task main required"
|
|
149
|
-
: "
|
|
150
|
+
: "each delivered WorkItem requires a ChangeSet and committed Integration"}`,
|
|
150
151
|
`Global review: ${reviewConfig === null
|
|
151
152
|
? "disabled"
|
|
152
153
|
: `${reviewConfig.roleName} (${reviewConfig.trigger})`}`,
|
|
@@ -259,10 +260,13 @@ export function runTaskContextCommand(args, store) {
|
|
|
259
260
|
? []
|
|
260
261
|
: [` Summary: ${compactText(latestRun.summary)}`])
|
|
261
262
|
]),
|
|
262
|
-
...
|
|
263
|
+
...renderReviewRounds(reviewRounds.filter((round) => round.workItemId === item.id))
|
|
263
264
|
];
|
|
264
265
|
})),
|
|
265
266
|
"",
|
|
267
|
+
"Task-final reviews:",
|
|
268
|
+
...renderReviewRounds(reviewRounds.filter((round) => (round.scope ?? "work-item") === "task")),
|
|
269
|
+
"",
|
|
266
270
|
...recentSection("AgentRuns", agentRuns, (run) => [
|
|
267
271
|
` ${run.id} [${run.status}/${run.purpose}] ${run.roleName} via ${run.effective.agentId}/${run.effective.adapterId}`,
|
|
268
272
|
` Effective: r${run.effective.sourceDesiredRevision}; Profile intent: ${run.effective.profileAccess}; permission: ${run.effective.permission.strategy}; model: ${run.effective.model ?? "default"}; effort: ${run.effective.effort ?? "default"}`,
|
|
@@ -358,16 +362,19 @@ function latestStallKind(events, runId) {
|
|
|
358
362
|
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
|
|
359
363
|
return event?.payload.kind ?? "workflow-not-progressing";
|
|
360
364
|
}
|
|
361
|
-
function
|
|
365
|
+
function renderReviewRounds(rounds) {
|
|
362
366
|
const latest = rounds.at(-1);
|
|
363
367
|
if (latest === undefined)
|
|
364
368
|
return [" ReviewRounds: none."];
|
|
369
|
+
const target = (latest.scope ?? "work-item") === "task"
|
|
370
|
+
? "frozen Task candidate"
|
|
371
|
+
: `Candidate ${latest.candidateId ?? "unavailable"}`;
|
|
365
372
|
return [
|
|
366
|
-
` ReviewRounds: ${rounds.length}; latest ${latest.id} [${latest.status}] ${latest.scope === "task" ? "Task-final" : "WorkItem"} for ${
|
|
373
|
+
` ReviewRounds: ${rounds.length}; latest ${latest.id} [${latest.status}] ${latest.scope === "task" ? "Task-final" : "WorkItem"} for ${target} via ${latest.reviewerRoleName} (${latest.requestedBy})`,
|
|
367
374
|
` Review base: ${latest.reviewBaseCommit}`,
|
|
368
375
|
...(latest.scope === "task"
|
|
369
376
|
? [
|
|
370
|
-
` Frozen
|
|
377
|
+
` Frozen Task heads: ${latest.taskCandidate?.projects
|
|
371
378
|
.map(({ projectId, commit }) => `${projectId}@${commit}`).join(", ") ?? "unavailable"}`,
|
|
372
379
|
` Task-final contract: ${latest.taskFinalReviewContract?.digest ?? "global policy"}`,
|
|
373
380
|
...(latest.deltaRecheck === undefined
|
|
@@ -2,7 +2,6 @@ import { taskNotFound, usageError } from "../errors/cliError.js";
|
|
|
2
2
|
import { projectNextAction } from "../task/nextAction.js";
|
|
3
3
|
import { projectCompletionReadiness } from "../task/completionReadiness.js";
|
|
4
4
|
import { extractReviewFindings, planRepairWave } from "../task/repairWave.js";
|
|
5
|
-
import { taskDeliveryPath } from "../task/task.js";
|
|
6
5
|
import { projectTaskOrchestration } from "../observability/orchestrationMetrics.js";
|
|
7
6
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
8
7
|
/**
|
|
@@ -75,7 +74,7 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
75
74
|
managedWorkspaces: reader.listManagedWorkspaces(taskId)
|
|
76
75
|
});
|
|
77
76
|
return {
|
|
78
|
-
|
|
77
|
+
taskType: facts.task.type ?? null,
|
|
79
78
|
action,
|
|
80
79
|
repairWave,
|
|
81
80
|
completionReadiness,
|
|
@@ -92,7 +91,7 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
92
91
|
}
|
|
93
92
|
return {
|
|
94
93
|
kind: "output",
|
|
95
|
-
output: renderNextAction(data.action, data.
|
|
94
|
+
output: renderNextAction(data.action, data.taskType, data.repairWave, data.completionReadiness, data.knowledgeProposals, data.orchestration.advisories),
|
|
96
95
|
data
|
|
97
96
|
};
|
|
98
97
|
}
|
|
@@ -110,10 +109,10 @@ function repairWaveFor(action, facts) {
|
|
|
110
109
|
return null;
|
|
111
110
|
return planRepairWave(round.id, findings);
|
|
112
111
|
}
|
|
113
|
-
function renderNextAction(action,
|
|
112
|
+
function renderNextAction(action, taskType, repairWave, completionReadiness, knowledgeProposals, orchestrationAdvisories) {
|
|
114
113
|
const lines = [
|
|
115
114
|
`Task: ${action.taskId}`,
|
|
116
|
-
`
|
|
115
|
+
`Type: ${taskType ?? "unspecified"}`,
|
|
117
116
|
`Next action: ${action.kind}`,
|
|
118
117
|
`Reason: ${action.reason}`,
|
|
119
118
|
...(action.refs.length === 0
|
|
@@ -119,8 +119,8 @@ function replaceTaskCommand(args, store, options) {
|
|
|
119
119
|
createArgs.push("--project", binding.projectId);
|
|
120
120
|
createArgs.push("--base", `${binding.projectId}=${binding.baseRef}`);
|
|
121
121
|
}
|
|
122
|
-
if (old.
|
|
123
|
-
createArgs.push("--
|
|
122
|
+
if (old.type !== undefined)
|
|
123
|
+
createArgs.push("--type", old.type);
|
|
124
124
|
const created = runTaskCommand(createArgs, store);
|
|
125
125
|
if (created.kind !== "output") {
|
|
126
126
|
throw new Error(`Task replacement could not be created for ${old.id}.`);
|
|
@@ -435,9 +435,6 @@ export function validateExecutionTarget(target, taskId) {
|
|
|
435
435
|
if (target.kind === "work-item" && target.workItemId === undefined) {
|
|
436
436
|
throw new Error("WorkItem ExecutionTarget requires a Work Item id.");
|
|
437
437
|
}
|
|
438
|
-
if (target.kind === "task-final-review" && target.candidateId === undefined) {
|
|
439
|
-
throw new Error("Task-final ExecutionTarget requires a Candidate id.");
|
|
440
|
-
}
|
|
441
438
|
if (target.workItemId !== undefined)
|
|
442
439
|
requireIdentity(target.workItemId, "Work Item id");
|
|
443
440
|
if (target.candidateId !== undefined)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { hasRecentTurnId, rememberRecentTurnId, validatePendingTurnCompletion, validateRecentTurnIds } from "./turnCompletion.js";
|
|
3
|
-
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
|
|
3
|
+
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, effectiveLaunchSnapshotsCompatibleForTaskReview, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
|
|
4
4
|
import { rebindProviderRuntimeRun, validateProviderRuntimeBinding } from "../runtime/providerRuntimeIdentity.js";
|
|
5
5
|
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
6
6
|
export function createRoleSessionSet(owner, activeAgentId, now) {
|
|
@@ -67,7 +67,9 @@ export function recordRoleAgentSession(set, input, now) {
|
|
|
67
67
|
throw new Error(`Role Agent session effective identity is inconsistent: ${agentId}.`);
|
|
68
68
|
}
|
|
69
69
|
if (existing !== undefined && existing.nativeSessionId === nativeSessionId
|
|
70
|
-
&& !effectiveLaunchSnapshotsCompatible(existing.effective, effective)
|
|
70
|
+
&& !effectiveLaunchSnapshotsCompatible(existing.effective, effective)
|
|
71
|
+
&& !(set.owner.scope === "task"
|
|
72
|
+
&& effectiveLaunchSnapshotsCompatibleForTaskReview(existing.effective, effective))) {
|
|
71
73
|
throw new Error(`Role Agent session effective launch cannot change: ${agentId}.`);
|
|
72
74
|
}
|
|
73
75
|
if (existing !== undefined && existing.nativeSessionId !== nativeSessionId
|
|
@@ -84,7 +84,9 @@ export function effectiveLaunchSnapshotsCompatible(existing, desired) {
|
|
|
84
84
|
* configuration with which it started. A later Run may resume that exact
|
|
85
85
|
* Session only when the current durable Task workspace proves that every
|
|
86
86
|
* non-commit workspace identity and every other launch field is unchanged.
|
|
87
|
-
*
|
|
87
|
+
* A Task-final Reviewer workspace is also mutable between semantic Rounds: it
|
|
88
|
+
* keeps one Role-owned physical identity while its frozen commits and Round
|
|
89
|
+
* identity advance. WorkItem and ExecutionLane workspaces remain strict.
|
|
88
90
|
*/
|
|
89
91
|
export function effectiveLaunchSnapshotsCompatibleForTaskMain(existing, desired, workspace) {
|
|
90
92
|
if (effectiveLaunchSnapshotsCompatible(existing, desired))
|
|
@@ -94,16 +96,34 @@ export function effectiveLaunchSnapshotsCompatibleForTaskMain(existing, desired,
|
|
|
94
96
|
if (workspace === null || workspace === undefined)
|
|
95
97
|
return false;
|
|
96
98
|
validateManagedWorkspace(workspace);
|
|
97
|
-
if (workspace.owner.type !== "task")
|
|
98
|
-
return false;
|
|
99
99
|
const durableWorkspace = {
|
|
100
100
|
root: workspace.root,
|
|
101
101
|
entries: workspace.entries.map((entry) => ({ ...entry }))
|
|
102
102
|
};
|
|
103
103
|
if (!isDeepStrictEqual(desired.workspace, durableWorkspace))
|
|
104
104
|
return false;
|
|
105
|
+
if (workspace.owner.type === "review-round") {
|
|
106
|
+
return effectiveLaunchSnapshotsCompatibleForTaskReview(existing, desired);
|
|
107
|
+
}
|
|
108
|
+
if (workspace.owner.type !== "task")
|
|
109
|
+
return false;
|
|
105
110
|
return isDeepStrictEqual(taskMainCompatibleSnapshot(existing), taskMainCompatibleSnapshot(desired));
|
|
106
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Same Task Reviewer Role, same stable physical workspace and launch policy,
|
|
114
|
+
* but a new semantic ReviewRound and frozen head. The native Session keeps its
|
|
115
|
+
* conversation; each AgentRun still records the new exact Round snapshot.
|
|
116
|
+
*/
|
|
117
|
+
export function effectiveLaunchSnapshotsCompatibleForTaskReview(existing, desired) {
|
|
118
|
+
validateEffectiveLaunchSnapshot(existing);
|
|
119
|
+
validateEffectiveLaunchSnapshot(desired);
|
|
120
|
+
if (existing.reviewRoundId === undefined
|
|
121
|
+
|| existing.reviewBaseCommit === undefined
|
|
122
|
+
|| desired.reviewRoundId === undefined
|
|
123
|
+
|| desired.reviewBaseCommit === undefined)
|
|
124
|
+
return false;
|
|
125
|
+
return isDeepStrictEqual(taskReviewCompatibleSnapshot(existing), taskReviewCompatibleSnapshot(desired));
|
|
126
|
+
}
|
|
107
127
|
/** Preserves a fixed Session's launch configuration while freezing fresh Task-main Git facts. */
|
|
108
128
|
export function effectiveLaunchWithTaskMainWorkspace(existing, workspace) {
|
|
109
129
|
validateEffectiveLaunchSnapshot(existing);
|
|
@@ -129,6 +149,16 @@ function taskMainCompatibleSnapshot(snapshot) {
|
|
|
129
149
|
}
|
|
130
150
|
};
|
|
131
151
|
}
|
|
152
|
+
function taskReviewCompatibleSnapshot(snapshot) {
|
|
153
|
+
const { sourceDesiredRevision: _sourceDesiredRevision, reviewRoundId: _reviewRoundId, reviewBaseCommit: _reviewBaseCommit, workspace, ...launch } = snapshot;
|
|
154
|
+
return {
|
|
155
|
+
...launch,
|
|
156
|
+
workspace: {
|
|
157
|
+
root: workspace.root,
|
|
158
|
+
entries: workspace.entries.map(({ baseCommit: _baseCommit, baseRef: _baseRef, ...entry }) => entry)
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
132
162
|
export function validateEffectiveLaunchSnapshot(snapshot) {
|
|
133
163
|
if (snapshot.schemaVersion !== 2) {
|
|
134
164
|
throw new Error("Effective launch snapshot must use schemaVersion 2.");
|
|
@@ -675,7 +675,7 @@ async function integrationCommitPlan(store, taskId, repositoryPath, changeSetIds
|
|
|
675
675
|
`${changeSet.baseCommit}..${changeSet.headCommit}`
|
|
676
676
|
])).trim().split("\n").filter(Boolean);
|
|
677
677
|
for (const commit of commits) {
|
|
678
|
-
// A
|
|
678
|
+
// A Task-main recovery may capture commits after they are already
|
|
679
679
|
// present on the exact target. Treat those commits as applied rather
|
|
680
680
|
// than attempting an empty cherry-pick; the later checks and CAS still
|
|
681
681
|
// fence the committed Integration to expectedHead.
|
|
@@ -83,19 +83,23 @@ export function validateExactRunReviewRound(store, run, options = {}) {
|
|
|
83
83
|
return { disposition: "obsolete", round, reason: "review-lane-workspace-lineage-mismatch" };
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
86
|
+
const task = store.getTask(run.taskId);
|
|
87
|
+
const taskScope = (round.scope ?? "work-item") === "task";
|
|
88
|
+
const item = taskScope || round.workItemId === undefined
|
|
89
|
+
? null
|
|
90
|
+
: store.getWorkItem(run.taskId, round.workItemId);
|
|
91
|
+
if (!taskScope && item === null) {
|
|
88
92
|
return { disposition: "obsolete", round, reason: "review-work-item-missing" };
|
|
89
93
|
}
|
|
90
|
-
const candidate =
|
|
91
|
-
|
|
94
|
+
const candidate = taskScope
|
|
95
|
+
? undefined
|
|
96
|
+
: item.candidates.find(({ id }) => id === round.candidateId);
|
|
97
|
+
if (!taskScope && candidate === undefined) {
|
|
92
98
|
return { disposition: "obsolete", round, reason: "review-candidate-missing" };
|
|
93
99
|
}
|
|
94
|
-
const task = store.getTask(run.taskId);
|
|
95
|
-
const taskScope = (round.scope ?? "work-item") === "task";
|
|
96
100
|
const frozenProjects = taskScope
|
|
97
101
|
? round.taskCandidate?.projects
|
|
98
|
-
: candidate
|
|
102
|
+
: candidate?.gitSnapshot?.projects;
|
|
99
103
|
if (taskScope) {
|
|
100
104
|
if (task === null || round.taskCandidate === undefined) {
|
|
101
105
|
return { disposition: "obsolete", round, reason: "review-task-candidate-missing" };
|
|
@@ -107,7 +111,7 @@ export function validateExactRunReviewRound(store, run, options = {}) {
|
|
|
107
111
|
return { disposition: "obsolete", round, reason: "review-frozen-project-scope-drift" };
|
|
108
112
|
}
|
|
109
113
|
}
|
|
110
|
-
else if (candidate
|
|
114
|
+
else if (candidate?.gitSnapshot !== undefined
|
|
111
115
|
&& candidate.gitSnapshot.reviewBaseCommit !== round.reviewBaseCommit) {
|
|
112
116
|
return { disposition: "obsolete", round, reason: "review-candidate-snapshot-drift" };
|
|
113
117
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { classifyReviewRoundOutcome } from "../review/reviewOutcomeClassifier.js";
|
|
2
2
|
import { projectFirstProgressStopLoss } from "../runtime/firstProgressStopLoss.js";
|
|
3
|
-
import { taskDeliveryPath } from "../task/task.js";
|
|
4
3
|
/** One Task's orchestration cost and advisory projection, with no writes. */
|
|
5
4
|
export function projectTaskOrchestration(facts) {
|
|
6
5
|
const evidence = {
|
|
@@ -53,7 +52,7 @@ export function projectTaskOrchestration(facts) {
|
|
|
53
52
|
.at(-1);
|
|
54
53
|
return Object.freeze({
|
|
55
54
|
taskId: facts.task.id,
|
|
56
|
-
|
|
55
|
+
taskType: facts.task.type ?? null,
|
|
57
56
|
timeToFirstProjectCommitMs: firstCommitAt === undefined
|
|
58
57
|
? null
|
|
59
58
|
: Math.max(0, Date.parse(firstCommitAt) - Date.parse(facts.task.createdAt)),
|
|
@@ -89,24 +88,11 @@ export function projectTaskOrchestration(facts) {
|
|
|
89
88
|
}
|
|
90
89
|
function projectAdvisories(facts, classifications, fullRounds, repeatedIdentities, stopLoss) {
|
|
91
90
|
const result = [];
|
|
92
|
-
if (
|
|
93
|
-
&& (facts.workItems.length > 0 || facts.reviewRounds.length > 0 || facts.integrations.length > 0)) {
|
|
91
|
+
if (facts.task.type === "bugfix" && facts.workItems.length > 0) {
|
|
94
92
|
result.push({
|
|
95
|
-
code: "
|
|
96
|
-
reason:
|
|
97
|
-
refs:
|
|
98
|
-
...facts.workItems.map(({ id }) => `work-item:${id}`),
|
|
99
|
-
...facts.reviewRounds.map(({ id }) => `review-round:${id}`),
|
|
100
|
-
...facts.integrations.map(({ id }) => `integration-attempt:${id}`)
|
|
101
|
-
]
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
const initial = facts.workItems.filter(({ dependsOn }) => dependsOn.length === 0);
|
|
105
|
-
if (taskDeliveryPath(facts.task) === "integrated" && initial.length > 1) {
|
|
106
|
-
result.push({
|
|
107
|
-
code: "guarded-workitem-fanout",
|
|
108
|
-
reason: `${initial.length} initial WorkItems were created for an integrated Task; start with one bounded fix unless independence is explicit.`,
|
|
109
|
-
refs: initial.map(({ id }) => `work-item:${id}`)
|
|
93
|
+
code: "bugfix-workitem-overhead",
|
|
94
|
+
reason: `Bugfix ${facts.task.id} created ${facts.workItems.length} WorkItem(s); bugfixes are Leader-owned, so reclassify expanding scope as a feature before delegating independent delivery units.`,
|
|
95
|
+
refs: facts.workItems.map(({ id }) => `work-item:${id}`)
|
|
110
96
|
});
|
|
111
97
|
}
|
|
112
98
|
const repairItems = facts.workItems.filter((item) => (item.acceptance.some((line) => line.startsWith("review-finding:"))));
|
|
@@ -75,7 +75,7 @@ export function builtinAgentProfileInputs() {
|
|
|
75
75
|
{
|
|
76
76
|
id: "reviewer",
|
|
77
77
|
description: "Review one candidate against the user's core outcome, supported behavior, and direct evidence.",
|
|
78
|
-
instructions: "Start from user intent and acceptance criteria. Inspect the complete relevant change and report only reachable, material, actionable problems with direct evidence. Separate defects from verification gaps, and prefer the smallest sufficient correction. Follow the bound Project's Policy and Knowledge for build, test, migration, release, and review expectations; do not import rules from another Project or Task. For normal software delivery, review the frozen
|
|
78
|
+
instructions: "Start from user intent and acceptance criteria. Inspect the complete relevant change and report only reachable, material, actionable problems with direct evidence. Separate defects from verification gaps, and prefer the smallest sufficient correction. Follow the bound Project's Policy and Knowledge for build, test, migration, release, and review expectations; do not import rules from another Project or Task. For normal software delivery, review the frozen Task result as one final ReviewRound rather than inventing a per-WorkItem protocol unless the Project Policy explicitly requires one. A Task-final Round has no synthetic WorkItem anchor, and a compatible Reviewer Session may continue across changed-head Rounds without reusing an earlier verdict. Do not turn speculative or extreme edge cases into new state, retries, fallbacks, or protocol. In a ReviewRound-owned workspace you may edit source or tests, run local checks, and optionally commit diagnostic evidence. Never push, integrate, mutate Task state, touch another workspace or stable checkout, or write the real Yui control-plane home. Report complete findings, checks actually run, uncertainty, and bounded next actions through the exact Review yield; Yui preserves the full free-form report. Expose evidence and options to the Leader, who decides.",
|
|
79
79
|
defaultAccess: "write"
|
|
80
80
|
}
|
|
81
81
|
];
|
|
@@ -123,6 +123,8 @@ export class TaskWorkspaceCoordinator {
|
|
|
123
123
|
if (round.status !== "completed" && round.status !== "failed") {
|
|
124
124
|
throw new Error(`ReviewRound must be terminal before cleanup: ${round.id}.`);
|
|
125
125
|
}
|
|
126
|
+
if (round.workspaceDisposition?.kind === "reassigned")
|
|
127
|
+
return "missing";
|
|
126
128
|
// Hold the per-Project maintenance fence so a concurrent migrate/rebuild/
|
|
127
129
|
// archive cannot interleave with worktree removal.
|
|
128
130
|
const workspace = this.store.getReviewRoundWorkspace(taskId, reviewRoundId);
|