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
package/src/texts/locate.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { RECOVERY_POLICY, USE_POLICY } from "./instructions.ts";
|
|
2
|
+
|
|
1
3
|
export const LOCATE_DESCRIPTION = `Find which line range of ONE big file (over {{LOCATE_MIN_KB}} KB) serves a goal, instead of reading it whole; known string or symbol -> {{names.grep}}.
|
|
2
|
-
Use for: a file too big to read whole (over {{LOCATE_MIN_KB}} KB) when you know what you need from it ("where are retries scheduled", "the part that parses flags")
|
|
4
|
+
Use for: a file too big to read whole (over {{LOCATE_MIN_KB}} KB) when you know what you need from it ("where are retries scheduled", "the part that parses flags") and finding a range can focus the next inspection.
|
|
3
5
|
Not for: an exact string or symbol → {{names.grep}}. Files under {{LOCATE_MIN_KB}} KB → read them directly. Several files → jev_ask_files or {{names.semantic}}.{{ompOutline}}
|
|
4
6
|
|
|
5
7
|
How Jev sees it: code cuts the file into declarations or sections, and Jev reads them all at a glance and picks the one that serves the goal. Several sections often share the answer (a getter and its setter), so the mass splits and the pick looks unsure even when both are right. It reads what is written, not what a function does at run time.
|
|
@@ -7,4 +9,8 @@ How Jev sees it: code cuts the file into declarations or sections, and Jev reads
|
|
|
7
9
|
path: one repo-relative file.
|
|
8
10
|
goal: what you need from the file, as a sentence.
|
|
9
11
|
|
|
10
|
-
Result: the range as path:start-end with its label and probability, and the read call to make next. An unmarked line above {{LOCATE_VERDICT_MIN}} is a verdict: read that range. unsure ({{LOCATE_GRAY_MIN}} to {{LOCATE_VERDICT_MIN}}) lists the two best by probability, often adjacent code that shares the answer, not the next section of the file: read both. Below {{LOCATE_GRAY_MIN}} Jev is asked once more among the three best plus none; the original unsure band is retained. A "none" means no section fits
|
|
12
|
+
Result: the range as path:start-end with its label and probability, and the read call to make next. An unmarked line above {{LOCATE_VERDICT_MIN}} is a verdict: read that range. unsure ({{LOCATE_GRAY_MIN}} to {{LOCATE_VERDICT_MIN}}) lists the two best by probability, often adjacent code that shares the answer, not the next section of the file: read both. Below {{LOCATE_GRAY_MIN}} Jev is asked once more among the three best plus none; the original unsure band is retained. A judged "none" means no supplied section fits: search elsewhere. Missing required answers or controls remain unjudged, without an invented probability. Typed diagnostics name parsing/window limits and native actions; primary and control provenance remain distinct. The reading guide defines every mark once.
|
|
13
|
+
|
|
14
|
+
The decision to call is discretionary: ${USE_POLICY}
|
|
15
|
+
|
|
16
|
+
${RECOVERY_POLICY}`;
|
package/src/texts/run-end.ts
CHANGED
|
@@ -6,8 +6,8 @@ export function runEndMessage(findings: readonly DocsFinding[]): string {
|
|
|
6
6
|
"jev_check_diff (docs) flagged documentation that may no longer match your changes:",
|
|
7
7
|
...findings.map(
|
|
8
8
|
(finding) =>
|
|
9
|
-
`- ${finding.section.path} § ${finding.section.heading} — ${JSON.stringify(finding.sentence?.text)} no longer
|
|
9
|
+
`- ${finding.section.path} § ${finding.section.heading} — ${JSON.stringify(finding.sentence?.text)} may no longer match ${finding.units.map((unit) => `${unit.name} in ${unit.file}`).join(", ")}`,
|
|
10
10
|
),
|
|
11
|
-
"
|
|
11
|
+
"Inspect each flagged sentence against the change; update it or explain with evidence why it remains correct. This limited automatic check does not certify documentation completeness and does not require another Jev call.",
|
|
12
12
|
].join("\n");
|
|
13
13
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { SELECT_FILE_SHARE, SELECT_MIN } from "../constants.ts";
|
|
2
|
+
import { RECOVERY_POLICY, USE_POLICY } from "./instructions.ts";
|
|
2
3
|
|
|
3
|
-
export const SELECT_TESTS_DESCRIPTION =
|
|
4
|
+
export const SELECT_TESTS_DESCRIPTION =
|
|
5
|
+
`Select existing tests affected by a diff and return runner commands; this tool never runs or collects tests. Use when selecting existing affected tests can usefully change the execution plan; use native read/grep/bash for exact names and execution. base defaults to HEAD; paths are candidate test files or globs, not diff paths. Static discovery reads tracked files, literal project configuration and imports, without evaluating third-party config. Touched tests are selected for free; import closure includes unchanged intermediates and applicable pytest conftest fixtures. Remaining scenarios receive a changed-unit pointer; selected when 1-p(none) >= ${SELECT_MIN}. Missing answers are selected; unavailable Jev falls back to all. Residual exported-unit checks report changed, run by no discovered test only within the discovered inventory, never global coverage or an obligation to add a scenario. Unsupported, unknown, calculated or unresolved runners keep that conclusion unsure and name the next manual action. Runtime and type tests are separate. Commands remain separate by cwd, config, project and framework; project options are preserved. At least ${SELECT_FILE_SHARE * 100}% selected, uncertain names or unknown counts run the entire file. max_calls bounds Jev calls; unjudged tests remain selected. witnesses controls residual coverage checks (auto by default); pointers have no witnesses. This does not detect that a new test scenario is required.` +
|
|
6
|
+
`\n\n${USE_POLICY}\n\n${RECOVERY_POLICY}`;
|
package/src/tools/ask-files.ts
CHANGED
|
@@ -3,8 +3,13 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { type Static, Type } from "@sinclair/typebox";
|
|
5
5
|
import { collectAskFiles } from "../adapters/ask-files.ts";
|
|
6
|
+
import {
|
|
7
|
+
resolveEvidenceContext,
|
|
8
|
+
withEvidenceContext,
|
|
9
|
+
} from "../adapters/evidence-context.ts";
|
|
6
10
|
import { shareGitInventory } from "../adapters/git-inventory.ts";
|
|
7
11
|
import { hostUsage } from "../adapters/usage.ts";
|
|
12
|
+
import { STATE_MAX_CHARS } from "../constants.ts";
|
|
8
13
|
import { compileAsks, readAsks, reverseQuestions } from "../core/asks.ts";
|
|
9
14
|
import { checkIntegrity } from "../core/integrity.ts";
|
|
10
15
|
import type {
|
|
@@ -14,10 +19,22 @@ import type {
|
|
|
14
19
|
EnvelopeInput,
|
|
15
20
|
} from "../core/output.ts";
|
|
16
21
|
import { buildEnvelope } from "../core/output.ts";
|
|
22
|
+
import {
|
|
23
|
+
type Action,
|
|
24
|
+
answerItemFromInput,
|
|
25
|
+
buildResultReport,
|
|
26
|
+
type Cause,
|
|
27
|
+
contextFromEvidence,
|
|
28
|
+
type Diagnostic,
|
|
29
|
+
type Item,
|
|
30
|
+
known,
|
|
31
|
+
type ResultReportV1,
|
|
32
|
+
unknown,
|
|
33
|
+
} from "../core/result-report.ts";
|
|
17
34
|
import { interpolate } from "../describe.ts";
|
|
18
35
|
import type { GuideContext } from "../guide.ts";
|
|
19
36
|
import type { Answer, Judgment, JudgmentMetadata } from "../jev/types.ts";
|
|
20
|
-
import {
|
|
37
|
+
import { renderResultReport } from "../render.ts";
|
|
21
38
|
import type { ToolDependencies } from "../runtime.ts";
|
|
22
39
|
import { ASK_FILES_DESCRIPTION } from "../texts/ask-files.ts";
|
|
23
40
|
import { NOT_CONFIGURED } from "../texts/configuration.ts";
|
|
@@ -25,6 +42,7 @@ import { asksParameter } from "./ask-schema.ts";
|
|
|
25
42
|
|
|
26
43
|
export const askFilesParameters = Type.Object(
|
|
27
44
|
{
|
|
45
|
+
root: Type.Optional(Type.String()),
|
|
28
46
|
paths: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
|
29
47
|
asks: asksParameter("files"),
|
|
30
48
|
max_calls: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
@@ -41,6 +59,7 @@ export interface AskFilesDetails {
|
|
|
41
59
|
files: FileJudgment[];
|
|
42
60
|
skipped: string[];
|
|
43
61
|
envelope: Envelope;
|
|
62
|
+
result: ResultReportV1;
|
|
44
63
|
}
|
|
45
64
|
export function createAskFilesTool(dependencies: ToolDependencies) {
|
|
46
65
|
const { host, runtime, exec: execute } = dependencies;
|
|
@@ -67,8 +86,74 @@ export function createAskFilesTool(dependencies: ToolDependencies) {
|
|
|
67
86
|
ctx: { cwd: string } & GuideContext,
|
|
68
87
|
) {
|
|
69
88
|
const client = dependencies.client;
|
|
89
|
+
const evidence = await resolveEvidenceContext(ctx.cwd, args.root, {
|
|
90
|
+
exec: execute,
|
|
91
|
+
signal,
|
|
92
|
+
origin: dependencies.evidenceOrigin,
|
|
93
|
+
});
|
|
94
|
+
const evidenceContext = evidence.context;
|
|
95
|
+
const cwd = evidence.ok ? evidence.cwd : evidenceContext.authority.path;
|
|
70
96
|
const started = performance.now();
|
|
71
97
|
const exec = shareGitInventory(execute);
|
|
98
|
+
const plan = compileAsks(args.asks, { surface: "files" });
|
|
99
|
+
let refusalCause: Cause = "invalid_arguments";
|
|
100
|
+
let collectedPaths: string[] | undefined;
|
|
101
|
+
let costKnown = true;
|
|
102
|
+
const reportItems: Item[] = [];
|
|
103
|
+
const diagnostics: Diagnostic[] = [];
|
|
104
|
+
const actions: Action[] = [];
|
|
105
|
+
const auxiliary = {
|
|
106
|
+
controls: { fresh: 0, cache: 0, notJudged: 0, static: 0 },
|
|
107
|
+
passages: { fresh: 0, cache: 0, notJudged: 0 },
|
|
108
|
+
};
|
|
109
|
+
const diagnose = (
|
|
110
|
+
cause: Cause,
|
|
111
|
+
fact: string,
|
|
112
|
+
scope: Diagnostic["scope"],
|
|
113
|
+
target?: string,
|
|
114
|
+
) => {
|
|
115
|
+
const id = `diagnostic:${diagnostics.length}`,
|
|
116
|
+
actionId = `action:${actions.length}`;
|
|
117
|
+
actions.push({
|
|
118
|
+
id: actionId,
|
|
119
|
+
code:
|
|
120
|
+
cause === "invalid_root"
|
|
121
|
+
? "correct_context"
|
|
122
|
+
: cause === "not_configured"
|
|
123
|
+
? "configure_client"
|
|
124
|
+
: "inspect_native",
|
|
125
|
+
target:
|
|
126
|
+
target === undefined
|
|
127
|
+
? unknown("target not established")
|
|
128
|
+
: known(target),
|
|
129
|
+
scope,
|
|
130
|
+
condition:
|
|
131
|
+
"Only after correcting context or supplying materially new evidence",
|
|
132
|
+
instruction:
|
|
133
|
+
"Read the admitted files natively; do not repeat the unchanged Jev call.",
|
|
134
|
+
repeatUnchanged: false,
|
|
135
|
+
});
|
|
136
|
+
diagnostics.push({
|
|
137
|
+
id,
|
|
138
|
+
cause,
|
|
139
|
+
fact,
|
|
140
|
+
target:
|
|
141
|
+
target === undefined
|
|
142
|
+
? unknown("target not established")
|
|
143
|
+
: known(target),
|
|
144
|
+
origin:
|
|
145
|
+
cause === "call_budget" || cause === "session_budget"
|
|
146
|
+
? "budget"
|
|
147
|
+
: "collection",
|
|
148
|
+
scope,
|
|
149
|
+
effect: "blocking",
|
|
150
|
+
material: true,
|
|
151
|
+
omittedMembers: [],
|
|
152
|
+
memberCount: known(0),
|
|
153
|
+
actionIds: [actionId],
|
|
154
|
+
});
|
|
155
|
+
return { diagnosticIds: [id], actionIds: [actionId] };
|
|
156
|
+
};
|
|
72
157
|
const totals = {
|
|
73
158
|
calls: 0,
|
|
74
159
|
questions: 0,
|
|
@@ -86,35 +171,156 @@ export function createAskFilesTool(dependencies: ToolDependencies) {
|
|
|
86
171
|
});
|
|
87
172
|
runtime.session.record(envelope);
|
|
88
173
|
runtime.guide.deliver(ctx);
|
|
174
|
+
if (!reportItems.length && !diagnostics.length)
|
|
175
|
+
diagnose(
|
|
176
|
+
input.refusal ? refusalCause : "collection_empty",
|
|
177
|
+
input.refusal ?? "No file-question results collected",
|
|
178
|
+
{ kind: "call" },
|
|
179
|
+
);
|
|
180
|
+
if (!reportItems.length && collectedPaths && plan.ok)
|
|
181
|
+
for (const path of collectedPaths)
|
|
182
|
+
for (const reading of plan.readings)
|
|
183
|
+
reportItems.push({
|
|
184
|
+
id: `${path}:${reading.id}`,
|
|
185
|
+
kind: "file_question",
|
|
186
|
+
label: `${path} ${reading.label}`,
|
|
187
|
+
groupId: `${path}:group:${plan.groups.findIndex((group) => group.includes(reading.id))}`,
|
|
188
|
+
evidence: [
|
|
189
|
+
{
|
|
190
|
+
target: path,
|
|
191
|
+
canonicalPath: known(resolve(cwd, path)),
|
|
192
|
+
aliases: [],
|
|
193
|
+
side: "current",
|
|
194
|
+
revision: unknown("working tree evidence"),
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
diagnosticIds: diagnostics.map((diagnostic) => diagnostic.id),
|
|
198
|
+
actionIds: actions.map((action) => action.id),
|
|
199
|
+
treatment: "not_judged",
|
|
200
|
+
source: "none",
|
|
201
|
+
});
|
|
202
|
+
for (const limitation of input.limitations ?? []) {
|
|
203
|
+
diagnose(
|
|
204
|
+
"evidence_limit",
|
|
205
|
+
limitation.fact,
|
|
206
|
+
{ kind: "call" },
|
|
207
|
+
limitation.path,
|
|
208
|
+
);
|
|
209
|
+
const diagnostic = diagnostics.at(-1);
|
|
210
|
+
if (diagnostic) diagnostic.effect = "reservation";
|
|
211
|
+
}
|
|
212
|
+
const report = buildResultReport({
|
|
213
|
+
tool: "jev_ask_files",
|
|
214
|
+
context: contextFromEvidence(evidenceContext, {
|
|
215
|
+
inventories: collectedPaths
|
|
216
|
+
? [
|
|
217
|
+
{
|
|
218
|
+
id: "files",
|
|
219
|
+
kind: "repository",
|
|
220
|
+
rules: [
|
|
221
|
+
"Requested paths expanded with admission and pruning",
|
|
222
|
+
],
|
|
223
|
+
restrictions: args.paths,
|
|
224
|
+
discovered: unknown(
|
|
225
|
+
"collector does not expose pre-pruning candidate count",
|
|
226
|
+
),
|
|
227
|
+
considered: known(collectedPaths.length),
|
|
228
|
+
scopeRestricted: true,
|
|
229
|
+
criteria: [],
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
: [],
|
|
233
|
+
}),
|
|
234
|
+
items: reportItems,
|
|
235
|
+
diagnostics,
|
|
236
|
+
actions,
|
|
237
|
+
metrics: {
|
|
238
|
+
...totals,
|
|
239
|
+
costUsd: costKnown && collectedPaths ? totals.costUsd : undefined,
|
|
240
|
+
elapsedMs: performance.now() - started,
|
|
241
|
+
},
|
|
242
|
+
auxiliary,
|
|
243
|
+
total:
|
|
244
|
+
collectedPaths && plan.ok
|
|
245
|
+
? known(collectedPaths.length * plan.readings.length)
|
|
246
|
+
: unknown("file-question inventory not collected"),
|
|
247
|
+
refused: !!input.refusal && refusalCause !== "not_configured",
|
|
248
|
+
});
|
|
89
249
|
const usage = hostUsage(host.isOmp, {
|
|
90
250
|
inputTokens: totals.inputTokens,
|
|
91
251
|
costUsd: totals.costUsd,
|
|
92
252
|
});
|
|
93
253
|
return {
|
|
94
|
-
content: [
|
|
95
|
-
|
|
254
|
+
content: [
|
|
255
|
+
{
|
|
256
|
+
type: "text" as const,
|
|
257
|
+
text: renderResultReport(report, { details: envelope }),
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
details: {
|
|
261
|
+
files: records,
|
|
262
|
+
skipped,
|
|
263
|
+
envelope,
|
|
264
|
+
evidenceContext,
|
|
265
|
+
result: report,
|
|
266
|
+
},
|
|
96
267
|
...usage,
|
|
97
268
|
};
|
|
98
269
|
};
|
|
99
|
-
if (!
|
|
100
|
-
|
|
270
|
+
if (!evidence.ok) {
|
|
271
|
+
refusalCause = evidence.cause ?? "invalid_root";
|
|
272
|
+
return finish({ refusal: evidence.error });
|
|
273
|
+
}
|
|
101
274
|
if (!plan.ok) return finish({ refusal: plan.error });
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
|
|
275
|
+
if (!client) {
|
|
276
|
+
refusalCause = "not_configured";
|
|
277
|
+
return finish({ refusal: NOT_CONFIGURED });
|
|
278
|
+
}
|
|
279
|
+
const collected = await collectAskFiles(cwd, args.paths, signal, exec);
|
|
280
|
+
if (!collected.ok) {
|
|
281
|
+
refusalCause = collected.cause ?? "file_unavailable";
|
|
282
|
+
return finish({ refusal: collected.error });
|
|
283
|
+
}
|
|
284
|
+
collectedPaths = collected.files.map((file) => file.path);
|
|
109
285
|
skipped = collected.skipped;
|
|
286
|
+
for (const skippedPath of skipped)
|
|
287
|
+
diagnose(
|
|
288
|
+
collected.secret.includes(skippedPath)
|
|
289
|
+
? "secret_pattern"
|
|
290
|
+
: "collection_omitted",
|
|
291
|
+
skippedPath,
|
|
292
|
+
{ kind: "inventory", inventoryIds: ["files"] },
|
|
293
|
+
);
|
|
110
294
|
if (!collected.files.length && skipped.length)
|
|
111
295
|
return finish({
|
|
112
296
|
refusal: `No readable evidence: ${skipped.join("; ")}`,
|
|
113
297
|
});
|
|
298
|
+
const states = collected.files.map((file) => ({
|
|
299
|
+
file,
|
|
300
|
+
state: withEvidenceContext(
|
|
301
|
+
{ path: file.path, content: file.content },
|
|
302
|
+
evidenceContext,
|
|
303
|
+
),
|
|
304
|
+
}));
|
|
305
|
+
if (
|
|
306
|
+
states.some(
|
|
307
|
+
({ state }) => JSON.stringify(state).length > STATE_MAX_CHARS,
|
|
308
|
+
)
|
|
309
|
+
) {
|
|
310
|
+
refusalCause = "evidence_too_large";
|
|
311
|
+
return finish({
|
|
312
|
+
refusal: `required evidence exceeds STATE_MAX_CHARS=${STATE_MAX_CHARS}`,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
114
315
|
let sent = 0;
|
|
115
316
|
let budget: BudgetRefusal | undefined;
|
|
116
317
|
const options = {
|
|
117
318
|
signal,
|
|
319
|
+
...runtime.session.requestGate(),
|
|
320
|
+
admissionCause: () =>
|
|
321
|
+
budget?.kind === "session"
|
|
322
|
+
? ("session_budget" as const)
|
|
323
|
+
: ("call_budget" as const),
|
|
118
324
|
beforeRequest: (count: number) => {
|
|
119
325
|
if (args.max_calls !== undefined && sent >= args.max_calls) {
|
|
120
326
|
budget = {
|
|
@@ -141,17 +347,17 @@ export function createAskFilesTool(dependencies: ToolDependencies) {
|
|
|
141
347
|
totals.cacheRequests += result.cacheRequests ?? 0;
|
|
142
348
|
totals.inputTokens += result.usage?.inputTokens ?? 0;
|
|
143
349
|
totals.costUsd += result.usage?.costUsd ?? 0;
|
|
350
|
+
if (!result.usage && result.calls !== 0) costKnown = false;
|
|
144
351
|
};
|
|
145
352
|
const rows = await Promise.all(
|
|
146
|
-
|
|
147
|
-
const state = { path: file.path, content: file.content };
|
|
353
|
+
states.map(async ({ file, state }) => {
|
|
148
354
|
const integrity = checkIntegrity(state, plan.asks, [
|
|
149
355
|
{
|
|
150
356
|
...file.identity,
|
|
151
|
-
insertedPath: resolve(
|
|
152
|
-
content:
|
|
357
|
+
insertedPath: resolve(cwd, file.path),
|
|
358
|
+
content: file.content,
|
|
153
359
|
insertedSha256: createHash("sha256")
|
|
154
|
-
.update(
|
|
360
|
+
.update(file.content)
|
|
155
361
|
.digest("hex"),
|
|
156
362
|
},
|
|
157
363
|
]);
|
|
@@ -193,6 +399,93 @@ export function createAskFilesTool(dependencies: ToolDependencies) {
|
|
|
193
399
|
const unchecked: string[] = [];
|
|
194
400
|
const unjudged: NonNullable<EnvelopeInput["unjudged"]>[number][] = [];
|
|
195
401
|
for (const row of rows) {
|
|
402
|
+
const readings = plan.readings;
|
|
403
|
+
for (const [index, reading] of readings.entries()) {
|
|
404
|
+
const groupId = `${row.file.path}:group:${plan.groups.findIndex((group) => group.includes(reading.id))}`;
|
|
405
|
+
const common = {
|
|
406
|
+
id: `${row.file.path}:${reading.id}`,
|
|
407
|
+
kind: "file_question" as const,
|
|
408
|
+
label: `${row.file.path} ${reading.label}`,
|
|
409
|
+
groupId,
|
|
410
|
+
evidence: [
|
|
411
|
+
{
|
|
412
|
+
target: row.file.path,
|
|
413
|
+
canonicalPath: known(resolve(cwd, row.file.path)),
|
|
414
|
+
aliases: [],
|
|
415
|
+
side: "current" as const,
|
|
416
|
+
revision: unknown("working tree evidence"),
|
|
417
|
+
},
|
|
418
|
+
],
|
|
419
|
+
diagnosticIds: [] as string[],
|
|
420
|
+
actionIds: [] as string[],
|
|
421
|
+
};
|
|
422
|
+
const answer = row.answers[index];
|
|
423
|
+
if (
|
|
424
|
+
answer &&
|
|
425
|
+
!answer.unjudged &&
|
|
426
|
+
(answer.source || answer.answer?.source)
|
|
427
|
+
)
|
|
428
|
+
reportItems.push(
|
|
429
|
+
answerItemFromInput(
|
|
430
|
+
common,
|
|
431
|
+
row.integrity.controls.length
|
|
432
|
+
? {
|
|
433
|
+
...answer,
|
|
434
|
+
band: "unsure",
|
|
435
|
+
reason: row.integrity.controls
|
|
436
|
+
.map((control) => control.fact)
|
|
437
|
+
.join("; "),
|
|
438
|
+
}
|
|
439
|
+
: answer,
|
|
440
|
+
),
|
|
441
|
+
);
|
|
442
|
+
else {
|
|
443
|
+
const raw = row.initial.ok
|
|
444
|
+
? row.initial.answers[reading.id]
|
|
445
|
+
: undefined;
|
|
446
|
+
const cause =
|
|
447
|
+
raw?.type === "unjudged" ? raw.cause : row.initial.failureCause;
|
|
448
|
+
const links = diagnose(
|
|
449
|
+
cause ??
|
|
450
|
+
(budget
|
|
451
|
+
? budget.kind === "session"
|
|
452
|
+
? "session_budget"
|
|
453
|
+
: "call_budget"
|
|
454
|
+
: "control_failure"),
|
|
455
|
+
answer?.reason ??
|
|
456
|
+
(row.initial.ok
|
|
457
|
+
? "Required group incomplete or response provenance not established"
|
|
458
|
+
: row.initial.error),
|
|
459
|
+
{ kind: "group", groupIds: [groupId] },
|
|
460
|
+
row.file.path,
|
|
461
|
+
);
|
|
462
|
+
reportItems.push({
|
|
463
|
+
...common,
|
|
464
|
+
...links,
|
|
465
|
+
treatment: "not_judged",
|
|
466
|
+
source: "none",
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
for (const id of [
|
|
470
|
+
reading.twin,
|
|
471
|
+
...Object.values(reading.controls ?? {}),
|
|
472
|
+
].filter((id): id is string => !!id)) {
|
|
473
|
+
const control = row.initial.ok
|
|
474
|
+
? row.initial.answers[id]
|
|
475
|
+
: undefined;
|
|
476
|
+
if (control && control.type !== "unjudged" && control.source)
|
|
477
|
+
auxiliary.controls[control.source]++;
|
|
478
|
+
else auxiliary.controls.notJudged++;
|
|
479
|
+
}
|
|
480
|
+
const reverse = row.reversed?.ok
|
|
481
|
+
? row.reversed.answers[reading.id]
|
|
482
|
+
: undefined;
|
|
483
|
+
if (row.reversed) {
|
|
484
|
+
if (reverse && reverse.type !== "unjudged" && reverse.source)
|
|
485
|
+
auxiliary.controls[reverse.source]++;
|
|
486
|
+
else auxiliary.controls.notJudged++;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
196
489
|
records.push({
|
|
197
490
|
path: row.file.path,
|
|
198
491
|
initial: row.initial,
|
|
@@ -203,7 +496,7 @@ export function createAskFilesTool(dependencies: ToolDependencies) {
|
|
|
203
496
|
unjudged.push({
|
|
204
497
|
label: row.file.path,
|
|
205
498
|
reason: row.initial.error,
|
|
206
|
-
next: "read the file or
|
|
499
|
+
next: "read the supplied file natively; a new Jev call needs materially changed evidence or recovered service",
|
|
207
500
|
});
|
|
208
501
|
continue;
|
|
209
502
|
}
|