@abelxiaoxing/cadence 1.4.0 → 1.5.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 (66) hide show
  1. package/README.md +130 -8
  2. package/agents/design-explorer.md +17 -11
  3. package/agents/diagnosis-worker.md +9 -1
  4. package/agents/implementation-worker.md +3 -1
  5. package/config/plan-draft.example.json +122 -0
  6. package/config/plan-draft.multiple-tasks.example.json +345 -0
  7. package/config/plan-draft.quick.example.json +78 -0
  8. package/package.json +10 -4
  9. package/prompts/abel-design.md +101 -44
  10. package/prompts/abel-implement.md +9 -0
  11. package/src/activity-contracts.ts +82 -0
  12. package/src/candidate-artifact.ts +143 -0
  13. package/src/candidate-patch.ts +31 -11
  14. package/src/candidate-workspace.ts +74 -0
  15. package/src/change-contract-schema.ts +191 -0
  16. package/src/change-contract.ts +5 -0
  17. package/src/change-verification.ts +920 -0
  18. package/src/child-budget.ts +58 -0
  19. package/src/child-model.ts +142 -0
  20. package/src/child-session.ts +289 -326
  21. package/src/contracts.ts +200 -76
  22. package/src/delivery-compiler.ts +105 -120
  23. package/src/delivery-revision.ts +42 -0
  24. package/src/design-control.ts +113 -4
  25. package/src/design-diagnostics.ts +150 -0
  26. package/src/durable-contracts.ts +213 -0
  27. package/src/durable-workflow.ts +148 -3570
  28. package/src/evidence-draft.ts +122 -0
  29. package/src/execution-profile.ts +38 -0
  30. package/src/implement-graph.ts +3 -0
  31. package/src/implement-plan.ts +70 -0
  32. package/src/index.ts +59 -522
  33. package/src/isolation-backend.ts +62 -40
  34. package/src/model-source.ts +22 -0
  35. package/src/operator-cli-entry.mjs +24 -0
  36. package/src/operator-cli.mjs +1421 -0
  37. package/src/operator-tools.ts +249 -0
  38. package/src/package-candidate.ts +227 -0
  39. package/src/package-verification.ts +86 -8
  40. package/src/package-workflow.ts +291 -0
  41. package/src/packet-runtime.ts +27 -48
  42. package/src/parent-provider.ts +86 -198
  43. package/src/phase-execution.ts +2248 -0
  44. package/src/pi-adapter.ts +19 -0
  45. package/src/plan-draft-summary.ts +149 -0
  46. package/src/plan-draft.ts +493 -0
  47. package/src/single-task-draft.ts +142 -0
  48. package/src/subagent-activity.ts +32 -66
  49. package/src/submit-tool.ts +20 -59
  50. package/src/transport-budget.ts +52 -0
  51. package/src/verification-capability.ts +5 -3
  52. package/src/verification-diagnostics.ts +76 -0
  53. package/src/verification-environment-io.ts +280 -0
  54. package/src/verification-environment.ts +56 -154
  55. package/src/verification-scan.ts +64 -0
  56. package/src/worker-broker.ts +39 -57
  57. package/src/workflow-policy.ts +14 -53
  58. package/src/workflow-recovery-policy.ts +95 -0
  59. package/src/workflow-scheduling.ts +105 -0
  60. package/src/workflow-state-machine.ts +105 -497
  61. package/src/workflow-status.ts +384 -0
  62. package/src/workspace-io-entry.mjs +19 -1
  63. package/src/workspace-io-worker.mjs +275 -50
  64. package/src/workspace-io.ts +6 -1
  65. package/src/empty-resource-loader.ts +0 -38
  66. package/src/parent-payload-bridge.ts +0 -523
