@zq-silk/yui 0.14.1 → 0.15.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 +91 -47
- package/README.md +275 -145
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateCommand.js +17 -9
- package/dist/cli/updateOrchestrator.js +87 -19
- package/dist/cli/updatePorts.js +79 -13
- package/dist/cli/upgradeCommand.js +105 -23
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/controllerCommands.js +1 -1
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +6 -5
- package/dist/doctor/doctor.js +37 -14
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +16 -82
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/observability/runtimeIdentity.js +48 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/release/runtimeRelease.js +9 -1
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +22 -296
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/currentTaskStore.js +6 -4
- package/dist/storage/sqliteSchema.js +135 -80
- package/dist/storage/sqliteStore.js +69 -52
- package/dist/storage/storageSchema.js +92 -230
- package/dist/storage/storageVersions.js +12 -13
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +276 -85
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/version.js +3 -3
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +9 -9
- package/i18n/README.zh-CN.md +189 -74
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +22 -0
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/storage/upgrade/recordVersions.js +0 -82
- package/dist/task/repairWave.js +0 -150
|
@@ -1,550 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { createTaskEvent } from "../event/taskEvent.js";
|
|
3
|
-
import { isSemanticReviewRound } from "./reviewOutcomeClassifier.js";
|
|
4
|
-
import { createReviewFinding, disposeReviewFinding, isReviewFindingBlocking, normalizeReviewFindingSeverity, redetectReviewFinding, resolveReviewFinding, reviewFindingStableKey, touchReviewFinding, validateReviewFinding } from "./reviewFinding.js";
|
|
5
|
-
/** Resolves the durable feature flag; absent config defaults to shadow. */
|
|
6
|
-
export function reviewFindingLedgerMode(config) {
|
|
7
|
-
const mode = config.review?.findingLedger;
|
|
8
|
-
return mode === "enforce" ? "enforce" : "shadow";
|
|
9
|
-
}
|
|
10
|
-
export const REVIEW_FINDINGS_RECONCILE_FAILED_EVENT = "review.findings-reconcile-failed";
|
|
11
|
-
/**
|
|
12
|
-
* Extracts reported findings from a completed Round. The reviewer's JSON
|
|
13
|
-
* report is the authoritative source; panel Rounds fall back to the findings
|
|
14
|
-
* attached to their ExecutionGroup lane results. A malformed or free-text
|
|
15
|
-
* report yields no findings (the raw report stays authoritative evidence).
|
|
16
|
-
*/
|
|
17
|
-
export function extractReportedFindings(round) {
|
|
18
|
-
const fromReport = extractFindingsFromReportJson(round.report ?? "");
|
|
19
|
-
if (fromReport.length > 0)
|
|
20
|
-
return fromReport;
|
|
21
|
-
return extractFindingsFromLanes(round);
|
|
22
|
-
}
|
|
23
|
-
function extractFindingsFromReportJson(report) {
|
|
24
|
-
let parsed;
|
|
25
|
-
try {
|
|
26
|
-
parsed = JSON.parse(report);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
32
|
-
return [];
|
|
33
|
-
const record = parsed;
|
|
34
|
-
if (!Array.isArray(record.findings))
|
|
35
|
-
return [];
|
|
36
|
-
return record.findings.flatMap((entry) => {
|
|
37
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
38
|
-
return [];
|
|
39
|
-
const finding = entry;
|
|
40
|
-
const summary = typeof finding.summary === "string" && finding.summary.trim().length > 0
|
|
41
|
-
? finding.summary.trim()
|
|
42
|
-
: typeof finding.title === "string" && finding.title.trim().length > 0
|
|
43
|
-
? finding.title.trim()
|
|
44
|
-
: null;
|
|
45
|
-
if (summary === null)
|
|
46
|
-
return [];
|
|
47
|
-
let severity;
|
|
48
|
-
try {
|
|
49
|
-
severity = normalizeReviewFindingSeverity(finding.severity);
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
const status = finding.status === "resolved" ? "resolved" : "open";
|
|
55
|
-
const title = typeof finding.title === "string" && finding.title.trim().length > 0
|
|
56
|
-
? finding.title.trim()
|
|
57
|
-
: summary;
|
|
58
|
-
const invariant = typeof finding.invariant === "string" && finding.invariant.trim().length > 0
|
|
59
|
-
? finding.invariant.trim()
|
|
60
|
-
: typeof finding.category === "string" && finding.category.trim().length > 0
|
|
61
|
-
? finding.category.trim()
|
|
62
|
-
: "review-finding";
|
|
63
|
-
return [{
|
|
64
|
-
...(typeof finding.id === "string" && finding.id.trim().length > 0
|
|
65
|
-
? { sourceId: finding.id.trim() }
|
|
66
|
-
: {}),
|
|
67
|
-
severity,
|
|
68
|
-
status,
|
|
69
|
-
invariant,
|
|
70
|
-
title,
|
|
71
|
-
affectedPaths: stringList(finding.paths ?? finding.affectedPaths),
|
|
72
|
-
affectedSymbols: stringList(finding.symbols ?? finding.affectedSymbols),
|
|
73
|
-
evidence: stringList(finding.evidence)
|
|
74
|
-
}];
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
function extractFindingsFromLanes(round) {
|
|
78
|
-
const lanes = round.executionGroup?.lanes ?? [];
|
|
79
|
-
return lanes.flatMap((lane) => {
|
|
80
|
-
const findings = lane.result?.findings ?? [];
|
|
81
|
-
return findings.flatMap((finding) => {
|
|
82
|
-
let severity;
|
|
83
|
-
try {
|
|
84
|
-
severity = normalizeReviewFindingSeverity(finding.severity);
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
return [];
|
|
88
|
-
}
|
|
89
|
-
return [{
|
|
90
|
-
sourceId: finding.id,
|
|
91
|
-
severity,
|
|
92
|
-
status: finding.status === "resolved" ? "resolved" : "open",
|
|
93
|
-
invariant: "review-finding",
|
|
94
|
-
title: finding.summary,
|
|
95
|
-
affectedPaths: [],
|
|
96
|
-
affectedSymbols: [],
|
|
97
|
-
evidence: []
|
|
98
|
-
}];
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
function stringList(value) {
|
|
103
|
-
if (!Array.isArray(value))
|
|
104
|
-
return [];
|
|
105
|
-
return value
|
|
106
|
-
.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
107
|
-
.map((entry) => entry.trim());
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Reconciles one completed Round's findings into the Task ledger. Stable-key
|
|
111
|
-
* matches update the existing record; ambiguous matches (multiple records with
|
|
112
|
-
* the same key) create a `mergeRequired` record instead of silently merging.
|
|
113
|
-
* Infra (failed) Rounds are skipped without touching the ledger.
|
|
114
|
-
*/
|
|
115
|
-
export function reconcileReviewFindings(store, taskId, roundId, now) {
|
|
116
|
-
const round = store.getReviewRound(taskId, roundId);
|
|
117
|
-
if (round === null) {
|
|
118
|
-
return { roundId, skipped: true, reason: "ReviewRound not found.", created: [], updated: [], conflicts: [] };
|
|
119
|
-
}
|
|
120
|
-
if (!isSemanticReviewRound(round, store)) {
|
|
121
|
-
return {
|
|
122
|
-
roundId,
|
|
123
|
-
skipped: true,
|
|
124
|
-
reason: "ReviewRound is non-semantic or ambiguous, not a proven semantic report.",
|
|
125
|
-
created: [],
|
|
126
|
-
updated: [],
|
|
127
|
-
conflicts: []
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
const extracted = extractReportedFindings(round);
|
|
131
|
-
if (extracted.length === 0) {
|
|
132
|
-
return { roundId, skipped: true, reason: "ReviewRound report carries no findings.", created: [], updated: [], conflicts: [] };
|
|
133
|
-
}
|
|
134
|
-
const ledger = store.listReviewFindings(taskId);
|
|
135
|
-
const created = [];
|
|
136
|
-
const updated = [];
|
|
137
|
-
const conflicts = [];
|
|
138
|
-
for (const entry of extracted) {
|
|
139
|
-
const stableKey = reviewFindingStableKey({
|
|
140
|
-
invariant: entry.invariant,
|
|
141
|
-
primaryPath: entry.affectedPaths[0],
|
|
142
|
-
primarySymbol: entry.affectedSymbols[0],
|
|
143
|
-
title: entry.title
|
|
144
|
-
});
|
|
145
|
-
const matches = ledger.filter((finding) => finding.stableKey === stableKey);
|
|
146
|
-
if (matches.length === 1) {
|
|
147
|
-
const existing = matches[0];
|
|
148
|
-
const next = entry.status === "resolved"
|
|
149
|
-
? resolveReviewFinding(existing, {
|
|
150
|
-
reviewRoundId: round.id,
|
|
151
|
-
evidence: entry.evidence,
|
|
152
|
-
now
|
|
153
|
-
})
|
|
154
|
-
: existing.disposition === "accepted-risk"
|
|
155
|
-
|| existing.disposition === "not-actionable"
|
|
156
|
-
|| existing.disposition === "superseded"
|
|
157
|
-
? touchReviewFinding(existing, {
|
|
158
|
-
reviewRoundId: round.id,
|
|
159
|
-
evidence: entry.evidence,
|
|
160
|
-
now
|
|
161
|
-
})
|
|
162
|
-
: redetectReviewFinding(existing, {
|
|
163
|
-
reviewRoundId: round.id,
|
|
164
|
-
evidence: entry.evidence,
|
|
165
|
-
now
|
|
166
|
-
});
|
|
167
|
-
if (next !== existing) {
|
|
168
|
-
store.saveReviewFinding(taskId, next);
|
|
169
|
-
updated.push(next);
|
|
170
|
-
}
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
if (matches.length > 1) {
|
|
174
|
-
// Stable-key collision: keep every evidence trail and require an explicit
|
|
175
|
-
// Leader merge instead of silently overwriting either record.
|
|
176
|
-
const existingConflict = matches.find((finding) => finding.mergeRequired === true);
|
|
177
|
-
if (existingConflict !== undefined) {
|
|
178
|
-
// A merge-required record already exists for this stable key; refresh
|
|
179
|
-
// its evidence and last-Round pointer instead of creating unbounded
|
|
180
|
-
// duplicate conflict records.
|
|
181
|
-
const refreshed = touchReviewFinding(existingConflict, {
|
|
182
|
-
reviewRoundId: round.id,
|
|
183
|
-
evidence: entry.evidence,
|
|
184
|
-
now
|
|
185
|
-
});
|
|
186
|
-
if (refreshed !== existingConflict) {
|
|
187
|
-
store.saveReviewFinding(taskId, refreshed);
|
|
188
|
-
updated.push(refreshed);
|
|
189
|
-
}
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
const conflict = createReviewFinding(store.nextReviewFindingId(taskId), taskId, {
|
|
193
|
-
...(entry.sourceId === undefined ? {} : { sourceId: entry.sourceId }),
|
|
194
|
-
stableKey,
|
|
195
|
-
severity: entry.severity,
|
|
196
|
-
invariant: entry.invariant,
|
|
197
|
-
title: entry.title,
|
|
198
|
-
affectedPaths: entry.affectedPaths,
|
|
199
|
-
affectedSymbols: entry.affectedSymbols,
|
|
200
|
-
evidence: entry.evidence,
|
|
201
|
-
reviewRoundId: round.id
|
|
202
|
-
}, now);
|
|
203
|
-
const flagged = validateReviewFinding({ ...conflict, mergeRequired: true });
|
|
204
|
-
store.saveReviewFinding(taskId, flagged);
|
|
205
|
-
conflicts.push(flagged);
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
const createdFinding = createReviewFinding(store.nextReviewFindingId(taskId), taskId, {
|
|
209
|
-
...(entry.sourceId === undefined ? {} : { sourceId: entry.sourceId }),
|
|
210
|
-
stableKey,
|
|
211
|
-
severity: entry.severity,
|
|
212
|
-
invariant: entry.invariant,
|
|
213
|
-
title: entry.title,
|
|
214
|
-
affectedPaths: entry.affectedPaths,
|
|
215
|
-
affectedSymbols: entry.affectedSymbols,
|
|
216
|
-
evidence: entry.evidence,
|
|
217
|
-
reviewRoundId: round.id
|
|
218
|
-
}, now);
|
|
219
|
-
store.saveReviewFinding(taskId, createdFinding);
|
|
220
|
-
created.push(createdFinding);
|
|
221
|
-
}
|
|
222
|
-
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "review.findings-reconciled", {
|
|
223
|
-
reviewRoundId: round.id,
|
|
224
|
-
created: String(created.length),
|
|
225
|
-
updated: String(updated.length),
|
|
226
|
-
conflicts: String(conflicts.length)
|
|
227
|
-
}, now));
|
|
228
|
-
return { roundId, skipped: false, created, updated, conflicts };
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Reconciles a delivered Review without making ledger availability a
|
|
232
|
-
* precondition for preserving the reviewer's free-form report. A ledger
|
|
233
|
-
* read/write failure is recorded as a Task event; `enforce` completion fails
|
|
234
|
-
* closed until the Leader recovers the ledger.
|
|
235
|
-
*/
|
|
236
|
-
export function reconcileReviewFindingsAfterReview(store, taskId, roundId, now) {
|
|
237
|
-
try {
|
|
238
|
-
return reconcileReviewFindings(store, taskId, roundId, now);
|
|
239
|
-
}
|
|
240
|
-
catch (error) {
|
|
241
|
-
const reason = `Review finding ledger is unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
242
|
-
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, REVIEW_FINDINGS_RECONCILE_FAILED_EVENT, { reviewRoundId: roundId, reason }, now));
|
|
243
|
-
return {
|
|
244
|
-
roundId,
|
|
245
|
-
skipped: true,
|
|
246
|
-
reason,
|
|
247
|
-
created: [],
|
|
248
|
-
updated: [],
|
|
249
|
-
conflicts: []
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
/** True when a semantic Review could not be reconciled into the ledger. */
|
|
254
|
-
export function reviewFindingLedgerWriteFailed(store, taskId) {
|
|
255
|
-
return reviewFindingLedgerWriteFailedFromEvents(store.listEvents(taskId));
|
|
256
|
-
}
|
|
257
|
-
/** Pure event-fold variant of {@link reviewFindingLedgerWriteFailed}. */
|
|
258
|
-
export function reviewFindingLedgerWriteFailedFromEvents(events) {
|
|
259
|
-
const latestByRound = new Map();
|
|
260
|
-
for (const event of events) {
|
|
261
|
-
if (event.type !== REVIEW_FINDINGS_RECONCILE_FAILED_EVENT
|
|
262
|
-
&& event.type !== "review.findings-reconciled") {
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
const reviewRoundId = event.payload.reviewRoundId;
|
|
266
|
-
if (reviewRoundId !== undefined)
|
|
267
|
-
latestByRound.set(reviewRoundId, event);
|
|
268
|
-
}
|
|
269
|
-
return [...latestByRound.values()]
|
|
270
|
-
.some((event) => event.type === REVIEW_FINDINGS_RECONCILE_FAILED_EVENT);
|
|
271
|
-
}
|
|
272
|
-
/** Applies one explicit Leader disposition to a finding. */
|
|
273
|
-
export function dispositionReviewFinding(store, taskId, findingId, command) {
|
|
274
|
-
const existing = store.getReviewFinding(taskId, findingId);
|
|
275
|
-
if (existing === null) {
|
|
276
|
-
throw new Error(`ReviewFinding not found: ${taskId}/${findingId}.`);
|
|
277
|
-
}
|
|
278
|
-
const next = disposeReviewFinding(existing, {
|
|
279
|
-
disposition: command.disposition,
|
|
280
|
-
by: command.by,
|
|
281
|
-
...(command.note === undefined ? {} : { note: command.note }),
|
|
282
|
-
...(command.workItemId === undefined
|
|
283
|
-
&& command.commit === undefined
|
|
284
|
-
&& command.verification === undefined
|
|
285
|
-
? {}
|
|
286
|
-
: {
|
|
287
|
-
repair: {
|
|
288
|
-
...(command.workItemId === undefined ? {} : { workItemId: command.workItemId }),
|
|
289
|
-
...(command.commit === undefined ? {} : { commit: command.commit }),
|
|
290
|
-
...(command.verification === undefined ? {} : { verification: command.verification })
|
|
291
|
-
}
|
|
292
|
-
}),
|
|
293
|
-
...(command.supersededBy === undefined ? {} : { supersededBy: command.supersededBy }),
|
|
294
|
-
now: command.now
|
|
295
|
-
});
|
|
296
|
-
store.saveReviewFinding(taskId, next);
|
|
297
|
-
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "review.finding-dispositioned", {
|
|
298
|
-
reviewFindingId: next.id,
|
|
299
|
-
disposition: next.disposition,
|
|
300
|
-
by: command.by
|
|
301
|
-
}, command.now));
|
|
302
|
-
return next;
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Groups open P1/P2 findings into repair waves by file/symbol/invariant
|
|
306
|
-
* overlap. Findings sharing an affected path, symbol, or invariant land in
|
|
307
|
-
* the same group (union-find), so one WorkItem repairs one overlapping set
|
|
308
|
-
* while disjoint groups can run in parallel.
|
|
309
|
-
*/
|
|
310
|
-
export function planRepairGroups(store, taskId) {
|
|
311
|
-
const open = store.listReviewFindings(taskId)
|
|
312
|
-
.filter((finding) => finding.disposition === "open"
|
|
313
|
-
&& (finding.severity === "p1" || finding.severity === "p2"))
|
|
314
|
-
.sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }));
|
|
315
|
-
const parent = new Map(open.map((finding) => [finding.id, finding.id]));
|
|
316
|
-
const root = (id) => {
|
|
317
|
-
let current = id;
|
|
318
|
-
while (parent.get(current) !== current) {
|
|
319
|
-
current = parent.get(current);
|
|
320
|
-
}
|
|
321
|
-
let cursor = id;
|
|
322
|
-
while (parent.get(cursor) !== cursor) {
|
|
323
|
-
const next = parent.get(cursor);
|
|
324
|
-
parent.set(cursor, current);
|
|
325
|
-
cursor = next;
|
|
326
|
-
}
|
|
327
|
-
return current;
|
|
328
|
-
};
|
|
329
|
-
const union = (left, right) => {
|
|
330
|
-
const leftRoot = root(left);
|
|
331
|
-
const rightRoot = root(right);
|
|
332
|
-
if (leftRoot !== rightRoot)
|
|
333
|
-
parent.set(rightRoot, leftRoot);
|
|
334
|
-
};
|
|
335
|
-
const byPath = new Map();
|
|
336
|
-
const bySymbol = new Map();
|
|
337
|
-
const byInvariant = new Map();
|
|
338
|
-
for (const finding of open) {
|
|
339
|
-
for (const path of finding.affectedPaths) {
|
|
340
|
-
const seen = byPath.get(path);
|
|
341
|
-
if (seen === undefined)
|
|
342
|
-
byPath.set(path, finding.id);
|
|
343
|
-
else
|
|
344
|
-
union(seen, finding.id);
|
|
345
|
-
}
|
|
346
|
-
for (const symbol of finding.affectedSymbols) {
|
|
347
|
-
const seen = bySymbol.get(symbol);
|
|
348
|
-
if (seen === undefined)
|
|
349
|
-
bySymbol.set(symbol, finding.id);
|
|
350
|
-
else
|
|
351
|
-
union(seen, finding.id);
|
|
352
|
-
}
|
|
353
|
-
// Issue 06: only union on a real invariant. Findings that lack an
|
|
354
|
-
// explicit invariant/category all share the fallback "review-finding"
|
|
355
|
-
// label; unioning on it would collapse sparse reports into one group and
|
|
356
|
-
// defeat parallel repair.
|
|
357
|
-
if (finding.invariant !== "review-finding") {
|
|
358
|
-
const seen = byInvariant.get(finding.invariant);
|
|
359
|
-
if (seen === undefined)
|
|
360
|
-
byInvariant.set(finding.invariant, finding.id);
|
|
361
|
-
else
|
|
362
|
-
union(seen, finding.id);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
const groups = new Map();
|
|
366
|
-
for (const finding of open) {
|
|
367
|
-
const key = root(finding.id);
|
|
368
|
-
const members = groups.get(key) ?? [];
|
|
369
|
-
members.push(finding);
|
|
370
|
-
groups.set(key, members);
|
|
371
|
-
}
|
|
372
|
-
return [...groups.values()]
|
|
373
|
-
.map((findings) => ({
|
|
374
|
-
groupKey: findings.map(({ id }) => id).join("+"),
|
|
375
|
-
findings,
|
|
376
|
-
findingIds: findings.map(({ id }) => id),
|
|
377
|
-
affectedPaths: [...new Set(findings.flatMap(({ affectedPaths }) => affectedPaths))].sort(),
|
|
378
|
-
affectedSymbols: [...new Set(findings.flatMap(({ affectedSymbols }) => affectedSymbols))].sort(),
|
|
379
|
-
invariants: [...new Set(findings.map(({ invariant }) => invariant))].sort()
|
|
380
|
-
}))
|
|
381
|
-
.sort((left, right) => left.groupKey.localeCompare(right.groupKey, undefined, { numeric: true }));
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Completion gate: open (or repair-pending) P1/P2 findings block Task
|
|
385
|
-
* completion under `enforce`. P3/backlog findings never block.
|
|
386
|
-
*/
|
|
387
|
-
export function blockingOpenFindings(store, taskId) {
|
|
388
|
-
return store.listReviewFindings(taskId)
|
|
389
|
-
.filter(isReviewFindingBlocking)
|
|
390
|
-
.sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }));
|
|
391
|
-
}
|
|
392
|
-
/** True when the completion gate must fail closed for this Task. */
|
|
393
|
-
export function completionGateBlocked(store, taskId) {
|
|
394
|
-
if (reviewFindingLedgerMode(store.getConfig()) !== "enforce")
|
|
395
|
-
return false;
|
|
396
|
-
return reviewFindingLedgerWriteFailed(store, taskId)
|
|
397
|
-
|| blockingOpenFindings(store, taskId).length > 0;
|
|
398
|
-
}
|
|
399
|
-
export function summarizeFindingLedger(store, taskId) {
|
|
400
|
-
const findings = store.listReviewFindings(taskId)
|
|
401
|
-
.sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }));
|
|
402
|
-
const byDisposition = (disposition) => findings.filter((finding) => finding.disposition === disposition);
|
|
403
|
-
return {
|
|
404
|
-
total: findings.length,
|
|
405
|
-
open: byDisposition("open"),
|
|
406
|
-
fixedPendingReview: byDisposition("fixed-pending-review"),
|
|
407
|
-
verifiedFixed: byDisposition("verified-fixed"),
|
|
408
|
-
acceptedRisk: byDisposition("accepted-risk"),
|
|
409
|
-
notActionable: byDisposition("not-actionable"),
|
|
410
|
-
superseded: byDisposition("superseded"),
|
|
411
|
-
blocking: findings.filter(isReviewFindingBlocking),
|
|
412
|
-
mergeRequired: findings.filter((finding) => finding.mergeRequired === true)
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Renders the ledger summary as the incremental Review context block: it
|
|
417
|
-
* replaces repeated full-history restatement while still listing every
|
|
418
|
-
* disposition class the final report must account for.
|
|
419
|
-
*/
|
|
420
|
-
export function renderFindingLedgerContext(summary) {
|
|
421
|
-
const lines = ["Finding ledger:"];
|
|
422
|
-
lines.push(`- total: ${summary.total}`);
|
|
423
|
-
const renderList = (label, findings) => {
|
|
424
|
-
if (findings.length === 0)
|
|
425
|
-
return;
|
|
426
|
-
lines.push(`- ${label}:`);
|
|
427
|
-
for (const finding of findings) {
|
|
428
|
-
lines.push(` - ${finding.id} [${finding.severity}] ${finding.title}`
|
|
429
|
-
+ ` (invariant: ${finding.invariant}; first: ${finding.firstReviewRoundId}; last: ${finding.lastReviewRoundId})`);
|
|
430
|
-
if (finding.repair !== undefined) {
|
|
431
|
-
const repairParts = [
|
|
432
|
-
finding.repair.workItemId === undefined ? "" : `work-item=${finding.repair.workItemId}`,
|
|
433
|
-
finding.repair.commit === undefined ? "" : `commit=${finding.repair.commit}`,
|
|
434
|
-
finding.repair.verification === undefined ? "" : `verification=${finding.repair.verification}`
|
|
435
|
-
].filter((part) => part.length > 0);
|
|
436
|
-
if (repairParts.length > 0)
|
|
437
|
-
lines.push(` repair: ${repairParts.join("; ")}`);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
renderList("verified-fixed", summary.verifiedFixed);
|
|
442
|
-
renderList("open (new/repair pending)", summary.open);
|
|
443
|
-
renderList("fixed-pending-review", summary.fixedPendingReview);
|
|
444
|
-
renderList("accepted-risk", summary.acceptedRisk);
|
|
445
|
-
renderList("not-actionable (backlog)", summary.notActionable);
|
|
446
|
-
renderList("superseded", summary.superseded);
|
|
447
|
-
if (summary.mergeRequired.length > 0) {
|
|
448
|
-
lines.push(`- merge-required: ${summary.mergeRequired.map(({ id }) => id).join(", ")}`);
|
|
449
|
-
}
|
|
450
|
-
if (summary.blocking.length > 0) {
|
|
451
|
-
lines.push(`- residual blocking P1/P2: ${summary.blocking.map(({ id }) => id).join(", ")}`);
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
lines.push("- residual blocking P1/P2: none");
|
|
455
|
-
}
|
|
456
|
-
return lines.join("\n");
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Fallback gate-evidence reuse (Issue 08 artifacts are optional): a completed
|
|
460
|
-
* Task-final Round whose evidence commit exactly matches the candidate's
|
|
461
|
-
* primary head may donate its checks by digest. A changed head returns null,
|
|
462
|
-
* so an old GREEN can never be reused against a different tree.
|
|
463
|
-
*/
|
|
464
|
-
export function reusableTaskReviewEvidence(store, taskId, candidate) {
|
|
465
|
-
const rounds = store.listReviewRounds(taskId)
|
|
466
|
-
.filter((round) => (round.scope ?? "work-item") === "task"
|
|
467
|
-
&& isSemanticReviewRound(round, store)
|
|
468
|
-
&& round.evidenceCommit !== undefined
|
|
469
|
-
&& isSameTaskReviewCandidate(round.taskCandidate, candidate))
|
|
470
|
-
.sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }));
|
|
471
|
-
const latest = rounds.at(-1);
|
|
472
|
-
if (latest === undefined || latest.evidenceCommit === undefined)
|
|
473
|
-
return null;
|
|
474
|
-
const checks = latest.checks ?? [];
|
|
475
|
-
if (checks.length === 0 || checks.some((check) => check.outcome !== "passed"))
|
|
476
|
-
return null;
|
|
477
|
-
const digest = createHash("sha256")
|
|
478
|
-
.update(JSON.stringify(checks))
|
|
479
|
-
.digest("hex");
|
|
480
|
-
return {
|
|
481
|
-
reviewRoundId: latest.id,
|
|
482
|
-
evidenceCommit: latest.evidenceCommit,
|
|
483
|
-
digest,
|
|
484
|
-
checks
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* Builds the Issue 06 incremental Review context. The Reviewer receives the
|
|
489
|
-
* cross-Round ledger, exact old/new frozen heads, repair evidence, and any
|
|
490
|
-
* reusable exact-head checks. A changed head or failed/absent checks returns
|
|
491
|
-
* no reusable evidence, so an old GREEN can never be borrowed for a new tree.
|
|
492
|
-
*/
|
|
493
|
-
export function buildTaskFinalReviewFindingContext(store, taskId, candidate) {
|
|
494
|
-
const previousSemanticRound = store.listReviewRounds(taskId)
|
|
495
|
-
.filter((round) => (round.scope ?? "work-item") === "task"
|
|
496
|
-
&& isSemanticReviewRound(round, store))
|
|
497
|
-
.sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }))
|
|
498
|
-
.at(-1) ?? null;
|
|
499
|
-
const reusableEvidence = reusableTaskReviewEvidence(store, taskId, candidate);
|
|
500
|
-
// dbonly: the finding ledger is SQLite-native. A file-only Home (no yui.db)
|
|
501
|
-
// cannot serve findings; the Review dispatch must still proceed, so the
|
|
502
|
-
// ledger context degrades to an explicit "unavailable" note.
|
|
503
|
-
let ledgerContext;
|
|
504
|
-
try {
|
|
505
|
-
ledgerContext = renderFindingLedgerContext(summarizeFindingLedger(store, taskId));
|
|
506
|
-
}
|
|
507
|
-
catch {
|
|
508
|
-
ledgerContext = "Finding ledger: unavailable (a current SQLite Home is required).";
|
|
509
|
-
}
|
|
510
|
-
const lines = [
|
|
511
|
-
"Review convergence context:",
|
|
512
|
-
ledgerContext,
|
|
513
|
-
...(previousSemanticRound === null
|
|
514
|
-
? ["Previous semantic Task-final ReviewRound: none."]
|
|
515
|
-
: [
|
|
516
|
-
`Previous semantic Task-final ReviewRound: ${previousSemanticRound.id}`,
|
|
517
|
-
...candidate.projects.map((project) => {
|
|
518
|
-
const previous = previousSemanticRound.taskCandidate?.projects
|
|
519
|
-
.find((entry) => entry.projectId === project.projectId)?.commit;
|
|
520
|
-
return previous === undefined
|
|
521
|
-
? `Exact review boundary for ${project.projectId}: new head ${project.commit}`
|
|
522
|
-
: previous === project.commit
|
|
523
|
-
? `Exact review boundary for ${project.projectId}: unchanged at ${project.commit}`
|
|
524
|
-
: `Exact diff for ${project.projectId}: ${previous}..${project.commit}`;
|
|
525
|
-
})
|
|
526
|
-
]),
|
|
527
|
-
...(reusableEvidence === null
|
|
528
|
-
? [
|
|
529
|
-
"Reusable gate evidence: none (head changed, checks are absent, or a check did not pass); rerun the checks needed for this exact tree."
|
|
530
|
-
]
|
|
531
|
-
: [
|
|
532
|
-
`Reusable gate evidence: ${reusableEvidence.reviewRoundId}@${reusableEvidence.evidenceCommit}`,
|
|
533
|
-
`Reusable check digest: ${reusableEvidence.digest}`,
|
|
534
|
-
`Reusable checks: ${reusableEvidence.checks.map(({ name, outcome }) => `${name}=${outcome}`).join(", ")}`
|
|
535
|
-
]),
|
|
536
|
-
"Reuse each listed ledger finding id when it is still valid; explain why a new finding is not already covered by the ledger.",
|
|
537
|
-
"Your final report must clearly list verified-fixed findings, new findings, accepted risks, and residual verification gaps."
|
|
538
|
-
];
|
|
539
|
-
return {
|
|
540
|
-
context: lines.join("\n"),
|
|
541
|
-
previousSemanticRound,
|
|
542
|
-
reusableEvidence
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
function isSameTaskReviewCandidate(left, right) {
|
|
546
|
-
return left !== undefined
|
|
547
|
-
&& left.projects.length === right.projects.length
|
|
548
|
-
&& left.projects.every((project, index) => (project.projectId === right.projects[index]?.projectId
|
|
549
|
-
&& project.commit === right.projects[index]?.commit));
|
|
550
|
-
}
|