jev-agent-tools 0.2.0 → 0.3.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 (117) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/CONTRIBUTING.md +3 -0
  3. package/README.md +22 -14
  4. package/SECURITY.md +17 -1
  5. package/dist/adapters/ask-files.js +11 -2
  6. package/dist/adapters/ask-proof.js +63 -7
  7. package/dist/adapters/command.js +82 -29
  8. package/dist/adapters/docs.js +30 -10
  9. package/dist/adapters/evidence-context.js +119 -0
  10. package/dist/adapters/files.js +141 -16
  11. package/dist/adapters/find.js +34 -6
  12. package/dist/adapters/git-base.js +7 -1
  13. package/dist/adapters/git.js +51 -7
  14. package/dist/adapters/locate-file.js +47 -9
  15. package/dist/adapters/private-storage.js +14 -6
  16. package/dist/adapters/risk-callers.js +3 -0
  17. package/dist/adapters/shell.js +23 -7
  18. package/dist/adapters/test-inventory.js +10 -2
  19. package/dist/configuration.js +17 -7
  20. package/dist/constants.js +26 -5
  21. package/dist/core/ask-references.js +193 -109
  22. package/dist/core/asks.js +78 -7
  23. package/dist/core/locate.js +8 -8
  24. package/dist/core/output.js +17 -0
  25. package/dist/core/result-report.js +302 -0
  26. package/dist/core/secret-path.js +34 -0
  27. package/dist/core/state.js +8 -1
  28. package/dist/core/units.js +1 -1
  29. package/dist/jev/client.js +34 -12
  30. package/dist/mcp/protocol.js +50 -27
  31. package/dist/mcp/tools.js +20 -7
  32. package/dist/render.js +72 -0
  33. package/dist/report-schema.js +1356 -0
  34. package/dist/result-types.js +1 -0
  35. package/dist/texts/ask-files.js +3 -1
  36. package/dist/texts/ask.js +3 -1
  37. package/dist/texts/check-diff.js +7 -4
  38. package/dist/texts/find.js +7 -2
  39. package/dist/texts/guide.js +3 -16
  40. package/dist/texts/instructions.js +72 -0
  41. package/dist/texts/locate.js +7 -2
  42. package/dist/texts/select-tests.js +3 -1
  43. package/dist/tools/ask-files.js +248 -15
  44. package/dist/tools/ask.js +523 -62
  45. package/dist/tools/check-diff.js +222 -30
  46. package/dist/tools/docs-check.js +122 -13
  47. package/dist/tools/find.js +320 -27
  48. package/dist/tools/locate.js +317 -18
  49. package/dist/tools/review-report.js +230 -0
  50. package/dist/tools/select-tests.js +273 -19
  51. package/dist/tools/spec-check.js +119 -22
  52. package/docs/adr/0001-strict-typescript-pure-core-offline-tests.md +3 -3
  53. package/docs/agent-instructions.md +59 -30
  54. package/docs/design.md +13 -1
  55. package/docs/mcp.md +8 -6
  56. package/docs/tools/jev_ask.md +8 -5
  57. package/docs/tools/jev_ask_files.md +2 -1
  58. package/docs/tools/jev_check_diff.md +4 -1
  59. package/docs/tools/jev_find_files.md +2 -1
  60. package/docs/tools/jev_locate_in_file.md +5 -0
  61. package/docs/tools/jev_select_tests.md +4 -1
  62. package/package.json +1 -1
  63. package/rules/jev-ask.md +22 -1
  64. package/server.json +2 -2
  65. package/src/adapters/ask-files.ts +11 -3
  66. package/src/adapters/ask-proof.ts +69 -11
  67. package/src/adapters/command.ts +96 -33
  68. package/src/adapters/docs.ts +33 -14
  69. package/src/adapters/evidence-context.ts +169 -0
  70. package/src/adapters/files.ts +146 -16
  71. package/src/adapters/find.ts +37 -7
  72. package/src/adapters/git-base.ts +7 -1
  73. package/src/adapters/git.ts +61 -8
  74. package/src/adapters/locate-file.ts +51 -9
  75. package/src/adapters/private-storage.ts +17 -5
  76. package/src/adapters/risk-callers.ts +3 -0
  77. package/src/adapters/shell.ts +23 -7
  78. package/src/adapters/test-inventory.ts +12 -4
  79. package/src/configuration.ts +16 -2
  80. package/src/constants.ts +26 -5
  81. package/src/core/ask-references.ts +262 -146
  82. package/src/core/asks.ts +79 -7
  83. package/src/core/import-boundaries.ts +8 -3
  84. package/src/core/locate.ts +8 -5
  85. package/src/core/output.ts +34 -0
  86. package/src/core/result-report.ts +410 -0
  87. package/src/core/secret-path.ts +37 -0
  88. package/src/core/state.ts +8 -1
  89. package/src/core/units.ts +3 -2
  90. package/src/index.ts +3 -0
  91. package/src/jev/client.ts +54 -16
  92. package/src/jev/types.ts +18 -3
  93. package/src/mcp/protocol.ts +91 -41
  94. package/src/mcp/tools.ts +26 -13
  95. package/src/render.ts +109 -0
  96. package/src/report-schema.ts +1380 -0
  97. package/src/result-types.ts +234 -0
  98. package/src/result.ts +4 -1
  99. package/src/runtime.ts +6 -0
  100. package/src/texts/ask-files.ts +4 -1
  101. package/src/texts/ask.ts +8 -1
  102. package/src/texts/check-diff.ts +7 -4
  103. package/src/texts/find.ts +8 -2
  104. package/src/texts/guide.ts +8 -16
  105. package/src/texts/instructions.ts +98 -0
  106. package/src/texts/locate.ts +8 -2
  107. package/src/texts/run-end.ts +2 -2
  108. package/src/texts/select-tests.ts +4 -1
  109. package/src/tools/ask-files.ts +309 -14
  110. package/src/tools/ask.ts +700 -77
  111. package/src/tools/check-diff.ts +331 -28
  112. package/src/tools/docs-check.ts +241 -39
  113. package/src/tools/find.ts +386 -29
  114. package/src/tools/locate.ts +384 -19
  115. package/src/tools/review-report.ts +308 -0
  116. package/src/tools/select-tests.ts +479 -21
  117. package/src/tools/spec-check.ts +193 -19
