@zq-silk/yui 0.6.13 → 0.6.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -5
- package/dist/cli/commandCatalog.js +173 -57
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli.js +99 -11
- package/dist/commands/configCommands.js +521 -171
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +56 -3
- package/dist/commands/projectCommands.js +504 -2
- package/dist/commands/releaseCommands.js +0 -1
- package/dist/commands/taskActor.js +17 -0
- package/dist/commands/taskBaseCommands.js +29 -0
- package/dist/commands/taskCommands.js +577 -126
- package/dist/commands/taskContextCommand.js +36 -2
- package/dist/commands/taskNextActionCommand.js +48 -3
- package/dist/commands/taskPublicationCommands.js +319 -0
- package/dist/commands/taskRoleRuntimeStatus.js +83 -59
- package/dist/commands/telemetryCommands.js +14 -13
- package/dist/config/yuiConfig.js +161 -8
- package/dist/context/sessionContextBudget.js +68 -0
- package/dist/context/wakeNotification.js +65 -0
- package/dist/controller/clientRuntime.js +2 -1
- package/dist/controller/ephemeralResourceReaper.js +2 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +183 -16
- package/dist/controller/jobSupervisor.js +5 -4
- package/dist/controller/resourceCleanupLinux.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +3 -3
- package/dist/controller/runtime.js +88 -10
- package/dist/controller/updateReconciliation.js +4 -3
- package/dist/doctor/doctor.js +26 -7
- package/dist/executor/agentConfigurationCatalog.js +18 -0
- package/dist/executor/fileRoleLaunchPlanner.js +3 -3
- package/dist/lifecycle/contextBudgetRollover.js +81 -0
- package/dist/lifecycle/exactRunTerminalization.js +11 -1
- package/dist/lifecycle/providerErrorClass.js +33 -12
- package/dist/observability/executionAudit.js +214 -6
- package/dist/output/table.js +18 -0
- package/dist/repository/gitWorkspace.js +92 -0
- package/dist/repository/project.js +218 -4
- package/dist/repository/taskBaseFreshness.js +318 -0
- package/dist/repository/taskWorkspacePreparer.js +16 -2
- package/dist/review/deltaRecheck.js +232 -0
- package/dist/review/reviewConfig.js +31 -0
- package/dist/review/reviewFindingLedger.js +5 -1
- package/dist/review/reviewRound.js +156 -1
- package/dist/run/providerRetry.js +21 -3
- package/dist/run/providerRetryConfig.js +13 -60
- package/dist/run/recoveryProjection.js +199 -0
- package/dist/runtime/builtinAgentDrivers.js +3 -0
- package/dist/runtime/builtinTranscriptUsage.js +76 -32
- package/dist/runtime/continuationManager.js +17 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/launchDiagnostics.js +154 -0
- package/dist/runtime/lifecycleReservation.js +13 -0
- package/dist/runtime/providerContinuation.js +38 -0
- package/dist/runtime/providerContinuationReconciliationService.js +1 -0
- package/dist/runtime/providerErrorCodes.js +278 -0
- package/dist/runtime/runtimeHealthPolicy.js +20 -0
- package/dist/runtime/runtimeObservation.js +1 -0
- package/dist/runtime/runtimeProjection.js +115 -23
- package/dist/runtime/tmuxAdapters.js +242 -48
- package/dist/scheduler/activeRoleRunDelivery.js +139 -3
- package/dist/scheduler/activeTaskProgress.js +4 -3
- package/dist/scheduler/leaderWakeupProcessor.js +105 -15
- package/dist/scheduler/roleRunLiveness.js +2 -1
- package/dist/scheduler/roleRunStall.js +3 -2
- package/dist/scheduler/taskWake.js +72 -0
- package/dist/scheduler/wakeReason.js +64 -0
- package/dist/scheduler/wakeupQueue.js +2 -1
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/migration/productionRegistry.js +325 -1
- package/dist/storage/sqliteSchema.js +61 -2
- package/dist/storage/sqliteStore.js +129 -2
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/taskStore.js +262 -5
- package/dist/storage/upgrade/recordVersions.js +6 -1
- package/dist/storage/upgrade/sqliteStateMigration.js +22 -2
- package/dist/task/completionReadiness.js +282 -0
- package/dist/task/publicationReference.js +123 -0
- package/dist/task/taskRecordReference.js +3 -1
- package/dist/telemetry/telemetryConfig.js +23 -18
- package/dist/telemetry/telemetryWiring.js +8 -8
- package/dist/tmux/tmuxManager.js +50 -9
- package/dist/web/assets/client/i18n.js +4 -0
- package/dist/web/assets/client/view.js +18 -0
- package/dist/web/webSnapshot.js +100 -10
- package/i18n/README.zh-CN.md +5 -5
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +49 -10
- package/skills/yui-operator/SKILL.md +13 -3
- package/skills/yui-worker/SKILL.md +8 -0
|
@@ -16,6 +16,7 @@ import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
|
16
16
|
import { resolveTaskStoreBackendForHome } from "../storage/sqliteStore.js";
|
|
17
17
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
18
18
|
import { classifyAgentRunFailure, classifyIntegrationAttempt, classifyReviewRound, classifyWakeReasons, countFaultClasses } from "./faultClassification.js";
|
|
19
|
+
import { RUNTIME_LAUNCH_KINDS, RUNTIME_LAUNCH_PHASES } from "../runtime/launchDiagnostics.js";
|
|
19
20
|
import { UNSUPPORTED } from "./runtimeIdentity.js";
|
|
20
21
|
export function createProductionExecutionAuditPorts() {
|
|
21
22
|
return {
|
|
@@ -84,11 +85,86 @@ function roleBucket(roleName) {
|
|
|
84
85
|
}
|
|
85
86
|
return "other";
|
|
86
87
|
}
|
|
88
|
+
/** Maps a provider-retry lifecycle event to its last recorded decision. */
|
|
89
|
+
function providerRetryDecision(event) {
|
|
90
|
+
if (event.type === "runtime.provider-retry-waiting")
|
|
91
|
+
return "waiting";
|
|
92
|
+
if (event.type === "runtime.provider-retry-resume")
|
|
93
|
+
return "resume";
|
|
94
|
+
if (event.type === "runtime.provider-retry-session-dead")
|
|
95
|
+
return "session-dead";
|
|
96
|
+
if (event.type === "runtime.provider-retry-budget-exhausted")
|
|
97
|
+
return "budget-exhausted";
|
|
98
|
+
if (event.payload.note === "native-turn-completion-durable")
|
|
99
|
+
return "suppressed:completion-durable";
|
|
100
|
+
if (event.payload.note === "retry-budget-exhausted")
|
|
101
|
+
return "budget-exhausted";
|
|
102
|
+
if (event.payload.wouldRetry === "true")
|
|
103
|
+
return "retrying";
|
|
104
|
+
if (event.payload.shadow === "true")
|
|
105
|
+
return "shadow";
|
|
106
|
+
return "not-retried";
|
|
107
|
+
}
|
|
87
108
|
function durationMs(run) {
|
|
88
109
|
if (run.endedAt === undefined)
|
|
89
110
|
return 0;
|
|
90
111
|
return Math.max(0, Date.parse(run.endedAt) - Date.parse(run.createdAt));
|
|
91
112
|
}
|
|
113
|
+
function emptyLaunchFailureCounts() {
|
|
114
|
+
const counts = {
|
|
115
|
+
total: 0,
|
|
116
|
+
byPhase: Object.fromEntries(RUNTIME_LAUNCH_PHASES.map((phase) => [phase, 0])),
|
|
117
|
+
byKind: Object.fromEntries(RUNTIME_LAUNCH_KINDS.map((kind) => [kind, 0]))
|
|
118
|
+
};
|
|
119
|
+
return counts;
|
|
120
|
+
}
|
|
121
|
+
function addLaunchFailureCounts(counts, summary) {
|
|
122
|
+
if (summary === undefined)
|
|
123
|
+
return;
|
|
124
|
+
const phase = /failurePhase=([a-z-]+)/u.exec(summary)?.[1];
|
|
125
|
+
const kind = /failureKind=([a-z-]+)/u.exec(summary)?.[1];
|
|
126
|
+
if (phase === undefined
|
|
127
|
+
|| kind === undefined
|
|
128
|
+
|| !RUNTIME_LAUNCH_PHASES.includes(phase)
|
|
129
|
+
|| !RUNTIME_LAUNCH_KINDS.includes(kind)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
counts.byPhase[phase] += 1;
|
|
133
|
+
counts.byKind[kind] += 1;
|
|
134
|
+
counts.total += 1;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Issue 09: classify a terminal Session (stopped/broken) by its relationship
|
|
138
|
+
* to the last Run it carried. Runs and Sessions are separate axes: a Session
|
|
139
|
+
* that stops after its Run yielded is a post-completion stop, not a Run
|
|
140
|
+
* failure. Correlation is by Role + Agent + Adapter (the durable identity a
|
|
141
|
+
* Run and its Session share) and the Session's terminal update timestamp.
|
|
142
|
+
*/
|
|
143
|
+
function classifyTerminalSessionRunRelation(runs, roleName, session, counts) {
|
|
144
|
+
const terminalAt = Date.parse(session.updatedAt);
|
|
145
|
+
const carried = runs
|
|
146
|
+
.filter((run) => (run.roleName === roleName
|
|
147
|
+
&& run.effective.agentId === session.agentId
|
|
148
|
+
&& run.effective.adapterId === session.adapterId
|
|
149
|
+
&& Date.parse(run.createdAt) <= terminalAt))
|
|
150
|
+
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt));
|
|
151
|
+
const lastRun = carried[0];
|
|
152
|
+
if (lastRun === undefined) {
|
|
153
|
+
counts.noRun += 1;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (lastRun.status === "yielded"
|
|
157
|
+
&& lastRun.endedAt !== undefined
|
|
158
|
+
&& Date.parse(lastRun.endedAt) <= terminalAt) {
|
|
159
|
+
counts.postRunYielded += 1;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (lastRun.status === "failed") {
|
|
163
|
+
counts.runFailed += 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
counts.activeRun += 1;
|
|
167
|
+
}
|
|
92
168
|
function ok(data) {
|
|
93
169
|
return { status: "ok", data };
|
|
94
170
|
}
|
|
@@ -126,7 +202,9 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
126
202
|
sessions: section,
|
|
127
203
|
reviews: section,
|
|
128
204
|
integrations: section,
|
|
205
|
+
publications: section,
|
|
129
206
|
events: section,
|
|
207
|
+
providerRetries: section,
|
|
130
208
|
workItems: section,
|
|
131
209
|
storage: section,
|
|
132
210
|
topLongRunning: section
|
|
@@ -174,6 +252,7 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
174
252
|
const byRole = { leader: 0, reviewer: 0, implementer: 0, other: 0 };
|
|
175
253
|
const byPurpose = { execution: 0, review: 0 };
|
|
176
254
|
const failures = [];
|
|
255
|
+
const launchFailures = emptyLaunchFailureCounts();
|
|
177
256
|
for (const taskId of taskIds) {
|
|
178
257
|
for (const run of store.listAgentRuns(taskId)) {
|
|
179
258
|
if (!inWindow(run.createdAt, options))
|
|
@@ -197,6 +276,7 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
197
276
|
failedCount += 1;
|
|
198
277
|
failedDurationMs += duration;
|
|
199
278
|
cumulativeDurationMs += duration;
|
|
279
|
+
addLaunchFailureCounts(launchFailures, run.summary);
|
|
200
280
|
failures.push(classifyAgentRunFailure(run));
|
|
201
281
|
}
|
|
202
282
|
}
|
|
@@ -211,7 +291,8 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
211
291
|
failedDurationMs,
|
|
212
292
|
byRole,
|
|
213
293
|
byPurpose,
|
|
214
|
-
faultClasses: countFaultClasses(failures)
|
|
294
|
+
faultClasses: countFaultClasses(failures),
|
|
295
|
+
launchFailures
|
|
215
296
|
});
|
|
216
297
|
}
|
|
217
298
|
catch (error) {
|
|
@@ -224,6 +305,7 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
224
305
|
let withWakeReasons = 0;
|
|
225
306
|
let orphanWakes = 0;
|
|
226
307
|
let orphanYieldOnly = 0;
|
|
308
|
+
let suppressedWakes = 0;
|
|
227
309
|
const byReason = new Map();
|
|
228
310
|
for (const taskId of taskIds) {
|
|
229
311
|
for (const run of store.listAgentRuns(taskId)) {
|
|
@@ -247,6 +329,13 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
247
329
|
orphanYieldOnly += 1;
|
|
248
330
|
}
|
|
249
331
|
}
|
|
332
|
+
for (const event of store.listEvents(taskId)) {
|
|
333
|
+
if (event.type !== "wake.suppressed")
|
|
334
|
+
continue;
|
|
335
|
+
if (!inWindow(event.createdAt, options))
|
|
336
|
+
continue;
|
|
337
|
+
suppressedWakes += 1;
|
|
338
|
+
}
|
|
250
339
|
}
|
|
251
340
|
return ok({
|
|
252
341
|
leaderRuns,
|
|
@@ -254,9 +343,10 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
254
343
|
orphanWakes,
|
|
255
344
|
orphanYieldOnly,
|
|
256
345
|
byReason: Object.fromEntries([...byReason.entries()].sort((left, right) => right[1] - left[1])),
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
|
|
346
|
+
// Scheduler single-flight suppression: wakes that were coalesced
|
|
347
|
+
// because the Role runtime lifecycle lane was busy. These are
|
|
348
|
+
// scheduler outcomes, never failed Runs.
|
|
349
|
+
suppressedWakes: { status: "ok", data: suppressedWakes }
|
|
260
350
|
});
|
|
261
351
|
}
|
|
262
352
|
catch (error) {
|
|
@@ -272,7 +362,14 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
272
362
|
let resets = 0;
|
|
273
363
|
let lifecycleEvents = 0;
|
|
274
364
|
let stopFailures = 0;
|
|
365
|
+
const terminalByRunRelation = {
|
|
366
|
+
postRunYielded: 0,
|
|
367
|
+
runFailed: 0,
|
|
368
|
+
activeRun: 0,
|
|
369
|
+
noRun: 0
|
|
370
|
+
};
|
|
275
371
|
for (const taskId of taskIds) {
|
|
372
|
+
const runs = store.listAgentRuns(taskId);
|
|
276
373
|
for (const set of store.listRoleSessionSets(taskId)) {
|
|
277
374
|
const history = Array.isArray(set.history) ? set.history : [];
|
|
278
375
|
for (const session of [...history, ...Object.values(set.sessions)]) {
|
|
@@ -283,6 +380,9 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
283
380
|
stopped += 1;
|
|
284
381
|
else
|
|
285
382
|
other += 1;
|
|
383
|
+
if (session.status === "broken" || session.status === "stopped") {
|
|
384
|
+
classifyTerminalSessionRunRelation(runs, set.owner.roleName, session, terminalByRunRelation);
|
|
385
|
+
}
|
|
286
386
|
}
|
|
287
387
|
}
|
|
288
388
|
for (const event of store.listEvents(taskId)) {
|
|
@@ -304,7 +404,8 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
304
404
|
other,
|
|
305
405
|
resets,
|
|
306
406
|
lifecycleEvents,
|
|
307
|
-
stopFailures
|
|
407
|
+
stopFailures,
|
|
408
|
+
terminalByRunRelation
|
|
308
409
|
});
|
|
309
410
|
}
|
|
310
411
|
catch (error) {
|
|
@@ -318,6 +419,10 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
318
419
|
let failedCount = 0;
|
|
319
420
|
let infraFailed = 0;
|
|
320
421
|
let semanticNegative = 0;
|
|
422
|
+
let deltaTotal = 0;
|
|
423
|
+
let deltaAccepted = 0;
|
|
424
|
+
let deltaFinding = 0;
|
|
425
|
+
let deltaEscalated = 0;
|
|
321
426
|
const classes = [];
|
|
322
427
|
for (const taskId of taskIds) {
|
|
323
428
|
for (const round of store.listReviewRounds(taskId)) {
|
|
@@ -328,6 +433,15 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
328
433
|
completed += 1;
|
|
329
434
|
else if (round.status === "failed")
|
|
330
435
|
failedCount += 1;
|
|
436
|
+
if (round.deltaRecheck !== undefined) {
|
|
437
|
+
deltaTotal += 1;
|
|
438
|
+
if (round.deltaRecheck.disposition === "equivalent-and-accepted")
|
|
439
|
+
deltaAccepted += 1;
|
|
440
|
+
else if (round.deltaRecheck.disposition === "finding")
|
|
441
|
+
deltaFinding += 1;
|
|
442
|
+
else if (round.deltaRecheck.disposition === "requires-full-review")
|
|
443
|
+
deltaEscalated += 1;
|
|
444
|
+
}
|
|
331
445
|
const classification = classifyReviewRound(round);
|
|
332
446
|
if (classification.faultClass === "review-infra")
|
|
333
447
|
infraFailed += 1;
|
|
@@ -344,7 +458,13 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
344
458
|
failed: failedCount,
|
|
345
459
|
infraFailed,
|
|
346
460
|
semanticNegative,
|
|
347
|
-
faultClasses: countFaultClasses(classes)
|
|
461
|
+
faultClasses: countFaultClasses(classes),
|
|
462
|
+
deltaRechecks: {
|
|
463
|
+
total: deltaTotal,
|
|
464
|
+
equivalentAndAccepted: deltaAccepted,
|
|
465
|
+
finding: deltaFinding,
|
|
466
|
+
requiresFullReview: deltaEscalated
|
|
467
|
+
}
|
|
348
468
|
});
|
|
349
469
|
}
|
|
350
470
|
catch (error) {
|
|
@@ -411,6 +531,41 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
411
531
|
return failed(error);
|
|
412
532
|
}
|
|
413
533
|
})();
|
|
534
|
+
const publications = (() => {
|
|
535
|
+
try {
|
|
536
|
+
let total = 0;
|
|
537
|
+
let merged = 0;
|
|
538
|
+
let verified = 0;
|
|
539
|
+
let open = 0;
|
|
540
|
+
let closed = 0;
|
|
541
|
+
let superseded = 0;
|
|
542
|
+
for (const taskId of taskIds) {
|
|
543
|
+
const references = store.listPublicationReferences(taskId);
|
|
544
|
+
const supersededIds = new Set(references
|
|
545
|
+
.map((reference) => reference.supersedes)
|
|
546
|
+
.filter((id) => id !== undefined));
|
|
547
|
+
for (const reference of references) {
|
|
548
|
+
if (!inWindow(reference.createdAt, options))
|
|
549
|
+
continue;
|
|
550
|
+
total += 1;
|
|
551
|
+
if (reference.state === "merged")
|
|
552
|
+
merged += 1;
|
|
553
|
+
if (reference.state === "open")
|
|
554
|
+
open += 1;
|
|
555
|
+
if (reference.state === "closed")
|
|
556
|
+
closed += 1;
|
|
557
|
+
if (reference.verification === "verified")
|
|
558
|
+
verified += 1;
|
|
559
|
+
if (supersededIds.has(reference.id))
|
|
560
|
+
superseded += 1;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return ok({ total, merged, verified, open, closed, superseded });
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
return failed(error);
|
|
567
|
+
}
|
|
568
|
+
})();
|
|
414
569
|
const events = (() => {
|
|
415
570
|
try {
|
|
416
571
|
let total = 0;
|
|
@@ -445,6 +600,57 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
445
600
|
return failed(error);
|
|
446
601
|
}
|
|
447
602
|
})();
|
|
603
|
+
const providerRetries = (() => {
|
|
604
|
+
try {
|
|
605
|
+
const entries = new Map();
|
|
606
|
+
for (const taskId of taskIds) {
|
|
607
|
+
for (const event of store.listEvents(taskId)) {
|
|
608
|
+
if (!inWindow(event.createdAt, options))
|
|
609
|
+
continue;
|
|
610
|
+
if (!event.type.startsWith("runtime.provider-retry-"))
|
|
611
|
+
continue;
|
|
612
|
+
const runId = event.payload.runId;
|
|
613
|
+
if (typeof runId !== "string")
|
|
614
|
+
continue;
|
|
615
|
+
const key = `${taskId}/${runId}`;
|
|
616
|
+
const previous = entries.get(key);
|
|
617
|
+
const attempt = Number(event.payload.attempt ?? "0");
|
|
618
|
+
const decision = providerRetryDecision(event);
|
|
619
|
+
const terminal = decision === "session-dead" || decision === "budget-exhausted";
|
|
620
|
+
const next = {
|
|
621
|
+
taskId,
|
|
622
|
+
runId,
|
|
623
|
+
roleName: typeof event.payload.roleName === "string"
|
|
624
|
+
? event.payload.roleName
|
|
625
|
+
: previous?.roleName ?? "",
|
|
626
|
+
attempts: Number.isFinite(attempt) ? Math.max(previous?.attempts ?? 0, attempt) : (previous?.attempts ?? 0),
|
|
627
|
+
errorClass: typeof event.payload.errorClass === "string"
|
|
628
|
+
? event.payload.errorClass
|
|
629
|
+
: previous?.errorClass ?? "",
|
|
630
|
+
firstFailureAt: previous?.firstFailureAt ?? event.createdAt,
|
|
631
|
+
lastFailureAt: event.createdAt,
|
|
632
|
+
...(typeof event.payload.nextAttemptAt === "string"
|
|
633
|
+
? { lastBackoffAt: event.payload.nextAttemptAt }
|
|
634
|
+
: previous?.lastBackoffAt !== undefined
|
|
635
|
+
? { lastBackoffAt: previous.lastBackoffAt }
|
|
636
|
+
: {}),
|
|
637
|
+
decision,
|
|
638
|
+
terminal: (previous?.terminal ?? false) || terminal
|
|
639
|
+
};
|
|
640
|
+
entries.set(key, next);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const list = [...entries.values()];
|
|
644
|
+
return ok({
|
|
645
|
+
total: list.length,
|
|
646
|
+
terminal: list.filter((entry) => entry.terminal).length,
|
|
647
|
+
entries: list.map(({ terminal: _terminal, ...entry }) => entry)
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
catch (error) {
|
|
651
|
+
return failed(error);
|
|
652
|
+
}
|
|
653
|
+
})();
|
|
448
654
|
const workItems = (() => {
|
|
449
655
|
try {
|
|
450
656
|
let total = 0;
|
|
@@ -540,7 +746,9 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
540
746
|
sessions,
|
|
541
747
|
reviews,
|
|
542
748
|
integrations,
|
|
749
|
+
publications,
|
|
543
750
|
events,
|
|
751
|
+
providerRetries,
|
|
544
752
|
workItems,
|
|
545
753
|
storage,
|
|
546
754
|
topLongRunning
|
package/dist/output/table.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { padVisibleEnd, visibleWidth, wrapVisibleText } from "./terminal.js";
|
|
2
|
+
/**
|
|
3
|
+
* The shared renderer for every command whose output is row/column data
|
|
4
|
+
* (lists, overviews, settings such as `config show`). The consistent rules:
|
|
5
|
+
*
|
|
6
|
+
* - Column width: each column declares `minWidth`/`maxWidth`; widths start at
|
|
7
|
+
* the widest cell and shrink largest-spare-first until the table fits.
|
|
8
|
+
* - Alignment: cells are left-aligned and padded by visible width, so CJK and
|
|
9
|
+
* other wide characters align correctly.
|
|
10
|
+
* - Empty values: rendered as empty cells (trimmed at line end); the
|
|
11
|
+
* narrow-terminal record fallback skips empty detail values.
|
|
12
|
+
* - Terminal width: callers pass `defaultTableWidth()` (46–140 columns) unless
|
|
13
|
+
* a command has a narrower natural bound.
|
|
14
|
+
* - JSON mode: commands that have structured data pass it as `emit`'s `data`
|
|
15
|
+
* argument; the table text is the human view of the same values.
|
|
16
|
+
*
|
|
17
|
+
* Do not use this for long-form prose, diagnostic detail, or interactive
|
|
18
|
+
* prompts — those keep their own non-tabular formats.
|
|
19
|
+
*/
|
|
2
20
|
export function renderTable(title, columns, rows, maxWidth) {
|
|
3
21
|
if (columns.length === 0)
|
|
4
22
|
return title;
|
|
@@ -80,6 +80,44 @@ export class NodeGitWorkspace {
|
|
|
80
80
|
]);
|
|
81
81
|
return output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
82
82
|
}
|
|
83
|
+
/** Issue 07: exact unified diff text between two commits. */
|
|
84
|
+
async diffTextBetween(input) {
|
|
85
|
+
return git([
|
|
86
|
+
"-C", input.repositoryPath,
|
|
87
|
+
"diff", "--no-color", "--no-ext-diff",
|
|
88
|
+
input.fromCommit, input.toCommit
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
/** Issue 07: sums `git diff --numstat` added/deleted lines. */
|
|
92
|
+
async diffNumstatBetween(input) {
|
|
93
|
+
const output = await git([
|
|
94
|
+
"-C", input.repositoryPath,
|
|
95
|
+
"diff", "--numstat",
|
|
96
|
+
input.fromCommit, input.toCommit
|
|
97
|
+
]);
|
|
98
|
+
let addedLines = 0;
|
|
99
|
+
let deletedLines = 0;
|
|
100
|
+
for (const line of output.split("\n")) {
|
|
101
|
+
const trimmed = line.trim();
|
|
102
|
+
if (trimmed.length === 0)
|
|
103
|
+
continue;
|
|
104
|
+
const [added, deleted] = trimmed.split("\t");
|
|
105
|
+
// Binary files report "-" for both counts; they change the tree and
|
|
106
|
+
// must not be treated as zero-line edits.
|
|
107
|
+
if (added === "-" || deleted === "-") {
|
|
108
|
+
throw new Error("Delta recheck cannot assess a binary diff.");
|
|
109
|
+
}
|
|
110
|
+
const addedCount = Number(added);
|
|
111
|
+
const deletedCount = Number(deleted);
|
|
112
|
+
if (!Number.isInteger(addedCount) || !Number.isInteger(deletedCount)
|
|
113
|
+
|| addedCount < 0 || deletedCount < 0) {
|
|
114
|
+
throw new Error("Git returned invalid numstat output.");
|
|
115
|
+
}
|
|
116
|
+
addedLines += addedCount;
|
|
117
|
+
deletedLines += deletedCount;
|
|
118
|
+
}
|
|
119
|
+
return { addedLines, deletedLines };
|
|
120
|
+
}
|
|
83
121
|
/**
|
|
84
122
|
* Files deleted between two commits. Uses `--diff-filter=D` so a rename
|
|
85
123
|
* reports its source path here (and its destination in `changedFilesBetween`),
|
|
@@ -541,6 +579,60 @@ export class NodeGitWorkspace {
|
|
|
541
579
|
safeRef(descendant)
|
|
542
580
|
]);
|
|
543
581
|
}
|
|
582
|
+
async inspectRemoteTracking(input) {
|
|
583
|
+
const root = (await this.inspect(input.repositoryPath)).root;
|
|
584
|
+
const wantedUrl = requireText(input.remoteUrl, "Project remote URL");
|
|
585
|
+
let output;
|
|
586
|
+
try {
|
|
587
|
+
output = await git([
|
|
588
|
+
"-C", root,
|
|
589
|
+
"config", "--get-regexp", "--null",
|
|
590
|
+
"^remote\\..*\\.url$"
|
|
591
|
+
]);
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
const records = output.split("\0").filter((record) => record.length > 0);
|
|
597
|
+
const matches = [];
|
|
598
|
+
for (const record of records) {
|
|
599
|
+
const separator = record.indexOf("\n");
|
|
600
|
+
if (separator < 0)
|
|
601
|
+
continue;
|
|
602
|
+
const key = record.slice(0, separator).trim();
|
|
603
|
+
const url = record.slice(separator + 1).trim();
|
|
604
|
+
const prefix = "remote.";
|
|
605
|
+
const suffix = ".url";
|
|
606
|
+
if (!key.startsWith(prefix) || !key.endsWith(suffix) || url !== wantedUrl)
|
|
607
|
+
continue;
|
|
608
|
+
matches.push({ remoteName: key.slice(prefix.length, -suffix.length), url });
|
|
609
|
+
}
|
|
610
|
+
if (matches.length !== 1)
|
|
611
|
+
return null;
|
|
612
|
+
const remoteName = requireText(matches[0].remoteName, "Git remote name");
|
|
613
|
+
const branch = await safeFetchBranch(input.branch);
|
|
614
|
+
const ref = `refs/remotes/${remoteName}/${branch}`;
|
|
615
|
+
if (!await gitSucceeds(["check-ref-format", ref])) {
|
|
616
|
+
throw new Error("Git remote tracking ref is invalid.");
|
|
617
|
+
}
|
|
618
|
+
if (!await this.refExists(root, ref))
|
|
619
|
+
return null;
|
|
620
|
+
return {
|
|
621
|
+
remoteName,
|
|
622
|
+
remoteUrl: matches[0].url,
|
|
623
|
+
ref,
|
|
624
|
+
commit: (await this.inspect(root, ref)).baseCommit
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
async mergeBase(input) {
|
|
628
|
+
const root = (await this.inspect(input.repositoryPath)).root;
|
|
629
|
+
return gitLine([
|
|
630
|
+
"-C", root,
|
|
631
|
+
"merge-base",
|
|
632
|
+
safeRef(input.leftCommit),
|
|
633
|
+
safeRef(input.rightCommit)
|
|
634
|
+
]);
|
|
635
|
+
}
|
|
544
636
|
async headRef(repositoryPath) {
|
|
545
637
|
const requested = await canonicalDirectory(repositoryPath, "Project");
|
|
546
638
|
return gitLine(["-C", requested, "rev-parse", "--abbrev-ref", "HEAD"]);
|