jev-agent-tools 0.1.3

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 (95) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/LICENSE +21 -0
  3. package/README.md +119 -0
  4. package/docs/design.md +187 -0
  5. package/docs/tools/jev_ask.md +92 -0
  6. package/docs/tools/jev_ask_files.md +80 -0
  7. package/docs/tools/jev_check_diff.md +77 -0
  8. package/docs/tools/jev_find_files.md +59 -0
  9. package/docs/tools/jev_locate_in_file.md +51 -0
  10. package/docs/tools/jev_select_tests.md +59 -0
  11. package/package.json +85 -0
  12. package/rules/jev-ask.md +4 -0
  13. package/src/adapters/analysis-context.ts +100 -0
  14. package/src/adapters/ask-files.ts +236 -0
  15. package/src/adapters/ask-proof.ts +202 -0
  16. package/src/adapters/ask-syntax.ts +463 -0
  17. package/src/adapters/command.ts +240 -0
  18. package/src/adapters/docs.ts +222 -0
  19. package/src/adapters/files.ts +411 -0
  20. package/src/adapters/find.ts +151 -0
  21. package/src/adapters/git-base.ts +32 -0
  22. package/src/adapters/git-inventory.ts +94 -0
  23. package/src/adapters/git.ts +525 -0
  24. package/src/adapters/locate-file.ts +197 -0
  25. package/src/adapters/output-lines.ts +50 -0
  26. package/src/adapters/risk-callers.ts +525 -0
  27. package/src/adapters/runner-version.ts +102 -0
  28. package/src/adapters/syntax.ts +229 -0
  29. package/src/adapters/test-inventory.ts +168 -0
  30. package/src/adapters/usage.ts +21 -0
  31. package/src/adapters/utf8.ts +57 -0
  32. package/src/constants.ts +109 -0
  33. package/src/core/ask-closure.ts +419 -0
  34. package/src/core/ask-proof.ts +32 -0
  35. package/src/core/ask-references.ts +249 -0
  36. package/src/core/asks.ts +616 -0
  37. package/src/core/batches.ts +83 -0
  38. package/src/core/command-output.ts +249 -0
  39. package/src/core/diff.ts +226 -0
  40. package/src/core/docs.ts +399 -0
  41. package/src/core/find.ts +157 -0
  42. package/src/core/git.ts +5 -0
  43. package/src/core/import-boundaries.ts +102 -0
  44. package/src/core/imports.ts +691 -0
  45. package/src/core/integrity.ts +64 -0
  46. package/src/core/lexical.ts +130 -0
  47. package/src/core/locate.ts +213 -0
  48. package/src/core/output.ts +264 -0
  49. package/src/core/pointer.ts +51 -0
  50. package/src/core/risk-callers.ts +1270 -0
  51. package/src/core/runner-version.ts +66 -0
  52. package/src/core/sections.ts +269 -0
  53. package/src/core/state.ts +53 -0
  54. package/src/core/syntax.ts +8 -0
  55. package/src/core/test-commands.ts +430 -0
  56. package/src/core/test-coverage.ts +103 -0
  57. package/src/core/test-discovery.ts +1695 -0
  58. package/src/core/test-evidence.ts +649 -0
  59. package/src/core/test-state.ts +99 -0
  60. package/src/core/truncate.ts +14 -0
  61. package/src/core/units.ts +531 -0
  62. package/src/describe.ts +26 -0
  63. package/src/guide.ts +42 -0
  64. package/src/host.ts +22 -0
  65. package/src/index.ts +40 -0
  66. package/src/jev/client.ts +505 -0
  67. package/src/jev/pool.ts +60 -0
  68. package/src/jev/types.ts +60 -0
  69. package/src/presets/docs.ts +85 -0
  70. package/src/presets/risk.ts +263 -0
  71. package/src/presets/spec.ts +111 -0
  72. package/src/presets/witnesses.ts +313 -0
  73. package/src/render.ts +45 -0
  74. package/src/result.ts +4 -0
  75. package/src/run-end.ts +157 -0
  76. package/src/runtime.ts +16 -0
  77. package/src/session.ts +106 -0
  78. package/src/texts/ask-files.ts +2 -0
  79. package/src/texts/ask.ts +4 -0
  80. package/src/texts/check-diff.ts +24 -0
  81. package/src/texts/configuration.ts +2 -0
  82. package/src/texts/find.ts +14 -0
  83. package/src/texts/guide.ts +16 -0
  84. package/src/texts/locate.ts +10 -0
  85. package/src/texts/run-end.ts +13 -0
  86. package/src/texts/select-tests.ts +3 -0
  87. package/src/tools/ask-files.ts +263 -0
  88. package/src/tools/ask-schema.ts +116 -0
  89. package/src/tools/ask.ts +925 -0
  90. package/src/tools/check-diff.ts +510 -0
  91. package/src/tools/docs-check.ts +399 -0
  92. package/src/tools/find.ts +529 -0
  93. package/src/tools/locate.ts +369 -0
  94. package/src/tools/select-tests.ts +746 -0
  95. package/src/tools/spec-check.ts +210 -0