@@ -0,0 +1,142 @@
1
+ import { DesignPlanValidationError } from "./design-diagnostics.ts";
2
+ import type {
3
+ DraftVerification,
4
+ PlanDraft,
5
+ PlanTaskInput,
6
+ } from "./plan-draft.ts";
7
+
8
+ /** Small author input. Evidence, scope and the complete suite remain explicit. */
9
+ export interface SingleTaskDraft {
10
+ changeId: string;
11
+ changeContract?: PlanDraft["changeContract"];
12
+ singleTask: {
13
+ taskId: string;
14
+ objective: string;
15
+ context: PlanTaskInput["context"];
16
+ roots: string[];
17
+ read: string[];
18
+ greenWrite: string[];
19
+ redWrite?: string[];
20
+ expectedFailure?: string;
21
+ verificationMode?: "behavior" | "mechanical" | "refactor";
22
+ verification: DraftVerification;
23
+ fullSuite: DraftVerification;
24
+ impactClosure: PlanTaskInput["impactClosure"];
25
+ agents: PlanTaskInput["agents"];
26
+ recovery?: { artifactAttempts: number; repairAttempts: number };
27
+ };
28
+ }
29
+
30
+ function invalid(code: string, field = "singleTask"): never {
31
+ throw new DesignPlanValidationError(code, [{ code, field }]);
32
+ }
33
+
34
+ function record(value: unknown): value is Record<string, unknown> {
35
+ return value !== null && typeof value === "object" && !Array.isArray(value);
36
+ }
37
+
38
+ /** Mechanical expansion only; the ordinary compiler validates all resulting fields. */
39
+ export function expandSingleTaskDraft(value: unknown): unknown {
40
+ if (!record(value) || !Object.hasOwn(value, "singleTask")) return value;
41
+ if (
42
+ Object.keys(value).some(
43
+ (key) => !["changeId", "changeContract", "singleTask"].includes(key),
44
+ ) ||
45
+ !record(value.singleTask)
46
+ )
47
+ invalid("single-task-draft-invalid");
48
+ const input = value.singleTask;
49
+ const required = [
50
+ "taskId",
51
+ "objective",
52
+ "context",
53
+ "roots",
54
+ "read",
55
+ "greenWrite",
56
+ "verification",
57
+ "fullSuite",
58
+ "impactClosure",
59
+ "agents",
60
+ ];
61
+ const optional = [
62
+ "redWrite",
63
+ "expectedFailure",
64
+ "verificationMode",
65
+ "recovery",
66
+ ];
67
+ if (
68
+ required.some((key) => !Object.hasOwn(input, key)) ||
69
+ Object.keys(input).some((key) => ![...required, ...optional].includes(key))
70
+ )
71
+ invalid("single-task-draft-invalid");
72
+ const task = structuredClone(
73
+ input,
74
+ ) as unknown as SingleTaskDraft["singleTask"];
75
+ const mode = task.verificationMode ?? "behavior";
76
+ if (mode === "behavior" && (!task.redWrite?.length || !task.expectedFailure))
77
+ invalid("single-task-red-required");
78
+ if (
79
+ mode !== "behavior" &&
80
+ (task.redWrite !== undefined || task.expectedFailure !== undefined)
81
+ )
82
+ invalid("single-task-red-unexpected");
83
+ if (
84
+ Object.hasOwn(input, "recovery") &&
85
+ (!record(task.recovery) ||
86
+ !Object.hasOwn(task.recovery, "artifactAttempts") ||
87
+ !Object.hasOwn(task.recovery, "repairAttempts") ||
88
+ Object.keys(task.recovery).some(
89
+ (key) => !["artifactAttempts", "repairAttempts"].includes(key),
90
+ ))
91
+ )
92
+ invalid("single-task-recovery-invalid");
93
+ return {
94
+ changeId: value.changeId,
95
+ ...(value.changeContract === undefined
96
+ ? {}
97
+ : { changeContract: value.changeContract }),
98
+ tasks: [
99
+ {
100
+ taskId: task.taskId,
101
+ objective: task.objective,
102
+ context: task.context,
103
+ roots: task.roots,
104
+ read: task.read,
105
+ dependsOn: [],
106
+ verificationMode: mode,
107
+ phases: {
108
+ ...(mode === "behavior"
109
+ ? {
110
+ red: {
111
+ write: task.redWrite,
112
+ delete: [],
113
+ verification: {
114
+ ...task.verification,
115
+ expectedFailure: task.expectedFailure,
116
+ },
117
+ },
118
+ }
119
+ : {}),
120
+ green: {
121
+ write: task.greenWrite,
122
+ delete: [],
123
+ verification: task.verification,
124
+ },
125
+ },
126
+ affectedVerification: task.fullSuite,
127
+ repairVerification: task.fullSuite,
128
+ scheduling: { conflicts: [], resources: [] },
129
+ agents: task.agents,
130
+ approvedDependencies: [],
131
+ impactClosure: task.impactClosure,
132
+ },
133
+ ],
134
+ outputs: [],
135
+ verification: {
136
+ baseline: { fullSuite: task.fullSuite },
137
+ change: { fullSuite: task.fullSuite, postApply: task.fullSuite },
138
+ artifactCorrection: { maxAttempts: task.recovery?.artifactAttempts ?? 2 },
139
+ repair: { maxAttempts: task.recovery?.repairAttempts ?? 1 },
140
+ },
141
+ };
142
+ }
@@ -12,8 +12,11 @@ import {
12
12
  import type {
13
13
  PacketActivityEvent,
14
14
  PacketActivityObserver,
15
- PacketActivityState,
16
- } from "./packet-runtime.ts";
15
+ } from "./activity-contracts.ts";
16
+ import {
17
+ projectDesignDiagnostic,
18
+ type SafeDesignDiagnostic,
19
+ } from "./design-diagnostics.ts";
17
20
 
