jev-agent-tools 0.1.4 → 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.
- package/CHANGELOG.md +106 -1
- package/CONTRIBUTING.md +43 -0
- package/README.md +58 -17
- package/SECURITY.md +43 -0
- package/dist/adapters/analysis-context.js +75 -0
- package/dist/adapters/ask-files.js +198 -0
- package/dist/adapters/ask-proof.js +200 -0
- package/dist/adapters/ask-syntax.js +385 -0
- package/dist/adapters/canonical-path.js +17 -0
- package/dist/adapters/command.js +234 -0
- package/dist/adapters/docs.js +192 -0
- package/dist/adapters/evidence-context.js +119 -0
- package/dist/adapters/exec.js +207 -0
- package/dist/adapters/files.js +418 -0
- package/dist/adapters/find.js +150 -0
- package/dist/adapters/git-base.js +32 -0
- package/dist/adapters/git-inventory.js +71 -0
- package/dist/adapters/git.js +483 -0
- package/dist/adapters/locate-file.js +197 -0
- package/dist/adapters/output-lines.js +46 -0
- package/dist/adapters/private-storage.js +106 -0
- package/dist/adapters/risk-callers.js +429 -0
- package/dist/adapters/runner-version.js +78 -0
- package/dist/adapters/shell.js +92 -0
- package/dist/adapters/syntax.js +187 -0
- package/dist/adapters/test-inventory.js +139 -0
- package/dist/adapters/usage.js +20 -0
- package/dist/adapters/utf8.js +47 -0
- package/dist/configuration.js +267 -0
- package/dist/constants.js +140 -0
- package/dist/core/ask-closure.js +282 -0
- package/dist/core/ask-proof.js +1 -0
- package/dist/core/ask-references.js +278 -0
- package/dist/core/asks.js +507 -0
- package/dist/core/batches.js +65 -0
- package/dist/core/command-output.js +224 -0
- package/dist/core/diff.js +178 -0
- package/dist/core/docs.js +302 -0
- package/dist/core/find.js +108 -0
- package/dist/core/git.js +1 -0
- package/dist/core/imports.js +550 -0
- package/dist/core/integrity.js +45 -0
- package/dist/core/lexical.js +132 -0
- package/dist/core/locate.js +169 -0
- package/dist/core/output.js +137 -0
- package/dist/core/pointer.js +29 -0
- package/dist/core/result-report.js +302 -0
- package/dist/core/risk-callers.js +851 -0
- package/dist/core/runner-version.js +45 -0
- package/dist/core/secret-path.js +34 -0
- package/dist/core/sections.js +230 -0
- package/dist/core/state.js +51 -0
- package/dist/core/syntax.js +1 -0
- package/dist/core/test-commands.js +334 -0
- package/dist/core/test-coverage.js +74 -0
- package/dist/core/test-discovery.js +1382 -0
- package/dist/core/test-evidence.js +527 -0
- package/dist/core/test-state.js +81 -0
- package/dist/core/truncate.js +12 -0
- package/dist/core/units.js +349 -0
- package/dist/describe.js +23 -0
- package/dist/guide.js +33 -0
- package/dist/host.js +24 -0
- package/dist/jev/client.js +456 -0
- package/dist/jev/pool.js +54 -0
- package/dist/jev/types.js +1 -0
- package/dist/mcp/main.js +124 -0
- package/dist/mcp/protocol.js +210 -0
- package/dist/mcp/tools.js +129 -0
- package/dist/presets/docs.js +62 -0
- package/dist/presets/risk.js +179 -0
- package/dist/presets/spec.js +81 -0
- package/dist/presets/witnesses.js +249 -0
- package/dist/render.js +114 -0
- package/dist/report-schema.js +1356 -0
- package/dist/result-types.js +1 -0
- package/dist/result.js +3 -0
- package/dist/runtime.js +1 -0
- package/dist/session.js +147 -0
- package/dist/texts/ask-files.js +3 -0
- package/dist/texts/ask.js +4 -0
- package/dist/texts/check-diff.js +20 -0
- package/dist/texts/configuration.js +1 -0
- package/dist/texts/find.js +19 -0
- package/dist/texts/guide.js +3 -0
- package/dist/texts/instructions.js +72 -0
- package/dist/texts/locate.js +15 -0
- package/dist/texts/select-tests.js +4 -0
- package/dist/tools/ask-files.js +450 -0
- package/dist/tools/ask-schema.js +70 -0
- package/dist/tools/ask.js +1147 -0
- package/dist/tools/check-diff.js +594 -0
- package/dist/tools/docs-check.js +408 -0
- package/dist/tools/find.js +682 -0
- package/dist/tools/locate.js +602 -0
- package/dist/tools/review-report.js +230 -0
- package/dist/tools/select-tests.js +821 -0
- package/dist/tools/spec-check.js +263 -0
- package/docs/adr/0001-strict-typescript-pure-core-offline-tests.md +31 -0
- package/docs/adr/0002-one-http-protocol-across-hosts.md +17 -0
- package/docs/adr/0003-explicit-scope-conservative-automation.md +19 -0
- package/docs/adr/0004-compiled-typed-intents.md +19 -0
- package/docs/adr/0005-evidence-construction-before-judgment.md +19 -0
- package/docs/adr/0006-visible-uncertainty-constrained-controls.md +21 -0
- package/docs/adr/0007-bounded-evidence-visible-limits.md +21 -0
- package/docs/adr/0008-static-test-discovery-conservative-plans.md +19 -0
- package/docs/adr/0009-session-cache-requested-model-identity.md +17 -0
- package/docs/adr/0010-mcp-server-thin-host.md +23 -0
- package/docs/agent-instructions.md +120 -0
- package/docs/design.md +16 -4
- package/docs/mcp.md +233 -0
- package/docs/tools/jev_ask.md +8 -5
- package/docs/tools/jev_ask_files.md +2 -1
- package/docs/tools/jev_check_diff.md +4 -1
- package/docs/tools/jev_find_files.md +2 -1
- package/docs/tools/jev_locate_in_file.md +5 -0
- package/docs/tools/jev_select_tests.md +4 -1
- package/package.json +19 -4
- package/rules/jev-ask.md +22 -1
- package/server.json +57 -0
- package/src/adapters/ask-files.ts +11 -3
- package/src/adapters/ask-proof.ts +69 -11
- package/src/adapters/canonical-path.ts +18 -0
- package/src/adapters/command.ts +102 -36
- package/src/adapters/docs.ts +33 -14
- package/src/adapters/evidence-context.ts +169 -0
- package/src/adapters/exec.ts +226 -0
- package/src/adapters/files.ts +146 -16
- package/src/adapters/find.ts +37 -7
- package/src/adapters/git-base.ts +7 -1
- package/src/adapters/git.ts +61 -8
- package/src/adapters/locate-file.ts +51 -9
- package/src/adapters/private-storage.ts +155 -0
- package/src/adapters/risk-callers.ts +7 -2
- package/src/adapters/shell.ts +113 -0
- package/src/adapters/test-inventory.ts +12 -4
- package/src/configuration.ts +55 -14
- package/src/constants.ts +37 -5
- package/src/core/ask-references.ts +262 -146
- package/src/core/asks.ts +79 -7
- package/src/core/command-output.ts +17 -1
- package/src/core/import-boundaries.ts +8 -3
- package/src/core/locate.ts +8 -5
- package/src/core/output.ts +34 -0
- package/src/core/result-report.ts +410 -0
- package/src/core/secret-path.ts +37 -0
- package/src/core/state.ts +8 -1
- package/src/core/units.ts +3 -2
- package/src/host.ts +11 -0
- package/src/index.ts +3 -0
- package/src/jev/client.ts +66 -16
- package/src/jev/types.ts +24 -3
- package/src/mcp/main.ts +135 -0
- package/src/mcp/protocol.ts +332 -0
- package/src/mcp/tools.ts +179 -0
- package/src/render.ts +109 -0
- package/src/report-schema.ts +1380 -0
- package/src/result-types.ts +234 -0
- package/src/result.ts +4 -1
- package/src/runtime.ts +6 -0
- package/src/session.ts +59 -0
- package/src/setup.ts +13 -5
- package/src/texts/ask-files.ts +4 -1
- package/src/texts/ask.ts +8 -1
- package/src/texts/check-diff.ts +7 -4
- package/src/texts/find.ts +8 -2
- package/src/texts/guide.ts +8 -16
- package/src/texts/instructions.ts +98 -0
- package/src/texts/locate.ts +8 -2
- package/src/texts/run-end.ts +2 -2
- package/src/texts/select-tests.ts +4 -1
- package/src/tools/ask-files.ts +311 -18
- package/src/tools/ask.ts +722 -95
- package/src/tools/check-diff.ts +337 -31
- package/src/tools/docs-check.ts +241 -38
- package/src/tools/find.ts +389 -29
- package/src/tools/locate.ts +387 -25
- package/src/tools/review-report.ts +308 -0
- package/src/tools/select-tests.ts +484 -23
- package/src/tools/spec-check.ts +194 -19
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { resolveEvidenceContext, withEvidenceContext, } from "../adapters/evidence-context.js";
|
|
3
|
+
import { shareGitInventory } from "../adapters/git-inventory.js";
|
|
4
|
+
import { readLocateFile, scanRange } from "../adapters/locate-file.js";
|
|
5
|
+
import { loadSyntaxParser } from "../adapters/syntax.js";
|
|
6
|
+
import { hostUsage } from "../adapters/usage.js";
|
|
7
|
+
import { CHOICE_MAX_OPTIONS, LOCATE_GRAY_MIN, LOCATE_MIN_KB, LOCATE_SECTION_MAX_LINES, LOCATE_SHRINK_TOP, LOCATE_VERDICT_MIN, STATE_MAX_CHARS, } from "../constants.js";
|
|
8
|
+
import { locateDisplay, outlineEvidence, sectionOutline, sectionState, sectionStateFits, } from "../core/locate.js";
|
|
9
|
+
import { buildEnvelope, } from "../core/output.js";
|
|
10
|
+
import { needsReverse, pointerQuestion, readPointer } from "../core/pointer.js";
|
|
11
|
+
import { answerItemFromInput, buildResultReport, contextFromEvidence, known, unknown, } from "../core/result-report.js";
|
|
12
|
+
import { sectionFile } from "../core/sections.js";
|
|
13
|
+
import { interpolate } from "../describe.js";
|
|
14
|
+
import { renderResultReport } from "../render.js";
|
|
15
|
+
import { NOT_CONFIGURED } from "../texts/configuration.js";
|
|
16
|
+
import { LOCATE_DESCRIPTION } from "../texts/locate.js";
|
|
17
|
+
const parameters = Type.Object({
|
|
18
|
+
root: Type.Optional(Type.String()),
|
|
19
|
+
path: Type.String({ minLength: 1 }),
|
|
20
|
+
goal: Type.String({ minLength: 1 }),
|
|
21
|
+
}, { additionalProperties: false });
|
|
22
|
+
export function createLocateTool(dependencies, loadParser = loadSyntaxParser) {
|
|
23
|
+
const { host, runtime, exec: execute } = dependencies;
|
|
24
|
+
return {
|
|
25
|
+
name: "jev_locate_in_file",
|
|
26
|
+
label: "Jev locate in file",
|
|
27
|
+
description: interpolate(LOCATE_DESCRIPTION.replace("{{ompOutline}}", host.isOmp
|
|
28
|
+
? " read already outlines code files; use this when the outline does not tell you which range to open."
|
|
29
|
+
: ""), host.names),
|
|
30
|
+
parameters,
|
|
31
|
+
...(host.isOmp
|
|
32
|
+
? { approval: "read", loadMode: "essential" }
|
|
33
|
+
: {
|
|
34
|
+
promptSnippet: "Find which line range of one large file serves a goal",
|
|
35
|
+
}),
|
|
36
|
+
async execute(_id, args, signal, _update, ctx) {
|
|
37
|
+
const client = dependencies.client;
|
|
38
|
+
const evidence = await resolveEvidenceContext(ctx.cwd, args.root, {
|
|
39
|
+
exec: execute,
|
|
40
|
+
signal,
|
|
41
|
+
origin: dependencies.evidenceOrigin,
|
|
42
|
+
});
|
|
43
|
+
const evidenceContext = evidence.context;
|
|
44
|
+
const cwd = evidence.ok ? evidence.cwd : evidenceContext.authority.path;
|
|
45
|
+
const started = performance.now();
|
|
46
|
+
const exec = shareGitInventory(execute);
|
|
47
|
+
const results = [];
|
|
48
|
+
const limitations = [];
|
|
49
|
+
const context = contextFromEvidence(evidenceContext);
|
|
50
|
+
const diagnostics = [];
|
|
51
|
+
const actions = [];
|
|
52
|
+
const auxiliary = {
|
|
53
|
+
controls: { fresh: 0, cache: 0, notJudged: 0, static: 0 },
|
|
54
|
+
passages: { fresh: 0, cache: 0, notJudged: 0 },
|
|
55
|
+
};
|
|
56
|
+
let primary;
|
|
57
|
+
let control;
|
|
58
|
+
let requiredControl = false;
|
|
59
|
+
let failureCause = "invalid_response";
|
|
60
|
+
const raw = (answer) => answer.type === "choice"
|
|
61
|
+
? Object.entries(answer.probabilities).map(([label, probability]) => ({
|
|
62
|
+
label,
|
|
63
|
+
value: label,
|
|
64
|
+
probability: known(probability),
|
|
65
|
+
}))
|
|
66
|
+
: [];
|
|
67
|
+
const diagnose = (cause, fact, members = []) => {
|
|
68
|
+
const id = `d${diagnostics.length + 1}`, actionId = `a${actions.length + 1}`;
|
|
69
|
+
const code = cause === "invalid_root"
|
|
70
|
+
? "correct_context"
|
|
71
|
+
: cause === "not_configured"
|
|
72
|
+
? "configure_client"
|
|
73
|
+
: cause === "transport_failure" || cause === "service_unavailable"
|
|
74
|
+
? "recover_service"
|
|
75
|
+
: "inspect_native";
|
|
76
|
+
actions.push({
|
|
77
|
+
id: actionId,
|
|
78
|
+
code,
|
|
79
|
+
target: known(args.path),
|
|
80
|
+
scope: { kind: "call" },
|
|
81
|
+
condition: code === "correct_context"
|
|
82
|
+
? "If the requested root is incorrect"
|
|
83
|
+
: code === "recover_service"
|
|
84
|
+
? "Only after service recovery is observed"
|
|
85
|
+
: "If the range decision remains open",
|
|
86
|
+
instruction: code === "correct_context"
|
|
87
|
+
? "Use the exact registered worktree top-level; no fallback was performed."
|
|
88
|
+
: code === "configure_client"
|
|
89
|
+
? "Configure the Jev client or inspect the file natively."
|
|
90
|
+
: "Read or search the cited file within existing permissions; revisit Jev only with decisive new evidence or materially changed context.",
|
|
91
|
+
repeatUnchanged: false,
|
|
92
|
+
});
|
|
93
|
+
diagnostics.push({
|
|
94
|
+
id,
|
|
95
|
+
cause,
|
|
96
|
+
fact,
|
|
97
|
+
target: known(cause === "invalid_root" ? (args.root ?? cwd) : args.path),
|
|
98
|
+
origin: cause === "invalid_root" || cause === "forbidden_path"
|
|
99
|
+
? "input"
|
|
100
|
+
: cause === "control_failure"
|
|
101
|
+
? "control"
|
|
102
|
+
: cause === "session_budget"
|
|
103
|
+
? "budget"
|
|
104
|
+
: cause === "service_unavailable" ||
|
|
105
|
+
cause === "transport_failure" ||
|
|
106
|
+
cause === "invalid_response" ||
|
|
107
|
+
cause === "provider_context_refusal" ||
|
|
108
|
+
cause === "not_configured"
|
|
109
|
+
? "provider"
|
|
110
|
+
: "collection",
|
|
111
|
+
scope: { kind: "call" },
|
|
112
|
+
effect: "blocking",
|
|
113
|
+
material: true,
|
|
114
|
+
omittedMembers: members,
|
|
115
|
+
memberCount: known(members.length),
|
|
116
|
+
actionIds: [actionId],
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
const finish = (input) => {
|
|
120
|
+
const usage = results.reduce((sum, result) => ({
|
|
121
|
+
inputTokens: sum.inputTokens + (result.usage?.inputTokens ?? 0),
|
|
122
|
+
costUsd: sum.costUsd + (result.usage?.costUsd ?? 0),
|
|
123
|
+
}), { inputTokens: 0, costUsd: 0 });
|
|
124
|
+
const metrics = {
|
|
125
|
+
calls: 0,
|
|
126
|
+
questions: 0,
|
|
127
|
+
cacheHits: 0,
|
|
128
|
+
cacheRequests: 0,
|
|
129
|
+
};
|
|
130
|
+
for (const result of results) {
|
|
131
|
+
metrics.calls += result.calls ?? 0;
|
|
132
|
+
metrics.questions += result.questions ?? 0;
|
|
133
|
+
metrics.cacheHits += result.cacheHits ?? 0;
|
|
134
|
+
metrics.cacheRequests += result.cacheRequests ?? 0;
|
|
135
|
+
}
|
|
136
|
+
const envelope = buildEnvelope({
|
|
137
|
+
...input,
|
|
138
|
+
limitations: [...limitations, ...(input.limitations ?? [])],
|
|
139
|
+
yield: {
|
|
140
|
+
...metrics,
|
|
141
|
+
costUsd: usage.costUsd,
|
|
142
|
+
elapsedMs: performance.now() - started,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
if (input.refusal)
|
|
146
|
+
diagnose(failureCause, input.refusal);
|
|
147
|
+
if (input.answers?.[0] && primary?.source)
|
|
148
|
+
actions.push({
|
|
149
|
+
id: `a${actions.length + 1}`,
|
|
150
|
+
code: "inspect_native",
|
|
151
|
+
target: known(input.answers[0].label === "none"
|
|
152
|
+
? args.path
|
|
153
|
+
: input.answers[0].label),
|
|
154
|
+
scope: { kind: "group", groupIds: ["pointer"] },
|
|
155
|
+
condition: "If the range decision remains open",
|
|
156
|
+
instruction: input.answers[0].label === "none"
|
|
157
|
+
? "Search elsewhere for the goal; read any cited alternative range if the result is unsure. Do not retry unchanged evidence."
|
|
158
|
+
: "Read the current primary and alternative ranges; retain uncertainty if no decisive new evidence is available. Do not retry unchanged evidence.",
|
|
159
|
+
repeatUnchanged: false,
|
|
160
|
+
});
|
|
161
|
+
if (!input.refusal &&
|
|
162
|
+
(!primary?.source || (requiredControl && !control?.source)))
|
|
163
|
+
diagnose("invalid_response", "pointer decision lacks established answer provenance or its required control");
|
|
164
|
+
const common = {
|
|
165
|
+
id: "pointer",
|
|
166
|
+
kind: "pointer",
|
|
167
|
+
label: args.path,
|
|
168
|
+
groupId: "pointer",
|
|
169
|
+
evidence: primary
|
|
170
|
+
? [
|
|
171
|
+
{
|
|
172
|
+
target: args.path,
|
|
173
|
+
canonicalPath: evidenceContext.effectiveRoot
|
|
174
|
+
? known(`${evidenceContext.effectiveRoot.path}/${args.path}`)
|
|
175
|
+
: unknown("file admission not established"),
|
|
176
|
+
aliases: [],
|
|
177
|
+
side: "current",
|
|
178
|
+
revision: context.resolvedBase,
|
|
179
|
+
},
|
|
180
|
+
]
|
|
181
|
+
: [],
|
|
182
|
+
diagnosticIds: diagnostics.map((d) => d.id),
|
|
183
|
+
actionIds: actions.map((a) => a.id),
|
|
184
|
+
};
|
|
185
|
+
if (input.answers?.[0])
|
|
186
|
+
common.label = input.answers[0].label;
|
|
187
|
+
const displayed = input.answers?.[0];
|
|
188
|
+
let item = { ...common, treatment: "not_judged", source: "none" };
|
|
189
|
+
if (displayed &&
|
|
190
|
+
primary?.type === "choice" &&
|
|
191
|
+
primary.source &&
|
|
192
|
+
(!requiredControl || (control?.type === "choice" && control.source))) {
|
|
193
|
+
auxiliary.passages[primary.source]--;
|
|
194
|
+
item = answerItemFromInput(common, {
|
|
195
|
+
...displayed,
|
|
196
|
+
answer: primary,
|
|
197
|
+
publicResult: displayed.value?.head ?? primary.choice,
|
|
198
|
+
reason: [...limitations, ...(input.limitations ?? [])]
|
|
199
|
+
.map((limit) => limit.fact)
|
|
200
|
+
.join("; "),
|
|
201
|
+
rawValues: raw(primary),
|
|
202
|
+
reportControls: control?.type === "choice" && control.source
|
|
203
|
+
? [
|
|
204
|
+
{
|
|
205
|
+
id: "pointer-order",
|
|
206
|
+
kind: "order",
|
|
207
|
+
source: control.source,
|
|
208
|
+
outcome: displayed.orderDependent
|
|
209
|
+
? "order-dependent"
|
|
210
|
+
: "consistent",
|
|
211
|
+
rawValues: raw(control),
|
|
212
|
+
},
|
|
213
|
+
]
|
|
214
|
+
: [],
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const report = buildResultReport({
|
|
218
|
+
tool: "jev_locate_in_file",
|
|
219
|
+
context,
|
|
220
|
+
items: [item],
|
|
221
|
+
diagnostics,
|
|
222
|
+
actions,
|
|
223
|
+
metrics: {
|
|
224
|
+
...metrics,
|
|
225
|
+
costUsd: results.every((r) => !r.calls || r.usage !== undefined)
|
|
226
|
+
? usage.costUsd
|
|
227
|
+
: undefined,
|
|
228
|
+
elapsedMs: performance.now() - started,
|
|
229
|
+
},
|
|
230
|
+
auxiliary,
|
|
231
|
+
total: known(1),
|
|
232
|
+
refused: Boolean(input.refusal &&
|
|
233
|
+
(failureCause === "invalid_root" ||
|
|
234
|
+
failureCause === "forbidden_path" ||
|
|
235
|
+
failureCause === "outside_inventory" ||
|
|
236
|
+
failureCause === "group_too_large" ||
|
|
237
|
+
failureCause === "evidence_too_large")),
|
|
238
|
+
});
|
|
239
|
+
runtime.session.record(envelope);
|
|
240
|
+
runtime.guide.deliver(ctx);
|
|
241
|
+
return {
|
|
242
|
+
content: [
|
|
243
|
+
{
|
|
244
|
+
type: "text",
|
|
245
|
+
text: renderResultReport(report, {
|
|
246
|
+
details: {
|
|
247
|
+
lines: [
|
|
248
|
+
...envelope.lines,
|
|
249
|
+
{
|
|
250
|
+
type: "list",
|
|
251
|
+
title: "pointer reservations",
|
|
252
|
+
items: [...limitations, ...(input.limitations ?? [])].map((limit) => limit.fact),
|
|
253
|
+
},
|
|
254
|
+
...(displayed
|
|
255
|
+
? [
|
|
256
|
+
{
|
|
257
|
+
type: "list",
|
|
258
|
+
title: "read ranges",
|
|
259
|
+
items: [
|
|
260
|
+
...(displayed.label === "none"
|
|
261
|
+
? []
|
|
262
|
+
: [displayed.label]),
|
|
263
|
+
...(displayed.candidates ?? [])
|
|
264
|
+
.filter((candidate) => candidate.label !== "none")
|
|
265
|
+
.map((candidate) => `${candidate.label} (${candidate.value.p})`),
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
]
|
|
269
|
+
: []),
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
}),
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
details: { results, evidenceContext, result: report },
|
|
276
|
+
...hostUsage(host.isOmp, results.some((r) => r.usage) ? usage : undefined),
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
if (!evidence.ok) {
|
|
280
|
+
failureCause = evidence.cause ?? "invalid_root";
|
|
281
|
+
return finish({ refusal: evidence.error });
|
|
282
|
+
}
|
|
283
|
+
failureCause = "file_unavailable";
|
|
284
|
+
const file = await readLocateFile(cwd, args.path, signal, exec);
|
|
285
|
+
if (!file.ok) {
|
|
286
|
+
failureCause = file.cause ?? "file_unavailable";
|
|
287
|
+
return finish({ refusal: file.error });
|
|
288
|
+
}
|
|
289
|
+
failureCause = "outside_inventory";
|
|
290
|
+
if (file.bytes < LOCATE_MIN_KB * 1000)
|
|
291
|
+
return finish({
|
|
292
|
+
refusal: `small file (${file.lines} lines): read it directly`,
|
|
293
|
+
});
|
|
294
|
+
if (!client) {
|
|
295
|
+
failureCause = "not_configured";
|
|
296
|
+
return finish({ refusal: NOT_CONFIGURED });
|
|
297
|
+
}
|
|
298
|
+
let sections;
|
|
299
|
+
if (file.kind === "whole") {
|
|
300
|
+
const parser = await loadParser();
|
|
301
|
+
const declarations = parser?.supports(args.path)
|
|
302
|
+
? parser.declarations(args.path, file.text, {
|
|
303
|
+
descendAbove: LOCATE_SECTION_MAX_LINES,
|
|
304
|
+
})
|
|
305
|
+
: undefined;
|
|
306
|
+
if (args.path.endsWith(".py") && !parser?.supports(args.path))
|
|
307
|
+
limitations.push({
|
|
308
|
+
fact: "Python: unsupported language (plugin missing), heuristic sections",
|
|
309
|
+
next: "install @ast-grep/lang-python",
|
|
310
|
+
});
|
|
311
|
+
for (const limit of declarations?.limits ?? [])
|
|
312
|
+
limitations.push({
|
|
313
|
+
fact: limit.kind === "parse_failed"
|
|
314
|
+
? "syntax parse failed: heuristic sections"
|
|
315
|
+
: `parse_partial: ${args.path}${limit.declaration ? `: ${limit.declaration}` : ""}`,
|
|
316
|
+
next: limit.kind === "parse_failed"
|
|
317
|
+
? "read the selected range directly"
|
|
318
|
+
: "read the complete source and check its syntax",
|
|
319
|
+
});
|
|
320
|
+
sections = sectionFile(args.path, file.text, declarations?.limits.some((limit) => limit.kind === "parse_failed")
|
|
321
|
+
? undefined
|
|
322
|
+
: declarations?.declarations).sections;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
sections = file.windows.map((window, index) => ({
|
|
326
|
+
id: `S${index + 1}`,
|
|
327
|
+
...window,
|
|
328
|
+
}));
|
|
329
|
+
limitations.push({
|
|
330
|
+
fact: "large file: window outline, not declarations",
|
|
331
|
+
next: "read the selected range directly",
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const sectionCount = sections.length;
|
|
335
|
+
context.inventories.push({
|
|
336
|
+
id: "sections",
|
|
337
|
+
kind: "sections",
|
|
338
|
+
rules: [
|
|
339
|
+
file.kind === "whole"
|
|
340
|
+
? "declarations or heuristic sections from complete file"
|
|
341
|
+
: "bounded window outline",
|
|
342
|
+
],
|
|
343
|
+
restrictions: [
|
|
344
|
+
args.path,
|
|
345
|
+
...sections.map((section) => `${section.id}: ${section.start}-${section.end}`),
|
|
346
|
+
],
|
|
347
|
+
discovered: known(sectionCount),
|
|
348
|
+
considered: known(sectionCount),
|
|
349
|
+
scopeRestricted: true,
|
|
350
|
+
criteria: [],
|
|
351
|
+
});
|
|
352
|
+
if (!sectionCount) {
|
|
353
|
+
diagnose("collection_empty", "no sections discovered; no pointer judgment");
|
|
354
|
+
return finish({});
|
|
355
|
+
}
|
|
356
|
+
failureCause = "group_too_large";
|
|
357
|
+
const judge = async (candidates, outline = false) => {
|
|
358
|
+
primary = undefined;
|
|
359
|
+
control = undefined;
|
|
360
|
+
requiredControl = false;
|
|
361
|
+
const inventory = context.inventories.find((inventory) => inventory.id === "pointer-candidates");
|
|
362
|
+
const restricted = {
|
|
363
|
+
id: "pointer-candidates",
|
|
364
|
+
kind: "sections",
|
|
365
|
+
rules: ["actual candidates for the latest pointer stage"],
|
|
366
|
+
restrictions: [
|
|
367
|
+
args.path,
|
|
368
|
+
...candidates.map((section) => `${section.id}: ${section.start}-${section.end}`),
|
|
369
|
+
],
|
|
370
|
+
discovered: known(candidates.length),
|
|
371
|
+
considered: known(candidates.length),
|
|
372
|
+
scopeRestricted: true,
|
|
373
|
+
criteria: [],
|
|
374
|
+
};
|
|
375
|
+
if (inventory)
|
|
376
|
+
Object.assign(inventory, restricted);
|
|
377
|
+
else
|
|
378
|
+
context.inventories.push(restricted);
|
|
379
|
+
const state = withEvidenceContext(sectionState(args.path, args.goal, candidates, outline), evidenceContext);
|
|
380
|
+
if (JSON.stringify(state).length > STATE_MAX_CHARS)
|
|
381
|
+
return {
|
|
382
|
+
ok: false,
|
|
383
|
+
error: `required evidence exceeds STATE_MAX_CHARS=${STATE_MAX_CHARS}`,
|
|
384
|
+
};
|
|
385
|
+
const ask = async (reverse) => {
|
|
386
|
+
const result = await client.judge(state, {
|
|
387
|
+
pointer: pointerQuestion({
|
|
388
|
+
instructions: `Which section serves this goal: ${args.goal}? Choose none if no section fits. Judge the supplied text, not the label alone.`,
|
|
389
|
+
noneLabel: "No section serves the goal",
|
|
390
|
+
candidates,
|
|
391
|
+
reverse,
|
|
392
|
+
}),
|
|
393
|
+
}, {
|
|
394
|
+
signal,
|
|
395
|
+
...runtime.session.requestGate(),
|
|
396
|
+
beforeRequest: (n) => runtime.session.admit(n),
|
|
397
|
+
admissionCause: () => "session_budget",
|
|
398
|
+
onUsage: (usage) => runtime.session.recordUsage(usage),
|
|
399
|
+
});
|
|
400
|
+
results.push(result);
|
|
401
|
+
if (!result.ok) {
|
|
402
|
+
failureCause = result.failureCause ?? "invalid_response";
|
|
403
|
+
if (reverse)
|
|
404
|
+
auxiliary.controls.notJudged++;
|
|
405
|
+
else
|
|
406
|
+
auxiliary.passages.notJudged++;
|
|
407
|
+
}
|
|
408
|
+
if (!result.ok)
|
|
409
|
+
return { ok: false, error: result.error };
|
|
410
|
+
const answer = result.answers.pointer;
|
|
411
|
+
if (reverse)
|
|
412
|
+
control = answer;
|
|
413
|
+
else
|
|
414
|
+
primary = answer;
|
|
415
|
+
if (answer?.type === "unjudged")
|
|
416
|
+
failureCause = answer.cause ?? "invalid_response";
|
|
417
|
+
if (answer?.type === "choice" && answer.source) {
|
|
418
|
+
if (reverse)
|
|
419
|
+
auxiliary.controls[answer.source]++;
|
|
420
|
+
else
|
|
421
|
+
auxiliary.passages[answer.source]++;
|
|
422
|
+
}
|
|
423
|
+
else if (reverse)
|
|
424
|
+
auxiliary.controls.notJudged++;
|
|
425
|
+
else
|
|
426
|
+
auxiliary.passages.notJudged++;
|
|
427
|
+
return answer?.type === "choice"
|
|
428
|
+
? { ok: true, probabilities: answer.probabilities }
|
|
429
|
+
: {
|
|
430
|
+
ok: false,
|
|
431
|
+
error: answer?.type === "unjudged"
|
|
432
|
+
? answer.reason
|
|
433
|
+
: "Pointer answer missing.",
|
|
434
|
+
};
|
|
435
|
+
};
|
|
436
|
+
const first = await ask(false);
|
|
437
|
+
if (!first.ok)
|
|
438
|
+
return first;
|
|
439
|
+
let pointer = readPointer(first.probabilities);
|
|
440
|
+
if (needsReverse(first.probabilities, candidates.length)) {
|
|
441
|
+
requiredControl = true;
|
|
442
|
+
const second = await ask(true);
|
|
443
|
+
if (!second.ok) {
|
|
444
|
+
if (failureCause === "invalid_response")
|
|
445
|
+
failureCause = "control_failure";
|
|
446
|
+
return second;
|
|
447
|
+
}
|
|
448
|
+
pointer = readPointer(first.probabilities, second.probabilities);
|
|
449
|
+
}
|
|
450
|
+
return { ok: true, ...pointer };
|
|
451
|
+
};
|
|
452
|
+
const emptyState = sectionState(args.path, args.goal, []);
|
|
453
|
+
const contextChars = JSON.stringify(withEvidenceContext(emptyState, evidenceContext))
|
|
454
|
+
.length - JSON.stringify(emptyState).length;
|
|
455
|
+
const evidenceMaxChars = STATE_MAX_CHARS - contextChars;
|
|
456
|
+
const fits = (candidates) => sectionStateFits(args.path, args.goal, candidates, false, evidenceMaxChars);
|
|
457
|
+
const twoStage = file.kind === "outline" || !fits(sections);
|
|
458
|
+
let planUnsure = false;
|
|
459
|
+
if (twoStage) {
|
|
460
|
+
const outline = sectionOutline(args.path, args.goal, sections, evidenceMaxChars);
|
|
461
|
+
if (outline.length < 2 || outline.length >= CHOICE_MAX_OPTIONS)
|
|
462
|
+
return finish({
|
|
463
|
+
refusal: "No usable multi-section plan fits the budget; use grep or read directly.",
|
|
464
|
+
});
|
|
465
|
+
const evidence = outlineEvidence(args.path, args.goal, outline, evidenceMaxChars);
|
|
466
|
+
if (!fits(evidence))
|
|
467
|
+
return finish({
|
|
468
|
+
refusal: "Block plan exceeds the state budget; use grep.",
|
|
469
|
+
});
|
|
470
|
+
if (outline.length < sections.length)
|
|
471
|
+
limitations.push({
|
|
472
|
+
fact: "adjacent sections grouped; a class may span outline blocks",
|
|
473
|
+
next: "read neighboring blocks when needed",
|
|
474
|
+
});
|
|
475
|
+
const plan = await judge(evidence);
|
|
476
|
+
if (!plan.ok)
|
|
477
|
+
return finish({ refusal: plan.error });
|
|
478
|
+
const head = plan.ranked[0];
|
|
479
|
+
if (!head)
|
|
480
|
+
return finish({ refusal: "Pointer probabilities missing." });
|
|
481
|
+
if (head.id === "none") {
|
|
482
|
+
const display = locateDisplay(args.path, outline, plan.ranked, false);
|
|
483
|
+
if (!display)
|
|
484
|
+
return finish({ refusal: "Pointer probabilities missing." });
|
|
485
|
+
return finish({
|
|
486
|
+
answers: [
|
|
487
|
+
{
|
|
488
|
+
...display.primary,
|
|
489
|
+
band: display.band,
|
|
490
|
+
candidates: display.candidates,
|
|
491
|
+
orderDependent: plan.orderDependent,
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
limitations: [
|
|
495
|
+
{
|
|
496
|
+
fact: "no block serves the goal",
|
|
497
|
+
next: display.band === "unsure"
|
|
498
|
+
? "read the alternative block or search elsewhere"
|
|
499
|
+
: "search elsewhere",
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
const block = outline.find((s) => s.id === head.id);
|
|
505
|
+
if (!block)
|
|
506
|
+
return finish({ refusal: "Pointer named an unknown block." });
|
|
507
|
+
planUnsure = head.p < LOCATE_VERDICT_MIN;
|
|
508
|
+
if (planUnsure) {
|
|
509
|
+
const alternative = outline.find((s) => s.id === plan.ranked[1]?.id);
|
|
510
|
+
limitations.push({
|
|
511
|
+
fact: `plan unsure: refined only the top block; also consider ${alternative ? `${args.path}:${alternative.start}-${alternative.end}` : "none"}`,
|
|
512
|
+
next: "read the other block if needed",
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
if (file.kind === "outline") {
|
|
516
|
+
const selected = await scanRange(cwd, args.path, block, signal, exec);
|
|
517
|
+
if (!selected.ok) {
|
|
518
|
+
failureCause = selected.cause ?? "file_unavailable";
|
|
519
|
+
return finish({ refusal: selected.error });
|
|
520
|
+
}
|
|
521
|
+
const lines = selected.text.split("\n");
|
|
522
|
+
sections = sections
|
|
523
|
+
.filter((s) => s.start >= block.start && s.end <= block.end)
|
|
524
|
+
.map((section) => ({
|
|
525
|
+
...section,
|
|
526
|
+
label: `lines ${section.start}-${section.end}`,
|
|
527
|
+
text: lines
|
|
528
|
+
.slice(section.start - block.start, section.end - block.start + 1)
|
|
529
|
+
.join("\n"),
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
532
|
+
else
|
|
533
|
+
sections = sections.filter((s) => s.start >= block.start && s.end <= block.end);
|
|
534
|
+
if (sections.length < 2)
|
|
535
|
+
return finish({
|
|
536
|
+
refusal: "Selected block has fewer than two sections; use grep or read directly.",
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
if (!fits(sections))
|
|
540
|
+
return finish({
|
|
541
|
+
refusal: `Selected block exceeds ${STATE_MAX_CHARS} chars or the choice limit; read ${args.path}:${sections[0]?.start}-${sections.at(-1)?.end} directly.`,
|
|
542
|
+
});
|
|
543
|
+
let pointer = await judge(sections);
|
|
544
|
+
if (!pointer.ok)
|
|
545
|
+
return finish({ refusal: pointer.error });
|
|
546
|
+
let shrunk = false;
|
|
547
|
+
if ((pointer.ranked[0]?.p ?? 0) < LOCATE_GRAY_MIN) {
|
|
548
|
+
const ids = pointer.ranked
|
|
549
|
+
.filter((s) => s.id !== "none")
|
|
550
|
+
.slice(0, LOCATE_SHRINK_TOP)
|
|
551
|
+
.map((s) => s.id);
|
|
552
|
+
sections = ids.flatMap((id) => sections.filter((s) => s.id === id));
|
|
553
|
+
pointer = await judge(sections);
|
|
554
|
+
if (!pointer.ok)
|
|
555
|
+
return finish({ refusal: pointer.error });
|
|
556
|
+
shrunk = true;
|
|
557
|
+
if (twoStage)
|
|
558
|
+
limitations.push({
|
|
559
|
+
fact: "two-stage pointer: shrinking re-ask not measured for outlines",
|
|
560
|
+
next: "read the selected evidence",
|
|
561
|
+
});
|
|
562
|
+
limitations.push({
|
|
563
|
+
fact: "pointer below gray threshold: narrowed to top three plus none",
|
|
564
|
+
next: "read both candidates or search elsewhere",
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const head = pointer.ranked[0];
|
|
568
|
+
if (!head)
|
|
569
|
+
return finish({ refusal: "Pointer probabilities missing." });
|
|
570
|
+
const displayed = locateDisplay(args.path, sections, pointer.ranked, shrunk || planUnsure);
|
|
571
|
+
if (!displayed)
|
|
572
|
+
return finish({ refusal: "Pointer probabilities missing." });
|
|
573
|
+
const { primary: displayedPrimary, band, candidates } = displayed;
|
|
574
|
+
if (band === "unsure" || head.id === "none")
|
|
575
|
+
limitations.push({
|
|
576
|
+
fact: head.id === "none"
|
|
577
|
+
? "no section fits"
|
|
578
|
+
: "two sections share the mass",
|
|
579
|
+
next: head.id === "none" ? "search elsewhere" : "read both",
|
|
580
|
+
});
|
|
581
|
+
return finish({
|
|
582
|
+
answers: [
|
|
583
|
+
{
|
|
584
|
+
...displayedPrimary,
|
|
585
|
+
band,
|
|
586
|
+
orderDependent: pointer.orderDependent,
|
|
587
|
+
candidates,
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
lines: head.id !== "none"
|
|
591
|
+
? [{ type: "list", title: "read", items: [displayedPrimary.label] }]
|
|
592
|
+
: [],
|
|
593
|
+
limitations: [
|
|
594
|
+
{
|
|
595
|
+
fact: `${file.lines} lines, ${sectionCount} sections`,
|
|
596
|
+
next: "open the cited line range",
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
});
|
|
600
|
+
},
|
|
601
|
+
};
|
|
602
|
+
}
|