@@ -0,0 +1,529 @@
1
+ import { type Static, Type } from "@sinclair/typebox";
2
+ import { collectSearchExcerpts } from "../adapters/files.ts";
3
+ import { prefilter } from "../adapters/find.ts";
4
+ import { shareGitInventory } from "../adapters/git-inventory.ts";
5
+ import { hostUsage } from "../adapters/usage.ts";
6
+ import * as C from "../constants.ts";
7
+ import {
8
+ contentWords,
9
+ findKeywords,
10
+ type RankedFile,
11
+ rankFiles,
12
+ retainFiles,
13
+ } from "../core/find.ts";
14
+ import {
15
+ type Band,
16
+ type BudgetRefusal,
17
+ buildEnvelope,
18
+ type EnvelopeInput,
19
+ } from "../core/output.ts";
20
+ import { needsReverse, pointerQuestion, readPointer } from "../core/pointer.ts";
21
+ import { interpolate } from "../describe.ts";
22
+ import type { GuideContext } from "../guide.ts";
23
+ import type { Judgment, Question, State } from "../jev/types.ts";
24
+ import { renderEnvelope } from "../render.ts";
25
+ import type { ToolDependencies } from "../runtime.ts";
26
+ import { NOT_CONFIGURED } from "../texts/configuration.ts";
27
+ import { FIND_DESCRIPTION } from "../texts/find.ts";
28
+
29
+ export const findParameters = Type.Object(
30
+ {
31
+ goal: Type.String({ minLength: 1 }),
32
+ keywords: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
33
+ scope: Type.Optional(
34
+ Type.Union([Type.String(), Type.Array(Type.String())]),
35
+ ),
36
+ exclude: Type.Optional(Type.Array(Type.String())),
37
+ effort: Type.Optional(
38
+ Type.Union([
39
+ Type.Literal("quick"),
40
+ Type.Literal("default"),
41
+ Type.Literal("thorough"),
42
+ ]),
43
+ ),
44
+ max_calls: Type.Optional(Type.Integer({ minimum: 0 })),
45
+ },
46
+ { additionalProperties: false },
47
+ );
48
+ export type FindArgs = Static<typeof findParameters>;
49
+ export function createFindFilesTool({
50
+ client,
51
+ host,
52
+ runtime,
53
+ exec: execute,
54
+ }: ToolDependencies) {
55
+ return {
56
+ name: "jev_find_files",
57
+ label: "Jev find files",
58
+ description: interpolate(FIND_DESCRIPTION, host.names),
59
+ parameters: findParameters,
60
+ ...(host.isOmp
61
+ ? {
62
+ approval: "read" as const,
63
+ loadMode: "essential" as const,
64
+ defaultInactive: true,
65
+ }
66
+ : {
67
+ promptSnippet:
68
+ "Find the files for a goal described in plain words, ranked, with an entry point",
69
+ promptGuidelines: [
70
+ "jev_find_files: describe the behavior in one sentence of five words or more, not a guessed identifier",
71
+ ],
72
+ }),
73
+ async execute(
74
+ _id: string,
75
+ args: FindArgs,
76
+ signal: AbortSignal | undefined,
77
+ _update: unknown,
78
+ ctx: { cwd: string } & GuideContext,
79
+ ) {
80
+ const started = performance.now();
81
+ const exec = shareGitInventory(execute);
82
+ const totals = {
83
+ calls: 0,
84
+ questions: 0,
85
+ cacheHits: 0,
86
+ cacheRequests: 0,
87
+ usage: { inputTokens: 0, costUsd: 0 },
88
+ };
89
+ const limitations: NonNullable<EnvelopeInput["limitations"]>[number][] =
90
+ [];
91
+ const unchecked: string[] = [];
92
+ let budget: BudgetRefusal | undefined;
93
+ let sent = 0;
94
+ const finish = (input: Omit<EnvelopeInput, "yield">) => {
95
+ const envelope = buildEnvelope({
96
+ ...input,
97
+ limitations: [...limitations, ...(input.limitations ?? [])],
98
+ ...(budget ? { budget, unchecked: [...new Set(unchecked)] } : {}),
99
+ yield: {
100
+ ...totals,
101
+ costUsd: totals.usage.costUsd,
102
+ elapsedMs: performance.now() - started,
103
+ },
104
+ });
105
+ runtime.session.record(envelope);
106
+ runtime.guide.deliver(ctx);
107
+ return {
108
+ content: [{ type: "text" as const, text: renderEnvelope(envelope) }],
109
+ details: totals,
110
+ ...hostUsage(host.isOmp, totals.usage),
111
+ };
112
+ };
113
+ if (!client) return finish({ refusal: NOT_CONFIGURED });
114
+ const words = contentWords(args.goal);
115
+ const short = words.length < C.FIND_GOAL_MIN_WORDS;
116
+ if (short)
117
+ limitations.push({
118
+ fact: `goal has ${words.length} content words: verdicts are capped at unsure`,
119
+ next: "describe the behavior in one sentence",
120
+ });
121
+ const keywords = findKeywords(args.goal, args.keywords);
122
+ const candidateLimit =
123
+ args.effort === "quick"
124
+ ? C.FIND_QUICK_CANDIDATES
125
+ : args.effort === "thorough"
126
+ ? C.FIND_THOROUGH_CANDIDATES
127
+ : C.FIND_NAME_CANDIDATES;
128
+ const readLimit =
129
+ args.effort === "quick"
130
+ ? C.FIND_QUICK_READ
131
+ : args.effort === "thorough"
132
+ ? C.FIND_THOROUGH_READ
133
+ : C.FIND_FILES_READ;
134
+ const candidates = await prefilter(
135
+ exec,
136
+ ctx.cwd,
137
+ keywords,
138
+ candidateLimit,
139
+ args.scope,
140
+ args.exclude,
141
+ signal,
142
+ );
143
+ if (!candidates.ok) return finish({ refusal: candidates.error });
144
+ const entry = (
145
+ path: string,
146
+ p: number,
147
+ band: Band,
148
+ alternatives: RankedFile[] = [],
149
+ orderDependent = false,
150
+ orderProbabilities?: readonly [number, number],
151
+ relevant: RankedFile[] = [],
152
+ ): NonNullable<EnvelopeInput["lines"]>[number] => ({
153
+ type: "entry",
154
+ candidate: { label: path, value: { head: path, p } },
155
+ band,
156
+ candidates: alternatives.map((item) => ({
157
+ label: item.path,
158
+ value: { head: item.path, p: item.p },
159
+ })),
160
+ orderDependent,
161
+ orderProbabilities,
162
+ relevant: relevant.map((item) => ({
163
+ label: item.path,
164
+ value: { head: item.path, p: item.p },
165
+ })),
166
+ nextRead:
167
+ path === "none"
168
+ ? []
169
+ : [
170
+ path,
171
+ ...(band === "unsure"
172
+ ? alternatives
173
+ .map((item) => item.path)
174
+ .filter((path) => path !== "none")
175
+ : []),
176
+ ],
177
+ });
178
+ if (!candidates.paths.length) {
179
+ limitations.push({
180
+ fact:
181
+ candidates.scopeFiles === 0
182
+ ? "scope matched 0 files"
183
+ : "no file shares a word with the goal",
184
+ next: "rephrase goal or widen scope",
185
+ });
186
+ return finish({
187
+ lines: [entry("none", 1, short ? "unsure" : "verdict")],
188
+ });
189
+ }
190
+ const judge = async (
191
+ state: State,
192
+ questions: Record<string, Question>,
193
+ ): Promise<Judgment> => {
194
+ const result = await client.judge(state, questions, {
195
+ signal,
196
+ beforeRequest: (count) => {
197
+ if (args.max_calls !== undefined && sent >= args.max_calls) {
198
+ budget = {
199
+ kind: "max_calls",
200
+ message: `max_calls=${args.max_calls} reached`,
201
+ };
202
+ return { ok: false, error: budget.message };
203
+ }
204
+ const admission = runtime.session.admit(count);
205
+ if (!admission.ok) {
206
+ budget = { kind: "session", message: admission.error };
207
+ return admission;
208
+ }
209
+ sent++;
210
+ return admission;
211
+ },
212
+ onUsage: (usage) => runtime.session.recordUsage(usage),
213
+ });
214
+ for (const key of [
215
+ "calls",
216
+ "questions",
217
+ "cacheHits",
218
+ "cacheRequests",
219
+ ] as const)
220
+ totals[key] += result[key] ?? 0;
221
+ totals.usage.inputTokens += result.usage?.inputTokens ?? 0;
222
+ totals.usage.costUsd += result.usage?.costUsd ?? 0;
223
+ return result;
224
+ };
225
+ const boolQuestions = (
226
+ paths: string[],
227
+ content: boolean,
228
+ ): Record<string, Question> =>
229
+ Object.fromEntries(
230
+ paths.map((path) => [
231
+ path,
232
+ {
233
+ type: "bool",
234
+ instructions: content
235
+ ? `Does the passage from ${path} implement or document behavior that helps with this goal: ${args.goal}?`
236
+ : `Could the file ${path} help with this goal: ${args.goal}?`,
237
+ criteria: {
238
+ true: "The file could help with the goal",
239
+ false: "The file does not help with the goal",
240
+ },
241
+ },
242
+ ]),
243
+ );
244
+ const names: RankedFile[] = [];
245
+ const nameBatches = Array.from(
246
+ { length: Math.ceil(candidates.paths.length / C.FIND_NAME_BATCH) },
247
+ (_, index) =>
248
+ candidates.paths.slice(
249
+ index * C.FIND_NAME_BATCH,
250
+ (index + 1) * C.FIND_NAME_BATCH,
251
+ ),
252
+ );
253
+ const nameResults = await Promise.all(
254
+ nameBatches.map(async (paths) => ({
255
+ paths,
256
+ result: await judge(
257
+ { goal: args.goal, paths },
258
+ boolQuestions(paths, false),
259
+ ),
260
+ })),
261
+ );
262
+ for (const { paths, result } of nameResults) {
263
+ if (!result.ok && !budget) return finish({ refusal: result.error });
264
+ for (const path of paths) {
265
+ const answer = result.ok ? result.answers[path] : undefined;
266
+ if (answer?.type === "bool") names.push({ path, p: answer.p });
267
+ else unchecked.push(path);
268
+ }
269
+ }
270
+ const rankedNames = rankFiles(names);
271
+ let bestRanking = rankedNames;
272
+ const fallback = () => {
273
+ const top = bestRanking.slice(0, 2);
274
+ return finish({
275
+ lines: [
276
+ entry(
277
+ top[0]?.path ?? "none",
278
+ top[0]?.p ?? 0,
279
+ "unsure",
280
+ top.slice(1),
281
+ ),
282
+ ],
283
+ answers: bestRanking.map((item) => ({
284
+ label: item.path,
285
+ value: { head: "relevant", p: item.p },
286
+ band: "unsure" as const,
287
+ })),
288
+ });
289
+ };
290
+ if (budget) {
291
+ unchecked.push(
292
+ ...candidates.paths.filter(
293
+ (path) => !names.some((item) => item.path === path),
294
+ ),
295
+ );
296
+ return fallback();
297
+ }
298
+ const readPaths = rankedNames
299
+ .slice(0, readLimit)
300
+ .map((item) => item.path);
301
+ const files: Record<string, string> = {};
302
+ const content: RankedFile[] = [];
303
+ const contentBatches = Array.from(
304
+ { length: Math.ceil(readPaths.length / C.FIND_READ_PER_CALL) },
305
+ (_, index) =>
306
+ readPaths.slice(
307
+ index * C.FIND_READ_PER_CALL,
308
+ (index + 1) * C.FIND_READ_PER_CALL,
309
+ ),
310
+ );
311
+ const contentResults = await Promise.all(
312
+ contentBatches.map(async (paths) => {
313
+ const read = await collectSearchExcerpts(
314
+ ctx.cwd,
315
+ paths,
316
+ keywords,
317
+ signal,
318
+ exec,
319
+ );
320
+ if (!read.ok) return { paths, result: read };
321
+ Object.assign(files, read.pointers);
322
+ limitations.push(
323
+ ...read.limits.map((limit) => ({
324
+ fact: `${limit.path}: ${limit.reason}`,
325
+ next: "provide a UTF-8 text file",
326
+ })),
327
+ );
328
+ if (read.ignored.length)
329
+ limitations.push({
330
+ fact: `${read.ignored.length} binary, empty or unreadable paths ignored: ${read.ignored.join(", ")}`,
331
+ next: "narrow scope to text files",
332
+ });
333
+ const textPaths = paths.filter(
334
+ (path) => read.files[path] !== undefined,
335
+ );
336
+ if (!textPaths.length)
337
+ return { paths: [], result: { ok: true as const, answers: {} } };
338
+ return {
339
+ paths: textPaths,
340
+ result: await judge(
341
+ {
342
+ goal: args.goal,
343
+ files: Object.fromEntries(
344
+ Object.entries(read.files).sort(([a], [b]) =>
345
+ a.localeCompare(b),
346
+ ),
347
+ ),
348
+ },
349
+ boolQuestions(textPaths, true),
350
+ ),
351
+ };
352
+ }),
353
+ );
354
+ for (const { paths, result } of contentResults) {
355
+ if (!result.ok && !budget) return finish({ refusal: result.error });
356
+ for (const path of paths) {
357
+ const answer = result.ok ? result.answers[path] : undefined;
358
+ if (answer?.type === "bool") content.push({ path, p: answer.p });
359
+ else unchecked.push(path);
360
+ }
361
+ }
362
+ if (content.length) bestRanking = rankFiles(content);
363
+ if (budget) {
364
+ unchecked.push(
365
+ ...readPaths.filter(
366
+ (path) => !content.some((item) => item.path === path),
367
+ ),
368
+ );
369
+ return fallback();
370
+ }
371
+ const retained = retainFiles(content, rankedNames);
372
+ limitations.push({
373
+ fact: `cascade: ${names.length} names judged → ${content.length} files read → ${retained.length} shown to the pointer`,
374
+ next: "",
375
+ });
376
+ const relevant = rankFiles(content).filter((item) =>
377
+ retained.some((file) => file.path === item.path),
378
+ );
379
+ const output = (
380
+ path: string,
381
+ p: number,
382
+ band: Band,
383
+ alternatives: RankedFile[] = [],
384
+ orderDependent = false,
385
+ orderProbabilities?: readonly [number, number],
386
+ ) =>
387
+ finish({
388
+ lines: [
389
+ entry(
390
+ path,
391
+ p,
392
+ band,
393
+ alternatives,
394
+ orderDependent,
395
+ orderProbabilities,
396
+ relevant,
397
+ ),
398
+ ],
399
+ });
400
+ if (!retained.length) {
401
+ limitations.push({
402
+ fact: "no file retained by content or names guard",
403
+ next: "rephrase goal or widen scope",
404
+ });
405
+ return output("none", 1, short ? "unsure" : "verdict");
406
+ }
407
+ const idByPath = Object.fromEntries(
408
+ retained.map((item, index) => [item.path, `c${index + 1}`]),
409
+ );
410
+ const pathById = Object.fromEntries(
411
+ retained.map((item, index) => [`c${index + 1}`, item.path]),
412
+ );
413
+ const pointer = async (paths: string[]): Promise<Judgment> => {
414
+ return judge(
415
+ {
416
+ goal: args.goal,
417
+ candidates: paths.map((path) => ({
418
+ id: idByPath[path] ?? "",
419
+ path,
420
+ excerpt: files[path] ?? "",
421
+ })),
422
+ },
423
+ {
424
+ entry: pointerQuestion({
425
+ instructions: `Which candidate is the best entry point for this goal: ${args.goal}? Choose none if no passage helps.`,
426
+ noneLabel: "No candidate implements or documents the goal",
427
+ candidates: paths.map((path) => ({
428
+ id: idByPath[path] ?? "",
429
+ label: `Candidate ${idByPath[path]}`,
430
+ })),
431
+ }),
432
+ },
433
+ );
434
+ };
435
+ const paths = retained.map((item) => item.path);
436
+ const firstResult = await pointer(paths);
437
+ if (!firstResult.ok && !budget)
438
+ return finish({ refusal: firstResult.error });
439
+ const first = firstResult.ok ? firstResult.answers.entry : undefined;
440
+ if (first?.type !== "choice") {
441
+ unchecked.push("entry");
442
+ return fallback();
443
+ }
444
+ const ranked = (probabilities: Record<string, number>) =>
445
+ readPointer(probabilities).ranked.map(({ id, p }) => ({ path: id, p }));
446
+ const firstRanks = ranked(first.probabilities);
447
+ const firstTop = firstRanks[0];
448
+ if (!firstTop)
449
+ return finish({ refusal: "Pointer returned no probabilities." });
450
+ let ranks = firstRanks;
451
+ let band: Band =
452
+ firstTop.p >= C.ORDER_REVERSE_BELOW ? "verdict" : "unsure";
453
+ let disagreement = false;
454
+ let orderDependent = false;
455
+ let orderProbabilities: readonly [number, number] | undefined;
456
+ if (
457
+ retained.length > 1 &&
458
+ (args.effort === "thorough" ||
459
+ (args.effort !== "quick" &&
460
+ needsReverse(first.probabilities, retained.length)))
461
+ ) {
462
+ const secondResult = await pointer([...paths].reverse());
463
+ if (!secondResult.ok && !budget)
464
+ return finish({ refusal: secondResult.error });
465
+ const second = secondResult.ok ? secondResult.answers.entry : undefined;
466
+ if (second?.type !== "choice") {
467
+ unchecked.push("entry confirmation");
468
+ band = "unsure";
469
+ } else {
470
+ const secondRanks = ranked(second.probabilities);
471
+ const combined = readPointer(
472
+ first.probabilities,
473
+ second.probabilities,
474
+ );
475
+ ranks = combined.ranked.map(({ id, p }) => ({ path: id, p }));
476
+ disagreement = firstTop.path !== secondRanks[0]?.path;
477
+ const chosen = ranks[0]?.path ?? firstTop.path;
478
+ orderProbabilities = [
479
+ first.probabilities[chosen] ?? 0,
480
+ second.probabilities[chosen] ?? 0,
481
+ ];
482
+ orderDependent = combined.orderDependent;
483
+ band =
484
+ !disagreement && (ranks[0]?.p ?? 0) >= C.FIND_CONFIRM_MIN
485
+ ? "verdict"
486
+ : "unsure";
487
+ }
488
+ }
489
+ if (disagreement)
490
+ limitations.push({
491
+ fact: "the two orders disagree",
492
+ next: ranks.slice(0, 2).every((item) => item.path !== "none")
493
+ ? "read both"
494
+ : "read the named file if present; rephrase goal or widen scope",
495
+ });
496
+ else if (band === "unsure")
497
+ limitations.push({
498
+ fact: "entry probability below confirmation threshold",
499
+ next: ranks.slice(0, 2).every((item) => item.path !== "none")
500
+ ? "read both"
501
+ : "read the named file if present; rephrase goal or widen scope",
502
+ });
503
+ const top = ranks[0];
504
+ if (!top)
505
+ return finish({ refusal: "Pointer returned no probabilities." });
506
+ if (top.path === "none")
507
+ limitations.push({
508
+ fact: "pointer chose none",
509
+ next: "rephrase goal or widen scope",
510
+ });
511
+ return output(
512
+ top.path === "none" ? "none" : (pathById[top.path] ?? top.path),
513
+ top.p,
514
+ short || budget ? "unsure" : band,
515
+ band === "unsure" || short
516
+ ? ranks.slice(1, 2).map((item) => ({
517
+ path:
518
+ item.path === "none"
519
+ ? "none"
520
+ : (pathById[item.path] ?? item.path),
521
+ p: item.p,
522
+ }))
523
+ : [],
524
+ orderDependent,
525
+ orderProbabilities,
526
+ );
527
+ },
528
+ };
529
+ }