18
21
  export const ACTIVITY_DETAILS_KEY = "activityDisplay" as const;
19
22
  export const ACTIVITY_WIDGET_KEY = "abel-subagents" as const;
@@ -22,51 +25,20 @@ export const ACTIVITY_REFRESH_MS = 100;
22
25
  export const ACTIVITY_WIDGET_MAX_LINES = 12;
23
26
  export const ACTIVITY_WIDGET_MIN_WIDTH = 3;
24
27
 
25
- export const WORKFLOW_ACTIVITY_STATES = [
26
- "queued",
27
- "connecting",
28
- "waiting-first-response",
29
- "running",
30
- "validating",
31
- "retrying",
32
- "verifying",
33
- "paused",
34
- "approval-needed",
35
- "applying",
36
- "recovering",
37
- "operation-cancelled",
38
- "discarded",
39
- "rejected",
40
- "completed",
41
- ] as const;
42
-
43
- export type WorkflowActivityState = (typeof WORKFLOW_ACTIVITY_STATES)[number];
44
- export type ActivityState = PacketActivityState | WorkflowActivityState;
45
- export type TerminalActivityState =
46
- | Exclude<
47
- PacketActivityState,
48
- | "queued"
49
- | "connecting"
50
- | "waiting-first-response"
51
- | "running"
52
- | "retrying"
53
- >
54
- | WorkflowActivityState;
55
-
56
- export interface WorkflowActivityUpdate {
57
- state: WorkflowActivityState;
58
- stage?: "abel-design" | "abel-implement";
59
- runId?: string;
60
- change?: string;
61
- taskId?: string;
62
- phase?: string;
63
- objective?: string;
64
- code?: string;
65
- attempt?: number;
66
- maxAttempts?: number;
67
- wait?: string;
68
- legalCommands?: string[];
69
- }
28
+ export type {
29
+ ActivityState,
30
+ TerminalActivityState,
31
+ WorkflowActivityState,
32
+ WorkflowActivityUpdate,
33
+ } from "./activity-contracts.ts";
34
+ export { WORKFLOW_ACTIVITY_STATES } from "./activity-contracts.ts";
35
+
36
+ import type {
37
+ ActivityState,
38
+ TerminalActivityState,
39
+ WorkflowActivityState,
40
+ WorkflowActivityUpdate,
41
+ } from "./activity-contracts.ts";
70
42
 