@@ -1,5 +1,5 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import { canonicalPath } from "../adapters/canonical-path.js";
2
+ import { resolveEvidenceContext, withEvidenceContext, } from "../adapters/evidence-context.js";
3
3
  import { collectSearchExcerpts } from "../adapters/files.js";
4
4
  import { prefilter } from "../adapters/find.js";
5
5
  import { shareGitInventory } from "../adapters/git-inventory.js";
@@ -8,11 +8,14 @@ import * as C from "../constants.js";
8
8
  import { contentWords, findKeywords, rankFiles, retainFiles, } from "../core/find.js";
9
9
  import { buildEnvelope, } from "../core/output.js";
10
10
  import { needsReverse, pointerQuestion, readPointer } from "../core/pointer.js";
11
+ import { answerItemFromInput, buildResultReport, contextFromEvidence, known, } from "../core/result-report.js";
12
+ import { secretRefusal } from "../core/secret-path.js";
11
13
  import { interpolate } from "../describe.js";
12
- import { renderEnvelope } from "../render.js";
14
+ import { renderResultReport } from "../render.js";
13
15
  import { NOT_CONFIGURED } from "../texts/configuration.js";
14
16
  import { FIND_DESCRIPTION } from "../texts/find.js";
15
17
  export const findParameters = Type.Object({
18
+ root: Type.Optional(Type.String()),
16
19
  goal: Type.String({ minLength: 1 }),
17
20
  keywords: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
18
21
  scope: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])),
@@ -45,7 +48,13 @@ export function createFindFilesTool(dependencies) {
45
48
  }),