71
43
  export interface EvidenceActivitySummary {
72
44
  kind: "evidence";
@@ -224,7 +196,7 @@ export function sanitizeFailureReason(value: unknown, maxLength = 240): string {
224
196
  function safeDesignFailure(value: unknown): {
225
197
  operation: string;
226
198
  code: string;
227
- diagnostics: Array<Record<string, string>>;
199
+ diagnostics: SafeDesignDiagnostic[];
228
200
  } | null {
229
201
  const failure = asRecord(value);
230
202
  if (
@@ -237,23 +209,9 @@ function safeDesignFailure(value: unknown): {
237
209
  ) {
238
210
  return null;
239
211
  }
240
- const diagnostics = failure.diagnostics.flatMap((candidate) => {
241
- const record = asRecord(candidate);
242
- if (
243
- !record ||
244
- typeof record.code !== "string" ||
245
- !SAFE_DESIGN_DETAIL_VALUE.test(record.code)
246
- ) {
247
- return [];
248
- }
249
- return [
250
- Object.fromEntries(
251
- Object.entries(record).filter(
252
- ([, entry]) =>
253
- typeof entry === "string" && SAFE_DESIGN_DETAIL_VALUE.test(entry),
254
- ),
255
- ) as Record<string, string>,
256
- ];
212
+ const diagnostics = failure.diagnostics.slice(0, 64).flatMap((candidate) => {
213
+ const diagnostic = projectDesignDiagnostic(candidate);
214
+ return diagnostic ? [diagnostic] : [];
257
215
  });
258
216
  return {
259
217
  operation: failure.operation,
@@ -286,7 +244,10 @@ function renderDesignFailure(
286
244
  const lines = failure.diagnostics.map((diagnostic) => {
287
245
  const details = Object.entries(diagnostic)
288
246
  .filter(([key]) => key !== "code")
289
- .map(([key, value]) => `${key}=${value}`)
247
+ .map(
248
+ ([key, value]) =>
249
+ `${key}=${Array.isArray(value) ? JSON.stringify(value) : value}`,
250
+ )
290
251
  .join(" · ");
291
252
  return ` - ${diagnostic.code}${details ? ` · ${details}` : ""}`;
292
253
  });
@@ -315,6 +276,7 @@ function stateGlyph(state: ActivityState, spinnerFrame = 0): string {
315
276
  switch (state) {
316
277
  case "queued":
317
278
  return "…";
279
+ case "preparing":
318
280
  case "connecting":
319
281
  case "waiting-first-response":
320
282
  case "running":
@@ -563,6 +525,8 @@ function workflowStateOf(
563
525
  case "ready":
564
526
  case "queued":
565
527
  return "queued";
528
+ case "preparing":
529
+ return "preparing";
566
530
  case "connecting":
567
531
  return "connecting";
568
532
  case "waiting-first-response":
@@ -681,6 +645,7 @@ function workflowWait(
681
645
  ) {
682
646
  return approval.gate;
683
647
  }
648
+ if (state === "preparing") return "worker-setup";
684
649
  if (state === "connecting") return "connection";
685
650
  if (state === "waiting-first-response") return "first-response";
686
651
  if (state === "retrying") return "bounded-policy";
@@ -867,6 +832,7 @@ function terminalState(
867
832
  } {
868
833
  return ![
869
834
  "queued",
835
+ "preparing",
870
836
  "connecting",
871
837
  "waiting-first-response",
872
838
  "running",
@@ -17,65 +17,16 @@ import {
17
17
  validateDiffResult,
18
18
  validateEvidenceResult,
19
19
  } from "./contracts.ts";
20
+ import {
21
+ completeEvidenceDraft,
22
+ evidenceDraftSchema,
23
+ } from "./evidence-draft.ts";
20
24
  import {
21
25
  type BeginCandidateInput,
22
26
  TASK_LEDGER_LIMITS,
23
27
  type TaskLedger,
24
28
  } from "./task-ledger.ts";
25
29
 
26
- const compactEvidenceSchema = Type.Object({
27
- id: Type.String(),
28
- role: Type.String(),
29
- kind: Type.Literal("evidence"),
30
- conclusions: Type.Array(Type.String()),
31
- citations: Type.Array(
32
- Type.Object({ path: Type.String(), lines: Type.String() }),
33
- ),
34
- constraints: Type.Array(Type.String()),
35
- dependencies: Type.Array(Type.String()),
36
- risks: Type.Array(Type.String()),
37
- blockingQuestions: Type.Array(Type.String()),
38
- hints: Type.Object({
39
- writeSet: Type.Array(Type.String()),
40
- verification: Type.String(),
41
- agentsImpact: Type.String(),
42
- }),
43
- });
44
-
45
- const designEvidenceSchema = Type.Object(
46
- {
47
- id: Type.String(),
48
- role: Type.Literal("design-explorer"),
49
- kind: Type.Literal("evidence"),
50
- packet_id: Type.String(),
51
- module_name: Type.String(),
52
- scope: Type.Array(Type.String()),
53
- files_read: Type.Array(Type.String()),
54
- evidence: Type.Array(
55
- Type.Object(
56
- {
57
- claim: Type.String(),
58
- path: Type.String(),
59
- line_start: Type.Integer({ minimum: 1 }),
60
- line_end: Type.Integer({ minimum: 1 }),
61
- },
62
- { additionalProperties: false },
63
- ),
64
- ),
65
- existing_structures: Type.Array(Type.String()),
66
- existing_conventions: Type.Array(Type.String()),
67
- constraints_discovered: Type.Array(Type.String()),
68
- open_questions: Type.Array(Type.String()),
69
- dependencies: Type.Array(Type.String()),
70
- write_set_hints: Type.Array(Type.String()),
71
- validation_hints: Type.Array(Type.String()),
72
- agents_impact_hints: Type.Array(Type.String()),
73
- risks: Type.Array(Type.String()),
74
- success_criteria_hints: Type.Array(Type.String()),
75
- },
76
- { additionalProperties: false },
77
- );
78
-
79
30
  const diffSchema = Type.Object({
80
31
  id: Type.String(),
81
32
  role: Type.String(),
@@ -881,13 +832,11 @@ export function createSubmitTool(input: {
881
832
  name: "abel_submit_result",
882
833
  label: "Submit Abel Result",
883
834
  description:
884
- "Submit the one final structured Abel evidence or complete unified-diff result.",
835
+ "Submit one accepted final result. Evidence identity and omitted advisory fields are completed by this tool; keep citations, constraints, risks, and open questions explicit. After a rejection, correct once in this same session. Brief accompanying text is harmless.",
885
836
  executionMode: "sequential",
886
837
  parameters:
887
838
  input.output === "evidence"
888
- ? input.role === "design-explorer"
889
- ? designEvidenceSchema
890
- : compactEvidenceSchema
839
+ ? evidenceDraftSchema(input.requestId, input.role)
891
840
  : diffSchema,
892
841
  async execute(_toolCallId, params) {
893
842
  attempts++;
@@ -916,7 +865,11 @@ export function createSubmitTool(input: {
916
865
  phase: true,
917
866
  };
918
867
  failure = undefined;
919
- const value = params as unknown as Record<string, unknown>;
868
+ const value = (
869
+ input.output === "evidence"
870
+ ? completeEvidenceDraft(params, input.requestId, input.role)
871
+ : params
872
+ ) as Record<string, unknown>;
920
873
  if (value === null || typeof value !== "object") {
921
874
  identity.request = false;
922
875
  identity.role = false;
@@ -963,7 +916,15 @@ export function createSubmitTool(input: {
963
916
  stage: "structural-submit",
964
917
  });
965
918
  throw new Error(
966
- validation.reason ?? "submitted result identity does not match",
919
+ JSON.stringify({
920
+ code: "code" in failure ? failure.code : failure.kind,
921
+ reason:
922
+ validation.reason ?? "submitted result identity does not match",
923
+ identityMismatch: Object.entries(identity)
924
+ .filter(([, matches]) => !matches)
925
+ .map(([field]) => field),
926
+ remainingCorrections: Math.max(0, 2 - attempts),
927
+ }),
967
928
  );
968
929
  }
969
930
  submitted = structuredClone(value) as unknown as
@@ -0,0 +1,52 @@
1
+ /** Request budgets describe observable model progress, never TCP/TLS events. */
2
+ export const REQUEST_BOUNDS = Object.freeze({
3
+ firstProgressMs: 90_000,
4
+ streamIdleMs: 180_000,
5
+ cancellationDrainMs: 250,
6
+ });
7
+ export type TransportTimeoutCode =
8
+ | "first-progress-timeout"
9
+ | "stream-idle-timeout"
10
+ | "attempt-timeout";
11
+ export class TransportTimeout extends Error {
12
+ readonly scope: "request" | "attempt";
13
+ constructor(readonly code: TransportTimeoutCode) {
14
+ super(code);
15
+ this.name = "TransportTimeout";
16
+ this.scope = code === "attempt-timeout" ? "attempt" : "request";
17
+ }
18
+ }
19
+ export function isTransportTimeoutCode(
20
+ code: string,
21
+ ): code is TransportTimeoutCode {
22
+ return [
23
+ "first-progress-timeout",
24
+ "stream-idle-timeout",
25
+ "attempt-timeout",
26
+ ].includes(code);
27
+ }
28
+ export function transportFailureError(code: string): Error {
29
+ return isTransportTimeoutCode(code)
30
+ ? new TransportTimeout(code)
31
+ : new Error(
32
+ code === "child-provider-rate-limited" ? code : "transport-failure",
33
+ );
34
+ }
35
+
36
+ /** Snapshot at the start of each request; Workers cannot override these values. */
37
+ export function requestBounds(env: NodeJS.ProcessEnv = process.env) {
38
+ const read = (name: string, fallback: number) => {
39
+ const value = Number(env[name] ?? fallback);
40
+ if (!Number.isSafeInteger(value) || value < 1 || value > 1_200_000)
41
+ throw new Error("request-budget-invalid");
42
+ return value;
43
+ };
44
+ return {
45
+ ...REQUEST_BOUNDS,
46
+ firstProgressMs: read(
47
+ "ABEL_FIRST_PROGRESS_MS",
48
+ REQUEST_BOUNDS.firstProgressMs,
49
+ ),
50
+ streamIdleMs: read("ABEL_STREAM_IDLE_MS", REQUEST_BOUNDS.streamIdleMs),
51
+ };
52
+ }
@@ -16,6 +16,7 @@ import {
16
16
  verificationInputPaths,
17
17
  verificationSteps,
18
18
  } from "./contracts.ts";
19
+ import { executionProfile } from "./execution-profile.ts";
19
20
  import {
20
21
  type Bound,
21
22
  isCurrent,
@@ -828,9 +829,10 @@ function validateAtomicCapability(
828
829
  continue;
829
830
  if (
830
831
  lstatSync(path.join(root, relative)).size > 64 * 1024 ||
831
- unsupportedExecutionConfiguration(
832
- readFileSync(path.join(root, relative), "utf8"),
833
- )
832
+ (executionProfile().mode !== "local-trusted" &&
833
+ unsupportedExecutionConfiguration(
834
+ readFileSync(path.join(root, relative), "utf8"),
835
+ ))
834
836
  ) {
835
837
  return adapterFailure(
836
838
  step.id,
@@ -0,0 +1,76 @@
1
+ import type {
2
+ VerificationFailureSummary,
3
+ VerificationObservation,
4
+ } from "./contracts.ts";
5
+
6
+ /** Diagnostic text is evidence for repair, never a failure identity or authority. */
7
+
8
+ export function diagnosticText(value: string, maximum = 4096): string {
9
+ const clean = value
10
+ .replace(
11
+ new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*[A-Za-z]`, "gu"),
12
+ "",
13
+ )
14
+ .replace(/(Bearer\s+)\S+/giu, "$1[redacted]")
15
+ .replace(
16
+ /((?:api[_-]?key|token|password|secret)\s*[:=]\s*)[^\s,;]+/giu,
17
+ "$1[redacted]",
18
+ )
19
+ .replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/giu, "$1[redacted]@");
20
+ return clean.length <= maximum
21
+ ? clean
22
+ : `${clean.slice(0, Math.floor(maximum / 2))}\n…[truncated]…\n${clean.slice(-Math.floor(maximum / 2))}`;
23
+ }
24
+
25
+ /** Bounded operation-local feedback; no raw transcripts or global cross-run cache. */
26
+ export class VerificationFeedback {
27
+ readonly #entries = new Map<string, VerificationFailureSummary>();
28
+ observe(result: VerificationObservation): void {
29
+ if (result.kind === "cancelled") return;
30
+ const id =
31
+ result.kind === "unavailable"
32
+ ? result.verificationId
33
+ : result.evidence.id;
34
+ this.#entries.delete(id);
35
+ if (result.kind === "accepted") return;
36
+ const summary = result.diagnostic ?? {
37
+ verificationId: id,
38
+ code: result.code,
39
+ failures: [],
40
+ stdout: "",
41
+ stderr: "",
42
+ truncated: false,
43
+ nextStep:
44
+ "Repair the verification environment or adapter before changing product code.",
45
+ };
46
+ this.#entries.set(id, structuredClone(summary));
47
+ const oldest = this.#entries.keys().next().value;
48
+ if (this.#entries.size > 32 && oldest !== undefined)
49
+ this.#entries.delete(oldest);
50
+ }
51
+ current(): VerificationFailureSummary[] {
52
+ const result: VerificationFailureSummary[] = [];
53
+ let bytes = 0;
54
+ for (const entry of [...this.#entries.values()].reverse()) {
55
+ const summary = {
56
+ ...entry,
57
+ failures: entry.failures
58
+ .slice(0, 4)
59
+ .map((text) => diagnosticText(text, 256)),
60
+ stdout: diagnosticText(entry.stdout, 512),
61
+ stderr: diagnosticText(entry.stderr, 1024),
62
+ truncated:
63
+ entry.truncated ||
64
+ entry.failures.length > 4 ||
65
+ entry.failures.some((text) => text.length > 256) ||
66
+ entry.stdout.length > 512 ||
67
+ entry.stderr.length > 1024,
68
+ };
69
+ const size = Buffer.byteLength(JSON.stringify(summary), "utf8");
70
+ if (bytes + size > 6000) break;
71
+ result.push(summary);
72
+ bytes += size;
73
+ }
74
+ return structuredClone(result.reverse());
75
+ }
76
+ }