46
49
  async execute(_id, args, signal, _update, ctx) {
47
50
  const client = dependencies.client;
48
- const cwd = await canonicalPath(ctx.cwd);
51
+ const evidence = await resolveEvidenceContext(ctx.cwd, args.root, {
52
+ exec: execute,
53
+ signal,
54
+ origin: dependencies.evidenceOrigin,
55
+ });
56
+ const evidenceContext = evidence.context;
57
+ const cwd = evidence.ok ? evidence.cwd : evidenceContext.authority.path;
49
58
  const started = performance.now();
50
59
  const exec = shareGitInventory(execute);
51
60
  const totals = {
@@ -59,6 +68,88 @@ export function createFindFilesTool(dependencies) {
59
68
  const unchecked = [];
60
69
  let budget;
61
70
  let sent = 0;
71
+ const context = contextFromEvidence(evidenceContext);
72
+ const diagnostics = [];
73
+ const actions = [];
74
+ const auxiliary = {
75
+ controls: { fresh: 0, cache: 0, notJudged: 0, static: 0 },
76
+ passages: { fresh: 0, cache: 0, notJudged: 0 },
77
+ };
78
+ let entryPaths = [];
79
+ let costObserved = true;
80
+ let failureCause = "invalid_response";
81
+ let primary;
82
+ let confirmation;
83
+ let confirmationRequired = false;
84
+ const raw = (answer) => answer.type === "choice"
85
+ ? Object.entries(answer.probabilities).map(([label, probability]) => ({
86
+ label,
87
+ value: label,
88
+ probability: known(probability),
89
+ }))
90
+ : [];
91
+ const diagnostic = (cause, fact, members = []) => {
92
+ const id = `d${diagnostics.length + 1}`;
93
+ const actionId = `a${actions.length + 1}`;
94
+ const code = cause === "invalid_root"
95
+ ? "correct_context"
96
+ : cause === "not_configured"
97
+ ? "configure_client"
98
+ : cause === "service_unavailable" || cause === "transport_failure"
99
+ ? "recover_service"
100
+ : "inspect_native";
101
+ actions.push({
102
+ id: actionId,
103
+ code,
104
+ target: known(members.join(", ") || "entry"),
105
+ scope: { kind: "call" },
106
+ condition: code === "correct_context"
107
+ ? "If the requested root is incorrect"
108
+ : code === "configure_client"
109
+ ? "If Jev judgment is still useful after configuring the client"
110
+ : code === "recover_service"
111
+ ? "Only after service recovery is observed"
112
+ : "If the entry decision remains open",
113
+ instruction: code === "correct_context"
114
+ ? "Provide the exact registered worktree top-level; no fallback was performed."
115
+ : code === "configure_client"
116
+ ? "Configure the Jev client, or continue with native discovery."
117
+ : code === "recover_service"
118
+ ? "Continue natively while unavailable; revisit only after a material service change."
119
+ : "Inspect the candidate paths or collected passages natively; retain uncertainty without repeating unchanged evidence.",
120
+ repeatUnchanged: false,
121
+ });
122
+ diagnostics.push({
123
+ id,
124
+ cause,
125
+ fact,
126
+ target: known(members.join(", ") ||
127
+ (cause === "invalid_root"
128
+ ? (args.root ?? cwd)
129
+ : cause === "forbidden_path"
130
+ ? JSON.stringify({ scope: args.scope, exclude: args.exclude })
131
+ : "entry")),
132
+ origin: cause === "invalid_root" || cause === "forbidden_path"
133
+ ? "input"
134
+ : cause === "call_budget" || cause === "session_budget"
135
+ ? "budget"
136
+ : cause === "control_failure"
137
+ ? "control"
138
+ : cause === "service_unavailable" ||
139
+ cause === "transport_failure" ||
140
+ cause === "invalid_response" ||
141
+ cause === "provider_context_refusal" ||
142
+ cause === "not_configured"
143
+ ? "provider"
144
+ : "collection",
145
+ scope: { kind: "call" },
146
+ effect: "blocking",
147
+ material: true,
148
+ omittedMembers: members,
149
+ memberCount: known(members.length),
150
+ actionIds: [actionId],
151
+ });
152
+ };
62
153
  const finish = (input) => {
63
154
  const envelope = buildEnvelope({
64
155
  ...input,
@@ -70,16 +161,140 @@ export function createFindFilesTool(dependencies) {
70
161
  elapsedMs: performance.now() - started,
71
162
  },
72
163
  });
164
+ if (input.refusal)
165
+ diagnostic(failureCause, input.refusal);
166
+ if (!input.refusal &&
167
+ !diagnostics.length &&
168
+ !budget &&
169
+ !unchecked.length &&
170
+ !primary?.source)
171
+ diagnostic("invalid_response", "entry decision was not received with established answer provenance");
172
+ if (budget)
173
+ diagnostic(budget.kind === "max_calls" ? "call_budget" : "session_budget", budget.message, [...new Set(unchecked)]);
174
+ if (unchecked.length && !budget)
175
+ diagnostic(failureCause, "candidate ranking or entry work was not judged", [...new Set(unchecked)]);
176
+ const entryLine = input.lines?.find((line) => line.type === "entry");
177
+ if (entryLine?.type === "entry" && primary?.source)
178
+ actions.push({
179
+ id: `a${actions.length + 1}`,
180
+ code: "inspect_native",
181
+ target: known((entryLine.nextRead ?? []).join(", ") || "candidate inventory"),
182
+ scope: { kind: "group", groupIds: ["entry"] },
183
+ condition: "If the entry decision remains open after this bounded judgment",
184
+ instruction: entryLine.candidate.label === "none"
185
+ ? "Search elsewhere or widen the admitted scope for the goal; inspect any named alternative before collecting genuinely new evidence. Do not retry unchanged evidence."
186
+ : "Read the named candidate passages; add only genuinely discriminating evidence if needed. Do not reword unchanged evidence to retry.",
187
+ repeatUnchanged: false,
188
+ });
189
+ const common = {
190
+ id: "entry",
191
+ kind: "entry",
192
+ label: "entry",
193
+ groupId: "entry",
194
+ evidence: entryPaths.map((path) => ({
195
+ target: path,
196
+ canonicalPath: known(`${cwd}/${path}`),
197
+ aliases: [],
198
+ side: "current",
199
+ revision: context.resolvedBase,
200
+ })),
201
+ diagnosticIds: diagnostics.map((d) => d.id),
202
+ actionIds: actions.map((a) => a.id),
203
+ };
204
+ let item = { ...common, treatment: "not_judged", source: "none" };
205
+ if (entryLine?.type === "entry" &&
206
+ primary?.type === "choice" &&
207
+ primary.source &&
208
+ (!confirmationRequired ||
209
+ (confirmation?.type === "choice" && confirmation.source))) {
210
+ auxiliary.passages[primary.source]--;
211
+ item = answerItemFromInput(common, {
212
+ label: "entry",
213
+ answer: primary,
214
+ value: entryLine.candidate.value,
215
+ publicResult: entryLine.candidate.value.head,
216
+ band: entryLine.band,
217
+ reason: limitations.map((limit) => limit.fact).join("; "),
218
+ rawValues: raw(primary),
219
+ reportControls: confirmation?.type === "choice" && confirmation.source
220
+ ? [
221
+ {
222
+ id: "entry-order",
223
+ kind: "order",
224
+ source: confirmation.source,
225
+ outcome: entryLine.orderDependent
226
+ ? "order-dependent"
227
+ : "consistent",
228
+ rawValues: raw(confirmation),
229
+ },
230
+ ]
231
+ : [],
232
+ });
233
+ }
234
+ const report = buildResultReport({
235
+ tool: "jev_find_files",
236
+ context,
237
+ items: [item],
238
+ diagnostics,
239
+ actions,
240
+ metrics: {
241
+ ...totals,
242
+ costUsd: costObserved ? totals.usage.costUsd : undefined,
243
+ elapsedMs: performance.now() - started,
244
+ },
245
+ auxiliary,
246
+ total: known(1),
247
+ refused: Boolean(input.refusal &&
248
+ (failureCause === "invalid_root" ||
249
+ failureCause === "forbidden_path")),
250
+ missingWork: unchecked.length > 0,
251
+ });
73
252
  runtime.session.record(envelope);
74
253
  runtime.guide.deliver(ctx);
75
254
  return {
76
- content: [{ type: "text", text: renderEnvelope(envelope) }],
77
- details: totals,
255
+ content: [
256
+ {
257
+ type: "text",
258
+ text: renderResultReport(report, {
259
+ details: {
260
+ lines: [
261
+ ...envelope.lines,
262
+ ...(entryLine?.type === "entry"
263
+ ? [
264
+ {
265
+ type: "list",
266
+ title: "read",
267
+ items: [...(entryLine.nextRead ?? [])],
268
+ },
269
+ {
270
+ type: "list",
271
+ title: "auxiliary ranking",
272
+ items: (entryLine.relevant ?? []).map((candidate) => `${candidate.label} (${candidate.value.p})`),
273
+ },
274
+ ]
275
+ : []),
276
+ {
277
+ type: "list",
278
+ title: "discovery reservations",
279
+ items: [...limitations, ...(input.limitations ?? [])].map((limit) => limit.fact),
280
+ },
281
+ ],
282
+ },
283
+ }),
284
+ },
285
+ ],
286
+ details: { ...totals, evidenceContext, result: report },
78
287
  ...hostUsage(host.isOmp, totals.usage),
79
288
  };
80
289
  };
81
- if (!client)
290
+ if (!evidence.ok) {
291
+ failureCause = evidence.cause ?? "invalid_root";
292
+ return finish({ refusal: evidence.error });
293
+ }
294
+ if (!client) {
295
+ failureCause = "not_configured";
82
296
  return finish({ refusal: NOT_CONFIGURED });
297
+ }
83
298
  const words = contentWords(args.goal);
84
299
  const short = words.length < C.FIND_GOAL_MIN_WORDS;
85
300
  if (short)
@@ -99,8 +314,30 @@ export function createFindFilesTool(dependencies) {
99
314
  ? C.FIND_THOROUGH_READ
100
315
  : C.FIND_FILES_READ;
101
316
  const candidates = await prefilter(exec, cwd, keywords, candidateLimit, args.scope, args.exclude, signal);
102
- if (!candidates.ok)
317
+ if (!candidates.ok) {
318
+ failureCause = candidates.cause ?? "git_failure";
103
319
  return finish({ refusal: candidates.error });
320
+ }
321
+ for (const path of candidates.secret)
322
+ diagnostic("secret_pattern", secretRefusal(path), [path]);
323
+ context.inventories.push({
324
+ id: "candidates",
325
+ kind: "repository",
326
+ rules: [
327
+ "Git tracked and non-ignored untracked files",
328
+ "keyword prefilter with existing effort limit",
329
+ ],
330
+ restrictions: [
331
+ ...(typeof args.scope === "string"
332
+ ? [args.scope]
333
+ : (args.scope ?? [])),
334
+ ...(args.exclude ?? []).map((path) => `exclude ${path}`),
335
+ ],
336
+ discovered: known(candidates.scopeFiles),
337
+ considered: known(candidates.paths.length),
338
+ scopeRestricted: Boolean(args.scope || args.exclude?.length),
339
+ criteria: [],
340
+ });
104
341
  const entry = (path, p, band, alternatives = [], orderDependent = false, orderProbabilities, relevant = []) => ({
105
342
  type: "entry",
106
343
  candidate: { label: path, value: { head: path, p } },
@@ -127,17 +364,18 @@ export function createFindFilesTool(dependencies) {
127
364
  ],
128
365
  });
129
366
  if (!candidates.paths.length) {
130
- limitations.push({
131
- fact: candidates.scopeFiles === 0
132
- ? "scope matched 0 files"
133
- : "no file shares a word with the goal",
134
- next: "rephrase goal or widen scope",
135
- });
136
- return finish({
137
- lines: [entry("none", 1, short ? "unsure" : "verdict")],
138
- });
367
+ diagnostic("collection_empty", candidates.scopeFiles === 0
368
+ ? "scope matched 0 files; no entry judgment"
369
+ : "no file shares a word with the goal; no entry judgment");
370
+ return finish({});
139
371
  }
140
372
  const judge = async (state, questions) => {
373
+ state = withEvidenceContext(state, evidenceContext);
374
+ if (JSON.stringify(state).length > C.STATE_MAX_CHARS)
375
+ return {
376
+ ok: false,
377
+ error: `required evidence exceeds STATE_MAX_CHARS=${C.STATE_MAX_CHARS}`,
378
+ };
141
379
  const result = await client.judge(state, questions, {
142
380
  signal,
143
381
  ...runtime.session.requestGate(),
@@ -158,6 +396,7 @@ export function createFindFilesTool(dependencies) {
158
396
  return admission;
159
397
  },
160
398
  onUsage: (usage) => runtime.session.recordUsage(usage),
399
+ admissionCause: () => budget?.kind === "max_calls" ? "call_budget" : "session_budget",
161
400
  });
162
401
  for (const key of [
163
402
  "calls",
@@ -167,7 +406,25 @@ export function createFindFilesTool(dependencies) {
167
406
  ])
168
407
  totals[key] += result[key] ?? 0;
169
408
  totals.usage.inputTokens += result.usage?.inputTokens ?? 0;
409
+ if (result.calls && !result.usage)
410
+ costObserved = false;
411
+ if (!result.ok) {
412
+ failureCause = result.failureCause ?? "invalid_response";
413
+ auxiliary.passages.notJudged += Object.keys(questions).length;
414
+ }
170
415
  totals.usage.costUsd += result.usage?.costUsd ?? 0;
416
+ if (result.ok)
417
+ for (const id of Object.keys(questions)) {
418
+ const answer = result.answers[id];
419
+ if (answer?.type === "unjudged") {
420
+ failureCause = answer.cause ?? "invalid_response";
421
+ auxiliary.passages.notJudged++;
422
+ }
423
+ else if (answer?.source)
424
+ auxiliary.passages[answer.source]++;
425
+ else
426
+ auxiliary.passages.notJudged++;
427
+ }
171
428
  return result;
172
429
  };
173
430
  const boolQuestions = (paths, content) => Object.fromEntries(paths.map((path) => [
@@ -203,6 +460,12 @@ export function createFindFilesTool(dependencies) {
203
460
  const rankedNames = rankFiles(names);
204
461
  let bestRanking = rankedNames;
205
462
  const fallback = () => {
463
+ primary = undefined;
464
+ if (bestRanking.length)
465
+ limitations.push({
466
+ fact: `auxiliary ranking retained: ${bestRanking.map((item) => `${item.path} (${item.p})`).join(" · ")}; entry was not judged`,
467
+ next: "inspect these paths natively",
468
+ });
206
469
  const top = bestRanking.slice(0, 2);
207
470
  return finish({
208
471
  lines: [
@@ -227,18 +490,23 @@ export function createFindFilesTool(dependencies) {
227
490
  const contentBatches = Array.from({ length: Math.ceil(readPaths.length / C.FIND_READ_PER_CALL) }, (_, index) => readPaths.slice(index * C.FIND_READ_PER_CALL, (index + 1) * C.FIND_READ_PER_CALL));
228
491
  const contentResults = await Promise.all(contentBatches.map(async (paths) => {
229
492
  const read = await collectSearchExcerpts(cwd, paths, keywords, signal, exec);
230
- if (!read.ok)
493
+ if (!read.ok) {
494
+ failureCause = read.cause ?? "file_unavailable";
231
495
  return { paths, result: read };
496
+ }
232
497
  Object.assign(files, read.pointers);
233
498
  limitations.push(...read.limits.map((limit) => ({
234
499
  fact: `${limit.path}: ${limit.reason}`,
235
500
  next: "provide a UTF-8 text file",
236
501
  })));
237
- if (read.ignored.length)
502
+ if (read.ignored.length) {
503
+ for (const omission of read.omissions)
504
+ diagnostic(omission.cause, `${omission.path}: ${omission.reason}`, [omission.path]);
238
505
  limitations.push({
239
506
  fact: `${read.ignored.length} binary, empty or unreadable paths ignored: ${read.ignored.join(", ")}`,
240
507
  next: "narrow scope to text files",
241
508
  });
509
+ }
242
510
  const textPaths = paths.filter((path) => read.files[path] !== undefined);
243
511
  if (!textPaths.length)
244
512
  return { paths: [], result: { ok: true, answers: {} } };
@@ -279,12 +547,23 @@ export function createFindFilesTool(dependencies) {
279
547
  ],
280
548
  });
281
549
  if (!retained.length) {
282
- limitations.push({
283
- fact: "no file retained by content or names guard",
284
- next: "rephrase goal or widen scope",
285
- });
286
- return output("none", 1, short ? "unsure" : "verdict");
550
+ diagnostic("collection_empty", "no file retained by content or names guard; no entry decision was judged");
551
+ return finish({});
287
552
  }
553
+ entryPaths = retained.map((item) => item.path);
554
+ context.inventories.push({
555
+ id: "entry-passages",
556
+ kind: "repository",
557
+ rules: [
558
+ "name ranking then bounded passage ranking",
559
+ "existing content and names guard",
560
+ ],
561
+ restrictions: entryPaths,
562
+ discovered: known(content.length),
563
+ considered: known(retained.length),
564
+ scopeRestricted: true,
565
+ criteria: [],
566
+ });
288
567
  const idByPath = Object.fromEntries(retained.map((item, index) => [item.path, `c${index + 1}`]));
289
568
  const pathById = Object.fromEntries(retained.map((item, index) => [`c${index + 1}`, item.path]));
290
569
  const pointer = async (paths) => {
@@ -311,6 +590,7 @@ export function createFindFilesTool(dependencies) {
311
590
  if (!firstResult.ok && !budget)
312
591
  return finish({ refusal: firstResult.error });
313
592
  const first = firstResult.ok ? firstResult.answers.entry : undefined;
593
+ primary = first;
314
594
  if (first?.type !== "choice") {
315
595
  unchecked.push("entry");
316
596
  return fallback();
@@ -329,15 +609,28 @@ export function createFindFilesTool(dependencies) {
329
609
  (args.effort === "thorough" ||
330
610
  (args.effort !== "quick" &&
331
611
  needsReverse(first.probabilities, retained.length)))) {
612
+ confirmationRequired = true;
332
613
  const secondResult = await pointer([...paths].reverse());
333
- if (!secondResult.ok && !budget)
614
+ if (!secondResult.ok && !budget) {
615
+ auxiliary.passages.notJudged--;
616
+ auxiliary.controls.notJudged++;
334
617
  return finish({ refusal: secondResult.error });
618
+ }
335
619
  const second = secondResult.ok ? secondResult.answers.entry : undefined;
336
- if (second?.type !== "choice") {
337
- unchecked.push("entry confirmation");
338
- band = "unsure";
620
+ confirmation = second;
621
+ if (second?.type === "choice" && second.source) {
622
+ auxiliary.passages[second.source]--;
623
+ auxiliary.controls[second.source]++;
339
624
  }
340
625
  else {
626
+ auxiliary.passages.notJudged--;
627
+ auxiliary.controls.notJudged++;
628
+ diagnostic(second?.type === "unjudged"
629
+ ? (second.cause ?? "control_failure")
630
+ : "control_failure", "required reversed-order entry control was not judged");
631
+ return fallback();
632
+ }
633
+ {
341
634
  const secondRanks = ranked(second.probabilities);
342
635
  const combined = readPointer(first.probabilities, second.probabilities);
343
636
  ranks = combined.ranked.map(({ id, p }) => ({ path: id, p }));